From baa39930ac95ac422951fbc537278d09b7266597 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 29 Mar 2017 11:07:10 -0700 Subject: [PATCH 001/742] Initialize new branch with minimal POM --- pom.xml | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..53b84e183aa --- /dev/null +++ b/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + com.datastax.oss + java-driver-parent + 4.0.0-SNAPSHOT + pom + + DataStax Java driver for Apache Cassandra™ + + A driver for Apache Cassandra™ 2.1+ that works exclusively with the Cassandra Query Language + version 3 (CQL3) and Cassandra's native protocol versions 3 and above. + + https://github.com/datastax/java-driver + 2017 + + + true + UTF-8 + + + + + + + maven-compiler-plugin + 3.6.1 + + + com.coveo + fmt-maven-plugin + 1.5.0 + + + com.mycila + license-maven-plugin + 3.0 + + + maven-surefire-plugin + 2.19.1 + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + true + true + true + + false + + + + com.coveo + fmt-maven-plugin + + ${format.validateOnly} + + + + + format + + + + + + com.mycila + license-maven-plugin + + + + + src/**/*.java + src/**/*.xml + src/**/*.properties + **/pom.xml + + + **/src/main/config/ide/** + + + SLASHSTAR_STYLE + SCRIPT_STYLE + + true + + + + check-license + initialize + + check + + + + + + maven-surefire-plugin + + + + usedefaultlisteners + false + + + + + + + From f2a92e41d1d47c4071d4f84a2a85429764b77f23 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 29 Mar 2017 11:28:11 -0700 Subject: [PATCH 002/742] Add .gitignore --- .gitignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..e6b8c10a1be --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +target/ +.settings +.classpath +.project +.DS_Store + +/.idea +*.iml + +.java-version + From fa4885341de2e969aa7d7814c228cb85bd7deeb0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 29 Mar 2017 14:07:45 -0700 Subject: [PATCH 003/742] Initialize core module with PrimitiveCodec implementation --- core/pom.xml | 52 +++ .../core/protocol/ByteBufPrimitiveCodec.java | 236 ++++++++++++ .../com/datastax/oss/driver/Assertions.java | 24 ++ .../datastax/oss/driver/ByteBufAssert.java | 39 ++ .../protocol/ByteBufPrimitiveCodecTest.java | 359 ++++++++++++++++++ pom.xml | 33 +- pre-commit.sh | 8 + 7 files changed, 749 insertions(+), 2 deletions(-) create mode 100644 core/pom.xml create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java create mode 100644 core/src/test/java/com/datastax/oss/driver/Assertions.java create mode 100644 core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java create mode 100755 pre-commit.sh diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 00000000000..5a766624ec7 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-SNAPSHOT + + + java-driver-core + jar + + DataStax Java driver for Apache Cassandra® - core + + + + com.datastax.oss + native-protocol + + + io.netty + netty-handler + + + org.testng + testng + + + org.assertj + assertj-core + + + diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java new file mode 100644 index 00000000000..818f74a6c55 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.protocol.internal.PrimitiveCodec; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import io.netty.util.CharsetUtil; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class ByteBufPrimitiveCodec implements PrimitiveCodec { + + private final ByteBufAllocator allocator; + + public ByteBufPrimitiveCodec(ByteBufAllocator allocator) { + this.allocator = allocator; + } + + @Override + public ByteBuf allocate(int size) { + return allocator.ioBuffer(); + } + + @Override + public void release(ByteBuf toRelease) { + toRelease.release(); + } + + @Override + public int sizeOf(ByteBuf toMeasure) { + return toMeasure.readableBytes(); + } + + @Override + public ByteBuf concat(ByteBuf left, ByteBuf right) { + if (!left.isReadable()) { + return right.duplicate(); + } else if (!right.isReadable()) { + return left.duplicate(); + } else { + CompositeByteBuf c = allocator.compositeBuffer(2); + c.addComponents(left, right); + // c.readerIndex() is 0, which is the first readable byte in left + c.writerIndex( + left.writerIndex() - left.readerIndex() + right.writerIndex() - right.readerIndex()); + return c; + } + } + + @Override + public byte readByte(ByteBuf source) { + return source.readByte(); + } + + @Override + public int readInt(ByteBuf source) { + return source.readInt(); + } + + @Override + public InetSocketAddress readInet(ByteBuf source) { + int length = readByte(source) & 0xFF; + byte[] bytes = new byte[length]; + source.readBytes(bytes); + int port = source.readInt(); + return new InetSocketAddress(newInetAddress(bytes), port); + } + + @Override + public InetAddress readInetAddr(ByteBuf source) { + int length = readByte(source) & 0xFF; + byte[] bytes = new byte[length]; + source.readBytes(bytes); + return newInetAddress(bytes); + } + + @Override + public long readLong(ByteBuf source) { + return source.readLong(); + } + + @Override + public int readUnsignedShort(ByteBuf source) { + return source.readUnsignedShort(); + } + + @Override + public ByteBuffer readBytes(ByteBuf source) { + int length = readInt(source); + if (length < 0) return null; + ByteBuf slice = source.readSlice(length); + return ByteBuffer.wrap(readRawBytes(slice)); + } + + @Override + public byte[] readShortBytes(ByteBuf source) { + try { + int length = readUnsignedShort(source); + byte[] bytes = new byte[length]; + source.readBytes(bytes); + return bytes; + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "Not enough bytes to read a byte array preceded by its 2 bytes length"); + } + } + + @Override + public String readString(ByteBuf source) { + int length = readUnsignedShort(source); + return readString(source, length); + } + + @Override + public String readLongString(ByteBuf source) { + int length = readInt(source); + return readString(source, length); + } + + @Override + public void writeByte(byte b, ByteBuf dest) { + dest.writeByte(b); + } + + @Override + public void writeInt(int i, ByteBuf dest) { + dest.writeInt(i); + } + + @Override + public void writeInet(InetSocketAddress inet, ByteBuf dest) { + writeInetAddr(inet.getAddress(), dest); + writeInt(inet.getPort(), dest); + } + + @Override + public void writeInetAddr(InetAddress inetAddr, ByteBuf dest) { + byte[] bytes = inetAddr.getAddress(); + writeByte((byte) bytes.length, dest); + dest.writeBytes(bytes); + } + + @Override + public void writeLong(long l, ByteBuf dest) { + dest.writeLong(l); + } + + @Override + public void writeUnsignedShort(int i, ByteBuf dest) { + dest.writeShort(i); + } + + @Override + public void writeString(String s, ByteBuf dest) { + byte[] bytes = s.getBytes(CharsetUtil.UTF_8); + writeUnsignedShort(bytes.length, dest); + dest.writeBytes(bytes); + } + + @Override + public void writeLongString(String s, ByteBuf dest) { + byte[] bytes = s.getBytes(CharsetUtil.UTF_8); + writeInt(bytes.length, dest); + dest.writeBytes(bytes); + } + + @Override + public void writeBytes(ByteBuffer bytes, ByteBuf dest) { + if (bytes == null) { + writeInt(-1, dest); + } else { + writeInt(bytes.remaining(), dest); + dest.writeBytes(bytes.duplicate()); + } + } + + @Override + public void writeShortBytes(byte[] bytes, ByteBuf dest) { + writeUnsignedShort(bytes.length, dest); + dest.writeBytes(bytes); + } + + // Reads *all* readable bytes from a buffer and return them. + // If the buffer is backed by an array, this will return the underlying array directly, without copy. + private static byte[] readRawBytes(ByteBuf buffer) { + if (buffer.hasArray() && buffer.readableBytes() == buffer.array().length) { + // Move the readerIndex just so we consistently consume the input + buffer.readerIndex(buffer.writerIndex()); + return buffer.array(); + } + + // Otherwise, just read the bytes in a new array + byte[] bytes = new byte[buffer.readableBytes()]; + buffer.readBytes(bytes); + return bytes; + } + + private static String readString(ByteBuf source, int length) { + try { + String str = source.toString(source.readerIndex(), length, CharsetUtil.UTF_8); + source.readerIndex(source.readerIndex() + length); + return str; + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "Not enough bytes to read an UTF-8 serialized string of size " + length, e); + } + } + + private InetAddress newInetAddress(byte[] bytes) { + try { + return InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + // Per the Javadoc, the only way this can happen is if the length is illegal + throw new IllegalArgumentException( + String.format("Invalid address length: %d (%s)", bytes.length, Arrays.toString(bytes))); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/Assertions.java b/core/src/test/java/com/datastax/oss/driver/Assertions.java new file mode 100644 index 00000000000..d6a595ef27a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/Assertions.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver; + +import io.netty.buffer.ByteBuf; + +public class Assertions extends org.assertj.core.api.Assertions { + public static ByteBufAssert assertThat(ByteBuf actual) { + return new ByteBufAssert(actual); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java b/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java new file mode 100644 index 00000000000..bb674342d62 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver; + +import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.buffer.ByteBuf; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ByteBufAssert extends AbstractAssert { + public ByteBufAssert(ByteBuf actual) { + super(actual, ByteBufAssert.class); + } + + public ByteBufAssert containsExactly(String hexString) { + ByteBuf copy = actual.duplicate(); + byte[] expectedBytes = Bytes.fromHexString(hexString).array(); + byte[] actualBytes = new byte[expectedBytes.length]; + copy.readBytes(actualBytes); + assertThat(actualBytes).containsExactly(expectedBytes); + // And nothing more + assertThat(copy.isReadable()).isFalse(); + return this; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java new file mode 100644 index 00000000000..7f9a752bf95 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +/** + * Note: we don't test trivial methods that simply delegate to ByteBuf, nor default implementations + * inherited from {@link com.datastax.oss.protocol.internal.PrimitiveCodec}. + */ +public class ByteBufPrimitiveCodecTest { + private ByteBufPrimitiveCodec codec = new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT); + + @Test + public void should_concatenate() { + ByteBuf left = wrap(0xca, 0xfe); + ByteBuf right = wrap(0xba, 0xbe); + assertThat(codec.concat(left, right)).containsExactly("0xcafebabe"); + } + + @Test + public void should_concatenate_slices() { + ByteBuf left = wrap(0x00, 0xca, 0xfe, 0x00).slice(1, 2); + ByteBuf right = wrap(0x00, 0x00, 0xba, 0xbe, 0x00).slice(2, 2); + + assertThat(codec.concat(left, right)).containsExactly("0xcafebabe"); + } + + @Test + public void should_read_inet_v4() { + ByteBuf source = + wrap( + // length (as a byte) + 0x04, + // address + 0x7f, + 0x00, + 0x00, + 0x01, + // port (as an int) + 0x00, + 0x00, + 0x23, + 0x52); + InetSocketAddress inet = codec.readInet(source); + assertThat(inet.getAddress().getHostAddress()).isEqualTo("127.0.0.1"); + assertThat(inet.getPort()).isEqualTo(9042); + } + + @Test + public void should_read_inet_v6() { + ByteBuf lengthAndAddress = allocate(17); + lengthAndAddress.writeByte(16); + lengthAndAddress.writeLong(0); + lengthAndAddress.writeLong(1); + ByteBuf source = + codec.concat( + lengthAndAddress, + // port (as an int) + wrap(0x00, 0x00, 0x23, 0x52)); + InetSocketAddress inet = codec.readInet(source); + assertThat(inet.getAddress().getHostAddress()).isEqualTo("0:0:0:0:0:0:0:1"); + assertThat(inet.getPort()).isEqualTo(9042); + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Invalid address length: 3 \\(\\[127, 0, 1\\]\\)" + ) + public void should_fail_to_read_inet_if_length_invalid() { + ByteBuf source = + wrap( + // length (as a byte) + 0x03, + // address + 0x7f, + 0x00, + 0x01, + // port (as an int) + 0x00, + 0x00, + 0x23, + 0x52); + codec.readInet(source); + } + + @Test + public void should_read_inetaddr_v4() { + ByteBuf source = + wrap( + // length (as a byte) + 0x04, + // address + 0x7f, + 0x00, + 0x00, + 0x01); + InetAddress inetAddr = codec.readInetAddr(source); + assertThat(inetAddr.getHostAddress()).isEqualTo("127.0.0.1"); + } + + @Test + public void should_read_inetaddr_v6() { + ByteBuf source = allocate(17); + source.writeByte(16); + source.writeLong(0); + source.writeLong(1); + InetAddress inetAddr = codec.readInetAddr(source); + assertThat(inetAddr.getHostAddress()).isEqualTo("0:0:0:0:0:0:0:1"); + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Invalid address length: 3 \\(\\[127, 0, 1\\]\\)" + ) + public void should_fail_to_read_inetaddr_if_length_invalid() { + ByteBuf source = + wrap( + // length (as a byte) + 0x03, + // address + 0x7f, + 0x00, + 0x01); + codec.readInetAddr(source); + } + + @Test + public void should_read_bytes() { + ByteBuf source = + wrap( + // length (as an int) + 0x00, + 0x00, + 0x00, + 0x04, + // contents + 0xca, + 0xfe, + 0xba, + 0xbe); + ByteBuffer bytes = codec.readBytes(source); + assertThat(Bytes.toHexString(bytes)).isEqualTo("0xcafebabe"); + } + + @Test + public void should_read_null_bytes() { + ByteBuf source = wrap(0xFF, 0xFF, 0xFF, 0xFF); // -1 (as an int) + assertThat(codec.readBytes(source)).isNull(); + } + + @Test + public void should_read_short_bytes() { + ByteBuf source = + wrap( + // length (as an unsigned short) + 0x00, + 0x04, + // contents + 0xca, + 0xfe, + 0xba, + 0xbe); + assertThat(Bytes.toHexString(codec.readShortBytes(source))).isEqualTo("0xcafebabe"); + } + + @Test + public void should_read_string() { + ByteBuf source = + wrap( + // length (as an unsigned short) + 0x00, + 0x05, + // UTF-8 contents + 0x68, + 0x65, + 0x6c, + 0x6c, + 0x6f); + assertThat(codec.readString(source)).isEqualTo("hello"); + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = + "Not enough bytes to read an UTF-8 serialized string of size 4" + ) + public void should_fail_to_read_string_if_not_enough_characters() { + ByteBuf source = codec.allocate(2); + source.writeShort(4); + + codec.readString(source); + } + + @Test + public void should_read_long_string() { + ByteBuf source = + wrap( + // length (as an int) + 0x00, + 0x00, + 0x00, + 0x05, + // UTF-8 contents + 0x68, + 0x65, + 0x6c, + 0x6c, + 0x6f); + assertThat(codec.readLongString(source)).isEqualTo("hello"); + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = + "Not enough bytes to read an UTF-8 serialized string of size 4" + ) + public void should_fail_to_read_long_string_if_not_enough_characters() { + ByteBuf source = codec.allocate(2); + source.writeInt(4); + + codec.readLongString(source); + } + + @Test + public void should_write_inet_v4() throws Exception { + ByteBuf dest = allocate(1 + 4 + 4); + InetSocketAddress inet = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 9042); + codec.writeInet(inet, dest); + assertThat(dest) + .containsExactly( + "0x04" // size as a byte + + "7f000001" // address + + "00002352" // port + ); + } + + @Test + public void should_write_inet_v6() throws Exception { + ByteBuf dest = allocate(1 + 16 + 4); + InetSocketAddress inet = new InetSocketAddress(InetAddress.getByName("::1"), 9042); + codec.writeInet(inet, dest); + assertThat(dest) + .containsExactly( + "0x10" // size as a byte + + "00000000000000000000000000000001" // address + + "00002352" // port + ); + } + + @Test + public void should_write_inetaddr_v4() throws Exception { + ByteBuf dest = allocate(1 + 4); + InetAddress inetAddr = InetAddress.getByName("127.0.0.1"); + codec.writeInetAddr(inetAddr, dest); + assertThat(dest) + .containsExactly( + "0x04" // size as a byte + + "7f000001" // address + ); + } + + @Test + public void should_write_inetaddr_v6() throws Exception { + ByteBuf dest = allocate(1 + 16); + InetAddress inetAddr = InetAddress.getByName("::1"); + codec.writeInetAddr(inetAddr, dest); + assertThat(dest) + .containsExactly( + "0x10" // size as a byte + + "00000000000000000000000000000001" // address + ); + } + + @Test + public void should_write_string() { + ByteBuf dest = allocate(7); + codec.writeString("hello", dest); + assertThat(dest) + .containsExactly( + "0x0005" // size as an unsigned short + + "68656c6c6f" // UTF-8 contents + ); + } + + @Test + public void should_write_long_string() { + ByteBuf dest = allocate(9); + codec.writeLongString("hello", dest); + assertThat(dest) + .containsExactly( + "0x00000005" + + // size as an int + "68656c6c6f" // UTF-8 contents + ); + } + + @Test + public void should_write_bytes() { + ByteBuf dest = allocate(8); + codec.writeBytes(Bytes.fromHexString("0xcafebabe"), dest); + assertThat(dest) + .containsExactly( + "0x00000004" + + // size as an int + "cafebabe"); + } + + @Test + public void should_write_short_bytes() { + ByteBuf dest = allocate(6); + codec.writeShortBytes(new byte[] {(byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe}, dest); + assertThat(dest) + .containsExactly( + "0x0004" + + // size as an unsigned short + "cafebabe"); + } + + @Test + public void should_write_null_bytes() { + ByteBuf dest = allocate(4); + codec.writeBytes(null, dest); + assertThat(dest).containsExactly("0xFFFFFFFF"); + } + + private static ByteBuf wrap(int... bytes) { + ByteBuf bb = ByteBufAllocator.DEFAULT.buffer(bytes.length); + for (int b : bytes) { + bb.writeByte(b); + } + return bb; + } + + private static ByteBuf allocate(int length) { + return ByteBufAllocator.DEFAULT.buffer(length); + } +} diff --git a/pom.xml b/pom.xml index 53b84e183aa..879892dcb11 100644 --- a/pom.xml +++ b/pom.xml @@ -25,19 +25,48 @@ 4.0.0-SNAPSHOT pom - DataStax Java driver for Apache Cassandra™ + DataStax Java driver for Apache Cassandra® - A driver for Apache Cassandra™ 2.1+ that works exclusively with the Cassandra Query Language + A driver for Apache Cassandra® 2.1+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol versions 3 and above. https://github.com/datastax/java-driver 2017 + + core + + true UTF-8 + + + + com.datastax.oss + native-protocol + 1.4.0-SNAPSHOT + + + io.netty + netty-handler + 4.1.9.Final + + + org.testng + testng + 6.11 + + + org.assertj + assertj-core + 3.6.2 + + + + diff --git a/pre-commit.sh b/pre-commit.sh new file mode 100755 index 00000000000..9d00aa086f2 --- /dev/null +++ b/pre-commit.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +git stash -q --keep-index +mvn clean test +RESULT=$? +git stash pop -q +[ $RESULT -ne 0 ] && exit 1 +exit 0 From 719a288dda31c7c3117f2b9a568b63855b26aa68 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 31 Mar 2017 17:34:08 -0700 Subject: [PATCH 004/742] Add initial channel implementation --- core/pom.xml | 26 +- .../driver/api/core/CoreProtocolVersion.java | 58 ++++ .../oss/driver/api/core/CqlIdentifier.java | 133 ++++++++ .../oss/driver/api/core/ProtocolVersion.java | 41 +++ .../UnsupportedProtocolVersionException.java | 67 ++++ .../driver/api/core/auth/AuthProvider.java | 50 +++ .../core/auth/AuthenticationException.java | 30 ++ .../driver/api/core/auth/Authenticator.java | 83 +++++ .../api/core/auth/PlainTextAuthProvider.java | 93 +++++ .../api/core/auth/SyncAuthenticator.java | 70 ++++ .../driver/api/core/auth/package-info.java | 22 ++ .../api/core/config/CoreDriverOption.java | 49 +++ .../driver/api/core/config/DriverConfig.java | 30 ++ .../api/core/config/DriverConfigProfile.java | 35 ++ .../driver/api/core/config/DriverOption.java | 23 ++ .../driver/api/core/config/package-info.java | 22 ++ .../connection/BusyConnectionException.java | 30 ++ .../core/connection/ConnectionException.java | 27 ++ .../api/core/connection/package-info.java | 22 ++ .../oss/driver/api/core/package-info.java | 17 + .../datastax/oss/driver/api/package-info.java | 22 ++ .../internal/core/DefaultDriverContext.java | 140 ++++++++ .../driver/internal/core/DriverContext.java | 54 +++ .../core/ProtocolVersionRegistry.java | 99 ++++++ .../internal/core/channel/ChannelFactory.java | 169 ++++++++++ .../channel/ClusterNameMismatchException.java | 52 +++ .../internal/core/channel/DriverChannel.java | 136 ++++++++ .../core/channel/HeartbeatHandler.java | 99 ++++++ .../core/channel/InFlightHandler.java | 228 +++++++++++++ .../core/channel/InternalRequest.java | 103 ++++++ .../core/channel/ProtocolInitHandler.java | 257 ++++++++++++++ .../core/channel/ResponseCallback.java | 68 ++++ .../core/channel/StreamIdGenerator.java | 60 ++++ .../internal/core/channel/package-info.java | 17 + .../config/typesafe/TypeSafeDriverConfig.java | 104 ++++++ .../typesafe/TypesafeDriverConfigProfile.java | 54 +++ .../core/config/typesafe/package-info.java | 20 ++ .../internal/core/protocol/FrameDecoder.java | 60 ++++ .../core/protocol/FrameDecodingException.java | 27 ++ .../internal/core/protocol/FrameEncoder.java | 38 +++ .../internal/core/protocol/package-info.java | 17 + .../internal/core/util/ProtocolUtils.java | 114 +++++++ .../driver/internal/core/util/Reflection.java | 77 +++++ .../driver/internal/core/util/Strings.java | 300 +++++++++++++++++ .../core/util/concurrent/LazyReference.java | 53 +++ .../core/util/netty/ConnectInitHandler.java | 77 +++++ .../core/util/netty/package-info.java | 17 + .../internal/core/util/package-info.java | 17 + .../oss/driver/internal/package-info.java | 23 ++ core/src/main/resources/reference.conf | 62 ++++ .../com/datastax/oss/driver/Assertions.java | 18 + .../driver/api/core/CqlIdentifierTest.java | 69 ++++ .../internal/core/CompletionStageAssert.java | 67 ++++ .../internal/core/DriverConfigAssert.java | 38 +++ .../internal/core/NettyFutureAssert.java | 72 ++++ .../core/ProtocolVersionRegistryTest.java | 109 ++++++ .../driver/internal/core/TestResponses.java | 44 +++ .../ChannelFactoryClusterNameTest.java | 87 +++++ ...ChannelFactoryProtocolNegotiationTest.java | 202 +++++++++++ .../core/channel/ChannelFactoryTestBase.java | 226 +++++++++++++ .../core/channel/ChannelHandlerTestBase.java | 77 +++++ .../core/channel/InFlightHandlerTest.java | 317 ++++++++++++++++++ .../core/channel/MockAuthenticator.java | 48 +++ .../core/channel/ProtocolInitHandlerTest.java | 290 ++++++++++++++++ .../core/channel/StreamIdGeneratorTest.java | 61 ++++ .../core/config/typesafe/MockOptions.java | 41 +++ .../typesafe/TypeSafeDriverConfigTest.java | 76 +++++ .../protocol/ByteBufPrimitiveCodecTest.java | 37 +- .../core/protocol/FrameDecoderTest.java | 113 +++++++ .../driver/internal/core/util/ByteBufs.java | 39 +++ .../util/netty/ConnectInitHandlerTest.java | 123 +++++++ core/src/test/resources/logback-test.xml | 28 ++ pom.xml | 35 ++ 73 files changed, 5686 insertions(+), 23 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/Authenticator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/auth/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/ProtocolUtils.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/package-info.java create mode 100644 core/src/main/resources/reference.conf create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockAuthenticator.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/ByteBufs.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java create mode 100644 core/src/test/resources/logback-test.xml diff --git a/core/pom.xml b/core/pom.xml index 5a766624ec7..1adcd132db3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -25,7 +25,7 @@ java-driver-parent 4.0.0-SNAPSHOT - + java-driver-core jar @@ -40,13 +40,37 @@ io.netty netty-handler + + com.google.guava + guava + + + com.typesafe + config + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + test + org.testng testng + test org.assertj assertj-core + test + + + org.mockito + mockito-core + test diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java new file mode 100644 index 00000000000..57a7640219e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.protocol.internal.ProtocolConstants; + +/** + * A protocol version supported by default by the driver. + * + *

Legacy versions 1 (Cassandra 1.2) and 2 (Cassandra 2.0) are not supported anymore. + */ +public enum CoreProtocolVersion implements ProtocolVersion { + + /** Version 3, supported by Cassandra 2.1 and above. */ + V3(ProtocolConstants.Version.V3, false), + + /** Version 4, supported by Cassandra 2.2 and above. */ + V4(ProtocolConstants.Version.V4, false), + + /** + * Version 5, currently supported as a beta preview in Cassandra 3.10 and above. + * + *

Do not use this in production. + * + * @see ProtocolVersion#isBeta() + */ + V5(ProtocolConstants.Version.V5, true); + + private final int code; + private final boolean beta; + + CoreProtocolVersion(int code, boolean beta) { + this.code = code; + this.beta = beta; + } + + public int getCode() { + return code; + } + + @Override + public boolean isBeta() { + return beta; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java new file mode 100644 index 00000000000..21a6b62fb81 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.internal.core.util.Strings; +import com.google.common.base.Preconditions; + +/** + * The identifier of CQL element (keyspace, table, column, etc). + * + *

It has two representations: + * + *

    + *
  • the "CQL" form, which is how you would type the identifier in a CQL query. It is + * case-insensitive unless enclosed in double quotation marks; in addition, identifiers that + * contain special characters (anything other than alphanumeric and underscore), or match CQL + * keywords, must be double-quoted (with inner double quotes escaped as {@code ""}). + *
  • the "internal" form, which is how the name is stored in Cassandra system tables. It is + * lower-case for case-sensitive identifiers, and in the exact case for case-sensitive + * identifiers. + *
+ * + * Examples: + * + * + * + * + * + * + * + * + * + *
Create statementCase-sensitive?CQL idInternal id
CREATE TABLE t(foo int PRIMARY KEY)Nofoofoo
CREATE TABLE t(Foo int PRIMARY KEY)Nofoofoo
CREATE TABLE t("Foo" int PRIMARY KEY)Yes"Foo"Foo
CREATE TABLE t("foo bar" int PRIMARY KEY)Yes"foo bar"foo bar
CREATE TABLE t("foo""bar" int PRIMARY KEY)Yes"foo""bar"foo"bar
CREATE TABLE t("create" int PRIMARY KEY)Yes (reserved keyword)"create"create
+ * + * This class provides a common representation and avoids any ambiguity about which form the + * identifier is in. Driver clients will generally want to create instances from the CQL form with + * {@link #fromCql(String)}. + * + *

There is no internal caching; if you reuse the same identifiers often, + */ +public class CqlIdentifier { + + // IMPLEMENTATION NOTES: + // This is used internally, and for all API methods where the overhead of requiring the client to + // create an instance is acceptable (metadata, statement.getKeyspace, etc.) + // One exception is named getters, where we keep raw strings with the 3.x rules. + + /** Creates an identifier from its {@link CqlIdentifier CQL form}. */ + public static CqlIdentifier fromCql(String cql) { + Preconditions.checkNotNull(cql, "cql must not be null"); + final String internal; + if (Strings.isDoubleQuoted(cql)) { + internal = Strings.unDoubleQuote(cql); + } else { + internal = cql.toLowerCase(); + Preconditions.checkArgument( + !Strings.needsDoubleQuotes(internal), "Invalid CQL form [%s]: needs double quotes", cql); + } + return fromInternal(internal); + } + + /** Creates an identifier from its {@link CqlIdentifier internal form}. */ + public static CqlIdentifier fromInternal(String internal) { + Preconditions.checkNotNull(internal, "internal must not be null"); + return new CqlIdentifier(internal); + } + + private final String internal; + + private CqlIdentifier(String internal) { + this.internal = internal; + } + + /** + * Returns the identifier in the "internal" format. + * + * @return the identifier in its exact case, unquoted. + */ + public String asInternal() { + return this.internal; + } + + /** + * Returns the identifier in a format appropriate for concatenation in a CQL query. + * + * @return the double-quoted form, always. Note that this is not the most compact representation + * of case-insensitive identifiers (see {@link #asPrettyCql()}. + */ + public String asCql() { + return Strings.doubleQuote(internal); + } + + /** + * Returns the identifier in a format appropriate for concatenation in a CQL query, using the + * simplest possible representation. + * + * @return if the identifier is case-insensitive, an unquoted, lower-case string. Otherwise, the + * double-quoted form. + */ + public String asPrettyCql() { + return Strings.needsDoubleQuotes(internal) ? Strings.doubleQuote(internal) : internal; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof CqlIdentifier) { + CqlIdentifier that = (CqlIdentifier) other; + return this.internal.equals(that.internal); + } else { + return false; + } + } + + @Override + public int hashCode() { + return internal.hashCode(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java new file mode 100644 index 00000000000..c0b8a8452c2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +/** + * A version of the native protocol used by the driver to communicate with the server. + * + *

The only reason to have a separate abstraction above {@link CoreProtocolVersion} is to + * accommodate for custom protocol extensions. If you're connecting to a standard Apache Cassandra + * cluster, all {@code ProtocolVersion}s are {@code CoreProtocolVersion} instances. + */ +public interface ProtocolVersion { + /** + * A numeric code that uniquely identifies the version (this is the code used in network frames). + */ + int getCode(); + + /** A string representation of the version. */ + String name(); + + /** + * Whether the protocol version is in a beta status. + * + *

Beta versions are intended for Cassandra development. They should be used in a regular + * application, beta features may break at any point. + */ + boolean isBeta(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java new file mode 100644 index 00000000000..26335a356da --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.google.common.collect.ImmutableList; +import java.net.SocketAddress; +import java.util.Collections; +import java.util.List; + +/** + * Indicates that we've attempted to connect to a Cassandra node with a protocol version that it + * cannot handle (e.g., connecting to a C* 2.1 node with protocol version 4). + */ +public class UnsupportedProtocolVersionException extends RuntimeException { + private static final long serialVersionUID = 0; + + private final SocketAddress address; + private final List attemptedVersions; + + public static UnsupportedProtocolVersionException forSingleAttempt( + SocketAddress address, ProtocolVersion attemptedVersion) { + String message = + String.format("[%s] Host does not support protocol version %s", address, attemptedVersion); + return new UnsupportedProtocolVersionException( + address, message, Collections.singletonList(attemptedVersion)); + } + + public static UnsupportedProtocolVersionException forNegotiation( + SocketAddress address, List attemptedVersions) { + String message = + String.format( + "[%s] Protocol negotiation failed: could not find a common version (attempted: %s)", + address, attemptedVersions); + return new UnsupportedProtocolVersionException( + address, message, ImmutableList.copyOf(attemptedVersions)); + } + + private UnsupportedProtocolVersionException( + SocketAddress address, String message, List attemptedVersions) { + super(message); + this.address = address; + this.attemptedVersions = attemptedVersions; + } + + /** The address of the node that threw the error. */ + public SocketAddress getAddress() { + return address; + } + + /** The versions that were attempted. */ + public List getAttemptedVersions() { + return attemptedVersions; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java new file mode 100644 index 00000000000..9f3fc2ff66b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import java.net.SocketAddress; + +/** + * Provides {@link Authenticator} instances to use when connecting to Cassandra nodes. + * + *

See {@link PlainTextAuthProvider} for an implementation which uses SASL PLAIN mechanism to + * authenticate using username/password strings. + */ +public interface AuthProvider { + + /** + * A provider that provides no authentication capability. + * + *

This is only useful as a placeholder when no authentication is to be used. + */ + AuthProvider NONE = + (host, serverAuthenticator) -> { + throw new AuthenticationException( + host, + String.format( + "Host %s requires authentication, but no authenticator configured", host)); + }; + + /** + * The authenticator to use when connecting to {@code host}. + * + * @param host the Cassandra host to connect to. + * @param serverAuthenticator the configured authenticator on the host. + * @return the authentication implementation to use. + */ + Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) + throws AuthenticationException; +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java new file mode 100644 index 00000000000..049e1ddddb9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import java.net.SocketAddress; + +/** Indicates an error during the authentication phase while connecting to a node. */ +public class AuthenticationException extends RuntimeException { + private static final long serialVersionUID = 0; + + private final SocketAddress address; + + public AuthenticationException(SocketAddress address, String message) { + super(String.format("Authentication error on host %s: %s", address, message)); + this.address = address; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/Authenticator.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/Authenticator.java new file mode 100644 index 00000000000..364b92b5d75 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/Authenticator.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletionStage; + +/** + * Handles SASL authentication with Cassandra servers. + * + *

Each time a new connection is created and the server requires authentication, a new instance + * of this class will be created by the corresponding {@link AuthProvider} to handle that + * authentication. The lifecycle of that new {@code Authenticator} will be: + * + *

    + *
  1. the {@link #initialResponse} method will be called. The initial return value will be sent + * to the server to initiate the handshake. + *
  2. the server will respond to each client response by either issuing a challenge or indicating + * that the authentication is complete (successfully or not). If a new challenge is issued, + * the authenticator's {@link #evaluateChallenge} method will be called to produce a response + * that will be sent to the server. This challenge/response negotiation will continue until + * the server responds that authentication is successful (or an {@link + * AuthenticationException} is raised). + *
  3. When the server indicates that authentication is successful, the {@link + * #onAuthenticationSuccess} method will be called with the last information that the server + * may optionally have sent. + *
+ * + * The exact nature of the negotiation between client and server is specific to the authentication + * mechanism configured server side. + * + *

Note that, since the methods in this interface will be invoked on a driver I/O thread, they + * all return asynchronous results. If your implementation performs heavy computations or blocking + * calls, you'll want to schedule them on a separate executor, and return a {@code CompletionStage} + * that represents their future completion. If your implementation is fast, lightweight and does not + * perform blocking operations, it might be acceptable to run it on I/O threads directly; in that + * case, implement {@link SyncAuthenticator} instead of this interface. + */ +public interface Authenticator { + + /** + * Obtain an initial response token for initializing the SASL handshake. + * + * @return a completion stage that will complete with the initial response to send to the server + * (which may be null). + */ + CompletionStage initialResponse(); + + /** + * Evaluate a challenge received from the server. Generally, this method should return null when + * authentication is complete from the client perspective. + * + * @param challenge the server's SASL challenge. + * @return a completion stage that will complete with the updated SASL token (which may be null to + * indicate the client requires no further action). + */ + CompletionStage evaluateChallenge(ByteBuffer challenge); + + /** + * Called when authentication is successful with the last information optionally sent by the + * server. + * + * @param token the information sent by the server with the authentication successful message. + * This will be {@code null} if the server sends no particular information on authentication + * success. + * @return a completion stage that completes when the authenticator is done processing this + * response. + */ + CompletionStage onAuthenticationSuccess(ByteBuffer token); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java new file mode 100644 index 00000000000..636a824e76b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.google.common.base.Charsets; +import java.net.SocketAddress; +import java.nio.ByteBuffer; + +/** + * A simple {@code AuthProvider} implementation. + * + *

This provider allows to programmatically define authentication information that will then + * apply to all hosts. The {@link Authenticator} instances it returns support SASL authentication + * using the PLAIN mechanism for version 2 (or above) of the CQL native protocol. + */ +public class PlainTextAuthProvider implements AuthProvider { + + private final DriverConfigProfile config; + + /** + * Create a new simple authentication information provider with the supplied credentials. + * + * @param config the configuration of the driver (default profile). + */ + public PlainTextAuthProvider(DriverConfigProfile config) { + this.config = config; + } + + /** + * Use the supplied credentials and the SASL PLAIN mechanism to login to the server. + * + * @param host the Cassandra host with which we want to authenticate. + * @param serverAuthenticator the configured authenticator on the host. + * @return an authenticator instance which can be used to perform authentication negotiations on + * behalf of the client. + */ + @Override + public Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) { + String username = config.getString(CoreDriverOption.AUTHENTICATION_CONFIG_USERNAME); + String password = config.getString(CoreDriverOption.AUTHENTICATION_CONFIG_PASSWORD); + return new PlainTextAuthenticator(username, password); + } + + /** + * Simple implementation of {@link Authenticator} which can perform authentication against + * Cassandra servers configured with {@code PasswordAuthenticator}. + */ + private static class PlainTextAuthenticator implements SyncAuthenticator { + + private final ByteBuffer initialToken; + + PlainTextAuthenticator(String username, String password) { + byte[] usernameBytes = username.getBytes(Charsets.UTF_8); + byte[] passwordBytes = password.getBytes(Charsets.UTF_8); + this.initialToken = ByteBuffer.allocate(usernameBytes.length + passwordBytes.length + 2); + initialToken.put((byte) 0); + initialToken.put(usernameBytes); + initialToken.put((byte) 0); + initialToken.put(passwordBytes); + initialToken.flip(); + } + + @Override + public ByteBuffer initialResponseSync() { + return initialToken.duplicate(); + } + + @Override + public ByteBuffer evaluateChallengeSync(ByteBuffer token) { + return null; + } + + @Override + public void onAuthenticationSuccessSync(ByteBuffer token) { + // no-op, the server should send nothing anyway + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java new file mode 100644 index 00000000000..fb4ebd33547 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * An authenticator that performs all of its operations synchronously, on the calling thread. + * + *

This is intended for simple implementations that are fast and lightweight enough, and do not + * perform any blocking operations. + */ +public interface SyncAuthenticator extends Authenticator { + + /** + * Obtain an initial response token for initializing the SASL handshake. + * + *

{@link #initialResponse()} calls this and wraps the result in an immediately completed + * future. + */ + ByteBuffer initialResponseSync(); + + /** + * Evaluate a challenge received from the server. + * + *

{@link #evaluateChallenge(ByteBuffer)} calls this and wraps the result in an immediately + * completed future. + */ + ByteBuffer evaluateChallengeSync(ByteBuffer challenge); + + /** + * Called when authentication is successful with the last information optionally sent by the + * server. + * + *

{@link #onAuthenticationSuccess(ByteBuffer)} calls this, and then returns an immediately + * completed future. + */ + void onAuthenticationSuccessSync(ByteBuffer token); + + @Override + default CompletionStage initialResponse() { + return CompletableFuture.completedFuture(initialResponseSync()); + } + + @Override + default CompletionStage evaluateChallenge(ByteBuffer challenge) { + return CompletableFuture.completedFuture(evaluateChallengeSync(challenge)); + } + + @Override + default CompletionStage onAuthenticationSuccess(ByteBuffer token) { + onAuthenticationSuccessSync(token); + return CompletableFuture.completedFuture(null); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/package-info.java new file mode 100644 index 00000000000..a69d3107ce4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Support for authentication between the driver and Cassandra nodes. + * + *

Authentication is performed on each newly open connection. It is customizable via the {@link + * com.datastax.oss.driver.api.core.auth.AuthProvider} interface. + */ +package com.datastax.oss.driver.api.core.auth; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java new file mode 100644 index 00000000000..c5649c4334f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +public enum CoreDriverOption implements DriverOption { + PROTOCOL_VERSION("protocol.version", false), + + CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), + CONNECTION_SET_KEYSPACE_TIMEOUT("connection.set-keyspace-timeout", true), + CONNECTION_MAX_FRAME_LENGTH("connection.max-frame-length", true), + CONNECTION_MAX_REQUESTS("connection.max-requests-per-connection", true), + CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), + CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.interval", true), + + AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), + AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false), + AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false); + + private final String path; + private final boolean required; + + CoreDriverOption(String path, boolean required) { + this.path = path; + this.required = required; + } + + @Override + public String getPath() { + return path; + } + + @Override + public boolean required() { + return required; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java new file mode 100644 index 00000000000..f250138cb5d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +/** + * The configuration of the driver. + * + *

Is is composed of options, that are organized into profiles. There is a default profile that + * is always present, and additional, named profiles, that can override part of the options. + * Profiles can be used to categorize queries that use the same parameters (for example, an + * "analytics" profile vs. a "transactional" profile). + */ +public interface DriverConfig { + DriverConfigProfile defaultProfile(); + + DriverConfigProfile getProfile(String profileName); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java new file mode 100644 index 00000000000..14b88163e2b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +import java.util.concurrent.TimeUnit; + +/** + * A profile in the driver's configuration. + * + * @see DriverConfig + */ +public interface DriverConfigProfile { + boolean isDefined(DriverOption option); + + int getInt(DriverOption option); + + String getString(DriverOption option); + + long getBytes(DriverOption option); + + long getDuration(DriverOption option, TimeUnit targetUnit); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java new file mode 100644 index 00000000000..22ebe27006f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +/** Describes an option in the driver's configuration. */ +public interface DriverOption { + String getPath(); + + boolean required(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/package-info.java new file mode 100644 index 00000000000..51c7f4d6fa9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The configuration of the driver. + * + *

The public API is completely agnostic to the underlying implementation (where the + * configuration is loaded from, what framework is used...). + */ +package com.datastax.oss.driver.api.core.config; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java new file mode 100644 index 00000000000..e05f3267672 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +/** + * Indicates that a write was attempted on a connection that already handles too many simultaneous + * requests. + */ +public class BusyConnectionException extends ConnectionException { + + public BusyConnectionException(int maxAvailableIds) { + super( + String.format( + "" + "Connection has exceeded its maximum of %d simultaneous requests", + maxAvailableIds)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java new file mode 100644 index 00000000000..9d91e183201 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +/** A generic error on a connection to a node. */ +public class ConnectionException extends RuntimeException { + public ConnectionException(String message) { + super(message); + } + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/package-info.java new file mode 100644 index 00000000000..c949e16ea75 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Types related to a connection to a Cassandra node. + * + *

The driver generally connects to multiple nodes, and may keep multiple connections to each + * node. + */ +package com.datastax.oss.driver.api.core.connection; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/core/package-info.java new file mode 100644 index 00000000000..d726f2c88b1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** The core API of the driver, that deals with query execution and cluster metadata. */ +package com.datastax.oss.driver.api.core; diff --git a/core/src/main/java/com/datastax/oss/driver/api/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/package-info.java new file mode 100644 index 00000000000..4dc1eef4bd5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The driver's public API. + * + *

This package, and all of its subpackages, contains all the types that are intended to be used + * by clients applications. Binary compatibility is guaranteed across minor versions. + */ +package com.datastax.oss.driver.api; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java new file mode 100644 index 00000000000..266545cefdb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.auth.AuthProvider; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; +import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.util.Reflection; +import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; +import com.datastax.oss.protocol.internal.Compressor; +import com.datastax.oss.protocol.internal.FrameCodec; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.typesafe.config.ConfigFactory; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import java.util.concurrent.ThreadFactory; + +/** + * Default implementation of the driver context. + * + *

All non-constant components are stored as lazy references. Deadlocks or stack overflows may + * occur if there are cycles in the object graph. + */ +public class DefaultDriverContext implements DriverContext { + + private final LazyReference config = + new LazyReference<>("config", this::buildDriverConfig); + private final LazyReference> compressor = + new LazyReference<>("compressor", this::buildCompressor); + private final LazyReference> frameCodec = + new LazyReference<>("frameCodec", this::buildFrameCodec); + private final LazyReference protocolVersionRegistry = + new LazyReference<>("protocolVersionRegistry", this::buildProtocolVersionRegistry); + private final LazyReference ioThreadFactory = + new LazyReference<>("ioThreadFactory", this::buildIoThreadFactory); + private final LazyReference ioEventLoopGroup = + new LazyReference<>("ioEventLoopGroup", this::buildIoEventLoopGroup); + private final LazyReference authProvider = + new LazyReference<>("authProvider", this::buildAuthProvider); + + private DriverConfig buildDriverConfig() { + return new TypeSafeDriverConfig( + ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + } + + private Compressor buildCompressor() { + // TODO build alternate implementation if specified in conf + return Compressor.none(); + } + + protected FrameCodec buildFrameCodec() { + return FrameCodec.defaultClient( + new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), compressor()); + } + + protected ProtocolVersionRegistry buildProtocolVersionRegistry() { + return new ProtocolVersionRegistry(); + } + + protected ThreadFactory buildIoThreadFactory() { + // TODO use the driver instance's name + return new ThreadFactoryBuilder().build(); + } + + protected EventLoopGroup buildIoEventLoopGroup() { + return new NioEventLoopGroup(0, ioThreadFactory()); + } + + protected AuthProvider buildAuthProvider() { + DriverConfigProfile defaultConfig = config().defaultProfile(); + CoreDriverOption classOption = CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS; + if (defaultConfig.isDefined(classOption)) { + String className = defaultConfig.getString(classOption); + return Reflection.buildWithConfig( + className, AuthProvider.class, defaultConfig, classOption.getPath()); + } else { + return AuthProvider.NONE; + } + } + + @Override + public DriverConfig config() { + return config.get(); + } + + @Override + public Compressor compressor() { + return compressor.get(); + } + + @Override + public FrameCodec frameCodec() { + return frameCodec.get(); + } + + @Override + public ProtocolVersionRegistry protocolVersionRegistry() { + return protocolVersionRegistry.get(); + } + + @Override + public ThreadFactory ioThreadFactory() { + return ioThreadFactory.get(); + } + + @Override + public EventLoopGroup ioEventLoopGroup() { + return ioEventLoopGroup.get(); + } + + @Override + public Class channelClass() { + return NioSocketChannel.class; + } + + @Override + public AuthProvider authProvider() { + return authProvider.get(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java new file mode 100644 index 00000000000..1a42bbc0dcc --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.auth.AuthProvider; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.protocol.internal.Compressor; +import com.datastax.oss.protocol.internal.FrameCodec; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import java.util.concurrent.ThreadFactory; + +/** + * Holder for common singletons that are shared throughout the driver. + * + *

There is an instance of this class for each driver instance. + * + *

This is essentially poor man's dependency injection (even the simplest DI frameworks are too + * overkill for our needs, and they introduce extra dependencies). + * + *

This also provides extension points for stuff that is too low-level for the configuration. + */ +public interface DriverContext { + + DriverConfig config(); + + Compressor compressor(); + + FrameCodec frameCodec(); + + ProtocolVersionRegistry protocolVersionRegistry(); + + ThreadFactory ioThreadFactory(); + + EventLoopGroup ioEventLoopGroup(); + + Class channelClass(); + + AuthProvider authProvider(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java new file mode 100644 index 00000000000..486974f4497 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.google.common.base.Preconditions; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; + +/** Manages all the native protocol versions supported by the driver. */ +public class ProtocolVersionRegistry { + private final NavigableMap versionsByCode; + + public ProtocolVersionRegistry(ProtocolVersion[]... versionRanges) { + this.versionsByCode = byCode(versionRanges); + } + + /** Default implementation, initialized with the core OSS versions. */ + public ProtocolVersionRegistry() { + this(CoreProtocolVersion.values()); + } + + public ProtocolVersion fromCode(int code) { + ProtocolVersion protocolVersion = versionsByCode.get(code); + if (protocolVersion == null) { + throw new IllegalArgumentException("Unknown protocol version code: " + code); + } + return protocolVersion; + } + + public ProtocolVersion fromName(String name) { + for (ProtocolVersion version : versionsByCode.values()) { + if (version.name().equals(name)) { + return version; + } + } + throw new IllegalArgumentException("Unknown protocol version name: " + name); + } + + public ProtocolVersion highestNonBeta() { + ProtocolVersion highest = versionsByCode.lastEntry().getValue(); + if (!highest.isBeta()) { + return highest; + } else { + return downgrade(highest) + .orElseThrow(() -> new AssertionError("There should be at least one non-beta version")); + } + } + + /** + * Downgrade to a lower version if the current version is not supported by the server. This is + * used during the protocol negotiation process. + * + * @return an empty optional if there is no version to downgrade to. + */ + public Optional downgrade(ProtocolVersion version) { + Map.Entry previousEntry = + versionsByCode.lowerEntry(version.getCode()); + if (previousEntry == null) { + return Optional.empty(); + } else { + ProtocolVersion previousVersion = previousEntry.getValue(); + // Beta versions are skipped during negotiation + return (previousVersion.isBeta()) ? downgrade(previousVersion) : Optional.of(previousVersion); + } + } + + private NavigableMap byCode(ProtocolVersion[][] versionRanges) { + NavigableMap map = new TreeMap<>(); + for (ProtocolVersion[] versionRange : versionRanges) { + for (ProtocolVersion version : versionRange) { + ProtocolVersion previous = map.put(version.getCode(), version); + Preconditions.checkArgument( + previous == null, + "Duplicate version code: %s in %s and %s", + version.getCode(), + previous, + version); + } + } + return map; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java new file mode 100644 index 00000000000..7ecb04bf125 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; +import com.datastax.oss.driver.internal.core.protocol.FrameEncoder; +import com.google.common.annotations.VisibleForTesting; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import java.net.SocketAddress; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** Builds {@link DriverChannel} objects for an instance of the driver. */ +public class ChannelFactory { + + private static final Logger LOG = LoggerFactory.getLogger(ChannelFactory.class); + + protected final DriverContext driverContext; + + /** either set from the configuration, or null and will be negotiated */ + @VisibleForTesting ProtocolVersion protocolVersion; + + @VisibleForTesting volatile String clusterName; + + public ChannelFactory(DriverContext driverContext) { + this.driverContext = driverContext; + + DriverConfigProfile defaultConfig = driverContext.config().defaultProfile(); + if (defaultConfig.isDefined(CoreDriverOption.PROTOCOL_VERSION)) { + String versionName = defaultConfig.getString(CoreDriverOption.PROTOCOL_VERSION); + this.protocolVersion = driverContext.protocolVersionRegistry().fromName(versionName); + } // else it will be negotiated with the first opened connection + } + + public CompletionStage connect( + final SocketAddress address, CqlIdentifier keyspace) { + CompletableFuture resultFuture = new CompletableFuture<>(); + + ProtocolVersion currentVersion; + boolean isNegotiating; + List attemptedVersions = new CopyOnWriteArrayList<>(); + if (this.protocolVersion != null) { + currentVersion = protocolVersion; + isNegotiating = false; + } else { + currentVersion = driverContext.protocolVersionRegistry().highestNonBeta(); + isNegotiating = true; + } + + connect(address, keyspace, currentVersion, isNegotiating, attemptedVersions, resultFuture); + return resultFuture; + } + + private void connect( + SocketAddress address, + CqlIdentifier keyspace, + final ProtocolVersion currentVersion, + boolean isNegotiating, + List attemptedVersions, + CompletableFuture resultFuture) { + + Bootstrap bootstrap = + new Bootstrap() + .group(driverContext.ioEventLoopGroup()) + .channel(driverContext.channelClass()) + .handler(initializer(currentVersion, keyspace)); + ChannelFuture connectFuture = bootstrap.connect(address); + + connectFuture.addListener( + cf -> { + if (connectFuture.isSuccess()) { + DriverChannel driverChannel = new DriverChannel(connectFuture.channel()); + // If this is the first successful connection, remember the protocol version and + // cluster name for future connections. + if (isNegotiating) { + ChannelFactory.this.protocolVersion = currentVersion; + } + if (ChannelFactory.this.clusterName == null) { + ChannelFactory.this.clusterName = driverChannel.getClusterName(); + } + resultFuture.complete(driverChannel); + } else { + Throwable error = connectFuture.cause(); + if (error instanceof UnsupportedProtocolVersionException && isNegotiating) { + attemptedVersions.add(currentVersion); + Optional downgraded = + driverContext.protocolVersionRegistry().downgrade(currentVersion); + if (downgraded.isPresent()) { + LOG.info( + "Failed to connect with protocol {}, retrying with {}", + currentVersion, + downgraded.get()); + connect(address, keyspace, downgraded.get(), true, attemptedVersions, resultFuture); + } else { + resultFuture.completeExceptionally( + UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); + } + } else { + resultFuture.completeExceptionally(error); + } + } + }); + } + + @VisibleForTesting + ChannelInitializer initializer( + final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) { + return new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) throws Exception { + DriverConfigProfile defaultConfigProfile = driverContext.config().defaultProfile(); + + long setKeyspaceTimeoutMillis = + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS); + int maxFrameLength = + (int) defaultConfigProfile.getBytes(CoreDriverOption.CONNECTION_MAX_FRAME_LENGTH); + int maxRequestsPerConnection = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + + // TODO SSL + // TODO hook to add custom handlers + InFlightHandler inFlightHandler = + new InFlightHandler( + protocolVersion, + new StreamIdGenerator(maxRequestsPerConnection), + setKeyspaceTimeoutMillis); + ProtocolInitHandler initHandler = + new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace); + channel + .pipeline() + .addLast("encoder", new FrameEncoder(driverContext.frameCodec())) + .addLast("decoder", new FrameDecoder(driverContext.frameCodec(), maxFrameLength)) + .addLast("inflight", inFlightHandler) + .addLast("heartbeat", new HeartbeatHandler(defaultConfigProfile)) + .addLast("init", initHandler); + } + }; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java new file mode 100644 index 00000000000..989dc35cc06 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import java.net.SocketAddress; + +/** + * Indicates that we've attempted to connect to a node with a cluster name doesn't match that of the + * other nodes known to the driver. + * + *

The driver runs the following query on each newly established connection: + * + *

+ *     select cluster_name from system.local
+ * 
+ * + * The first connection sets the cluster name for this driver instance, all subsequent connections + * must match it or they will get rejected. This is intended to filter out errors in the discovery + * process (for example, stale entries in {@code system.peers}). + */ +public class ClusterNameMismatchException extends Exception { + + private static final long serialVersionUID = 0; + + public final SocketAddress address; + public final String expectedClusterName; + public final String actualClusterName; + + public ClusterNameMismatchException( + SocketAddress address, String actualClusterName, String expectedClusterName) { + super( + String.format( + "Host %s reports cluster name '%s' that doesn't match our cluster name '%s'.", + address, actualClusterName, expectedClusterName)); + this.address = address; + this.expectedClusterName = expectedClusterName; + this.actualClusterName = actualClusterName; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java new file mode 100644 index 00000000000..d246eac1203 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.protocol.internal.Message; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * A thin wrapper around a Netty {@link Channel}, to send requests to a Cassandra node and receive + * responses. + */ +public class DriverChannel { + static final AttributeKey CLUSTER_NAME_KEY = AttributeKey.newInstance("cluster_name"); + static final Object FORCE_CLOSE_EVENT = new Object(); + + private final Channel channel; + + DriverChannel(Channel channel) { + this.channel = channel; + } + + /** + * @return a future that succeeds when the request frame was successfully written on the channel. + * Beyond that, the caller will be notified through the {@code responseCallback}. + */ + public Future write( + Message request, + boolean tracing, + Map customPayload, + ResponseCallback responseCallback) { + RequestMessage message = new RequestMessage(request, tracing, customPayload, responseCallback); + return channel.writeAndFlush(message); //TODO coalesce flushes + } + + /** + * Releases a stream id if the client was holding onto it, and has now determined that it can be + * safely reused. + * + * @see ResponseCallback#holdStreamId() + */ + public void release(int streamId) { + channel.pipeline().fireUserEventTriggered(new ReleaseEvent(streamId)); + } + + /** + * Switches the underlying Cassandra connection to a new keyspace (as if a {@code USE ...} + * statement was issued). + * + *

The future will complete once the change is effective. Only one change may run at a given + * time, concurrent attempts will fail. + * + *

Changing the keyspace is inherently thread-unsafe: if other queries are running at the same + * time, the keyspace they will use is unpredictable. + */ + public Future setKeyspace(CqlIdentifier newKeyspace) { + Promise promise = channel.eventLoop().newPromise(); + channel.pipeline().fireUserEventTriggered(new SetKeyspaceEvent(newKeyspace, promise)); + return promise; + } + + /** + * @return the name of the Cassandra cluster as returned by {@code system.local.cluster_name} on + * this connection. + */ + public String getClusterName() { + return channel.attr(CLUSTER_NAME_KEY).get(); + } + + public Future close() { + return channel.close(); + } + + public Future forceClose() { + ChannelFuture closeFuture = channel.close(); + channel.pipeline().fireUserEventTriggered(FORCE_CLOSE_EVENT); + return closeFuture; + } + + // This is essentially a stripped-down Frame. We can't materialize the frame before writing, + // because we need the stream id, which is assigned from within the event loop. + static class RequestMessage { + final Message request; + final boolean tracing; + final Map customPayload; + final ResponseCallback responseCallback; + + RequestMessage( + Message message, + boolean tracing, + Map customPayload, + ResponseCallback responseCallback) { + this.request = message; + this.tracing = tracing; + this.customPayload = customPayload; + this.responseCallback = responseCallback; + } + } + + static class ReleaseEvent { + final int streamId; + + ReleaseEvent(int streamId) { + this.streamId = streamId; + } + } + + static class SetKeyspaceEvent { + final CqlIdentifier keyspaceName; + final Promise promise; + + public SetKeyspaceEvent(CqlIdentifier keyspaceName, Promise promise) { + this.keyspaceName = keyspaceName; + this.promise = promise; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java new file mode 100644 index 00000000000..48dd8630005 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.request.Options; +import com.datastax.oss.protocol.internal.response.Supported; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import io.netty.handler.timeout.IdleStateHandler; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class HeartbeatHandler extends IdleStateHandler { + + private static final Logger LOG = LoggerFactory.getLogger(HeartbeatHandler.class); + + private final DriverConfigProfile defaultConfigProfile; + + private HeartbeatRequest request; + + HeartbeatHandler(DriverConfigProfile defaultConfigProfile) { + super( + (int) + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL, TimeUnit.SECONDS), + 0, + 0); + this.defaultConfigProfile = defaultConfigProfile; + } + + @Override + protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { + if (evt.state() == IdleState.READER_IDLE) { + if (this.request != null) { + LOG.warn( + "Not sending heartbeat because a previous one is still in progress. " + + "Check that {} is not lower than {}.", + CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL.getPath(), + CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT.getPath()); + } else { + long timeoutMillis = + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT, TimeUnit.MILLISECONDS); + this.request = new HeartbeatRequest(ctx, timeoutMillis); + this.request.send(); + } + } + } + + private class HeartbeatRequest extends InternalRequest { + + HeartbeatRequest(ChannelHandlerContext ctx, long timeoutMillis) { + super(ctx, timeoutMillis); + } + + @Override + String describe() { + return "heartbeat"; + } + + @Override + Message getRequest() { + return Options.INSTANCE; + } + + @Override + void onResponse(Message response) { + if (response instanceof Supported) { + LOG.debug("{} Heartbeat query succeeded", ctx.channel()); + HeartbeatHandler.this.request = null; + } else { + failOnUnexpected(response); + } + } + + @Override + void fail(Throwable cause) { + ctx.fireExceptionCaught(cause); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java new file mode 100644 index 00000000000..d9225307114 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.connection.BusyConnectionException; +import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; +import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; +import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.util.concurrent.Promise; +import java.util.Map; + +/** Manages requests that are currently executing on a channel. */ +public class InFlightHandler extends ChannelDuplexHandler { + private final ProtocolVersion protocolVersion; + private final StreamIdGenerator streamIds; + private final Map inFlight; + private final long setKeyspaceTimeoutMillis; + private ChannelPromise closePromise; + private SetKeyspaceRequest setKeyspaceRequest; + + InFlightHandler( + ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis) { + this.protocolVersion = protocolVersion; + this.streamIds = streamIds; + this.inFlight = Maps.newHashMapWithExpectedSize(streamIds.getMaxAvailableIds()); + this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) + throws Exception { + if (closePromise != null) { + promise.setFailure(new IllegalStateException("Channel is closing")); + return; + } + + int streamId = streamIds.acquire(); + if (streamId < 0) { + promise.setFailure(new BusyConnectionException(streamIds.getMaxAvailableIds())); + return; + } + + if (inFlight.containsKey(streamId)) { + promise.setFailure( + new IllegalStateException("Found pending callback for stream id " + streamId)); + return; + } + + RequestMessage message = (RequestMessage) msg; + Frame frame = + Frame.forRequest( + protocolVersion.getCode(), + streamId, + message.tracing, + message.customPayload, + message.request); + + inFlight.put(streamId, message.responseCallback); + ChannelFuture writeFuture = ctx.write(frame, promise); + if (message.responseCallback.holdStreamId()) { + writeFuture.addListener( + future -> { + if (future.isSuccess()) { + message.responseCallback.onStreamIdAssigned(streamId); + } + }); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Frame responseFrame = (Frame) msg; + int streamId = responseFrame.streamId; + + ResponseCallback responseCallback = inFlight.get(streamId); + if (responseCallback != null) { + if (!responseCallback.holdStreamId()) { + release(streamId, ctx); + } + responseCallback.onResponse(responseFrame); + } + super.channelRead(ctx, msg); + } + + /** Called if an exception was thrown while processing an inbound event (i.e. a response). */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + int streamId; + if (cause instanceof FrameDecodingException + && (streamId = ((FrameDecodingException) cause).streamId) >= 0) { + // We know which request matches the failing response, fail that one only + ResponseCallback responseCallback = release(streamId, ctx); + responseCallback.onFailure(cause.getCause()); + } else { + // Otherwise fail all pending requests + abortAllInFlight(new ConnectionException("Unexpected error on channel", cause)); + ctx.close(); + } + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + // Delay the actual close if there are pending requests + if (inFlight.isEmpty()) { + super.close(ctx, promise); + } else { + this.closePromise = promise; + } + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception { + if (event == DriverChannel.FORCE_CLOSE_EVENT) { + Preconditions.checkState( + closePromise != null, "Channel should be closed before sending FORCE_CLOSE event"); + // Note: this is guaranteed by DriverChannel.forceClose + + abortAllInFlight(new ConnectionException("Channel was force-closed")); + super.close(ctx, closePromise); + } else if (event instanceof ReleaseEvent) { + release(((ReleaseEvent) event).streamId, ctx); + } else if (event instanceof SetKeyspaceEvent) { + SetKeyspaceEvent setKeyspaceEvent = (SetKeyspaceEvent) event; + if (this.setKeyspaceRequest != null) { + setKeyspaceEvent.promise.setFailure( + new IllegalStateException( + "Got a keyspace change request while another one was already in progress. " + + "This is generally a sign that your application issues USE queries too rapidly.")); + } else { + this.setKeyspaceRequest = new SetKeyspaceRequest(ctx, setKeyspaceEvent); + this.setKeyspaceRequest.send(); + } + } else { + super.userEventTriggered(ctx, event); + } + } + + private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { + ResponseCallback responseCallback = inFlight.remove(streamId); + streamIds.release(streamId); + // If we're in the middle of an orderly close and this was the last request, actually close + // the channel now + if (closePromise != null && inFlight.isEmpty()) { + try { + super.close(ctx, closePromise); + } catch (Exception e) { + ctx.fireExceptionCaught(e); + } + } + return responseCallback; + } + + private void abortAllInFlight(Throwable cause) { + for (ResponseCallback responseCallback : inFlight.values()) { + responseCallback.onFailure(cause); + } + inFlight.clear(); + // It's not necessary to release the stream ids, since we always call this method right before + // closing the channel + } + + private class SetKeyspaceRequest extends InternalRequest { + + private final CqlIdentifier keyspaceName; + private final Promise promise; + + SetKeyspaceRequest(ChannelHandlerContext ctx, SetKeyspaceEvent setKeyspaceEvent) { + super(ctx, setKeyspaceTimeoutMillis); + this.keyspaceName = setKeyspaceEvent.keyspaceName; + this.promise = setKeyspaceEvent.promise; + } + + @Override + String describe() { + return "set keyspace " + keyspaceName; + } + + @Override + Message getRequest() { + return new Query("USE " + keyspaceName.asCql()); + } + + @Override + void onResponse(Message response) { + if (response instanceof SetKeyspace) { + if (promise.trySuccess(null)) { + InFlightHandler.this.setKeyspaceRequest = null; + } + } else { + failOnUnexpected(response); + } + } + + @Override + void fail(Throwable cause) { + if (promise.tryFailure(cause)) { + InFlightHandler.this.setKeyspaceRequest = null; + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java new file mode 100644 index 00000000000..4e062b2d310 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.internal.core.util.ProtocolUtils; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.response.Error; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** Common infrastructure to send a native protocol request from a channel handler. */ +abstract class InternalRequest implements ResponseCallback { + + final Channel channel; + final ChannelHandlerContext ctx; + private final long timeoutMillis; + + private ScheduledFuture timeoutFuture; + + InternalRequest(ChannelHandlerContext ctx, long timeoutMillis) { + this.ctx = ctx; + this.channel = ctx.channel(); + this.timeoutMillis = timeoutMillis; + } + + abstract String describe(); + + abstract Message getRequest(); + + abstract void onResponse(Message response); + + abstract void fail(Throwable cause); + + void send() { + assert channel.eventLoop().inEventLoop(); + DriverChannel.RequestMessage message = + new DriverChannel.RequestMessage(getRequest(), false, Frame.NO_PAYLOAD, this); + ChannelFuture writeFuture = channel.writeAndFlush(message); + writeFuture.addListener(this::writeListener); + } + + private void writeListener(Future writeFuture) { + if (writeFuture.isSuccess()) { + timeoutFuture = + channel.eventLoop().schedule(this::onTimeout, timeoutMillis, TimeUnit.MILLISECONDS); + } else { + fail(new ConnectionException(describe() + ": error writing ", writeFuture.cause())); + } + } + + @Override + public final void onResponse(Frame responseFrame) { + timeoutFuture.cancel(true); + onResponse(responseFrame.message); + } + + @Override + public final void onFailure(Throwable error) { + timeoutFuture.cancel(true); + fail(new ConnectionException(describe() + ": unexpected failure", error)); + } + + private void onTimeout() { + fail(new TimeoutException(describe() + ": timed out after " + timeoutMillis + " ms")); + } + + void failOnUnexpected(Message response) { + if (response instanceof Error) { + Error error = (Error) response; + fail( + new ConnectionException( + String.format( + "%s: unexpected server error [%s] %s", + describe(), ProtocolUtils.errorCodeString(error.code), error.message))); + } else { + fail( + new ConnectionException( + String.format( + "%s: unexpected server response opcode=%s", + describe(), ProtocolUtils.opcodeString(response.opcode)))); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java new file mode 100644 index 00000000000..367f13c35c6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.auth.AuthenticationException; +import com.datastax.oss.driver.api.core.auth.Authenticator; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.util.ProtocolUtils; +import com.datastax.oss.driver.internal.core.util.netty.ConnectInitHandler; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.AuthResponse; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.Startup; +import com.datastax.oss.protocol.internal.response.AuthChallenge; +import com.datastax.oss.protocol.internal.response.AuthSuccess; +import com.datastax.oss.protocol.internal.response.Authenticate; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.Ready; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.base.Charsets; +import io.netty.channel.ChannelHandlerContext; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the sequence of internal requests that we send on a channel before it's ready to accept + * user requests. + */ +class ProtocolInitHandler extends ConnectInitHandler { + private static final Logger LOG = LoggerFactory.getLogger(ProtocolInitHandler.class); + private static final Query CLUSTER_NAME_QUERY = + new Query("SELECT cluster_name FROM system.local"); + + private final DriverContext driverContext; + private final long timeoutMillis; + private final ProtocolVersion initialProtocolVersion; + private final CqlIdentifier keyspaceName; + // might be null if this is the first channel to this cluster + private final String expectedClusterName; + + ProtocolInitHandler( + DriverContext driverContext, + ProtocolVersion protocolVersion, + String expectedClusterName, + CqlIdentifier keyspaceName) { + + this.driverContext = driverContext; + + DriverConfigProfile defaultConfig = driverContext.config().defaultProfile(); + + this.timeoutMillis = + defaultConfig.getDuration( + CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS); + this.initialProtocolVersion = protocolVersion; + this.expectedClusterName = expectedClusterName; + this.keyspaceName = keyspaceName; + } + + @Override + protected void onRealConnect(ChannelHandlerContext ctx) { + new InitRequest(ctx).send(); + } + + private enum Step { + STARTUP, + GET_CLUSTER_NAME, + SET_KEYSPACE, + AUTH_RESPONSE + } + + private class InitRequest extends InternalRequest { + // This class is a finite-state automaton, that sends a different query depending on the step + // in the initialization sequence. + private Step step; + private Authenticator authenticator; + private ByteBuffer authReponseToken; + + InitRequest(ChannelHandlerContext ctx) { + super(ctx, timeoutMillis); + this.step = Step.STARTUP; + } + + @Override + String describe() { + return "init query " + step; + } + + @Override + Message getRequest() { + switch (step) { + case STARTUP: + return new Startup(); + case GET_CLUSTER_NAME: + return CLUSTER_NAME_QUERY; + case SET_KEYSPACE: + return new Query("USE " + keyspaceName.asCql()); + case AUTH_RESPONSE: + return new AuthResponse(authReponseToken); + default: + throw new AssertionError("unhandled step: " + step); + } + } + + @Override + void onResponse(Message response) { + LOG.trace( + "[{} {}] received response opcode={}", + channel, + step, + ProtocolUtils.opcodeString(response.opcode)); + try { + if (step == Step.STARTUP && response instanceof Ready) { + step = Step.GET_CLUSTER_NAME; + send(); + } else if (step == Step.STARTUP && response instanceof Authenticate) { + Authenticate authenticate = (Authenticate) response; + authenticator = + driverContext + .authProvider() + .newAuthenticator(channel.remoteAddress(), authenticate.authenticator); + authenticator + .initialResponse() + .whenCompleteAsync( + (token, error) -> { + if (error != null) { + fail(new ConnectionException("authenticator threw an exception", error)); + } else { + step = Step.AUTH_RESPONSE; + authReponseToken = token; + send(); + } + }, + channel.eventLoop()); + } else if (step == Step.AUTH_RESPONSE && response instanceof AuthChallenge) { + ByteBuffer challenge = ((AuthChallenge) response).token; + authenticator + .evaluateChallenge(challenge) + .whenCompleteAsync( + (token, error) -> { + if (error != null) { + fail(new ConnectionException("authenticator threw an exception", error)); + } else { + step = Step.AUTH_RESPONSE; + authReponseToken = token; + send(); + } + }, + channel.eventLoop()); + } else if (step == Step.AUTH_RESPONSE && response instanceof AuthSuccess) { + ByteBuffer token = ((AuthSuccess) response).token; + authenticator + .onAuthenticationSuccess(token) + .whenCompleteAsync( + (ignored, error) -> { + if (error != null) { + fail(new ConnectionException("authenticator threw an exception", error)); + } else { + step = Step.GET_CLUSTER_NAME; + send(); + } + }, + channel.eventLoop()); + } else if (step == Step.AUTH_RESPONSE + && response instanceof Error + && ((Error) response).code == ProtocolConstants.ErrorCode.AUTH_ERROR) { + fail( + new AuthenticationException( + channel.remoteAddress(), + String.format("server replied '%s'", ((Error) response).message))); + } else if (step == Step.GET_CLUSTER_NAME && response instanceof Rows) { + Rows rows = (Rows) response; + List row = rows.data.poll(); + String actualClusterName = getString(row, 0); + if (expectedClusterName != null && !expectedClusterName.equals(actualClusterName)) { + fail( + new ClusterNameMismatchException( + channel.remoteAddress(), actualClusterName, expectedClusterName)); + } else { + if (expectedClusterName == null) { + // Store the actual name so that it can be retrieved from the factory + channel.attr(DriverChannel.CLUSTER_NAME_KEY).set(actualClusterName); + } + if (keyspaceName == null) { + setConnectSuccess(); + } else { + step = Step.SET_KEYSPACE; + send(); + } + } + } else if (step == Step.SET_KEYSPACE && response instanceof SetKeyspace) { + setConnectSuccess(); + } else if (response instanceof Error) { + Error error = (Error) response; + // Testing for a specific string is a tad fragile but Cassandra doesn't give us a more precise error + // code. + // C* 2.1 reports a server error instead of protocol error, see CASSANDRA-9451. + if (step == Step.STARTUP + && (error.code == ProtocolConstants.ErrorCode.PROTOCOL_ERROR + || error.code == ProtocolConstants.ErrorCode.SERVER_ERROR) + && error.message.contains("Invalid or unsupported protocol version")) { + fail( + UnsupportedProtocolVersionException.forSingleAttempt( + channel.remoteAddress(), initialProtocolVersion)); + } else { + failOnUnexpected(error); + } + } else { + failOnUnexpected(response); + } + } catch (AuthenticationException e) { + fail(e); + } + } + + @Override + void fail(Throwable cause) { + setConnectFailure(cause); + } + } + + // TODO we'll probably need a lightweight ResultSet implementation for internal uses, but this is good for now + private String getString(List row, int i) { + ByteBuffer bytes = row.get(i); + if (bytes == null) { + return null; + } else if (bytes.remaining() == 0) { + return ""; + } else { + return new String(Bytes.getArray(bytes), Charsets.UTF_8); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java new file mode 100644 index 00000000000..f9a6399bb9c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.protocol.internal.Frame; + +/** + * The outcome of a request sent to a Cassandra node. + * + *

This comes into play after the request has been successfully written to the channel. + */ +public interface ResponseCallback { + + /** + * Invoked when the server replies (note that the response frame might contain an error message). + */ + void onResponse(Frame responseFrame); + + /** + * Invoked if there was an error while waiting for the response. + * + *

This is generally triggered when a channel fails (for example because of a heartbeat + * failure) and all pending requests are aborted. + */ + void onFailure(Throwable error); + + /** + * Whether to hold the stream id beyond the first response. + * + *

By default, this is false, and the channel will release the stream id (and make it available + * for other requests) as soon as {@link #onResponse(Frame)} or {@link #onFailure(Throwable)} gets + * invoked. + * + *

If this is true, the channel will keep the stream id assigned to this request, and {@code + * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int)} will be called to + * notify the caller of the stream id, and it is the caller's responsibility to determine when the + * request is over, and then call {@link DriverChannel#release(int)} to release the stream id. + * + *

This is intended to allow streaming requests, that would send multiple chunks of data in + * response to a single request (this feature does not exist yet in Cassandra but might be + * implemented in the future). + */ + default boolean holdStreamId() { + return false; + } + + /** + * Reports the stream id to the caller if {@link #holdStreamId()} is true. + * + *

By default, this will never get called. + */ + default void onStreamIdAssigned(int streamId) { + // nothing to do by default + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java new file mode 100644 index 00000000000..a4467cd83f9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import java.util.BitSet; + +/** Manages the set of stream ids used to distinguish multiplexed requests on a channel. */ +class StreamIdGenerator { + + private final int maxAvailableIds; + // unset = available, set = borrowed (note that this is the opposite of the 3.x implementation) + private final BitSet ids; + private int availableIds; + + StreamIdGenerator(int maxAvailableIds) { + this.maxAvailableIds = maxAvailableIds; + this.ids = new BitSet(this.maxAvailableIds); + this.availableIds = this.maxAvailableIds; + } + + int acquire() { + int id = ids.nextClearBit(0); + if (id >= maxAvailableIds) { + return -1; + } + ids.set(id); + availableIds--; + return id; + } + + void release(int id) { + if (ids.get(id)) { + availableIds++; + } else { + throw new IllegalStateException("Tried to release id that hadn't been borrowed: " + id); + } + ids.clear(id); + } + + int getAvailableIds() { + return availableIds; + } + + int getMaxAvailableIds() { + return maxAvailableIds; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/package-info.java new file mode 100644 index 00000000000..7c29c49e013 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** Handling of a single connection to a Cassandra node. */ +package com.datastax.oss.driver.internal.core.channel; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java new file mode 100644 index 00000000000..2d8e5ba5aa0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigObject; +import com.typesafe.config.ConfigValue; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.typesafe.config.ConfigValueType.OBJECT; + +public class TypeSafeDriverConfig implements DriverConfig { + + private final TypesafeDriverConfigProfile defaultProfile; + private final ConcurrentMap profiles; + + public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) { + Collection options = merge(optionArrays); + + // Process the raw configuration to extract profiles. For example: + // { + // foo = 1, bar = 2 + // profiles { + // custom1 { bar = 3 } + // } + // } + // Would produce a map with the following entries: + // "default" => { foo = 1, bar = 2 } + // "custom1" => { foo = 1, bar = 3 } + + Config defaultProfileConfig = config.withoutPath("profiles"); + validateRequired(defaultProfileConfig, options); + this.defaultProfile = new TypesafeDriverConfigProfile(defaultProfileConfig); + + this.profiles = new ConcurrentHashMap<>(); + ConfigObject rootObject = config.root(); + if (rootObject.containsKey("profiles") && rootObject.get("profiles").valueType() == OBJECT) { + ConfigObject profileConfigs = (ConfigObject) rootObject.get("profiles"); + for (String profileName : profileConfigs.keySet()) { + ConfigValue profileValue = profileConfigs.get(profileName); + if (profileValue.valueType() == OBJECT) { + Config profileConfig = ((ConfigObject) profileValue).toConfig(); + this.profiles.put( + profileName, + new TypesafeDriverConfigProfile(profileConfig.withFallback(defaultProfileConfig))); + } + } + } + } + + @Override + public DriverConfigProfile defaultProfile() { + return defaultProfile; + } + + @Override + public DriverConfigProfile getProfile(String profileName) { + Preconditions.checkArgument( + profiles.containsKey(profileName), + "Unknown profile '%s'. Check your configuration.", + profileName); + return profiles.get(profileName); + } + + private static void validateRequired(Config config, Collection options) { + for (DriverOption option : options) { + Preconditions.checkArgument( + !option.required() || config.hasPath(option.getPath()), + "Missing option %s. Check your configuration file.", + option.getPath()); + } + } + + private Collection merge(DriverOption[][] optionArrays) { + Preconditions.checkArgument(optionArrays.length > 0, "Must provide some options"); + ImmutableList.Builder optionsBuilder = ImmutableList.builder(); + for (DriverOption[] optionArray : optionArrays) { + for (DriverOption driverOption : optionArray) { + optionsBuilder.add(driverOption); + } + } + return optionsBuilder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java new file mode 100644 index 00000000000..64e92ad75ff --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.typesafe.config.Config; +import java.util.concurrent.TimeUnit; + +public class TypesafeDriverConfigProfile implements DriverConfigProfile { + private volatile Config config; + + public TypesafeDriverConfigProfile(Config config) { + this.config = config; + } + + @Override + public boolean isDefined(DriverOption option) { + return config.hasPath(option.getPath()); + } + + @Override + public int getInt(DriverOption option) { + return config.getInt(option.getPath()); + } + + @Override + public long getDuration(DriverOption option, TimeUnit targetUnit) { + return config.getDuration(option.getPath(), targetUnit); + } + + @Override + public String getString(DriverOption option) { + return config.getString(option.getPath()); + } + + @Override + public long getBytes(DriverOption option) { + return config.getBytes(option.getPath()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/package-info.java new file mode 100644 index 00000000000..befb93df93c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Implementation of the driver configuration based on the Typesafe config library. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java new file mode 100644 index 00000000000..c60a2ef7fdf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.protocol.internal.FrameCodec; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +public class FrameDecoder extends LengthFieldBasedFrameDecoder { + + // Where the length of the frame is located in the payload + private static final int LENGTH_FIELD_OFFSET = 5; + private static final int LENGTH_FIELD_LENGTH = 4; + + private final FrameCodec frameCodec; + + public FrameDecoder(FrameCodec frameCodec, int maxFrameLengthInBytes) { + super(maxFrameLengthInBytes, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, 0, 0, true); + this.frameCodec = frameCodec; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + int startIndex = in.readerIndex(); + try { + ByteBuf buffer = (ByteBuf) super.decode(ctx, in); + return (buffer == null) + ? null // did not receive whole frame yet, keep reading + : frameCodec.decode(buffer); + } catch (Exception e) { + // If decoding failed, try to read at least the stream id, so that the error can be + // propagated to the client request matching that id (otherwise we have to fail all + // pending requests on this channel) + int streamId; + try { + streamId = in.getShort(startIndex + 2); + } catch (Exception e1) { + // Should never happen, super.decode does not return a non-null buffer until the length + // field has been read, and the stream id comes before + streamId = -1; + // TODO log e1? + } + throw new FrameDecodingException(streamId, e); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java new file mode 100644 index 00000000000..28781214011 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import io.netty.handler.codec.DecoderException; + +public class FrameDecodingException extends DecoderException { + public final int streamId; + + public FrameDecodingException(int streamId, Throwable cause) { + super("Error decoding frame for streamId " + streamId, cause); + this.streamId = streamId; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java new file mode 100644 index 00000000000..a1ed25cc06f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.FrameCodec; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import java.util.List; + +@ChannelHandler.Sharable +public class FrameEncoder extends MessageToMessageEncoder { + + private final FrameCodec frameCodec; + + public FrameEncoder(FrameCodec frameCodec) { + this.frameCodec = frameCodec; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Frame frame, List out) throws Exception { + out.add(frameCodec.encode(frame)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/package-info.java new file mode 100644 index 00000000000..dc32aeb88de --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** Specialization of the native protocol layer for the driver, based on Netty. */ +package com.datastax.oss.driver.internal.core.protocol; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ProtocolUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ProtocolUtils.java new file mode 100644 index 00000000000..0c8cee244f9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ProtocolUtils.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.datastax.oss.protocol.internal.ProtocolConstants; + +public class ProtocolUtils { + /** + * Formats a message opcode for logs and error messages. + * + *

Note that the reason why we don't use enums is because the driver can be extended with + * custom opcodes. + */ + public static String opcodeString(int opcode) { + switch (opcode) { + case ProtocolConstants.Opcode.ERROR: + return "ERROR"; + case ProtocolConstants.Opcode.STARTUP: + return "STARTUP"; + case ProtocolConstants.Opcode.READY: + return "READY"; + case ProtocolConstants.Opcode.AUTHENTICATE: + return "AUTHENTICATE"; + case ProtocolConstants.Opcode.OPTIONS: + return "OPTIONS"; + case ProtocolConstants.Opcode.SUPPORTED: + return "SUPPORTED"; + case ProtocolConstants.Opcode.QUERY: + return "QUERY"; + case ProtocolConstants.Opcode.RESULT: + return "RESULT"; + case ProtocolConstants.Opcode.PREPARE: + return "PREPARE"; + case ProtocolConstants.Opcode.EXECUTE: + return "EXECUTE"; + case ProtocolConstants.Opcode.REGISTER: + return "REGISTER"; + case ProtocolConstants.Opcode.EVENT: + return "EVENT"; + case ProtocolConstants.Opcode.BATCH: + return "BATCH"; + case ProtocolConstants.Opcode.AUTH_CHALLENGE: + return "AUTH_CHALLENGE"; + case ProtocolConstants.Opcode.AUTH_RESPONSE: + return "AUTH_RESPONSE"; + case ProtocolConstants.Opcode.AUTH_SUCCESS: + return "AUTH_SUCCESS"; + default: + return "0x" + Integer.toHexString(opcode); + } + } + + /** + * Formats an error code for logs and error messages. + * + *

Note that the reason why we don't use enums is because the driver can be extended with + * custom codes. + */ + public static String errorCodeString(int errorCode) { + switch (errorCode) { + case ProtocolConstants.ErrorCode.SERVER_ERROR: + return "SERVER_ERROR"; + case ProtocolConstants.ErrorCode.PROTOCOL_ERROR: + return "PROTOCOL_ERROR"; + case ProtocolConstants.ErrorCode.AUTH_ERROR: + return "AUTH_ERROR"; + case ProtocolConstants.ErrorCode.UNAVAILABLE: + return "UNAVAILABLE"; + case ProtocolConstants.ErrorCode.OVERLOADED: + return "OVERLOADED"; + case ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING: + return "IS_BOOTSTRAPPING"; + case ProtocolConstants.ErrorCode.TRUNCATE_ERROR: + return "TRUNCATE_ERROR"; + case ProtocolConstants.ErrorCode.WRITE_TIMEOUT: + return "WRITE_TIMEOUT"; + case ProtocolConstants.ErrorCode.READ_TIMEOUT: + return "READ_TIMEOUT"; + case ProtocolConstants.ErrorCode.READ_FAILURE: + return "READ_FAILURE"; + case ProtocolConstants.ErrorCode.FUNCTION_FAILURE: + return "FUNCTION_FAILURE"; + case ProtocolConstants.ErrorCode.WRITE_FAILURE: + return "WRITE_FAILURE"; + case ProtocolConstants.ErrorCode.SYNTAX_ERROR: + return "SYNTAX_ERROR"; + case ProtocolConstants.ErrorCode.UNAUTHORIZED: + return "UNAUTHORIZED"; + case ProtocolConstants.ErrorCode.INVALID: + return "INVALID"; + case ProtocolConstants.ErrorCode.CONFIG_ERROR: + return "CONFIG_ERROR"; + case ProtocolConstants.ErrorCode.ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case ProtocolConstants.ErrorCode.UNPREPARED: + return "UNPREPARED"; + default: + return "0x" + Integer.toHexString(errorCode); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java new file mode 100644 index 00000000000..9cf0fa5da1b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.google.common.base.Preconditions; +import java.lang.reflect.Constructor; + +public class Reflection { + /** + * Loads a class by name. + * + *

This methods tries first with the current thread's context class loader (the intent is that + * if the driver is in a low-level loader of an application server -- e.g. bootstrap or system -- + * it can still fidn classes in the application's class loader). If it is null, it defaults to the + * class loader that loaded the class calling this method. + */ + public static Class loadClass(String className, String source) { + try { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + return Class.forName(className, true, contextClassLoader); + } else { + return Class.forName(className); + } + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + String.format("Can't load class %s (specified by %s)", className, source), e); + } + } + + /** + * Builds a new instance of a class given its name, expecting a public constructor that takes the + * driver's configuration as argument. + */ + public static T buildWithConfig( + String className, Class expectedSuperType, DriverConfigProfile config, String source) { + Class clazz = loadClass(className, source); + Preconditions.checkArgument( + expectedSuperType.isAssignableFrom(clazz), + "Expected class %s (specified by %s) to be a subtype of %s", + className, + source, + expectedSuperType.getName()); + + Constructor constructor; + try { + constructor = clazz.getConstructor(DriverConfigProfile.class); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException( + String.format( + "Expected class %s (specified by %s) " + + "to have an accessible constructor with a single %s argument", + className, source, DriverConfigProfile.class.getSimpleName())); + } + try { + Object instance = constructor.newInstance(config); + return expectedSuperType.cast(instance); + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("Error instantiating class %s (specified by %s)", className, source), e); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java new file mode 100644 index 00000000000..e62fda88575 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; + +public class Strings { + + /** + * Return {@code true} if the given string is surrounded by single quotes, and {@code false} + * otherwise. + * + * @param value The string to inspect. + * @return {@code true} if the given string is surrounded by single quotes, and {@code false} + * otherwise. + */ + public static boolean isQuoted(String value) { + return isQuoted(value, '\''); + } + + /** + * Quote the given string; single quotes are escaped. If the given string is null, this method + * returns a quoted empty string ({@code ''}). + * + * @param value The value to quote. + * @return The quoted string. + */ + public static String quote(String value) { + return quote(value, '\''); + } + + /** + * Unquote the given string if it is quoted; single quotes are unescaped. If the given string is + * not quoted, it is returned without any modification. + * + * @param value The string to unquote. + * @return The unquoted string. + */ + public static String unquote(String value) { + return unquote(value, '\''); + } + + /** + * Return {@code true} if the given string is surrounded by double quotes, and {@code false} + * otherwise. + * + * @param value The string to inspect. + * @return {@code true} if the given string is surrounded by double quotes, and {@code false} + * otherwise. + */ + public static boolean isDoubleQuoted(String value) { + return isQuoted(value, '\"'); + } + + /** + * Double quote the given string; double quotes are escaped. If the given string is null, this + * method returns a quoted empty string ({@code ""}). + * + * @param value The value to double quote. + * @return The double quoted string. + */ + public static String doubleQuote(String value) { + return quote(value, '"'); + } + + /** + * Unquote the given string if it is double quoted; double quotes are unescaped. If the given + * string is not double quoted, it is returned without any modification. + * + * @param value The string to un-double quote. + * @return The un-double quoted string. + */ + public static String unDoubleQuote(String value) { + return unquote(value, '"'); + } + + /** Whether a string needs double quotes to be a valid CQL identifier. */ + public static boolean needsDoubleQuotes(String s) { + // this method should only be called for C*-provided identifiers, + // so we expect it to be non-null and non-empty. + assert s != null && !s.isEmpty(); + char c = s.charAt(0); + if (!(c >= 97 && c <= 122)) // a-z + return true; + for (int i = 1; i < s.length(); i++) { + c = s.charAt(i); + if (!((c >= 48 && c <= 57) // 0-9 + || (c == 95) // _ + || (c >= 97 && c <= 122) // a-z + )) { + return true; + } + } + return isReservedCqlKeyword(s); + } + + /** + * Return {@code true} if the given string is surrounded by the quote character given, and {@code + * false} otherwise. + * + * @param value The string to inspect. + * @return {@code true} if the given string is surrounded by the quote character, and {@code + * false} otherwise. + */ + private static boolean isQuoted(String value, char quoteChar) { + return value != null + && value.length() > 1 + && value.charAt(0) == quoteChar + && value.charAt(value.length() - 1) == quoteChar; + } + + /** + * @param quoteChar " or ' + * @return A quoted empty string. + */ + private static String emptyQuoted(char quoteChar) { + // don't handle non quote characters, this is done so that these are interned and don't create + // repeated empty quoted strings. + assert quoteChar == '"' || quoteChar == '\''; + if (quoteChar == '"') return "\"\""; + else return "''"; + } + + /** + * Quotes text and escapes any existing quotes in the text. {@code String.replace()} is a bit too + * inefficient (see JAVA-67, JAVA-1262). + * + * @param text The text. + * @param quoteChar The character to use as a quote. + * @return The text with surrounded in quotes with all existing quotes escaped with (i.e. ' + * becomes '') + */ + private static String quote(String text, char quoteChar) { + if (text == null || text.isEmpty()) return emptyQuoted(quoteChar); + + int nbMatch = 0; + int start = -1; + do { + start = text.indexOf(quoteChar, start + 1); + if (start != -1) ++nbMatch; + } while (start != -1); + + // no quotes found that need to be escaped, simply surround in quotes and return. + if (nbMatch == 0) return quoteChar + text + quoteChar; + + // 2 for beginning and end quotes. + // length for original text + // nbMatch for escape characters to add to quotes to be escaped. + int newLength = 2 + text.length() + nbMatch; + char[] result = new char[newLength]; + result[0] = quoteChar; + result[newLength - 1] = quoteChar; + int newIdx = 1; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == quoteChar) { + // escape quote with another occurrence. + result[newIdx++] = c; + result[newIdx++] = c; + } else { + result[newIdx++] = c; + } + } + return new String(result); + } + + /** + * Unquotes text and unescapes non surrounding quotes. {@code String.replace()} is a bit too + * inefficient (see JAVA-67, JAVA-1262). + * + * @param text The text + * @param quoteChar The character to use as a quote. + * @return The text with surrounding quotes removed and non surrounding quotes unescaped (i.e. '' + * becomes ') + */ + private static String unquote(String text, char quoteChar) { + if (!isQuoted(text, quoteChar)) return text; + + if (text.length() == 2) return ""; + + String search = emptyQuoted(quoteChar); + int nbMatch = 0; + int start = -1; + do { + start = text.indexOf(search, start + 2); + // ignore the second to last character occurrence, as the last character is a quote. + if (start != -1 && start != text.length() - 2) ++nbMatch; + } while (start != -1); + + // no escaped quotes found, simply remove surrounding quotes and return. + if (nbMatch == 0) return text.substring(1, text.length() - 1); + + // length of the new string will be its current length - the number of occurrences. + int newLength = text.length() - nbMatch - 2; + char[] result = new char[newLength]; + int newIdx = 0; + // track whenever a quoteChar is encountered and the previous character is not a quoteChar. + boolean firstFound = false; + for (int i = 1; i < text.length() - 1; i++) { + char c = text.charAt(i); + if (c == quoteChar) { + if (firstFound) { + // The previous character was a quoteChar, don't add this to result, this action in + // effect removes consecutive quotes. + firstFound = false; + } else { + // found a quoteChar and the previous character was not a quoteChar, include in result. + firstFound = true; + result[newIdx++] = c; + } + } else { + // non quoteChar encountered, include in result. + result[newIdx++] = c; + firstFound = false; + } + } + return new String(result); + } + + private static boolean isReservedCqlKeyword(String id) { + return id != null && RESERVED_KEYWORDS.contains(id.toLowerCase()); + } + + private Strings() {} + + private static final Set RESERVED_KEYWORDS = + ImmutableSet.of( + // See https://github.com/apache/cassandra/blob/trunk/doc/cql3/CQL.textile#appendixA + "add", + "allow", + "alter", + "and", + "any", + "apply", + "asc", + "authorize", + "batch", + "begin", + "by", + "columnfamily", + "create", + "delete", + "desc", + "drop", + "each_quorum", + "from", + "grant", + "in", + "index", + "inet", + "infinity", + "insert", + "into", + "keyspace", + "keyspaces", + "limit", + "local_one", + "local_quorum", + "modify", + "nan", + "norecursive", + "of", + "on", + "one", + "order", + "password", + "primary", + "quorum", + "rename", + "revoke", + "schema", + "select", + "set", + "table", + "to", + "token", + "three", + "truncate", + "two", + "unlogged", + "update", + "use", + "using", + "where", + "with"); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java new file mode 100644 index 00000000000..db4324f109d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Holds a reference to an object that is initialized on first access. */ +public class LazyReference { + private static final Logger LOG = LoggerFactory.getLogger(LazyReference.class); + + private final String name; + private final Supplier supplier; + private volatile T value; + private ReentrantLock lock; + + public LazyReference(String name, Supplier supplier) { + this.name = name; + this.supplier = supplier; + } + + public T get() { + T t = value; + if (t == null) { + lock.lock(); + try { + t = value; + if (t == null) { + LOG.debug("Initializing {}", name); + value = t = supplier.get(); + } + } finally { + lock.unlock(); + } + } + return t; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java new file mode 100644 index 00000000000..62f06dbce1c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.netty; + +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.PromiseCombiner; +import java.net.SocketAddress; + +/** + * A handler that delays the promise returned by {@code bootstrap.connect()}, in order to run a + * custom initialization process before making the channel available to clients. + * + *

This handler is not shareable. It must be installed by the channel initializer, as the last + * channel in the pipeline. + * + *

It will be notified via {@link #onRealConnect(ChannelHandlerContext)} when the real underlying + * connection is established. It can then start sending messages on the connection, while external + * clients are still waiting on their promise. Once the custom initialization is finished, the + * clients' promise can be completed with {@link #setConnectSuccess()} or {@link + * #setConnectFailure(Throwable)}. + */ +public abstract class ConnectInitHandler extends ChannelDuplexHandler { + // the completion of the custom initialization process + private ChannelPromise initPromise; + private ChannelHandlerContext ctx; + + @Override + public void connect( + ChannelHandlerContext ctx, + SocketAddress remoteAddress, + SocketAddress localAddress, + ChannelPromise callerPromise) + throws Exception { + this.ctx = ctx; + initPromise = ctx.channel().newPromise(); + + // the completion of the real underlying connection: + ChannelPromise realConnectPromise = ctx.channel().newPromise(); + super.connect(ctx, remoteAddress, localAddress, realConnectPromise); + realConnectPromise.addListener(future -> onRealConnect(ctx)); + + // Make the caller's promise wait on the other two: + PromiseCombiner combiner = new PromiseCombiner(); + combiner.addAll(new Future[] {realConnectPromise, initPromise}); + combiner.finish(callerPromise); + } + + protected abstract void onRealConnect(ChannelHandlerContext ctx); + + protected void setConnectSuccess() { + if (initPromise.trySuccess()) { + ctx.pipeline().remove(this); + } + } + + protected void setConnectFailure(Throwable cause) { + if (initPromise.tryFailure(cause)) { + ctx.channel().close(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java new file mode 100644 index 00000000000..613d197db75 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** Generic internal utilities. */ +package com.datastax.oss.driver.internal.core.util.netty; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/package-info.java new file mode 100644 index 00000000000..c65af78cd1e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** Internal utilities specific to Netty. */ +package com.datastax.oss.driver.internal.core.util; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/package-info.java new file mode 100644 index 00000000000..1762dc5b031 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Internal implementation details of the driver. + * + *

The types present here (and in subpackages) should not be used from client applications. If + * you decide to use them, do so at your own risk: binary compatibility is best-effort, and we + * reserve the right to break things at any time. Documentation may be sparse + */ +package com.datastax.oss.driver.internal; diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf new file mode 100644 index 00000000000..123949d69d1 --- /dev/null +++ b/core/src/main/resources/reference.conf @@ -0,0 +1,62 @@ +# Reference configuration for the DataStax Java driver for Apache Cassandra®. +# +# Unless you use a custom mechanism to load your configuration (see DriverContext), all the values +# declared here will be used as defaults if you don't override them in your own `application.conf`. +# +# This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md. +datastax-java-driver { + protocol { + # The native protocol version to use. + # + # This option is not required. If it is absent, the driver will negotiate it with the *first* + # node it tries to connect to. More precisely, it will try with the highest supported version, + # and if not supported fallback to the second highest and so on. + # Once the version is set, it will be used for the lifetime of the driver instance. + # Auto detection can be problematic with mixed-version clusters: if the driver connects first + # to one of the higher-version nodes, it will negotiate a version that might not work when + # connecting to lower-version nodes later. You should force the lowest common protocol version + # in that case. + // version = V4 + } + connection { + # The timeout to use for internal queries that run as part of the initialization process, just + # after we open a connection. If this timeout fires, the initialization of the connection will + # fail. If this is the first connection ever, the driver will fail to initialize as well, + # otherwise it will retry the connection later. + init-query-timeout = 5 seconds + + # The timeout to use when the driver changes the keyspace on a connection at runtime (this + # happens when the client issues a `USE ...` query, and all connections belonging to the + # current session need to be updated). + set-keyspace-timeout = ${datastax-java-driver.connection.init-query-timeout} + + # The maximum number of requests that can be executed concurrently on a connection. + # This must be between 1 and 32768. + max-requests-per-connection = 32768 + + heartbeat { + # The heartbeat interval. If a connection stays idle for that duration (no reads), the driver + # sends a dummy message on it to make sure it's still alive. If not, the connection is + # trashed and replaced. + interval = 30 seconds + # How long the driver waits for the response to a heartbeat. If this timeout fires, the + # heartbeat is considered failed. + timeout = ${datastax-java-driver.connection.init-query-timeout} + } + # The maximum length of the frames supported by the driver. Beyond that limit, requests will + # fail with an exception + max-frame-length = 256 MB + } + authentication { + # The auth provider class to use. The driver expects this class to have a public constructor + # that takes a DriverConfigProfile argument. It will invoke it with the default configuration + # profile. + // provider-class = com.datastax.driver.api.core.auth.PlainTextAuthProvider + # The configuration of the auth provider. This is specific to each implementation, and will + # therefore depend on the provider-class configured above. + // config { + // username = cassandra + // password = cassandra + // } + } +} \ No newline at end of file diff --git a/core/src/test/java/com/datastax/oss/driver/Assertions.java b/core/src/test/java/com/datastax/oss/driver/Assertions.java index d6a595ef27a..43b4414b754 100644 --- a/core/src/test/java/com/datastax/oss/driver/Assertions.java +++ b/core/src/test/java/com/datastax/oss/driver/Assertions.java @@ -15,10 +15,28 @@ */ package com.datastax.oss.driver; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.internal.core.CompletionStageAssert; +import com.datastax.oss.driver.internal.core.DriverConfigAssert; +import com.datastax.oss.driver.internal.core.NettyFutureAssert; import io.netty.buffer.ByteBuf; +import io.netty.util.concurrent.Future; +import java.util.concurrent.CompletionStage; public class Assertions extends org.assertj.core.api.Assertions { public static ByteBufAssert assertThat(ByteBuf actual) { return new ByteBufAssert(actual); } + + public static DriverConfigAssert assertThat(DriverConfig actual) { + return new DriverConfigAssert(actual); + } + + public static NettyFutureAssert assertThat(Future actual) { + return new NettyFutureAssert<>(actual); + } + + public static CompletionStageAssert assertThat(CompletionStage actual) { + return new CompletionStageAssert<>(actual); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java new file mode 100644 index 00000000000..aa679114c90 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class CqlIdentifierTest { + @Test + public void should_build_from_internal() { + assertThat(CqlIdentifier.fromInternal("foo").asInternal()).isEqualTo("foo"); + assertThat(CqlIdentifier.fromInternal("Foo").asInternal()).isEqualTo("Foo"); + assertThat(CqlIdentifier.fromInternal("foo bar").asInternal()).isEqualTo("foo bar"); + assertThat(CqlIdentifier.fromInternal("foo\"bar").asInternal()).isEqualTo("foo\"bar"); + assertThat(CqlIdentifier.fromInternal("create").asInternal()).isEqualTo("create"); + } + + @Test + public void should_build_from_valid_cql() { + assertThat(CqlIdentifier.fromCql("foo").asInternal()).isEqualTo("foo"); + assertThat(CqlIdentifier.fromCql("Foo").asInternal()).isEqualTo("foo"); + assertThat(CqlIdentifier.fromCql("\"Foo\"").asInternal()).isEqualTo("Foo"); + assertThat(CqlIdentifier.fromCql("\"foo bar\"").asInternal()).isEqualTo("foo bar"); + assertThat(CqlIdentifier.fromCql("\"foo\"\"bar\"").asInternal()).isEqualTo("foo\"bar"); + assertThat(CqlIdentifier.fromCql("\"create\"").asInternal()).isEqualTo("create"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_build_from_valid_cql_if_special_characters() { + CqlIdentifier.fromCql("foo bar"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_build_from_valid_cql_if_reserved_keyword() { + CqlIdentifier.fromCql("Create"); + } + + @Test + public void should_format_as_cql() { + assertThat(CqlIdentifier.fromInternal("foo").asCql()).isEqualTo("\"foo\""); + assertThat(CqlIdentifier.fromInternal("Foo").asCql()).isEqualTo("\"Foo\""); + assertThat(CqlIdentifier.fromInternal("foo bar").asCql()).isEqualTo("\"foo bar\""); + assertThat(CqlIdentifier.fromInternal("foo\"bar").asCql()).isEqualTo("\"foo\"\"bar\""); + assertThat(CqlIdentifier.fromInternal("create").asCql()).isEqualTo("\"create\""); + } + + @Test + public void should_format_as_pretty_cql() { + assertThat(CqlIdentifier.fromInternal("foo").asPrettyCql()).isEqualTo("foo"); + assertThat(CqlIdentifier.fromInternal("Foo").asPrettyCql()).isEqualTo("\"Foo\""); + assertThat(CqlIdentifier.fromInternal("foo bar").asPrettyCql()).isEqualTo("\"foo bar\""); + assertThat(CqlIdentifier.fromInternal("foo\"bar").asPrettyCql()).isEqualTo("\"foo\"\"bar\""); + assertThat(CqlIdentifier.fromInternal("create").asPrettyCql()).isEqualTo("\"create\""); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java new file mode 100644 index 00000000000..bb5d85157b1 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.fail; + +public class CompletionStageAssert + extends AbstractAssert, CompletionStage> { + + public CompletionStageAssert(CompletionStage actual) { + super(actual, CompletionStageAssert.class); + } + + public CompletionStageAssert isSuccess(Consumer valueAssertions) { + try { + V value = actual.toCompletableFuture().get(100, TimeUnit.MILLISECONDS); + valueAssertions.accept(value); + } catch (TimeoutException e) { + fail("Future did not complete within the timeout"); + } catch (Throwable t) { + fail("Unexpected error while waiting on the future", t); + } + return this; + } + + public CompletionStageAssert isSuccess() { + return isSuccess(v -> {}); + } + + public CompletionStageAssert isFailed(Consumer failureAssertions) { + try { + actual.toCompletableFuture().get(100, TimeUnit.MILLISECONDS); + fail("Expected completion stage to fail"); + } catch (TimeoutException e) { + fail("Future did not complete within the timeout"); + } catch (InterruptedException e) { + fail("Interrupted while waiting for future to fail"); + } catch (ExecutionException e) { + failureAssertions.accept(e.getCause()); + } + return this; + } + + public CompletionStageAssert isFailed() { + return isFailed(f -> {}); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java new file mode 100644 index 00000000000..67626310dab --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverOption; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DriverConfigAssert extends AbstractAssert { + public DriverConfigAssert(DriverConfig actual) { + super(actual, DriverConfigAssert.class); + } + + public DriverConfigAssert hasIntOption(DriverOption option, int expected) { + assertThat(actual.defaultProfile().getInt(option)).isEqualTo(expected); + return this; + } + + public DriverConfigAssert hasIntOption(String profileName, DriverOption option, int expected) { + assertThat(actual.getProfile(profileName).getInt(option)).isEqualTo(expected); + return this; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java new file mode 100644 index 00000000000..7285cdc4f59 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import io.netty.util.concurrent.Future; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class NettyFutureAssert extends AbstractAssert, Future> { + + public NettyFutureAssert(Future actual) { + super(actual, NettyFutureAssert.class); + } + + public NettyFutureAssert isNotDone() { + assertThat(actual.isDone()).isFalse(); + return this; + } + + public NettyFutureAssert isSuccess(Consumer valueAssertions) { + try { + V value = actual.get(100, TimeUnit.MILLISECONDS); + valueAssertions.accept(value); + } catch (TimeoutException e) { + fail("Future did not complete within the timeout"); + } catch (Throwable t) { + fail("Unexpected error while waiting on the future", t); + } + return this; + } + + public NettyFutureAssert isSuccess() { + return isSuccess(v -> {}); + } + + public NettyFutureAssert isFailed(Consumer failureAssertions) { + try { + actual.get(100, TimeUnit.MILLISECONDS); + fail("Expected future to fail"); + } catch (TimeoutException e) { + fail("Future did not fail within the timeout"); + } catch (InterruptedException e) { + fail("Interrupted while waiting for future to fail"); + } catch (ExecutionException e) { + failureAssertions.accept(e.getCause()); + } + return this; + } + + public NettyFutureAssert isFailed() { + return isFailed(f -> {}); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java new file mode 100644 index 00000000000..825711216f5 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.util.Optional; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ProtocolVersionRegistryTest { + + private static ProtocolVersion V3 = new MockProtocolVersion(3, false); + private static ProtocolVersion V4 = new MockProtocolVersion(4, false); + private static ProtocolVersion V5 = new MockProtocolVersion(5, false); + private static ProtocolVersion V5_BETA = new MockProtocolVersion(5, true); + private static ProtocolVersion V10 = new MockProtocolVersion(10, false); + private static ProtocolVersion V11 = new MockProtocolVersion(11, false); + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Duplicate version code: 5 in V5 and V5_BETA" + ) + public void should_fail_if_duplicate_version_code() { + new ProtocolVersionRegistry(new ProtocolVersion[] {V5, V5_BETA}); + } + + @Test + public void should_find_version_by_name() { + ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + assertThat(versions.fromName("V3")).isEqualTo(V3); + assertThat(versions.fromName("V4")).isEqualTo(V4); + } + + @Test + public void should_downgrade_if_lower_version_available() { + ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + Optional downgraded = versions.downgrade(V4); + downgraded.map(version -> assertThat(version).isEqualTo(V3)).orElseThrow(AssertionError::new); + } + + @Test + public void should_not_downgrade_if_no_lower_version() { + ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + Optional downgraded = versions.downgrade(V3); + assertThat(downgraded.isPresent()).isFalse(); + } + + @Test + public void should_downgrade_across_version_range() { + ProtocolVersionRegistry versions = + new ProtocolVersionRegistry( + new ProtocolVersion[] {V3, V4}, new ProtocolVersion[] {V10, V11}); + Optional downgraded = versions.downgrade(V10); + downgraded.map(version -> assertThat(version).isEqualTo(V4)).orElseThrow(AssertionError::new); + } + + @Test + public void should_downgrade_skipping_beta_version() { + ProtocolVersionRegistry versions = + new ProtocolVersionRegistry( + new ProtocolVersion[] {V4, V5_BETA}, new ProtocolVersion[] {V10, V11}); + Optional downgraded = versions.downgrade(V10); + downgraded.map(version -> assertThat(version).isEqualTo(V4)).orElseThrow(AssertionError::new); + } + + private static class MockProtocolVersion implements ProtocolVersion { + private final int code; + private final boolean beta; + + MockProtocolVersion(int code, boolean beta) { + this.code = code; + this.beta = beta; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String name() { + return "V" + code; + } + + @Override + public boolean isBeta() { + return beta; + } + + @Override + public String toString() { + return name() + (beta ? "_BETA" : ""); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java new file mode 100644 index 00000000000..7a684f047f0 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.RawType; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.RowsMetadata; +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Queue; + +public class TestResponses { + /** The response to the query run by each connection to check if the cluster name matches. */ + public static Rows clusterNameResponse(String actualClusterName) { + ColumnSpec colSpec = + new ColumnSpec( + "system", + "local", + "cluster_name", + RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR)); + RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); + Queue> data = Lists.newLinkedList(); + data.add(Lists.newArrayList(ByteBuffer.wrap(actualClusterName.getBytes(Charsets.UTF_8)))); + return new Rows(metadata, data); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java new file mode 100644 index 00000000000..e3361cde259 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.TestResponses; +import com.datastax.oss.protocol.internal.response.Ready; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ChannelFactoryClusterNameTest extends ChannelFactoryTestBase { + + @Test + public void should_set_cluster_name_from_first_connection() { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(false); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); + + // Then + assertThat(channelFuture).isSuccess(); + assertThat(factory.clusterName).isEqualTo("mockClusterName"); + } + + @Test + public void should_check_cluster_name_for_next_connections() throws Throwable { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(false); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + // open a first connection that will define the cluster name + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); + assertThat(channelFuture).isSuccess(); + // open a second connection that returns the same cluster name + channelFuture = factory.connect(SERVER_ADDRESS, null); + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); + + // Then + assertThat(channelFuture).isSuccess(); + + // When + // open a third connection that returns a different cluster name + channelFuture = factory.connect(SERVER_ADDRESS, null); + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); + + // Then + assertThat(channelFuture) + .isFailed( + e -> + assertThat(e) + .isInstanceOf(ClusterNameMismatchException.class) + .hasMessageContaining( + "reports cluster name 'wrongClusterName' that doesn't match " + + "our cluster name 'mockClusterName'.")); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java new file mode 100644 index 00000000000..2d74ebb8f4b --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.TestResponses; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.Ready; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ChannelFactoryProtocolNegotiationTest extends ChannelFactoryTestBase { + + @Test + public void should_succeed_if_version_specified_and_supported_by_server() { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn("V4"); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + Frame requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + + // Then + assertThat(channelFuture) + .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); + assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); + } + + @Test(dataProvider = "unsupportedProtocolCodes") + public void should_fail_if_version_specified_and_not_supported_by_server(int errorCode) { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn("V4"); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + // Server does not support v4 + writeInboundFrame( + requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); + + // Then + assertThat(channelFuture) + .isFailed( + e -> { + assertThat(e) + .isInstanceOf(UnsupportedProtocolVersionException.class) + .hasMessageContaining("Host does not support protocol version V4"); + assertThat(((UnsupportedProtocolVersionException) e).getAttemptedVersions()) + .containsExactly(CoreProtocolVersion.V4); + }); + } + + @Test + public void should_succeed_if_version_not_specified_and_server_supports_latest_supported() { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(false); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + + // Then + assertThat(channelFuture) + .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); + assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); + } + + @Test(dataProvider = "unsupportedProtocolCodes") + public void should_negotiate_if_version_not_specified_and_server_supports_legacy(int errorCode) { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(false); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V4)) + .thenReturn(Optional.of(CoreProtocolVersion.V3)); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + // Server does not support v4 + writeInboundFrame( + requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); + + // Then + // Factory should initialize a new connection, that retries with the lower version + requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V3.getCode()); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + assertThat(channelFuture) + .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); + assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V3); + } + + @Test(dataProvider = "unsupportedProtocolCodes") + public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) { + // Given + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(false); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V4)) + .thenReturn(Optional.of(CoreProtocolVersion.V3)); + Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V3)) + .thenReturn(Optional.empty()); + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + // Server does not support v4 + writeInboundFrame( + requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); + + // Client retries with v3 + requestFrame = readOutboundFrame(); + assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V3.getCode()); + // Server does not support v3 + writeInboundFrame( + requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); + + // Then + assertThat(channelFuture) + .isFailed( + e -> { + assertThat(e) + .isInstanceOf(UnsupportedProtocolVersionException.class) + .hasMessageContaining( + "Protocol negotiation failed: could not find a common version " + + "(attempted: [V4, V3])"); + assertThat(((UnsupportedProtocolVersionException) e).getAttemptedVersions()) + .containsExactly(CoreProtocolVersion.V4, CoreProtocolVersion.V3); + }); + } + + /** + * Depending on the Cassandra version, an "unsupported protocol" response can use different error + * codes, so we test all of them. + */ + @DataProvider(name = "unsupportedProtocolCodes") + public static Object[][] unsupportedProtocolCodes() { + return new Object[][] { + new Object[] {ProtocolConstants.ErrorCode.PROTOCOL_ERROR}, + // C* 2.1 reports a server error instead of protocol error, see CASSANDRA-9451. + new Object[] {ProtocolConstants.ErrorCode.SERVER_ERROR} + }; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java new file mode 100644 index 00000000000..917c47101b9 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.protocol.internal.Compressor; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.FrameCodec; +import com.datastax.oss.protocol.internal.Message; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.local.LocalChannel; +import io.netty.channel.local.LocalServerChannel; +import java.util.Collections; +import java.util.concurrent.Exchanger; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.fail; + +/** + * Sets up the infrastructure for channel factory tests. + * + *

Because the factory manages channel creation itself, {@link + * io.netty.channel.embedded.EmbeddedChannel} is not suitable. Instead, we launch an embedded server + * and connect to it with the local transport. + * + *

The current implementation assumes that only one connection will be tested at a time, but + * support for multiple simultaneous connections could easily be added: store multiple instances of + * requestFrameExchanger and serverResponseChannel, and add a parameter to readOutboundFrame and + * writeInboundFrame (for instance the position of the connection in creation order) to specify + * which instance to use. + */ +abstract class ChannelFactoryTestBase { + static final LocalAddress SERVER_ADDRESS = + new LocalAddress(ChannelFactoryTestBase.class.getSimpleName() + "-server"); + + DefaultEventLoopGroup serverGroup; + DefaultEventLoopGroup clientGroup; + + @Mock DriverContext driverContext; + @Mock DriverConfig driverConfig; + @Mock DriverConfigProfile defaultConfigProfile; + @Mock ProtocolVersionRegistry protocolVersionRegistry; + + // The server's I/O thread will store the last received request here, and block until the test + // thread retrieves it. This assumes readOutboundFrame() is called for each actual request, else + // the test will hang forever. + private final Exchanger requestFrameExchanger = new Exchanger<>(); + + // The channel that accepts incoming connections on the server + private LocalServerChannel serverAcceptChannel; + // The channel to send responses to the last open connection + private volatile LocalChannel serverResponseChannel; + + @BeforeMethod + public void setup() throws InterruptedException { + MockitoAnnotations.initMocks(this); + + serverGroup = new DefaultEventLoopGroup(1); + clientGroup = new DefaultEventLoopGroup(1); + + Mockito.when(driverContext.config()).thenReturn(driverConfig); + Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) + .thenReturn(false); + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS)) + .thenReturn(100L); + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS)) + .thenReturn(100L); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) + .thenReturn(1); + + Mockito.when(driverContext.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when(driverContext.ioEventLoopGroup()).thenReturn(clientGroup); + Mockito.when(driverContext.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); + Mockito.when(driverContext.frameCodec()) + .thenReturn( + FrameCodec.defaultClient( + new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); + + // Start local server + ServerBootstrap serverBootstrap = + new ServerBootstrap() + .group(serverGroup) + .channel(LocalServerChannel.class) + .localAddress(SERVER_ADDRESS) + .childHandler(new ServerInitializer()); + ChannelFuture channelFuture = serverBootstrap.bind().sync(); + serverAcceptChannel = (LocalServerChannel) channelFuture.sync().channel(); + } + + // Sets up the pipeline for our local server + private class ServerInitializer extends ChannelInitializer { + @Override + protected void initChannel(LocalChannel ch) throws Exception { + // Install a single handler that stores received requests, so that the test can check what + // the client sent + ch.pipeline() + .addLast( + new ChannelInboundHandlerAdapter() { + @Override + @SuppressWarnings("unchecked") + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + super.channelRead(ctx, msg); + requestFrameExchanger.exchange((Frame) msg); + } + }); + + // Store the channel so that the test can send responses back to the client + serverResponseChannel = ch; + } + } + + protected Frame readOutboundFrame() { + try { + return requestFrameExchanger.exchange(null, 100, MILLISECONDS); + } catch (InterruptedException e) { + fail("unexpected interruption while waiting for outbound frame", e); + } catch (TimeoutException e) { + fail("Timed out reading outbound frame"); + } + return null; // never reached + } + + protected void writeInboundFrame(Frame requestFrame, Message response) { + writeInboundFrame(requestFrame, response, requestFrame.protocolVersion); + } + + private void writeInboundFrame(Frame requestFrame, Message response, int protocolVersion) { + serverResponseChannel.writeAndFlush( + Frame.forResponse( + protocolVersion, + requestFrame.streamId, + null, + Frame.NO_PAYLOAD, + Collections.emptyList(), + response)); + } + + ChannelFactory newChannelFactory() { + return new TestChannelFactory(driverContext); + } + + // A simplified channel factory to use in the tests. + // It only installs high-level handlers on the pipeline, not the frame codecs. So we'll receive + // Frame objects on the server side, which is simpler to test. + private static class TestChannelFactory extends ChannelFactory { + + private TestChannelFactory(DriverContext driverContext) { + super(driverContext); + } + + @Override + ChannelInitializer initializer( + ProtocolVersion protocolVersion, CqlIdentifier keyspace) { + return new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) throws Exception { + DriverConfigProfile defaultConfigProfile = driverContext.config().defaultProfile(); + + long setKeyspaceTimeoutMillis = + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS); + int maxRequestsPerConnection = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + + InFlightHandler inFlightHandler = + new InFlightHandler( + protocolVersion, + new StreamIdGenerator(maxRequestsPerConnection), + setKeyspaceTimeoutMillis); + ProtocolInitHandler initHandler = + new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace); + channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); + } + }; + } + } + + @AfterMethod + public void tearDown() throws InterruptedException { + serverAcceptChannel.close(); + + serverGroup.shutdownGracefully(100, 100, TimeUnit.MILLISECONDS).sync(); + clientGroup.shutdownGracefully(100, 100, TimeUnit.MILLISECONDS).sync(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java new file mode 100644 index 00000000000..c1a055fcc88 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import io.netty.channel.embedded.EmbeddedChannel; +import java.util.Collections; +import org.testng.annotations.BeforeMethod; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Infrastructure for channel handler test. + * + *

It relies on an embedded channel where the tested handler is installed. Then the test can + * simulate incoming/outgoing messages, and check that the handler propagates the adequate messages + * upstream/downstream. + */ +public class ChannelHandlerTestBase { + protected EmbeddedChannel channel; + + @BeforeMethod + public void setup() { + channel = new EmbeddedChannel(); + } + + /** Reads a request frame that we expect the tested handler to have sent inbound. */ + protected Frame readInboundFrame() { + channel.runPendingTasks(); + Object o = channel.readInbound(); + assertThat(o).isInstanceOf(Frame.class); + return ((Frame) o); + } + + /** Reads a request frame that we expect the tested handler to have sent outbound. */ + protected Frame readOutboundFrame() { + channel.runPendingTasks(); + Object o = channel.readOutbound(); + assertThat(o).isInstanceOf(Frame.class); + return ((Frame) o); + } + + /** Writes a response frame for the tested handler to read. */ + protected void writeInboundFrame(Frame responseFrame) { + channel.writeInbound(responseFrame); + } + + /** Writes a response frame that matches the given request, with the given response message. */ + protected void writeInboundFrame(Frame requestFrame, Message response) { + channel.writeInbound(buildInboundFrame(requestFrame, response)); + } + + /** Builds a response frame matching a request frame. */ + protected Frame buildInboundFrame(Frame requestFrame, Message response) { + return Frame.forResponse( + requestFrame.protocolVersion, + requestFrame.streamId, + null, + requestFrame.customPayload, + Collections.emptyList(), + response); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java new file mode 100644 index 00000000000..a100ba38183 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.connection.BusyConnectionException; +import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.datastax.oss.protocol.internal.response.result.Void; +import com.google.common.collect.ImmutableList; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.TimeUnit; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + +public class InFlightHandlerTest extends ChannelHandlerTestBase { + private static final Query QUERY = new Query("select * from foo"); + public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + + @Mock private StreamIdGenerator streamIds; + + @BeforeMethod + @Override + public void setup() { + super.setup(); + MockitoAnnotations.initMocks(this); + channel + .pipeline() + .addLast( + new InFlightHandler(CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS)); + } + + @Test + public void should_fail_if_connection_busy() throws Throwable { + // Given + Mockito.when(streamIds.acquire()).thenReturn(-1); + + // When + ChannelFuture writeFuture = + channel.writeAndFlush( + new DriverChannel.RequestMessage( + QUERY, false, Frame.NO_PAYLOAD, new MockResponseCallback())); + + // Then + assertThat(writeFuture) + .isFailed(e -> assertThat(e).isInstanceOf(BusyConnectionException.class)); + } + + @Test + public void should_assign_streamid_and_send_frame() { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + + // When + ChannelFuture writeFuture = + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + + // Then + assertThat(writeFuture).isSuccess(); + Mockito.verify(streamIds).acquire(); + + Frame frame = readOutboundFrame(); + assertThat(frame.streamId).isEqualTo(42); + assertThat(frame.message).isEqualTo(QUERY); + } + + @Test + public void should_notify_callback_of_response() { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + Frame requestFrame = readOutboundFrame(); + + // When + Frame responseFrame = buildInboundFrame(requestFrame, Void.INSTANCE); + writeInboundFrame(responseFrame); + + // Then + assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); + Mockito.verify(streamIds).release(42); + } + + @Test + public void should_notify_response_promise_when_decoding_fails() throws Throwable { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + + // When + RuntimeException mockCause = new RuntimeException("test"); + channel.pipeline().fireExceptionCaught(new FrameDecodingException(42, mockCause)); + + // Then + assertThat(responseCallback.getFailure()).isSameAs(mockCause); + Mockito.verify(streamIds).release(42); + } + + @Test + public void should_delay_close_until_all_pending_complete() { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + + // When + ChannelFuture closeFuture = channel.close(); + + // Then + // not closed yet because there is one pending request + assertThat(closeFuture).isNotDone(); + + // When + // completing pending request + Frame requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, Void.INSTANCE); + + // Then + assertThat(closeFuture).isSuccess(); + } + + @Test + public void should_fail_all_pending_when_force_closed() throws Throwable { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42, 43); + MockResponseCallback responseCallback1 = new MockResponseCallback(); + MockResponseCallback responseCallback2 = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)); + + // When + ChannelFuture closeFuture = channel.close(); + assertThat(closeFuture).isNotDone(); + channel.pipeline().fireUserEventTriggered(DriverChannel.FORCE_CLOSE_EVENT); + + // Then + assertThat(closeFuture).isSuccess(); + for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { + assertThat(callback.getFailure()) + .isInstanceOf(ConnectionException.class) + .hasMessageContaining("Channel was force-closed"); + } + } + + @Test + public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() throws Throwable { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42, 43); + MockResponseCallback responseCallback1 = new MockResponseCallback(); + MockResponseCallback responseCallback2 = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)); + + // When + RuntimeException mockException = new RuntimeException("test"); + channel.pipeline().fireExceptionCaught(mockException); + + // Then + assertThat(channel.closeFuture()).isSuccess(); + for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { + Throwable failure = callback.getFailure(); + assertThat(failure).isInstanceOf(ConnectionException.class); + assertThat(failure.getCause()).isSameAs(mockException); + } + } + + @Test + public void should_hold_stream_id_if_required() { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(true); + + // When + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + channel.runPendingTasks(); + + // Then + // notify callback of stream id + assertThat(responseCallback.streamId).isEqualTo(42); + + Frame requestFrame = readOutboundFrame(); + for (int i = 0; i < 5; i++) { + // When + // completing pending request + Frame responseFrame = buildInboundFrame(requestFrame, Void.INSTANCE); + writeInboundFrame(responseFrame); + + // Then + assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); + // Stream id not released, callback can receive more responses + Mockito.verify(streamIds, never()).release(42); + } + + // When + // the client releases the stream id + channel.pipeline().fireUserEventTriggered(new DriverChannel.ReleaseEvent(42)); + + // Then + Mockito.verify(streamIds).release(42); + writeInboundFrame(requestFrame, Void.INSTANCE); + // if more responses use this stream id, the handler does not get them anymore + assertThat(responseCallback.getLastResponse()).isNull(); + } + + @Test + public void should_set_keyspace() { + // Given + ChannelPromise setKeyspacePromise = channel.newPromise(); + DriverChannel.SetKeyspaceEvent setKeyspaceEvent = + new DriverChannel.SetKeyspaceEvent(CqlIdentifier.fromCql("ks"), setKeyspacePromise); + + // When + channel.pipeline().fireUserEventTriggered(setKeyspaceEvent); + Frame requestFrame = readOutboundFrame(); + + // Then + assertThat(requestFrame.message).isInstanceOf(Query.class); + writeInboundFrame(requestFrame, new SetKeyspace("ks")); + assertThat(setKeyspacePromise).isSuccess(); + } + + @Test + public void should_fail_to_set_keyspace_if_query_times_out() throws InterruptedException { + // Given + ChannelPromise setKeyspacePromise = channel.newPromise(); + DriverChannel.SetKeyspaceEvent setKeyspaceEvent = + new DriverChannel.SetKeyspaceEvent(CqlIdentifier.fromCql("ks"), setKeyspacePromise); + + // When + channel.pipeline().fireUserEventTriggered(setKeyspaceEvent); + TimeUnit.MILLISECONDS.sleep(SET_KEYSPACE_TIMEOUT_MILLIS * 2); + channel.runPendingTasks(); + + // Then + assertThat(setKeyspacePromise).isFailed(); + } + + static class MockResponseCallback implements ResponseCallback { + private final boolean holdStreamId; + private final Queue responses = new LinkedList<>(); + + private volatile int streamId = -1; + + MockResponseCallback() { + this(false); + } + + MockResponseCallback(boolean holdStreamId) { + this.holdStreamId = holdStreamId; + } + + @Override + public void onResponse(Frame responseFrame) { + responses.offer(responseFrame); + } + + @Override + public void onFailure(Throwable error) { + responses.offer(error); + } + + @Override + public boolean holdStreamId() { + return holdStreamId; + } + + @Override + public void onStreamIdAssigned(int streamId) { + this.streamId = streamId; + } + + Frame getLastResponse() { + return (Frame) responses.poll(); + } + + Throwable getFailure() { + return (Throwable) responses.poll(); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockAuthenticator.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockAuthenticator.java new file mode 100644 index 00000000000..c628687e301 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockAuthenticator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.auth.SyncAuthenticator; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.nio.ByteBuffer; + +/** + * Dummy authenticator for our tests. + * + *

The initial response is hard-coded. When the server asks it to evaluate a challenge, it always + * replies with the same token. When authentication succeeds, the success token is stored for later + * inspection. + */ +public class MockAuthenticator implements SyncAuthenticator { + static final String INITIAL_RESPONSE = "0xcafebabe"; + + volatile String successToken; + + @Override + public ByteBuffer initialResponseSync() { + return Bytes.fromHexString(INITIAL_RESPONSE); + } + + @Override + public ByteBuffer evaluateChallengeSync(ByteBuffer challenge) { + return challenge; + } + + @Override + public void onAuthenticationSuccessSync(ByteBuffer token) { + successToken = Bytes.toHexString(token); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java new file mode 100644 index 00000000000..d001eb8fa8b --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.auth.AuthProvider; +import com.datastax.oss.driver.api.core.auth.AuthenticationException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.TestResponses; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.AuthResponse; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.Startup; +import com.datastax.oss.protocol.internal.response.AuthChallenge; +import com.datastax.oss.protocol.internal.response.AuthSuccess; +import com.datastax.oss.protocol.internal.response.Authenticate; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.Ready; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.channel.ChannelFuture; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { + + private static final long QUERY_TIMEOUT_MILLIS = 100L; + + @Mock private DriverContext driverContext; + @Mock private DriverConfig driverConfig; + @Mock private DriverConfigProfile defaultConfigProfile; + private ProtocolVersionRegistry protocolVersionRegistry = new ProtocolVersionRegistry(); + + @BeforeMethod + @Override + public void setup() { + super.setup(); + MockitoAnnotations.initMocks(this); + Mockito.when(driverContext.config()).thenReturn(driverConfig); + Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS)) + .thenReturn(QUERY_TIMEOUT_MILLIS); + Mockito.when(driverContext.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + + channel + .pipeline() + .addLast( + "inflight", + new InFlightHandler(CoreProtocolVersion.V4, new StreamIdGenerator(100), 100)); + } + + @Test + public void should_initialize_without_authentication() { + channel + .pipeline() + .addLast( + "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + // It should send a STARTUP message + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + Startup startup = (Startup) requestFrame.message; + assertThat(startup.options).doesNotContainKey("COMPRESSION"); + assertThat(connectFuture).isNotDone(); + + // Simulate a READY response + writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); + + // Simulate the cluster name check + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("someClusterName")); + + // Init should complete + assertThat(connectFuture).isSuccess(); + } + + @Test + public void should_fail_to_initialize_if_init_query_times_out() throws InterruptedException { + channel + .pipeline() + .addLast( + "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + readOutboundFrame(); + + // Simulate a pause longer than the timeout + TimeUnit.MILLISECONDS.sleep(QUERY_TIMEOUT_MILLIS * 2); + channel.runPendingTasks(); + + assertThat(connectFuture).isFailed(); + } + + @Test + public void should_initialize_with_authentication() { + channel + .pipeline() + .addLast( + "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + + String serverAuthenticator = "mockServerAuthenticator"; + AuthProvider authProvider = Mockito.mock(AuthProvider.class); + MockAuthenticator authenticator = new MockAuthenticator(); + Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) + .thenReturn(authenticator); + Mockito.when(driverContext.authProvider()).thenReturn(authProvider); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + assertThat(connectFuture).isNotDone(); + + // Simulate a response that says that the server requires authentication + writeInboundFrame(requestFrame, new Authenticate(serverAuthenticator)); + + // The connection should have created an authenticator from the auth provider + Mockito.verify(authProvider).newAuthenticator(channel.remoteAddress(), serverAuthenticator); + + // And sent an auth response + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(AuthResponse.class); + AuthResponse authResponse = (AuthResponse) requestFrame.message; + assertThat(Bytes.toHexString(authResponse.token)).isEqualTo(MockAuthenticator.INITIAL_RESPONSE); + assertThat(connectFuture).isNotDone(); + + // As long as the server sends an auth challenge, the client should reply with another auth_response + String mockToken = "0xabcd"; + for (int i = 0; i < 5; i++) { + writeInboundFrame(requestFrame, new AuthChallenge(Bytes.fromHexString(mockToken))); + + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(AuthResponse.class); + authResponse = (AuthResponse) requestFrame.message; + // Our mock impl happens to send back the same token + assertThat(Bytes.toHexString(authResponse.token)).isEqualTo(mockToken); + assertThat(connectFuture).isNotDone(); + } + + // When the server finally sends back a success message, should proceed to the cluster name check and succeed + writeInboundFrame(requestFrame, new AuthSuccess(Bytes.fromHexString(mockToken))); + assertThat(authenticator.successToken).isEqualTo(mockToken); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("someClusterName")); + + assertThat(connectFuture).isSuccess(); + } + + @Test + public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwable { + channel + .pipeline() + .addLast( + "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + + String serverAuthenticator = "mockServerAuthenticator"; + AuthProvider authProvider = Mockito.mock(AuthProvider.class); + MockAuthenticator authenticator = new MockAuthenticator(); + Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) + .thenReturn(authenticator); + Mockito.when(driverContext.authProvider()).thenReturn(authProvider); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + assertThat(connectFuture).isNotDone(); + + writeInboundFrame(requestFrame, new Authenticate("mockServerAuthenticator")); + + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(AuthResponse.class); + assertThat(connectFuture).isNotDone(); + + writeInboundFrame( + requestFrame, new Error(ProtocolConstants.ErrorCode.AUTH_ERROR, "mock error")); + + assertThat(connectFuture) + .isFailed( + e -> + assertThat(e) + .isInstanceOf(AuthenticationException.class) + .hasMessage( + "Authentication error on host embedded: server replied 'mock error'")); + } + + @Test + public void should_check_cluster_name_if_provided() { + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + driverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + Frame requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + Query query = (Query) requestFrame.message; + assertThat(query.query).isEqualTo("SELECT cluster_name FROM system.local"); + assertThat(connectFuture).isNotDone(); + + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("expectedClusterName")); + + assertThat(connectFuture).isSuccess(); + } + + @Test + public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Throwable { + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + driverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame( + readOutboundFrame(), TestResponses.clusterNameResponse("differentClusterName")); + + assertThat(connectFuture) + .isFailed( + e -> + assertThat(e) + .isInstanceOf(ClusterNameMismatchException.class) + .hasMessage( + "Host embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); + } + + @Test + public void should_initialize_with_keyspace() { + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + driverContext, CoreProtocolVersion.V4, null, CqlIdentifier.fromCql("ks"))); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + assertThat(((Query) requestFrame.message).query).isEqualTo("USE \"ks\""); + writeInboundFrame(requestFrame, new SetKeyspace("ks")); + + assertThat(connectFuture).isSuccess(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java new file mode 100644 index 00000000000..547334df61d --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class StreamIdGeneratorTest { + @Test + public void should_have_all_available_upon_creation() { + StreamIdGenerator generator = new StreamIdGenerator(8); + assertThat(generator.getAvailableIds()).isEqualTo(8); + } + + @Test + public void should_return_available_ids_in_sequence() { + StreamIdGenerator generator = new StreamIdGenerator(8); + for (int i = 0; i < 8; i++) { + assertThat(generator.acquire()).isEqualTo(i); + assertThat(generator.getAvailableIds()).isEqualTo(7 - i); + } + } + + @Test + public void should_return_minus_one_when_no_id_available() { + StreamIdGenerator generator = new StreamIdGenerator(8); + for (int i = 0; i < 8; i++) { + generator.acquire(); + } + assertThat(generator.getAvailableIds()).isEqualTo(0); + assertThat(generator.acquire()).isEqualTo(-1); + } + + @Test + public void should_return_previously_released_ids() { + StreamIdGenerator generator = new StreamIdGenerator(8); + for (int i = 0; i < 8; i++) { + generator.acquire(); + } + generator.release(7); + generator.release(2); + assertThat(generator.getAvailableIds()).isEqualTo(2); + assertThat(generator.acquire()).isEqualTo(2); + assertThat(generator.acquire()).isEqualTo(7); + assertThat(generator.acquire()).isEqualTo(-1); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java new file mode 100644 index 00000000000..677353e6836 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.DriverOption; + +enum MockOptions implements DriverOption { + REQUIRED_INT("required_int", true), + OPTIONAL_INT("optional_int", false); + + private final String path; + private final boolean required; + + MockOptions(String path, boolean required) { + this.path = path; + this.required = required; + } + + @Override + public String getPath() { + return path; + } + + @Override + public boolean required() { + return required; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java new file mode 100644 index 00000000000..0b13febf9fe --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class TypeSafeDriverConfigTest { + + @Test + public void should_load_minimal_config_with_required_options_and_no_profiles() { + DriverConfig config = parse("required_int = 42"); + assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); + } + + @Test + public void should_load_config_with_no_profiles_and_optional_values() { + DriverConfig config = parse("required_int = 42\n optional_int = 43"); + assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); + assertThat(config).hasIntOption(MockOptions.OPTIONAL_INT, 43); + } + + @Test( + expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Missing option required_int. Check your configuration file." + ) + public void should_fail_if_required_option_is_missing() { + parse(""); + } + + @Test + public void should_inherit_option_in_profile() { + DriverConfig config = parse("required_int = 42\n profiles { profile1 { } }"); + assertThat(config) + .hasIntOption(MockOptions.REQUIRED_INT, 42) + .hasIntOption("profile1", MockOptions.REQUIRED_INT, 42); + } + + @Test + public void should_override_option_in_profile() { + DriverConfig config = parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); + assertThat(config) + .hasIntOption(MockOptions.REQUIRED_INT, 42) + .hasIntOption("profile1", MockOptions.REQUIRED_INT, 43); + } + + @Test + public void should_load_default_driver_config() { + // No assertions here, but this validates that `reference.conf` is well-formed. + new TypeSafeDriverConfig( + ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + } + + private DriverConfig parse(String configString) { + Config config = ConfigFactory.parseString(configString); + return new TypeSafeDriverConfig(config, MockOptions.values()); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java index 7f9a752bf95..aeb07582b4d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import com.datastax.oss.driver.internal.core.util.ByteBufs; import com.datastax.oss.protocol.internal.util.Bytes; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -34,15 +35,15 @@ public class ByteBufPrimitiveCodecTest { @Test public void should_concatenate() { - ByteBuf left = wrap(0xca, 0xfe); - ByteBuf right = wrap(0xba, 0xbe); + ByteBuf left = ByteBufs.wrap(0xca, 0xfe); + ByteBuf right = ByteBufs.wrap(0xba, 0xbe); assertThat(codec.concat(left, right)).containsExactly("0xcafebabe"); } @Test public void should_concatenate_slices() { - ByteBuf left = wrap(0x00, 0xca, 0xfe, 0x00).slice(1, 2); - ByteBuf right = wrap(0x00, 0x00, 0xba, 0xbe, 0x00).slice(2, 2); + ByteBuf left = ByteBufs.wrap(0x00, 0xca, 0xfe, 0x00).slice(1, 2); + ByteBuf right = ByteBufs.wrap(0x00, 0x00, 0xba, 0xbe, 0x00).slice(2, 2); assertThat(codec.concat(left, right)).containsExactly("0xcafebabe"); } @@ -50,7 +51,7 @@ public void should_concatenate_slices() { @Test public void should_read_inet_v4() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as a byte) 0x04, // address @@ -78,7 +79,7 @@ public void should_read_inet_v6() { codec.concat( lengthAndAddress, // port (as an int) - wrap(0x00, 0x00, 0x23, 0x52)); + ByteBufs.wrap(0x00, 0x00, 0x23, 0x52)); InetSocketAddress inet = codec.readInet(source); assertThat(inet.getAddress().getHostAddress()).isEqualTo("0:0:0:0:0:0:0:1"); assertThat(inet.getPort()).isEqualTo(9042); @@ -90,7 +91,7 @@ public void should_read_inet_v6() { ) public void should_fail_to_read_inet_if_length_invalid() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as a byte) 0x03, // address @@ -108,7 +109,7 @@ public void should_fail_to_read_inet_if_length_invalid() { @Test public void should_read_inetaddr_v4() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as a byte) 0x04, // address @@ -136,7 +137,7 @@ public void should_read_inetaddr_v6() { ) public void should_fail_to_read_inetaddr_if_length_invalid() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as a byte) 0x03, // address @@ -149,7 +150,7 @@ public void should_fail_to_read_inetaddr_if_length_invalid() { @Test public void should_read_bytes() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as an int) 0x00, 0x00, @@ -166,14 +167,14 @@ public void should_read_bytes() { @Test public void should_read_null_bytes() { - ByteBuf source = wrap(0xFF, 0xFF, 0xFF, 0xFF); // -1 (as an int) + ByteBuf source = ByteBufs.wrap(0xFF, 0xFF, 0xFF, 0xFF); // -1 (as an int) assertThat(codec.readBytes(source)).isNull(); } @Test public void should_read_short_bytes() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as an unsigned short) 0x00, 0x04, @@ -188,7 +189,7 @@ public void should_read_short_bytes() { @Test public void should_read_string() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as an unsigned short) 0x00, 0x05, @@ -216,7 +217,7 @@ public void should_fail_to_read_string_if_not_enough_characters() { @Test public void should_read_long_string() { ByteBuf source = - wrap( + ByteBufs.wrap( // length (as an int) 0x00, 0x00, @@ -345,14 +346,6 @@ public void should_write_null_bytes() { assertThat(dest).containsExactly("0xFFFFFFFF"); } - private static ByteBuf wrap(int... bytes) { - ByteBuf bb = ByteBufAllocator.DEFAULT.buffer(bytes.length); - for (int b : bytes) { - bb.writeByte(b); - } - return bb; - } - private static ByteBuf allocate(int length) { return ByteBufAllocator.DEFAULT.buffer(length); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java new file mode 100644 index 00000000000..332f4e9808e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; +import com.datastax.oss.driver.internal.core.util.ByteBufs; +import com.datastax.oss.protocol.internal.Compressor; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.FrameCodec; +import com.datastax.oss.protocol.internal.response.AuthSuccess; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.TooLongFrameException; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class FrameDecoderTest extends ChannelHandlerTestBase { + // A valid binary payload for a response frame. + private static final ByteBuf VALID_PAYLOAD = + ByteBufs.fromHexString( + "0x84" // response frame, protocol version 4 + + "00" // flags (none) + + "002a" // stream id (42) + + "10" // opcode for AUTH_SUCCESS message + + "00000008" // body length + + "00000004cafebabe" // body + ); + + // A binary payload that is invalid because the protocol version is not supported by the codec + private static final ByteBuf INVALID_PAYLOAD = + ByteBufs.fromHexString( + "0xFF" // response frame, protocol version 127 + + "00002a100000000800000004cafebabe"); + + private FrameCodec frameCodec; + + @BeforeMethod + @Override + public void setup() { + super.setup(); + frameCodec = + FrameCodec.defaultClient(new ByteBufPrimitiveCodec(channel.alloc()), Compressor.none()); + } + + @Test + public void should_decode_valid_payload() { + // Given + FrameDecoder decoder = new FrameDecoder(frameCodec, 1024); + channel.pipeline().addLast(decoder); + + // When + channel.writeInbound(VALID_PAYLOAD.duplicate()); + Frame frame = readInboundFrame(); + + // Then + assertThat(frame.message).isInstanceOf(AuthSuccess.class); + } + + /** + * Checks that an exception carrying the stream id is thrown when decoding fails in the {@link + * LengthFieldBasedFrameDecoder} code. + */ + @Test + public void should_fail_to_decode_if_payload_is_valid_but_too_long() { + // Given + FrameDecoder decoder = new FrameDecoder(frameCodec, VALID_PAYLOAD.readableBytes() - 1); + channel.pipeline().addLast(decoder); + + // When + try { + channel.writeInbound(VALID_PAYLOAD.duplicate()); + fail("expected an exception"); + } catch (FrameDecodingException e) { + // Then + assertThat(e.streamId).isEqualTo(42); + assertThat(e.getCause()).isInstanceOf(TooLongFrameException.class); + } + } + + /** Checks that an exception carrying the stream id is thrown when decoding fails in our code. */ + @Test + public void should_fail_to_decode_if_payload_cannot_be_decoded() { + // Given + FrameDecoder decoder = new FrameDecoder(frameCodec, 1024); + channel.pipeline().addLast(decoder); + + // When + try { + channel.writeInbound(INVALID_PAYLOAD.duplicate()); + fail("expected an exception"); + } catch (FrameDecodingException e) { + // Then + assertThat(e.streamId).isEqualTo(42); + assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ByteBufs.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ByteBufs.java new file mode 100644 index 00000000000..6815fbc21f2 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ByteBufs.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import java.nio.ByteBuffer; + +/** Helper class to create {@link io.netty.buffer.ByteBuf} instances in tests. */ +public class ByteBufs { + public static ByteBuf wrap(int... bytes) { + ByteBuf bb = ByteBufAllocator.DEFAULT.buffer(bytes.length); + for (int b : bytes) { + bb.writeByte(b); + } + return bb; + } + + public static ByteBuf fromHexString(String hexString) { + ByteBuffer tmp = Bytes.fromHexString(hexString); + ByteBuf target = ByteBufAllocator.DEFAULT.buffer(tmp.remaining()); + target.writeBytes(tmp); + return target; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java new file mode 100644 index 00000000000..d087442993a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.netty; + +import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import java.net.InetSocketAddress; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ConnectInitHandlerTest extends ChannelHandlerTestBase { + + private TestHandler handler; + + @BeforeMethod + @Override + public void setup() { + super.setup(); + handler = new TestHandler(); + channel.pipeline().addLast(handler); + } + + @Test + public void should_call_onRealConnect_when_connection_succeeds() { + assertThat(handler.hasConnected).isFalse(); + + // When + channel.connect(new InetSocketAddress("localhost", 9042)); + + // Then + assertThat(handler.hasConnected).isTrue(); + } + + @Test + public void should_not_complete_connect_future_before_triggered_by_handler() { + // When + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + // Then + assertThat(connectFuture.isDone()).isFalse(); + } + + @Test + public void should_complete_connect_future_when_handler_completes() { + // Given + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + // When + handler.setConnectSuccess(); + + // Then + assertThat(connectFuture.isSuccess()).isTrue(); + } + + @Test + public void should_remove_handler_from_pipeline_when_handler_completes() { + // Given + channel.connect(new InetSocketAddress("localhost", 9042)); + + // When + handler.setConnectSuccess(); + + // Then + assertThat(channel.pipeline().get(TestHandler.class)).isNull(); + } + + @Test + public void should_fail_connect_future_when_handler_fails() { + // Given + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + Exception exception = new Exception("test"); + + // When + handler.setConnectFailure(exception); + + // Then + assertThat(connectFuture).isFailed(e -> assertThat(e).isEqualTo(exception)); + } + + /** + * Well-behaved implementations should not call setConnect* multiple times in a row, but check + * that we handle it gracefully if they do. + */ + @Test + public void should_ignore_subsequent_calls_if_handler_already_failed() { + // Given + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + Exception exception = new Exception("test"); + + // When + handler.setConnectFailure(exception); + handler.setConnectFailure(new Exception("test2")); + handler.setConnectSuccess(); + + // Then + assertThat(connectFuture).isFailed(e -> assertThat(e).isEqualTo(exception)); + } + + static class TestHandler extends ConnectInitHandler { + boolean hasConnected; + + @Override + protected void onRealConnect(ChannelHandlerContext ctx) { + hasConnected = true; + } + } +} diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..945e1d8a3c1 --- /dev/null +++ b/core/src/test/resources/logback-test.xml @@ -0,0 +1,28 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 879892dcb11..a655740f2cc 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,26 @@ netty-handler 4.1.9.Final + + com.google.guava + guava + 21.0 + + + com.typesafe + config + 1.3.1 + + + org.slf4j + slf4j-api + 1.7.25 + + + ch.qos.logback + logback-classic + 1.2.3 + org.testng testng @@ -64,6 +84,11 @@ assertj-core 3.6.2 + + org.mockito + mockito-core + 2.7.19 + @@ -88,6 +113,10 @@ maven-surefire-plugin 2.19.1 + + maven-javadoc-plugin + 2.10.4 + @@ -172,6 +201,12 @@ limitations under the License.]]> + + maven-javadoc-plugin + + -Xdoclint:none + + From 76ef0a2d045fc2bfd7e25269fd7d89f4c129ff8d Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 3 Apr 2017 16:18:25 -0700 Subject: [PATCH 005/742] Add write and flush coalescing This required a few changes to DriverChannel so that coalescing does not mess with the close/forceClose sequence. --- .../internal/core/DefaultDriverContext.java | 13 ++ .../driver/internal/core/DriverContext.java | 3 + .../internal/core/channel/ChannelFactory.java | 3 +- .../core/channel/DefaultWriteCoalescer.java | 138 ++++++++++++++++ .../internal/core/channel/DriverChannel.java | 36 +++- .../core/channel/InFlightHandler.java | 44 ++--- .../internal/core/channel/WriteCoalescer.java | 40 +++++ .../core/channel/ChannelHandlerTestBase.java | 6 + .../core/channel/DriverChannelTest.java | 154 ++++++++++++++++++ .../core/channel/InFlightHandlerTest.java | 83 ++++------ .../core/channel/MockResponseCallback.java | 63 +++++++ 11 files changed, 492 insertions(+), 91 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java index 266545cefdb..63274e1132f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; +import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.util.Reflection; @@ -57,6 +59,8 @@ public class DefaultDriverContext implements DriverContext { new LazyReference<>("ioEventLoopGroup", this::buildIoEventLoopGroup); private final LazyReference authProvider = new LazyReference<>("authProvider", this::buildAuthProvider); + private final LazyReference writeCoalescer = + new LazyReference<>("writeCoalescer", this::buildWriteCoalescer); private DriverConfig buildDriverConfig() { return new TypeSafeDriverConfig( @@ -98,6 +102,10 @@ protected AuthProvider buildAuthProvider() { } } + private WriteCoalescer buildWriteCoalescer() { + return new DefaultWriteCoalescer(5); + } + @Override public DriverConfig config() { return config.get(); @@ -137,4 +145,9 @@ public Class channelClass() { public AuthProvider authProvider() { return authProvider.get(); } + + @Override + public WriteCoalescer writeCoalescer() { + return writeCoalescer.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java index 1a42bbc0dcc..0a9b0a709fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; @@ -51,4 +52,6 @@ public interface DriverContext { Class channelClass(); AuthProvider authProvider(); + + WriteCoalescer writeCoalescer(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 7ecb04bf125..c7f4af14ab2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -98,7 +98,8 @@ private void connect( connectFuture.addListener( cf -> { if (connectFuture.isSuccess()) { - DriverChannel driverChannel = new DriverChannel(connectFuture.channel()); + DriverChannel driverChannel = + new DriverChannel(connectFuture.channel(), driverContext.writeCoalescer()); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java new file mode 100644 index 00000000000..749d2834882 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.util.internal.shaded.org.jctools.queues.atomic.MpscLinkedAtomicQueue; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Default write coalescing strategy. + * + *

It maintains a queue per event loop, with the writes targeting the channels that run on this + * loop. As soon as a write gets enqueued, it triggers a task that will flush the queue (other + * writes can get enqueued before the task runs). Once that task is complete, it re-triggers itself + * as long as new writes have been enqueued, or {@code maxRunsWithNoWork} times if there are no more + * tasks. + * + *

Note that Netty provides a similar mechanism out of the box ({@link + * io.netty.handler.flush.FlushConsolidationHandler}), but in our experience our approach allows + * more performance gains, because it allows consolidating not only the flushes, but also the write + * tasks themselves (a single consolidated write task is scheduled on the event loop, instead of + * multiple individual tasks, so there is less context switching). + */ +public class DefaultWriteCoalescer implements WriteCoalescer { + private final int maxRunsWithNoWork; + private final ConcurrentMap flushers = new ConcurrentHashMap<>(); + + public DefaultWriteCoalescer(int maxRunsWithNoWork) { + this.maxRunsWithNoWork = maxRunsWithNoWork; + } + + @Override + public ChannelFuture writeAndFlush(Channel channel, Object message) { + ChannelPromise writePromise = channel.newPromise(); + Write write = new Write(channel, message, writePromise); + enqueue(write, channel.eventLoop()); + return writePromise; + } + + private void enqueue(Write write, EventLoop eventLoop) { + Flusher flusher = flushers.computeIfAbsent(eventLoop, Flusher::new); + flusher.enqueue(write); + } + + private class Flusher { + private final EventLoop eventLoop; + + // These variables are accessed both from client threads and the event loop + private final Queue writes = new MpscLinkedAtomicQueue<>(); + private final AtomicBoolean running = new AtomicBoolean(); + + // These variables are accessed only from runOnEventLoop, they don't need to be thread-safe + private final Set channels = new HashSet<>(); + private int runsWithNoWork = 0; + + private Flusher(EventLoop eventLoop) { + this.eventLoop = eventLoop; + } + + private void enqueue(Write write) { + boolean added = writes.offer(write); + assert added; // always true (see MpscLinkedAtomicQueue implementation) + if (!running.get() && running.compareAndSet(false, true)) { + eventLoop.submit(this::runOnEventLoop); + } + } + + private void runOnEventLoop() { + assert eventLoop.inEventLoop(); + + boolean didSomeWork = false; + Write write; + while ((write = writes.poll()) != null) { + Channel channel = write.channel; + channels.add(channel); + channel.write(write.message, write.writePromise); + didSomeWork = true; + } + + for (Channel channel : channels) { + channel.flush(); + } + channels.clear(); + + if (didSomeWork) { + runsWithNoWork = 0; + } else if (++runsWithNoWork > maxRunsWithNoWork) { + // Prepare to stop + running.set(false); + // If no new writes have been enqueued since the previous line, we can return safely + if (writes.isEmpty()) { + return; + } + // Otherwise check if those writes have triggered a new run. If not, we need to do that + // ourselves (i.e. not return yet) + if (!running.compareAndSet(false, true)) { + return; + } + } + if (!eventLoop.isShuttingDown()) { + eventLoop.submit(this::runOnEventLoop); + } + } + } + + private static class Write { + private final Channel channel; + private final Object message; + private final ChannelPromise writePromise; + + private Write(Channel channel, Object message, ChannelPromise writePromise) { + this.channel = channel; + this.message = message; + this.writePromise = writePromise; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index d246eac1203..53c64447e8f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -18,12 +18,12 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import java.nio.ByteBuffer; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; /** * A thin wrapper around a Netty {@link Channel}, to send requests to a Cassandra node and receive @@ -31,12 +31,16 @@ */ public class DriverChannel { static final AttributeKey CLUSTER_NAME_KEY = AttributeKey.newInstance("cluster_name"); - static final Object FORCE_CLOSE_EVENT = new Object(); + static final Object GRACEFUL_CLOSE_MESSAGE = new Object(); + static final Object FORCEFUL_CLOSE_MESSAGE = new Object(); private final Channel channel; + private final WriteCoalescer writeCoalescer; + private final AtomicBoolean closing = new AtomicBoolean(); - DriverChannel(Channel channel) { + DriverChannel(Channel channel, WriteCoalescer writeCoalescer) { this.channel = channel; + this.writeCoalescer = writeCoalescer; } /** @@ -48,8 +52,11 @@ public Future write( boolean tracing, Map customPayload, ResponseCallback responseCallback) { + if (closing.get()) { + throw new IllegalStateException("Driver channel is closing"); + } RequestMessage message = new RequestMessage(request, tracing, customPayload, responseCallback); - return channel.writeAndFlush(message); //TODO coalesce flushes + return writeCoalescer.writeAndFlush(channel, message); } /** @@ -86,14 +93,27 @@ public String getClusterName() { return channel.attr(CLUSTER_NAME_KEY).get(); } + /** + * Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will + * be allowed to complete before the underlying channel is closed. + */ public Future close() { - return channel.close(); + if (closing.compareAndSet(false, true)) { + // go through the coalescer: this guarantees that we won't reject writes that were submitted + // before, but had not been coalesced yet. + writeCoalescer.writeAndFlush(channel, GRACEFUL_CLOSE_MESSAGE); + } + return channel.closeFuture(); } + /** + * Initiates a forced shutdown: any pending request will be aborted and the underlying channel + * will be closed. + */ public Future forceClose() { - ChannelFuture closeFuture = channel.close(); - channel.pipeline().fireUserEventTriggered(FORCE_CLOSE_EVENT); - return closeFuture; + closing.set(true); + writeCoalescer.writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE); + return channel.closeFuture(); } // This is essentially a stripped-down Frame. We can't materialize the frame before writing, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index d9225307114..41db2a86592 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -27,7 +27,6 @@ import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; -import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; @@ -42,7 +41,7 @@ public class InFlightHandler extends ChannelDuplexHandler { private final StreamIdGenerator streamIds; private final Map inFlight; private final long setKeyspaceTimeoutMillis; - private ChannelPromise closePromise; + private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; InFlightHandler( @@ -54,11 +53,17 @@ public class InFlightHandler extends ChannelDuplexHandler { } @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) - throws Exception { - if (closePromise != null) { + public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) throws Exception { + if (closingGracefully) { promise.setFailure(new IllegalStateException("Channel is closing")); return; + } else if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { + closingGracefully = true; + return; + } else if (in == DriverChannel.FORCEFUL_CLOSE_MESSAGE) { + abortAllInFlight(new ConnectionException("Channel was force-closed")); + ctx.channel().close(); + return; } int streamId = streamIds.acquire(); @@ -73,7 +78,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) return; } - RequestMessage message = (RequestMessage) msg; + RequestMessage message = (RequestMessage) in; Frame frame = Frame.forRequest( protocolVersion.getCode(), @@ -125,26 +130,9 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } } - @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - // Delay the actual close if there are pending requests - if (inFlight.isEmpty()) { - super.close(ctx, promise); - } else { - this.closePromise = promise; - } - } - @Override public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception { - if (event == DriverChannel.FORCE_CLOSE_EVENT) { - Preconditions.checkState( - closePromise != null, "Channel should be closed before sending FORCE_CLOSE event"); - // Note: this is guaranteed by DriverChannel.forceClose - - abortAllInFlight(new ConnectionException("Channel was force-closed")); - super.close(ctx, closePromise); - } else if (event instanceof ReleaseEvent) { + if (event instanceof ReleaseEvent) { release(((ReleaseEvent) event).streamId, ctx); } else if (event instanceof SetKeyspaceEvent) { SetKeyspaceEvent setKeyspaceEvent = (SetKeyspaceEvent) event; @@ -167,12 +155,8 @@ private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { streamIds.release(streamId); // If we're in the middle of an orderly close and this was the last request, actually close // the channel now - if (closePromise != null && inFlight.isEmpty()) { - try { - super.close(ctx, closePromise); - } catch (Exception e) { - ctx.fireExceptionCaught(e); - } + if (closingGracefully && inFlight.isEmpty()) { + ctx.channel().close(); } return responseCallback; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java new file mode 100644 index 00000000000..58c58084e33 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOutboundInvoker; + +/** + * Optimizes the write operations on Netty channels. + * + *

Flush operations are generally speaking expensive as these may trigger a syscall on the + * transport level. Thus it is in most cases (where write latency can be traded with throughput) a + * good idea to try to minimize flush operations as much as possible. This component allows writes + * to be accumulated and flushed together for better performance. + */ +public interface WriteCoalescer { + + /** Pass-through: all messages are written and flushed immediately. */ + WriteCoalescer NONE = ChannelOutboundInvoker::writeAndFlush; + + /** + * Writes and flushes the message to the channel, possibly at a later time, but the order of + * messages must be preserved. + */ + ChannelFuture writeAndFlush(Channel channel, Object message); +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java index c1a055fcc88..1dbc1f7c636 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java @@ -54,6 +54,12 @@ protected Frame readOutboundFrame() { return ((Frame) o); } + protected void assertNoOutboundFrame() { + channel.runPendingTasks(); + Object o = channel.readOutbound(); + assertThat(o).isNull(); + } + /** Writes a response frame for the tested handler to read. */ protected void writeInboundFrame(Frame responseFrame) { channel.writeInbound(responseFrame); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java new file mode 100644 index 00000000000..47beba2cc8b --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.result.Void; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.util.concurrent.Future; +import java.util.AbstractMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class DriverChannelTest extends ChannelHandlerTestBase { + public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + + private DriverChannel driverChannel; + private MockWriteCoalescer writeCoalescer; + + @Mock private StreamIdGenerator streamIds; + + @BeforeMethod + @Override + public void setup() { + super.setup(); + MockitoAnnotations.initMocks(this); + channel + .pipeline() + .addLast( + new InFlightHandler(CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS)); + writeCoalescer = new MockWriteCoalescer(); + driverChannel = new DriverChannel(channel, writeCoalescer); + } + + /** + * Ensures that the potential delay introduced by the write coalescer does not mess with the + * graceful shutdown sequence: any write submitted before {@link DriverChannel#close()} is + * guaranteed to complete. + */ + @Test + public void should_wait_for_coalesced_writes_when_closing_gracefully() { + // Given + MockResponseCallback responseCallback = new MockResponseCallback(); + driverChannel.write(new Query("test"), false, Frame.NO_PAYLOAD, responseCallback); + // nothing written yet because the coalescer hasn't flushed + assertNoOutboundFrame(); + + // When + Future closeFuture = driverChannel.close(); + + // Then + // not closed yet because there is still a pending write + assertThat(closeFuture).isNotDone(); + assertNoOutboundFrame(); + + // When + // the coalescer finally runs + writeCoalescer.triggerFlush(); + + // Then + // the pending write goes through + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame).isNotNull(); + // not closed yet because there is now a pending response + assertThat(closeFuture).isNotDone(); + + // When + // the pending response arrives + writeInboundFrame(requestFrame, Void.INSTANCE); + assertThat(responseCallback.getLastResponse().message).isEqualTo(Void.INSTANCE); + + // Then + assertThat(closeFuture).isSuccess(); + } + + /** + * Ensures that the potential delay introduced by the write coalescer does not mess with the + * forceful shutdown sequence: any write submitted before {@link DriverChannel#forceClose()} + * should get the "Channel was force-closed" error, whether it had been flushed or not. + */ + @Test + public void should_wait_for_coalesced_writes_when_closing_forcefully() { + // Given + MockResponseCallback responseCallback = new MockResponseCallback(); + driverChannel.write(new Query("test"), false, Frame.NO_PAYLOAD, responseCallback); + // nothing written yet because the coalescer hasn't flushed + assertNoOutboundFrame(); + + // When + Future closeFuture = driverChannel.forceClose(); + + // Then + // not closed yet because there is still a pending write + assertThat(closeFuture).isNotDone(); + assertNoOutboundFrame(); + + // When + // the coalescer finally runs + writeCoalescer.triggerFlush(); + // and the pending write goes through + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame).isNotNull(); + + // Then + assertThat(closeFuture).isSuccess(); + assertThat(responseCallback.getFailure()) + .isInstanceOf(ConnectionException.class) + .hasMessageContaining("Channel was force-closed"); + } + + // Simple implementation that holds all the writes, and flushes them when it's explicitly + // triggered. + private class MockWriteCoalescer implements WriteCoalescer { + private Queue> messages = new LinkedList<>(); + + @Override + public ChannelFuture writeAndFlush(Channel channel, Object message) { + assertThat(channel).isEqualTo(DriverChannelTest.this.channel); + ChannelPromise writePromise = channel.newPromise(); + messages.offer(new AbstractMap.SimpleEntry<>(message, writePromise)); + return writePromise; + } + + void triggerFlush() { + for (Map.Entry entry : messages) { + channel.writeAndFlush(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index a100ba38183..4b49ffed833 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -27,8 +27,6 @@ import com.google.common.collect.ImmutableList; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPromise; -import java.util.LinkedList; -import java.util.Queue; import java.util.concurrent.TimeUnit; import org.mockito.Mock; import org.mockito.Mockito; @@ -136,11 +134,11 @@ public void should_delay_close_until_all_pending_complete() { new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); // When - ChannelFuture closeFuture = channel.close(); + channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); // Then // not closed yet because there is one pending request - assertThat(closeFuture).isNotDone(); + assertThat(channel.closeFuture()).isNotDone(); // When // completing pending request @@ -148,7 +146,33 @@ public void should_delay_close_until_all_pending_complete() { writeInboundFrame(requestFrame, Void.INSTANCE); // Then - assertThat(closeFuture).isSuccess(); + assertThat(channel.closeFuture()).isSuccess(); + } + + @Test + public void should_refuse_new_writes_during_orderly_close() { + // Given + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + + // When + channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); + + // Then + // not closed yet because there is one pending request + assertThat(channel.closeFuture()).isNotDone(); + // should not allow other write + ChannelFuture otherWriteFuture = + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + assertThat(otherWriteFuture) + .isFailed( + e -> + assertThat(e) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Channel is closing")); } @Test @@ -163,12 +187,10 @@ public void should_fail_all_pending_when_force_closed() throws Throwable { new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)); // When - ChannelFuture closeFuture = channel.close(); - assertThat(closeFuture).isNotDone(); - channel.pipeline().fireUserEventTriggered(DriverChannel.FORCE_CLOSE_EVENT); + channel.write(DriverChannel.FORCEFUL_CLOSE_MESSAGE); // Then - assertThat(closeFuture).isSuccess(); + assertThat(channel.closeFuture()).isSuccess(); for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { assertThat(callback.getFailure()) .isInstanceOf(ConnectionException.class) @@ -271,47 +293,4 @@ public void should_fail_to_set_keyspace_if_query_times_out() throws InterruptedE // Then assertThat(setKeyspacePromise).isFailed(); } - - static class MockResponseCallback implements ResponseCallback { - private final boolean holdStreamId; - private final Queue responses = new LinkedList<>(); - - private volatile int streamId = -1; - - MockResponseCallback() { - this(false); - } - - MockResponseCallback(boolean holdStreamId) { - this.holdStreamId = holdStreamId; - } - - @Override - public void onResponse(Frame responseFrame) { - responses.offer(responseFrame); - } - - @Override - public void onFailure(Throwable error) { - responses.offer(error); - } - - @Override - public boolean holdStreamId() { - return holdStreamId; - } - - @Override - public void onStreamIdAssigned(int streamId) { - this.streamId = streamId; - } - - Frame getLastResponse() { - return (Frame) responses.poll(); - } - - Throwable getFailure() { - return (Throwable) responses.poll(); - } - } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java new file mode 100644 index 00000000000..366ac580460 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.protocol.internal.Frame; +import java.util.LinkedList; +import java.util.Queue; + +class MockResponseCallback implements ResponseCallback { + private final boolean holdStreamId; + private final Queue responses = new LinkedList<>(); + + volatile int streamId = -1; + + MockResponseCallback() { + this(false); + } + + MockResponseCallback(boolean holdStreamId) { + this.holdStreamId = holdStreamId; + } + + @Override + public void onResponse(Frame responseFrame) { + responses.offer(responseFrame); + } + + @Override + public void onFailure(Throwable error) { + responses.offer(error); + } + + @Override + public boolean holdStreamId() { + return holdStreamId; + } + + @Override + public void onStreamIdAssigned(int streamId) { + this.streamId = streamId; + } + + Frame getLastResponse() { + return (Frame) responses.poll(); + } + + Throwable getFailure() { + return (Throwable) responses.poll(); + } +} From 210cc7ff72594a95accdd531b82b2fbc6ebb5cdf Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 3 Apr 2017 16:45:33 -0700 Subject: [PATCH 006/742] Relocate handler implementation No need for a dedicated package. --- .../netty => channel}/ConnectInitHandler.java | 2 +- .../core/channel/ProtocolInitHandler.java | 1 - .../internal/core/util/netty/package-info.java | 17 ----------------- .../ConnectInitHandlerTest.java | 3 ++- 4 files changed, 3 insertions(+), 20 deletions(-) rename core/src/main/java/com/datastax/oss/driver/internal/core/{util/netty => channel}/ConnectInitHandler.java (98%) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java rename core/src/test/java/com/datastax/oss/driver/internal/core/{util/netty => channel}/ConnectInitHandlerTest.java (96%) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java similarity index 98% rename from core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java index 62f06dbce1c..ab3e4b733c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core.util.netty; +package com.datastax.oss.driver.internal.core.channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 367f13c35c6..391acb2b907 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -25,7 +25,6 @@ import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.internal.core.DriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; -import com.datastax.oss.driver.internal.core.util.netty.ConnectInitHandler; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java deleted file mode 100644 index 613d197db75..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/netty/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** Generic internal utilities. */ -package com.datastax.oss.driver.internal.core.util.netty; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java similarity index 96% rename from core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java index d087442993a..20fa46f4588 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/netty/ConnectInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core.util.netty; +package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; +import com.datastax.oss.driver.internal.core.channel.ConnectInitHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; From 17c7ab66cd8db2259db64364b8638f01a622ba14 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Apr 2017 08:49:52 -0700 Subject: [PATCH 007/742] Add Netty customization hooks --- .../internal/core/DefaultDriverContext.java | 37 ++-------- .../internal/core/DefaultNettyOptions.java | 67 +++++++++++++++++++ .../driver/internal/core/DriverContext.java | 6 +- .../driver/internal/core/NettyOptions.java | 62 +++++++++++++++++ .../internal/core/channel/ChannelFactory.java | 15 ++++- .../core/channel/ChannelFactoryTestBase.java | 8 ++- 6 files changed, 155 insertions(+), 40 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java index 63274e1132f..993a2bcbdb7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java @@ -27,15 +27,9 @@ import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.typesafe.config.ConfigFactory; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.channel.Channel; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import java.util.concurrent.ThreadFactory; /** * Default implementation of the driver context. @@ -53,10 +47,8 @@ public class DefaultDriverContext implements DriverContext { new LazyReference<>("frameCodec", this::buildFrameCodec); private final LazyReference protocolVersionRegistry = new LazyReference<>("protocolVersionRegistry", this::buildProtocolVersionRegistry); - private final LazyReference ioThreadFactory = - new LazyReference<>("ioThreadFactory", this::buildIoThreadFactory); - private final LazyReference ioEventLoopGroup = - new LazyReference<>("ioEventLoopGroup", this::buildIoEventLoopGroup); + private final LazyReference nettyOptions = + new LazyReference<>("nettyOptions", this::buildNettyOptions); private final LazyReference authProvider = new LazyReference<>("authProvider", this::buildAuthProvider); private final LazyReference writeCoalescer = @@ -74,20 +66,15 @@ private Compressor buildCompressor() { protected FrameCodec buildFrameCodec() { return FrameCodec.defaultClient( - new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), compressor()); + new ByteBufPrimitiveCodec(nettyOptions().allocator()), compressor()); } protected ProtocolVersionRegistry buildProtocolVersionRegistry() { return new ProtocolVersionRegistry(); } - protected ThreadFactory buildIoThreadFactory() { - // TODO use the driver instance's name - return new ThreadFactoryBuilder().build(); - } - - protected EventLoopGroup buildIoEventLoopGroup() { - return new NioEventLoopGroup(0, ioThreadFactory()); + protected NettyOptions buildNettyOptions() { + return new DefaultNettyOptions(); } protected AuthProvider buildAuthProvider() { @@ -127,18 +114,8 @@ public ProtocolVersionRegistry protocolVersionRegistry() { } @Override - public ThreadFactory ioThreadFactory() { - return ioThreadFactory.get(); - } - - @Override - public EventLoopGroup ioEventLoopGroup() { - return ioEventLoopGroup.get(); - } - - @Override - public Class channelClass() { - return NioSocketChannel.class; + public NettyOptions nettyOptions() { + return nettyOptions.get(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java new file mode 100644 index 00000000000..cc5925c24c4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; + +public class DefaultNettyOptions implements NettyOptions { + private final NioEventLoopGroup ioEventLoopGroup; + + public DefaultNettyOptions() { + // TODO use the driver instance's name + ThreadFactory ioThreadFactory = new ThreadFactoryBuilder().build(); + this.ioEventLoopGroup = new NioEventLoopGroup(0, ioThreadFactory); + } + + @Override + public EventLoopGroup ioEventLoopGroup() { + return ioEventLoopGroup; + } + + @Override + public Class channelClass() { + return NioSocketChannel.class; + } + + @Override + public ByteBufAllocator allocator() { + return ByteBufAllocator.DEFAULT; + } + + @Override + public void afterBootstrapInitialized(Bootstrap bootstrap) { + // nothing to do + } + + @Override + public void afterChannelInitialized(Channel channel) { + // nothing to do + } + + @Override + public Future onShutdown() { + return ioEventLoopGroup.shutdownGracefully(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java index 0a9b0a709fd..278399976a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java @@ -45,11 +45,7 @@ public interface DriverContext { ProtocolVersionRegistry protocolVersionRegistry(); - ThreadFactory ioThreadFactory(); - - EventLoopGroup ioEventLoopGroup(); - - Class channelClass(); + NettyOptions nettyOptions(); AuthProvider authProvider(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java new file mode 100644 index 00000000000..174b332cd41 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.Future; + +/** Low-level hooks to control certain aspects of Netty usage in the driver. */ +public interface NettyOptions { + + /** The event loop group that will be used for I/O. This must always return the same instance. */ + EventLoopGroup ioEventLoopGroup(); + + /** + * The class to create {@code Channel} instances from. This must be consistent with {@link + * #ioEventLoopGroup()}. + */ + Class channelClass(); + + /** + * The byte buffer allocator to use. This must always return the same instance. Note that this is + * also used by the default implementation of {@link DriverContext#frameCodec()}, and the built-in + * {@link com.datastax.oss.protocol.internal.Compressor} implementations. + */ + ByteBufAllocator allocator(); + + /** + * A hook invoked each time the driver creates a client bootstrap in order to open a channel. This + * is a good place to configure any custom option on the bootstrap. + */ + void afterBootstrapInitialized(Bootstrap bootstrap); + + /** + * A hook invoked on each channel, right after the channel has initialized it. This is a good + * place to register any custom handler on the channel's pipeline (note that built-in driver + * handlers are already installed at that point). + */ + void afterChannelInitialized(Channel channel); + + /** + * A hook involved when the driver instance shuts down. This is a good place to free any resources + * that you have allocated elsewhere in this component, for example shut down custom event loop + * groups. + */ + Future onShutdown(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index c7f4af14ab2..b0fedcb1831 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; import com.datastax.oss.driver.internal.core.protocol.FrameEncoder; import com.google.common.annotations.VisibleForTesting; @@ -28,6 +29,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -88,11 +90,17 @@ private void connect( List attemptedVersions, CompletableFuture resultFuture) { + NettyOptions nettyOptions = driverContext.nettyOptions(); + Bootstrap bootstrap = new Bootstrap() - .group(driverContext.ioEventLoopGroup()) - .channel(driverContext.channelClass()) + .group(nettyOptions.ioEventLoopGroup()) + .channel(nettyOptions.channelClass()) + .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) .handler(initializer(currentVersion, keyspace)); + + nettyOptions.afterBootstrapInitialized(bootstrap); + ChannelFuture connectFuture = bootstrap.connect(address); connectFuture.addListener( @@ -149,7 +157,6 @@ protected void initChannel(Channel channel) throws Exception { defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); // TODO SSL - // TODO hook to add custom handlers InFlightHandler inFlightHandler = new InFlightHandler( protocolVersion, @@ -164,6 +171,8 @@ protected void initChannel(Channel channel) throws Exception { .addLast("inflight", inFlightHandler) .addLast("heartbeat", new HeartbeatHandler(defaultConfigProfile)) .addLast("init", initHandler); + + driverContext.nettyOptions().afterChannelInitialized(channel); } }; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 917c47101b9..f2fa1bbf7d2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.NettyOptions; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.protocol.internal.Compressor; @@ -75,6 +76,7 @@ abstract class ChannelFactoryTestBase { @Mock DriverContext driverContext; @Mock DriverConfig driverConfig; @Mock DriverConfigProfile defaultConfigProfile; + @Mock NettyOptions nettyOptions; @Mock ProtocolVersionRegistry protocolVersionRegistry; // The server's I/O thread will store the last received request here, and block until the test @@ -110,8 +112,10 @@ public void setup() throws InterruptedException { .thenReturn(1); Mockito.when(driverContext.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); - Mockito.when(driverContext.ioEventLoopGroup()).thenReturn(clientGroup); - Mockito.when(driverContext.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); + Mockito.when(driverContext.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); + Mockito.when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); + Mockito.when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); Mockito.when(driverContext.frameCodec()) .thenReturn( FrameCodec.defaultClient( From 9d3e50130f0adcc00b3582e53ff6e65af36e20bd Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Apr 2017 08:58:51 -0700 Subject: [PATCH 008/742] Rename fields in DefaultDriverContext --- .../internal/core/DefaultDriverContext.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java index 993a2bcbdb7..e53517eac8b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java @@ -29,7 +29,6 @@ import com.datastax.oss.protocol.internal.FrameCodec; import com.typesafe.config.ConfigFactory; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; /** * Default implementation of the driver context. @@ -39,19 +38,19 @@ */ public class DefaultDriverContext implements DriverContext { - private final LazyReference config = + private final LazyReference configRef = new LazyReference<>("config", this::buildDriverConfig); - private final LazyReference> compressor = + private final LazyReference> compressorRef = new LazyReference<>("compressor", this::buildCompressor); - private final LazyReference> frameCodec = + private final LazyReference> frameCodecRef = new LazyReference<>("frameCodec", this::buildFrameCodec); - private final LazyReference protocolVersionRegistry = + private final LazyReference protocolVersionRegistryRef = new LazyReference<>("protocolVersionRegistry", this::buildProtocolVersionRegistry); - private final LazyReference nettyOptions = + private final LazyReference nettyOptionsRef = new LazyReference<>("nettyOptions", this::buildNettyOptions); - private final LazyReference authProvider = + private final LazyReference authProviderRef = new LazyReference<>("authProvider", this::buildAuthProvider); - private final LazyReference writeCoalescer = + private final LazyReference writeCoalescerRef = new LazyReference<>("writeCoalescer", this::buildWriteCoalescer); private DriverConfig buildDriverConfig() { @@ -95,36 +94,36 @@ private WriteCoalescer buildWriteCoalescer() { @Override public DriverConfig config() { - return config.get(); + return configRef.get(); } @Override public Compressor compressor() { - return compressor.get(); + return compressorRef.get(); } @Override public FrameCodec frameCodec() { - return frameCodec.get(); + return frameCodecRef.get(); } @Override public ProtocolVersionRegistry protocolVersionRegistry() { - return protocolVersionRegistry.get(); + return protocolVersionRegistryRef.get(); } @Override public NettyOptions nettyOptions() { - return nettyOptions.get(); + return nettyOptionsRef.get(); } @Override public AuthProvider authProvider() { - return authProvider.get(); + return authProviderRef.get(); } @Override public WriteCoalescer writeCoalescer() { - return writeCoalescer.get(); + return writeCoalescerRef.get(); } } From b8394cc8d55883200bb8b6b12db76bdb76c4a225 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Apr 2017 11:14:32 -0700 Subject: [PATCH 009/742] Add SSL support --- .../api/core/config/CoreDriverOption.java | 6 +- .../api/core/config/DriverConfigProfile.java | 3 + .../api/core/ssl/DefaultSslEngineFactory.java | 82 +++++++++++++++++++ .../driver/api/core/ssl/SslEngineFactory.java | 36 ++++++++ .../oss/driver/api/core/ssl/package-info.java | 17 ++++ .../internal/core/DefaultDriverContext.java | 39 ++++++--- .../driver/internal/core/DriverContext.java | 3 + .../internal/core/channel/ChannelFactory.java | 14 +++- .../typesafe/TypesafeDriverConfigProfile.java | 6 ++ .../core/ssl/JdkSslHandlerFactory.java | 38 +++++++++ .../internal/core/ssl/SslHandlerFactory.java | 51 ++++++++++++ .../driver/internal/core/util/Reflection.java | 32 ++++++-- core/src/main/resources/reference.conf | 14 ++++ .../core/channel/ChannelFactoryTestBase.java | 5 +- .../typesafe/TypeSafeDriverConfigTest.java | 1 + 15 files changed, 323 insertions(+), 24 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ssl/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index c5649c4334f..d0caa365947 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -27,7 +27,11 @@ public enum CoreDriverOption implements DriverOption { AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false), - AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false); + AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false), + + SSL_FACTORY_CLASS("ssl.factory-class", false), + SSL_CONFIG_CIPHER_SUITES("ssl.config.cipher-suites", false), + ; private final String path; private final boolean required; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 14b88163e2b..7541677fb00 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.config; +import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -29,6 +30,8 @@ public interface DriverConfigProfile { String getString(DriverOption option); + List getStringList(DriverOption option); + long getBytes(DriverOption option); long getDuration(DriverOption option, TimeUnit targetUnit); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java new file mode 100644 index 00000000000..fc174ea261c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +/** + * Default SSL implementation. + * + *

To activate this class, an {@code ssl} section must be included in the driver configuration, + * for example: + * + *

+ * datastax-java-driver {
+ *   ssl {
+ *     factory-class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory
+ *     config {
+ *       cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
+ *     }
+ *   }
+ * }
+ * 
+ * + * See the {@code reference.conf} file included with the driver for more information. + */ +public class DefaultSslEngineFactory implements SslEngineFactory { + + private final SSLContext context; + private final String[] cipherSuites; + + /** Builds a new instance from the driver configuration. */ + public DefaultSslEngineFactory(DriverConfigProfile config) { + try { + this.context = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot initialize SSL Context", e); + } + if (config.isDefined(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES)) { + List list = config.getStringList(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES); + String tmp[] = new String[list.size()]; + this.cipherSuites = list.toArray(tmp); + } else { + this.cipherSuites = null; + } + } + + @Override + public SSLEngine newSslEngine(SocketAddress remoteEndpoint) { + SSLEngine engine; + if (remoteEndpoint instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress) remoteEndpoint; + engine = context.createSSLEngine(address.getHostName(), address.getPort()); + } else { + engine = context.createSSLEngine(); + } + engine.setUseClientMode(true); + if (cipherSuites != null) { + engine.setEnabledCipherSuites(cipherSuites); + } + return engine; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java new file mode 100644 index 00000000000..724955b18dc --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import java.net.SocketAddress; +import javax.net.ssl.SSLEngine; + +/** + * Extension point to configure SSL based on the built-in JDK implementation. + * + *

Note that, for advanced use cases (such as bypassing the JDK in favor of another SSL + * implementation), the driver's internal API provides a lower-level interface: {@link + * com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory}. + */ +public interface SslEngineFactory { + /** + * Creates a new SSL engine each time a connection is established. + * + * @param remoteEndpoint the remote endpoint we are connecting to (the address of the Cassandra + * node). + */ + SSLEngine newSslEngine(SocketAddress remoteEndpoint); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/package-info.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/package-info.java new file mode 100644 index 00000000000..1b5d677c3e3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** Support for secured communication between the driver and Cassandra nodes. */ +package com.datastax.oss.driver.api.core.ssl; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java index e53517eac8b..ab99be542d9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java @@ -18,11 +18,13 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; +import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.driver.internal.core.util.Reflection; import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; import com.datastax.oss.protocol.internal.Compressor; @@ -52,6 +54,8 @@ public class DefaultDriverContext implements DriverContext { new LazyReference<>("authProvider", this::buildAuthProvider); private final LazyReference writeCoalescerRef = new LazyReference<>("writeCoalescer", this::buildWriteCoalescer); + private final LazyReference sslHandlerFactoryRef = + new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory); private DriverConfig buildDriverConfig() { return new TypeSafeDriverConfig( @@ -77,15 +81,25 @@ protected NettyOptions buildNettyOptions() { } protected AuthProvider buildAuthProvider() { - DriverConfigProfile defaultConfig = config().defaultProfile(); - CoreDriverOption classOption = CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS; - if (defaultConfig.isDefined(classOption)) { - String className = defaultConfig.getString(classOption); - return Reflection.buildWithConfig( - className, AuthProvider.class, defaultConfig, classOption.getPath()); - } else { - return AuthProvider.NONE; - } + AuthProvider configuredProvider = + Reflection.buildFromConfig( + config().defaultProfile(), + CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, + AuthProvider.class); + return (configuredProvider == null) ? AuthProvider.NONE : configuredProvider; + } + + protected SslHandlerFactory buildSslHandlerFactory() { + // If a JDK-based factory was provided through the public API, wrap that, otherwise no SSL. + SslEngineFactory sslEngineFactory = + Reflection.buildFromConfig( + config().defaultProfile(), CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); + return (sslEngineFactory == null) + ? SslHandlerFactory.NONE + : new JdkSslHandlerFactory(sslEngineFactory); + + // For more advanced options (like using Netty's native OpenSSL support instead of the JDK), + // extend DefaultDriverContext and override this method } private WriteCoalescer buildWriteCoalescer() { @@ -126,4 +140,9 @@ public AuthProvider authProvider() { public WriteCoalescer writeCoalescer() { return writeCoalescerRef.get(); } + + @Override + public SslHandlerFactory sslHandlerFactory() { + return sslHandlerFactoryRef.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java index 278399976a6..8e899016f60 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; +import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; @@ -50,4 +51,6 @@ public interface DriverContext { AuthProvider authProvider(); WriteCoalescer writeCoalescer(); + + SslHandlerFactory sslHandlerFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index b0fedcb1831..4bd01878933 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -30,6 +30,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -97,7 +98,7 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(currentVersion, keyspace)); + .handler(initializer(address, currentVersion, keyspace)); nettyOptions.afterBootstrapInitialized(bootstrap); @@ -142,7 +143,7 @@ private void connect( @VisibleForTesting ChannelInitializer initializer( - final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) { + SocketAddress address, final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { @@ -164,8 +165,13 @@ protected void initChannel(Channel channel) throws Exception { setKeyspaceTimeoutMillis); ProtocolInitHandler initHandler = new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace); - channel - .pipeline() + + ChannelPipeline pipeline = channel.pipeline(); + driverContext + .sslHandlerFactory() + .newSslHandler(channel, address) + .map(h -> pipeline.addLast("ssl", h)); + pipeline .addLast("encoder", new FrameEncoder(driverContext.frameCodec())) .addLast("decoder", new FrameDecoder(driverContext.frameCodec(), maxFrameLength)) .addLast("inflight", inFlightHandler) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 64e92ad75ff..956ced8f05f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.typesafe.config.Config; +import java.util.List; import java.util.concurrent.TimeUnit; public class TypesafeDriverConfigProfile implements DriverConfigProfile { @@ -47,6 +48,11 @@ public String getString(DriverOption option) { return config.getString(option.getPath()); } + @Override + public List getStringList(DriverOption option) { + return config.getStringList(option.getPath()); + } + @Override public long getBytes(DriverOption option) { return config.getBytes(option.getPath()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java new file mode 100644 index 00000000000..77e5c5272d6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.ssl; + +import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import io.netty.channel.Channel; +import io.netty.handler.ssl.SslHandler; +import java.net.SocketAddress; +import java.util.Optional; +import javax.net.ssl.SSLEngine; + +/** SSL handler factory used when JDK-based SSL was configured through the driver's public API. */ +public class JdkSslHandlerFactory implements SslHandlerFactory { + private final SslEngineFactory sslEngineFactory; + + public JdkSslHandlerFactory(SslEngineFactory sslEngineFactory) { + this.sslEngineFactory = sslEngineFactory; + } + + @Override + public Optional newSslHandler(Channel channel, SocketAddress remoteEndpoint) { + SSLEngine engine = sslEngineFactory.newSslEngine(remoteEndpoint); + return Optional.of(new SslHandler(engine)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java new file mode 100644 index 00000000000..94739a7854b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.ssl; + +import com.datastax.oss.driver.internal.core.DefaultDriverContext; +import io.netty.channel.Channel; +import io.netty.handler.ssl.SslHandler; +import java.net.SocketAddress; +import java.util.Optional; + +/** + * Low-level SSL extension point. + * + *

SSL is separated into two interfaces to avoid exposing Netty classes in our public API: + * + *

    + * <<<<<<< Updated upstream + *
  • "normal" (JDK-based) SSL is part of the public API, and can be configured via an instance + * of {@link com.datastax.oss.driver.api.core.ssl.SslEngineFactory} defined in the driver + * configuration. + *
  • this interface deals with Netty handlers directly. It can be used for more advanced cases, + * like using Netty's native OpenSSL integration instead of the JDK. This is considered expert + * level, and therefore part of our internal API. ======= + *
  • "normal" (JDK-based) SSL is part of the public API, and can be configured via an instance + * of {@link com.datastax.oss.driver.api.core.ssl.SslEngineFactory} defined in the driver + * configuration. + *
  • this interface deals with Netty handlers directly. It can be used for more advanced cases, + * like using Netty's native OpenSSL integration instead of the JDK. This is considered expert + * level, and therefore part of our internal API. >>>>>>> Stashed changes + *
+ * + * @see DefaultDriverContext#buildSslHandlerFactory() + */ +public interface SslHandlerFactory { + SslHandlerFactory NONE = (channel, remoteEndpoint) -> Optional.empty(); + + Optional newSslHandler(Channel channel, SocketAddress remoteEndpoint); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 9cf0fa5da1b..579808f8769 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.util; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.google.common.base.Preconditions; import java.lang.reflect.Constructor; @@ -43,17 +44,31 @@ public static Class loadClass(String className, String source) { } /** - * Builds a new instance of a class given its name, expecting a public constructor that takes the - * driver's configuration as argument. + * Tries to create an instance of a class, given its name defined in the driver configuration. + * + * @param config the driver configuration. + * @param classNameOption the configuration option that contains the fully-qualified name of the + * class to instantiate. It must have a constructor that takes the configuration as an + * argument. + * @param expectedSuperType a super-type that the class is expected to implement/extend. + * @return the new instance, or {@code null} if {@code classNameOption} is not defined in the + * configuration. */ - public static T buildWithConfig( - String className, Class expectedSuperType, DriverConfigProfile config, String source) { - Class clazz = loadClass(className, source); + public static T buildFromConfig( + DriverConfigProfile config, DriverOption classNameOption, Class expectedSuperType) { + + if (!config.isDefined(classNameOption)) { + return null; + } + + String className = config.getString(classNameOption); + String configPath = classNameOption.getPath(); + Class clazz = loadClass(className, configPath); Preconditions.checkArgument( expectedSuperType.isAssignableFrom(clazz), "Expected class %s (specified by %s) to be a subtype of %s", className, - source, + configPath, expectedSuperType.getName()); Constructor constructor; @@ -64,14 +79,15 @@ public static T buildWithConfig( String.format( "Expected class %s (specified by %s) " + "to have an accessible constructor with a single %s argument", - className, source, DriverConfigProfile.class.getSimpleName())); + className, configPath, DriverConfigProfile.class.getSimpleName())); } try { Object instance = constructor.newInstance(config); return expectedSuperType.cast(instance); } catch (Exception e) { throw new IllegalArgumentException( - String.format("Error instantiating class %s (specified by %s)", className, source), e); + String.format("Error instantiating class %s (specified by %s)", className, configPath), + e); } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 123949d69d1..79514be7c18 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -59,4 +59,18 @@ datastax-java-driver { // password = cassandra // } } + ssl { + # The SSL engine factory to use. The driver expects this class to implement SslEngineFactory, + # and have a public constructor that takes a DriverConfigProfile argument. It will invoke it + # with the default configuration profile. + # This property is optional; if it is not present, SSL won't be activated. + // factory-class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory + config { + # The cipher suites to enable when creating an SSLEngine for a connection. + # This property is optional. If it is not present, the driver won't explicitly enable cipher + # suites on the engine, which according to the JDK documentations results in "a minimum + # quality of service". + // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] + } + } } \ No newline at end of file diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index f2fa1bbf7d2..38b47d02c4c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.internal.core.NettyOptions; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; @@ -39,6 +40,7 @@ import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; +import java.net.SocketAddress; import java.util.Collections; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; @@ -120,6 +122,7 @@ public void setup() throws InterruptedException { .thenReturn( FrameCodec.defaultClient( new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); + Mockito.when(driverContext.sslHandlerFactory()).thenReturn(SslHandlerFactory.NONE); // Start local server ServerBootstrap serverBootstrap = @@ -195,7 +198,7 @@ private TestChannelFactory(DriverContext driverContext) { @Override ChannelInitializer initializer( - ProtocolVersion protocolVersion, CqlIdentifier keyspace) { + SocketAddress address, ProtocolVersion protocolVersion, CqlIdentifier keyspace) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 0b13febf9fe..e7ae3d29d03 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import java.util.List; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; From 194d6f87bbe5ec8876407044bc17518d1fd03f88 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Apr 2017 11:40:31 -0700 Subject: [PATCH 010/742] Revisit plain text auth provider docs --- .../api/core/auth/PlainTextAuthProvider.java | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index 636a824e76b..936778a9847 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -22,33 +22,35 @@ import java.nio.ByteBuffer; /** - * A simple {@code AuthProvider} implementation. + * A simple authentication provider that supports SASL authentication using the PLAIN mechanism for + * version 3 (or above) of the CQL native protocol. * - *

This provider allows to programmatically define authentication information that will then - * apply to all hosts. The {@link Authenticator} instances it returns support SASL authentication - * using the PLAIN mechanism for version 2 (or above) of the CQL native protocol. + *

To activate this provider, an {@code authentication} section must be included in the driver + * configuration, for example: + * + *

+ * datastax-java-driver {
+ *   authentication {
+ *     provider-class = com.datastax.driver.api.core.auth.PlainTextAuthProvider
+ *     config {
+ *       username = cassandra
+ *       password = cassandra
+ *     }
+ *   }
+ * }
+ * 
+ * + * See the {@code reference.conf} file included with the driver for more information. */ public class PlainTextAuthProvider implements AuthProvider { private final DriverConfigProfile config; - /** - * Create a new simple authentication information provider with the supplied credentials. - * - * @param config the configuration of the driver (default profile). - */ + /** Builds a new instance from the driver configuration. */ public PlainTextAuthProvider(DriverConfigProfile config) { this.config = config; } - /** - * Use the supplied credentials and the SASL PLAIN mechanism to login to the server. - * - * @param host the Cassandra host with which we want to authenticate. - * @param serverAuthenticator the configured authenticator on the host. - * @return an authenticator instance which can be used to perform authentication negotiations on - * behalf of the client. - */ @Override public Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) { String username = config.getString(CoreDriverOption.AUTHENTICATION_CONFIG_USERNAME); @@ -56,10 +58,6 @@ public Authenticator newAuthenticator(SocketAddress host, String serverAuthentic return new PlainTextAuthenticator(username, password); } - /** - * Simple implementation of {@link Authenticator} which can perform authentication against - * Cassandra servers configured with {@code PasswordAuthenticator}. - */ private static class PlainTextAuthenticator implements SyncAuthenticator { private final ByteBuffer initialToken; From 1e7d46e108c3f3a9a17648beead13980b89878e8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Apr 2017 17:11:55 -0700 Subject: [PATCH 011/742] Split driver context into public/internal interfaces --- .../driver/api/core/auth/AuthProvider.java | 13 -- .../api/core/auth/PlainTextAuthProvider.java | 7 +- .../api/core/context/DriverContext.java | 34 +++++ .../api/core/ssl/DefaultSslEngineFactory.java | 12 +- .../internal/core/channel/ChannelFactory.java | 37 +++--- .../core/channel/ProtocolInitHandler.java | 29 +++-- .../{ => context}/DefaultDriverContext.java | 95 ++++++++------ .../{ => context}/DefaultNettyOptions.java | 3 +- .../InternalDriverContext.java} | 29 ++--- .../core/{ => context}/NettyOptions.java | 6 +- .../core/ssl/JdkSslHandlerFactory.java | 5 +- .../internal/core/ssl/SslHandlerFactory.java | 16 +-- .../driver/internal/core/util/Reflection.java | 27 ++-- .../core/util/concurrent/CycleDetector.java | 81 ++++++++++++ .../core/util/concurrent/LazyReference.java | 17 ++- .../core/channel/ChannelFactoryTestBase.java | 30 +++-- .../core/channel/ProtocolInitHandlerTest.java | 32 +++-- .../util/concurrent/CycleDetectorTest.java | 117 ++++++++++++++++++ 18 files changed, 420 insertions(+), 170 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java rename core/src/main/java/com/datastax/oss/driver/internal/core/{ => context}/DefaultDriverContext.java (58%) rename core/src/main/java/com/datastax/oss/driver/internal/core/{ => context}/DefaultNettyOptions.java (95%) rename core/src/main/java/com/datastax/oss/driver/internal/core/{DriverContext.java => context/InternalDriverContext.java} (56%) rename core/src/main/java/com/datastax/oss/driver/internal/core/{ => context}/NettyOptions.java (90%) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java index 9f3fc2ff66b..ee91e88a624 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java @@ -25,19 +25,6 @@ */ public interface AuthProvider { - /** - * A provider that provides no authentication capability. - * - *

This is only useful as a placeholder when no authentication is to be used. - */ - AuthProvider NONE = - (host, serverAuthenticator) -> { - throw new AuthenticationException( - host, - String.format( - "Host %s requires authentication, but no authenticator configured", host)); - }; - /** * The authenticator to use when connecting to {@code host}. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index 936778a9847..aed2ea58a8c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Charsets; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -46,9 +47,9 @@ public class PlainTextAuthProvider implements AuthProvider { private final DriverConfigProfile config; - /** Builds a new instance from the driver configuration. */ - public PlainTextAuthProvider(DriverConfigProfile config) { - this.config = config; + /** Builds a new instance. */ + public PlainTextAuthProvider(DriverContext context) { + this.config = context.config().defaultProfile(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java new file mode 100644 index 00000000000..d19fceb85c7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.context; + +import com.datastax.oss.driver.api.core.auth.AuthProvider; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import java.util.Optional; + +/** Holds common components that are shared throughout a driver instance. */ +public interface DriverContext { + + /** The driver's configuration. */ + DriverConfig config(); + + /** The authentication provider, if authentication was configured. */ + Optional authProvider(); + + /** The SSL engine factory, if SSL was configured. */ + Optional sslEngineFactory(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java index fc174ea261c..74244cf87b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.NoSuchAlgorithmException; @@ -45,16 +46,17 @@ */ public class DefaultSslEngineFactory implements SslEngineFactory { - private final SSLContext context; + private final SSLContext sslContext; private final String[] cipherSuites; /** Builds a new instance from the driver configuration. */ - public DefaultSslEngineFactory(DriverConfigProfile config) { + public DefaultSslEngineFactory(DriverContext driverContext) { try { - this.context = SSLContext.getDefault(); + this.sslContext = SSLContext.getDefault(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Cannot initialize SSL Context", e); } + DriverConfigProfile config = driverContext.config().defaultProfile(); if (config.isDefined(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES)) { List list = config.getStringList(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES); String tmp[] = new String[list.size()]; @@ -69,9 +71,9 @@ public SSLEngine newSslEngine(SocketAddress remoteEndpoint) { SSLEngine engine; if (remoteEndpoint instanceof InetSocketAddress) { InetSocketAddress address = (InetSocketAddress) remoteEndpoint; - engine = context.createSSLEngine(address.getHostName(), address.getPort()); + engine = sslContext.createSSLEngine(address.getHostName(), address.getPort()); } else { - engine = context.createSSLEngine(); + engine = sslContext.createSSLEngine(); } engine.setUseClientMode(true); if (cipherSuites != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 4bd01878933..e25fba2fd77 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.internal.core.DriverContext; -import com.datastax.oss.driver.internal.core.NettyOptions; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; import com.datastax.oss.driver.internal.core.protocol.FrameEncoder; import com.google.common.annotations.VisibleForTesting; @@ -47,20 +47,20 @@ public class ChannelFactory { private static final Logger LOG = LoggerFactory.getLogger(ChannelFactory.class); - protected final DriverContext driverContext; + protected final InternalDriverContext internalDriverContext; /** either set from the configuration, or null and will be negotiated */ @VisibleForTesting ProtocolVersion protocolVersion; @VisibleForTesting volatile String clusterName; - public ChannelFactory(DriverContext driverContext) { - this.driverContext = driverContext; + public ChannelFactory(InternalDriverContext internalDriverContext) { + this.internalDriverContext = internalDriverContext; - DriverConfigProfile defaultConfig = driverContext.config().defaultProfile(); + DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); if (defaultConfig.isDefined(CoreDriverOption.PROTOCOL_VERSION)) { String versionName = defaultConfig.getString(CoreDriverOption.PROTOCOL_VERSION); - this.protocolVersion = driverContext.protocolVersionRegistry().fromName(versionName); + this.protocolVersion = internalDriverContext.protocolVersionRegistry().fromName(versionName); } // else it will be negotiated with the first opened connection } @@ -75,7 +75,7 @@ public CompletionStage connect( currentVersion = protocolVersion; isNegotiating = false; } else { - currentVersion = driverContext.protocolVersionRegistry().highestNonBeta(); + currentVersion = internalDriverContext.protocolVersionRegistry().highestNonBeta(); isNegotiating = true; } @@ -91,7 +91,7 @@ private void connect( List attemptedVersions, CompletableFuture resultFuture) { - NettyOptions nettyOptions = driverContext.nettyOptions(); + NettyOptions nettyOptions = internalDriverContext.nettyOptions(); Bootstrap bootstrap = new Bootstrap() @@ -108,7 +108,7 @@ private void connect( cf -> { if (connectFuture.isSuccess()) { DriverChannel driverChannel = - new DriverChannel(connectFuture.channel(), driverContext.writeCoalescer()); + new DriverChannel(connectFuture.channel(), internalDriverContext.writeCoalescer()); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -123,7 +123,7 @@ private void connect( if (error instanceof UnsupportedProtocolVersionException && isNegotiating) { attemptedVersions.add(currentVersion); Optional downgraded = - driverContext.protocolVersionRegistry().downgrade(currentVersion); + internalDriverContext.protocolVersionRegistry().downgrade(currentVersion); if (downgraded.isPresent()) { LOG.info( "Failed to connect with protocol {}, retrying with {}", @@ -147,7 +147,7 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = driverContext.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = internalDriverContext.config().defaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile.getDuration( @@ -164,21 +164,22 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis); ProtocolInitHandler initHandler = - new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler(internalDriverContext, protocolVersion, clusterName, keyspace); ChannelPipeline pipeline = channel.pipeline(); - driverContext + internalDriverContext .sslHandlerFactory() - .newSslHandler(channel, address) + .map(f -> f.newSslHandler(channel, address)) .map(h -> pipeline.addLast("ssl", h)); pipeline - .addLast("encoder", new FrameEncoder(driverContext.frameCodec())) - .addLast("decoder", new FrameDecoder(driverContext.frameCodec(), maxFrameLength)) + .addLast("encoder", new FrameEncoder(internalDriverContext.frameCodec())) + .addLast( + "decoder", new FrameDecoder(internalDriverContext.frameCodec(), maxFrameLength)) .addLast("inflight", inFlightHandler) .addLast("heartbeat", new HeartbeatHandler(defaultConfigProfile)) .addLast("init", initHandler); - driverContext.nettyOptions().afterChannelInitialized(channel); + internalDriverContext.nettyOptions().afterChannelInitialized(channel); } }; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 391acb2b907..cb2c7217eb0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ConnectionException; -import com.datastax.oss.driver.internal.core.DriverContext; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -40,6 +40,7 @@ import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.base.Charsets; import io.netty.channel.ChannelHandlerContext; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.TimeUnit; @@ -55,7 +56,7 @@ class ProtocolInitHandler extends ConnectInitHandler { private static final Query CLUSTER_NAME_QUERY = new Query("SELECT cluster_name FROM system.local"); - private final DriverContext driverContext; + private final InternalDriverContext internalDriverContext; private final long timeoutMillis; private final ProtocolVersion initialProtocolVersion; private final CqlIdentifier keyspaceName; @@ -63,14 +64,14 @@ class ProtocolInitHandler extends ConnectInitHandler { private final String expectedClusterName; ProtocolInitHandler( - DriverContext driverContext, + InternalDriverContext internalDriverContext, ProtocolVersion protocolVersion, String expectedClusterName, CqlIdentifier keyspaceName) { - this.driverContext = driverContext; + this.internalDriverContext = internalDriverContext; - DriverConfigProfile defaultConfig = driverContext.config().defaultProfile(); + DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); this.timeoutMillis = defaultConfig.getDuration( @@ -138,10 +139,7 @@ void onResponse(Message response) { send(); } else if (step == Step.STARTUP && response instanceof Authenticate) { Authenticate authenticate = (Authenticate) response; - authenticator = - driverContext - .authProvider() - .newAuthenticator(channel.remoteAddress(), authenticate.authenticator); + authenticator = buildAuthenticator(channel.remoteAddress(), authenticate.authenticator); authenticator .initialResponse() .whenCompleteAsync( @@ -240,6 +238,19 @@ void onResponse(Message response) { void fail(Throwable cause) { setConnectFailure(cause); } + + private Authenticator buildAuthenticator(SocketAddress address, String authenticator) { + return internalDriverContext + .authProvider() + .map(p -> p.newAuthenticator(address, authenticator)) + .orElseThrow( + () -> + new AuthenticationException( + address, + String.format( + "Host %s requires authentication (%s), but no authenticator configured", + address, authenticator))); + } } // TODO we'll probably need a lightweight ResultSet implementation for internal uses, but this is good for now diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java similarity index 58% rename from core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index ab99be542d9..c313b180f9d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core; +package com.datastax.oss.driver.internal.core.context; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; @@ -26,42 +27,71 @@ import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.driver.internal.core.util.Reflection; +import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector; import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import com.typesafe.config.ConfigFactory; import io.netty.buffer.ByteBuf; +import java.util.Optional; /** * Default implementation of the driver context. * - *

All non-constant components are stored as lazy references. Deadlocks or stack overflows may - * occur if there are cycles in the object graph. + *

All non-constant components are initialized lazily. Some components depend on others, so there + * might be deadlocks or stack overflows if the dependency graph is badly designed. This can be + * checked automatically with the system property {@code + * -Dcom.datastax.oss.driver.DETECT_CYCLES=true} (this might have a slight impact on startup time, + * so the check is disabled by default). + * + *

This is DIY dependency injection. We stayed away from DI frameworks for simplicity, to avoid + * an extra dependency, and because end users might want to access some of these components in their + * own implementations (which wouldn't work well with compile-time approaches like Dagger). + * + *

This also provides extension points for stuff that is too low-level for the driver + * configuration: the intent is that someone can extend this class, override one (or more) of the + * buildXxx methods, and initialize the cluster with this new implementation. */ -public class DefaultDriverContext implements DriverContext { +public class DefaultDriverContext implements InternalDriverContext { + + private final CycleDetector cycleDetector = + new CycleDetector("Detected cycle in context initialization"); private final LazyReference configRef = - new LazyReference<>("config", this::buildDriverConfig); + new LazyReference<>("config", this::buildDriverConfig, cycleDetector); + private final LazyReference> authProviderRef = + new LazyReference<>("authProvider", this::buildAuthProvider, cycleDetector); + private final LazyReference> sslEngineFactoryRef = + new LazyReference<>("sslEngineFactory", this::buildSslEngineFactory, cycleDetector); private final LazyReference> compressorRef = - new LazyReference<>("compressor", this::buildCompressor); + new LazyReference<>("compressor", this::buildCompressor, cycleDetector); private final LazyReference> frameCodecRef = - new LazyReference<>("frameCodec", this::buildFrameCodec); + new LazyReference<>("frameCodec", this::buildFrameCodec, cycleDetector); private final LazyReference protocolVersionRegistryRef = - new LazyReference<>("protocolVersionRegistry", this::buildProtocolVersionRegistry); + new LazyReference<>( + "protocolVersionRegistry", this::buildProtocolVersionRegistry, cycleDetector); private final LazyReference nettyOptionsRef = - new LazyReference<>("nettyOptions", this::buildNettyOptions); - private final LazyReference authProviderRef = - new LazyReference<>("authProvider", this::buildAuthProvider); + new LazyReference<>("nettyOptions", this::buildNettyOptions, cycleDetector); private final LazyReference writeCoalescerRef = - new LazyReference<>("writeCoalescer", this::buildWriteCoalescer); - private final LazyReference sslHandlerFactoryRef = - new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory); + new LazyReference<>("writeCoalescer", this::buildWriteCoalescer, cycleDetector); + private final LazyReference> sslHandlerFactoryRef = + new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory, cycleDetector); private DriverConfig buildDriverConfig() { return new TypeSafeDriverConfig( ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); } + protected Optional buildAuthProvider() { + return Reflection.buildFromConfig( + this, CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, AuthProvider.class); + } + + protected Optional buildSslEngineFactory() { + return Reflection.buildFromConfig( + this, CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); + } + private Compressor buildCompressor() { // TODO build alternate implementation if specified in conf return Compressor.none(); @@ -80,23 +110,9 @@ protected NettyOptions buildNettyOptions() { return new DefaultNettyOptions(); } - protected AuthProvider buildAuthProvider() { - AuthProvider configuredProvider = - Reflection.buildFromConfig( - config().defaultProfile(), - CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, - AuthProvider.class); - return (configuredProvider == null) ? AuthProvider.NONE : configuredProvider; - } - - protected SslHandlerFactory buildSslHandlerFactory() { - // If a JDK-based factory was provided through the public API, wrap that, otherwise no SSL. - SslEngineFactory sslEngineFactory = - Reflection.buildFromConfig( - config().defaultProfile(), CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); - return (sslEngineFactory == null) - ? SslHandlerFactory.NONE - : new JdkSslHandlerFactory(sslEngineFactory); + protected Optional buildSslHandlerFactory() { + // If a JDK-based factory was provided through the public API, wrap it + return buildSslEngineFactory().map(JdkSslHandlerFactory::new); // For more advanced options (like using Netty's native OpenSSL support instead of the JDK), // extend DefaultDriverContext and override this method @@ -111,6 +127,16 @@ public DriverConfig config() { return configRef.get(); } + @Override + public Optional authProvider() { + return authProviderRef.get(); + } + + @Override + public Optional sslEngineFactory() { + return sslEngineFactoryRef.get(); + } + @Override public Compressor compressor() { return compressorRef.get(); @@ -131,18 +157,13 @@ public NettyOptions nettyOptions() { return nettyOptionsRef.get(); } - @Override - public AuthProvider authProvider() { - return authProviderRef.get(); - } - @Override public WriteCoalescer writeCoalescer() { return writeCoalescerRef.get(); } @Override - public SslHandlerFactory sslHandlerFactory() { + public Optional sslHandlerFactory() { return sslHandlerFactoryRef.get(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java similarity index 95% rename from core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index cc5925c24c4..1ffe7760ad6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core; +package com.datastax.oss.driver.internal.core.context; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.Bootstrap; @@ -21,7 +21,6 @@ import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.Future; import java.util.concurrent.ThreadFactory; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java similarity index 56% rename from core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 8e899016f60..22b5c522ce0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -13,32 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core; +package com.datastax.oss.driver.internal.core.context; -import com.datastax.oss.driver.api.core.auth.AuthProvider; -import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.EventLoopGroup; -import java.util.concurrent.ThreadFactory; +import java.util.Optional; -/** - * Holder for common singletons that are shared throughout the driver. - * - *

There is an instance of this class for each driver instance. - * - *

This is essentially poor man's dependency injection (even the simplest DI frameworks are too - * overkill for our needs, and they introduce extra dependencies). - * - *

This also provides extension points for stuff that is too low-level for the configuration. - */ -public interface DriverContext { - - DriverConfig config(); +/** Extends the driver context with additional components that are not exposed by our public API. */ +public interface InternalDriverContext extends DriverContext { Compressor compressor(); @@ -48,9 +35,7 @@ public interface DriverContext { NettyOptions nettyOptions(); - AuthProvider authProvider(); - WriteCoalescer writeCoalescer(); - SslHandlerFactory sslHandlerFactory(); + Optional sslHandlerFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java similarity index 90% rename from core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 174b332cd41..9e52d19ed95 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core; +package com.datastax.oss.driver.internal.core.context; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; @@ -35,8 +35,8 @@ public interface NettyOptions { /** * The byte buffer allocator to use. This must always return the same instance. Note that this is - * also used by the default implementation of {@link DriverContext#frameCodec()}, and the built-in - * {@link com.datastax.oss.protocol.internal.Compressor} implementations. + * also used by the default implementation of {@link InternalDriverContext#frameCodec()}, and the + * built-in {@link com.datastax.oss.protocol.internal.Compressor} implementations. */ ByteBufAllocator allocator(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java index 77e5c5272d6..31af7135715 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java @@ -19,7 +19,6 @@ import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; import java.net.SocketAddress; -import java.util.Optional; import javax.net.ssl.SSLEngine; /** SSL handler factory used when JDK-based SSL was configured through the driver's public API. */ @@ -31,8 +30,8 @@ public JdkSslHandlerFactory(SslEngineFactory sslEngineFactory) { } @Override - public Optional newSslHandler(Channel channel, SocketAddress remoteEndpoint) { + public SslHandler newSslHandler(Channel channel, SocketAddress remoteEndpoint) { SSLEngine engine = sslEngineFactory.newSslEngine(remoteEndpoint); - return Optional.of(new SslHandler(engine)); + return new SslHandler(engine); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java index 94739a7854b..48e5d86606b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java @@ -15,11 +15,10 @@ */ package com.datastax.oss.driver.internal.core.ssl; -import com.datastax.oss.driver.internal.core.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; import java.net.SocketAddress; -import java.util.Optional; /** * Low-level SSL extension point. @@ -27,25 +26,16 @@ *

SSL is separated into two interfaces to avoid exposing Netty classes in our public API: * *

    - * <<<<<<< Updated upstream *
  • "normal" (JDK-based) SSL is part of the public API, and can be configured via an instance * of {@link com.datastax.oss.driver.api.core.ssl.SslEngineFactory} defined in the driver * configuration. *
  • this interface deals with Netty handlers directly. It can be used for more advanced cases, * like using Netty's native OpenSSL integration instead of the JDK. This is considered expert - * level, and therefore part of our internal API. ======= - *
  • "normal" (JDK-based) SSL is part of the public API, and can be configured via an instance - * of {@link com.datastax.oss.driver.api.core.ssl.SslEngineFactory} defined in the driver - * configuration. - *
  • this interface deals with Netty handlers directly. It can be used for more advanced cases, - * like using Netty's native OpenSSL integration instead of the JDK. This is considered expert - * level, and therefore part of our internal API. >>>>>>> Stashed changes + * level, and therefore part of our internal API. *
* * @see DefaultDriverContext#buildSslHandlerFactory() */ public interface SslHandlerFactory { - SslHandlerFactory NONE = (channel, remoteEndpoint) -> Optional.empty(); - - Optional newSslHandler(Channel channel, SocketAddress remoteEndpoint); + SslHandler newSslHandler(Channel channel, SocketAddress remoteEndpoint); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 579808f8769..89b2d56eb0e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -17,8 +17,10 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; import java.lang.reflect.Constructor; +import java.util.Optional; public class Reflection { /** @@ -46,19 +48,24 @@ public static Class loadClass(String className, String source) { /** * Tries to create an instance of a class, given its name defined in the driver configuration. * - * @param config the driver configuration. + *

By convention, a class instantiated through this method must have a constructor that takes a + * {@link DriverContext} as its single argument. + * + * @param context the driver context. * @param classNameOption the configuration option that contains the fully-qualified name of the - * class to instantiate. It must have a constructor that takes the configuration as an - * argument. + * class to instantiate. It will be looked up in the default profile of the configuration + * stored in the context. * @param expectedSuperType a super-type that the class is expected to implement/extend. - * @return the new instance, or {@code null} if {@code classNameOption} is not defined in the + * @return the new instance, or empty if {@code classNameOption} is not defined in the * configuration. */ - public static T buildFromConfig( - DriverConfigProfile config, DriverOption classNameOption, Class expectedSuperType) { + public static Optional buildFromConfig( + DriverContext context, DriverOption classNameOption, Class expectedSuperType) { + + DriverConfigProfile config = context.config().defaultProfile(); if (!config.isDefined(classNameOption)) { - return null; + return Optional.empty(); } String className = config.getString(classNameOption); @@ -73,7 +80,7 @@ public static T buildFromConfig( Constructor constructor; try { - constructor = clazz.getConstructor(DriverConfigProfile.class); + constructor = clazz.getConstructor(DriverContext.class); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format( @@ -82,8 +89,8 @@ public static T buildFromConfig( className, configPath, DriverConfigProfile.class.getSimpleName())); } try { - Object instance = constructor.newInstance(config); - return expectedSuperType.cast(instance); + Object instance = constructor.newInstance(context); + return Optional.of(expectedSuperType.cast(instance)); } catch (Exception e) { throw new IllegalArgumentException( String.format("Error instantiating class %s (specified by %s)", className, configPath), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java new file mode 100644 index 00000000000..da55f79aba1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.graph.Graphs; +import com.google.common.graph.MutableValueGraph; +import com.google.common.graph.ValueGraphBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Detects cycles between a set of {@link LazyReference} instances. */ +public class CycleDetector { + private static final boolean ENABLED = + Boolean.getBoolean("com.datastax.oss.driver.DETECT_CYCLES"); + private static final Logger LOG = LoggerFactory.getLogger(CycleDetector.class); + + private final String errorMessage; + private final boolean enabled; + private final MutableValueGraph graph; + + public CycleDetector(String errorMessage) { + this(errorMessage, ENABLED); + } + + @VisibleForTesting + CycleDetector(String errorMessage, boolean enabled) { + this.errorMessage = errorMessage; + this.enabled = enabled; + this.graph = enabled ? ValueGraphBuilder.directed().build() : null; + } + + void onTryLock(LazyReference reference) { + if (enabled) { + synchronized (this) { + Thread me = Thread.currentThread(); + LOG.debug("{} wants to initialize {}", me, reference.getName()); + graph.putEdgeValue(me.getName(), reference.getName(), "wants to initialize"); + LOG.debug("{}", graph); + if (Graphs.hasCycle(graph)) { + throw new IllegalStateException(errorMessage + " " + graph); + } + } + } + } + + void onLockAcquired(LazyReference reference) { + if (enabled) { + synchronized (this) { + Thread me = Thread.currentThread(); + LOG.debug("{} is initializing {}", me, reference.getName()); + String old = graph.removeEdge(me.getName(), reference.getName()); + assert "wants to initialize".equals(old); + graph.putEdgeValue(reference.getName(), me.getName(), "is getting initialized by"); + } + } + } + + void onReleaseLock(LazyReference reference) { + if (enabled) { + synchronized (this) { + Thread me = Thread.currentThread(); + LOG.debug("{} is done initializing {}", me, reference.getName()); + graph.removeEdge(reference.getName(), me); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java index db4324f109d..f04232d2de1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/LazyReference.java @@ -17,37 +17,42 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** Holds a reference to an object that is initialized on first access. */ public class LazyReference { - private static final Logger LOG = LoggerFactory.getLogger(LazyReference.class); private final String name; private final Supplier supplier; + private final CycleDetector checker; private volatile T value; - private ReentrantLock lock; + private ReentrantLock lock = new ReentrantLock(); - public LazyReference(String name, Supplier supplier) { + public LazyReference(String name, Supplier supplier, CycleDetector cycleDetector) { this.name = name; this.supplier = supplier; + this.checker = cycleDetector; } public T get() { T t = value; if (t == null) { + checker.onTryLock(this); lock.lock(); try { + checker.onLockAcquired(this); t = value; if (t == null) { - LOG.debug("Initializing {}", name); value = t = supplier.get(); } } finally { + checker.onReleaseLock(this); lock.unlock(); } } return t; } + + public String getName() { + return name; + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 38b47d02c4c..d4adaad62e2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.internal.core.DriverContext; -import com.datastax.oss.driver.internal.core.NettyOptions; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -42,6 +42,7 @@ import io.netty.channel.local.LocalServerChannel; import java.net.SocketAddress; import java.util.Collections; +import java.util.Optional; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -75,7 +76,7 @@ abstract class ChannelFactoryTestBase { DefaultEventLoopGroup serverGroup; DefaultEventLoopGroup clientGroup; - @Mock DriverContext driverContext; + @Mock InternalDriverContext internalDriverContext; @Mock DriverConfig driverConfig; @Mock DriverConfigProfile defaultConfigProfile; @Mock NettyOptions nettyOptions; @@ -98,7 +99,7 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); - Mockito.when(driverContext.config()).thenReturn(driverConfig); + Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) .thenReturn(false); @@ -113,16 +114,17 @@ public void setup() throws InterruptedException { Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); - Mockito.when(driverContext.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); - Mockito.when(driverContext.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(internalDriverContext.protocolVersionRegistry()) + .thenReturn(protocolVersionRegistry); + Mockito.when(internalDriverContext.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); Mockito.when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); Mockito.when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); - Mockito.when(driverContext.frameCodec()) + Mockito.when(internalDriverContext.frameCodec()) .thenReturn( FrameCodec.defaultClient( new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); - Mockito.when(driverContext.sslHandlerFactory()).thenReturn(SslHandlerFactory.NONE); + Mockito.when(internalDriverContext.sslHandlerFactory()).thenReturn(Optional.empty()); // Start local server ServerBootstrap serverBootstrap = @@ -184,7 +186,7 @@ private void writeInboundFrame(Frame requestFrame, Message response, int protoco } ChannelFactory newChannelFactory() { - return new TestChannelFactory(driverContext); + return new TestChannelFactory(internalDriverContext); } // A simplified channel factory to use in the tests. @@ -192,8 +194,8 @@ ChannelFactory newChannelFactory() { // Frame objects on the server side, which is simpler to test. private static class TestChannelFactory extends ChannelFactory { - private TestChannelFactory(DriverContext driverContext) { - super(driverContext); + private TestChannelFactory(InternalDriverContext internalDriverContext) { + super(internalDriverContext); } @Override @@ -202,7 +204,8 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = driverContext.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = + internalDriverContext.config().defaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile.getDuration( @@ -216,7 +219,8 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis); ProtocolInitHandler initHandler = - new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler( + internalDriverContext, protocolVersion, clusterName, keyspace); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index d001eb8fa8b..40deb3c7af5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -22,9 +22,9 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.internal.core.DriverContext; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; @@ -39,6 +39,7 @@ import com.datastax.oss.protocol.internal.util.Bytes; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; +import java.util.Optional; import java.util.concurrent.TimeUnit; import org.mockito.Mock; import org.mockito.Mockito; @@ -52,7 +53,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { private static final long QUERY_TIMEOUT_MILLIS = 100L; - @Mock private DriverContext driverContext; + @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; private ProtocolVersionRegistry protocolVersionRegistry = new ProtocolVersionRegistry(); @@ -62,13 +63,14 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { public void setup() { super.setup(); MockitoAnnotations.initMocks(this); - Mockito.when(driverContext.config()).thenReturn(driverConfig); + Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when( defaultConfigProfile.getDuration( CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS)) .thenReturn(QUERY_TIMEOUT_MILLIS); - Mockito.when(driverContext.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when(internalDriverContext.protocolVersionRegistry()) + .thenReturn(protocolVersionRegistry); channel .pipeline() @@ -82,7 +84,8 @@ public void should_initialize_without_authentication() { channel .pipeline() .addLast( - "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + "init", + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -110,7 +113,8 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt channel .pipeline() .addLast( - "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + "init", + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -128,14 +132,15 @@ public void should_initialize_with_authentication() { channel .pipeline() .addLast( - "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + "init", + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(driverContext.authProvider()).thenReturn(authProvider); + Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -184,14 +189,15 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa channel .pipeline() .addLast( - "init", new ProtocolInitHandler(driverContext, CoreProtocolVersion.V4, null, null)); + "init", + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(driverContext.authProvider()).thenReturn(authProvider); + Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -224,7 +230,7 @@ public void should_check_cluster_name_if_provided() { .addLast( "init", new ProtocolInitHandler( - driverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -249,7 +255,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th .addLast( "init", new ProtocolInitHandler( - driverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -273,7 +279,7 @@ public void should_initialize_with_keyspace() { .addLast( "init", new ProtocolInitHandler( - driverContext, CoreProtocolVersion.V4, null, CqlIdentifier.fromCql("ks"))); + internalDriverContext, CoreProtocolVersion.V4, null, CqlIdentifier.fromCql("ks"))); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java new file mode 100644 index 00000000000..a0f9dbddd6a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class CycleDetectorTest { + + @Test + public void should_detect_cycle_within_same_thread() { + CycleDetector checker = new CycleDetector("Detected cycle", true); + CyclicContext context = new CyclicContext(checker, false); + try { + context.a.get(); + } catch (Exception e) { + assertThat(e) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Detected cycle"); + } + } + + @Test + public void should_detect_cycle_between_different_threads() throws Throwable { + CycleDetector checker = new CycleDetector("Detected cycle", true); + CyclicContext context = new CyclicContext(checker, true); + ExecutorService executor = + Executors.newFixedThreadPool( + 3, new ThreadFactoryBuilder().setNameFormat("thread%d").build()); + Future futureA = executor.submit(() -> context.a.get()); + Future futureB = executor.submit(() -> context.b.get()); + Future futureC = executor.submit(() -> context.c.get()); + context.latchA.countDown(); + context.latchB.countDown(); + context.latchC.countDown(); + for (Future future : ImmutableList.of(futureA, futureB, futureC)) { + try { + Uninterruptibles.getUninterruptibly(future); + } catch (ExecutionException e) { + assertThat(e.getCause()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Detected cycle"); + } + } + } + + private static class CyclicContext { + private LazyReference a; + private LazyReference b; + private LazyReference c; + private CountDownLatch latchA; + private CountDownLatch latchB; + private CountDownLatch latchC; + + private CyclicContext(CycleDetector checker, boolean enableLatches) { + this.a = new LazyReference<>("a", this::buildA, checker); + this.b = new LazyReference<>("b", this::buildB, checker); + this.c = new LazyReference<>("c", this::buildC, checker); + if (enableLatches) { + this.latchA = new CountDownLatch(1); + this.latchB = new CountDownLatch(1); + this.latchC = new CountDownLatch(1); + } + } + + private String buildA() { + maybeAwaitUninterruptibly(latchA); + b.get(); + return "a"; + } + + private String buildB() { + maybeAwaitUninterruptibly(latchB); + c.get(); + return "b"; + } + + private String buildC() { + maybeAwaitUninterruptibly(latchC); + a.get(); + return "c"; + } + + private static void maybeAwaitUninterruptibly(CountDownLatch latch) { + if (latch != null) { + try { + latch.await(); + } catch (InterruptedException e) { + fail("interrupted", e); + } + } + } + } +} From d2e5c42d61db98bc767806699f49b1b5b2d84c30 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Apr 2017 08:36:39 -0700 Subject: [PATCH 012/742] Clean up a few imports --- .../core/connection/ReconnectionPolicy.java | 44 +++++++++++++++++++ .../internal/core/pool/ChannelPool.java | 18 ++++++++ .../core/channel/ChannelFactoryTestBase.java | 3 +- .../core/channel/ConnectInitHandlerTest.java | 2 - .../typesafe/TypeSafeDriverConfigTest.java | 1 - 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java new file mode 100644 index 00000000000..a9465b29ec5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import java.time.Duration; + +/** + * Decides how often the driver tries to re-establish lost connections to a node. + * + *

Each time a connection to a node is lost, a {@link #newSchedule() new schedule} instance gets + * created. Then {@link ReconnectionSchedule#nextDelay()} will be called each time the driver needs + * to schedule the next connection attempt. When the node is back to its required number of + * connections, the schedule will be reset (that is, the next failure will create a fresh schedule + * instance). + */ +public interface ReconnectionPolicy { + + /** Creates a new schedule. */ + ReconnectionSchedule newSchedule(); + + /** + * The reconnection schedule from the time a connection is lost, to the time all connections to + * this node have been restored. + */ + interface ReconnectionSchedule { + /** How long to wait before the next reconnection attempt. */ + Duration nextDelay(); + } + + // TODO lifecycle methods (cluster shutdown, etc.) +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java new file mode 100644 index 00000000000..660d6dc3651 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +public class ChannelPool {} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index d4adaad62e2..dacdcb001ad 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -20,11 +20,10 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; -import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; -import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java index 20fa46f4588..7ffb307af15 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java @@ -15,8 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; -import com.datastax.oss.driver.internal.core.channel.ConnectInitHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index e7ae3d29d03..0b13febf9fe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import java.util.List; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; From 03a22737173329c8bd5b566e86859cb050af79af Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Apr 2017 09:00:01 -0700 Subject: [PATCH 013/742] Improve pre-commit script Do not pop stash if nothing was saved initially. --- pre-commit.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pre-commit.sh b/pre-commit.sh index 9d00aa086f2..e1dc2bbc590 100755 --- a/pre-commit.sh +++ b/pre-commit.sh @@ -1,8 +1,15 @@ #!/usr/bin/env bash -git stash -q --keep-index +STASH_NAME="pre-commit-$(date +%s)" +git stash save --keep-index $STASH_NAME + mvn clean test RESULT=$? -git stash pop -q + +STASHES=$(git stash list) +if [[ $STASHES == *$STASH_NAME* ]]; then + git stash pop +fi + [ $RESULT -ne 0 ] && exit 1 exit 0 From 904b5fe274cd79ce95072be079ea36775f9d368f Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Apr 2017 09:08:30 -0700 Subject: [PATCH 014/742] Add event bus and fire channel open/close events --- .../internal/core/channel/ChannelEvent.java | 52 +++++++++++ .../internal/core/channel/ChannelFactory.java | 39 ++++---- .../core/channel/InFlightHandler.java | 6 +- .../core/context/DefaultDriverContext.java | 12 +++ .../internal/core/context/EventBus.java | 86 +++++++++++++++++ .../core/context/InternalDriverContext.java | 2 + .../channel/ChannelFactoryEventsTest.java | 86 +++++++++++++++++ .../core/channel/ChannelFactoryTestBase.java | 29 +++--- .../core/channel/InFlightHandlerTest.java | 13 ++- .../core/context/bus/EventBusTest.java | 92 +++++++++++++++++++ 10 files changed, 383 insertions(+), 34 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java new file mode 100644 index 00000000000..6f78a6a22b4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import java.net.SocketAddress; +import java.util.Objects; + +/** An event to notify other driver components when a channel has been opened or closed. */ +public class ChannelEvent { + public enum Type { + OPENED, + CLOSED + } + + public final Type type; + public final SocketAddress address; + + public ChannelEvent(Type type, SocketAddress address) { + this.type = type; + this.address = address; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ChannelEvent) { + ChannelEvent that = (ChannelEvent) other; + return this.type == that.type && Objects.equals(this.address, that.address); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(type, address); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index e25fba2fd77..681d3151a04 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent.Type; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; @@ -47,20 +48,20 @@ public class ChannelFactory { private static final Logger LOG = LoggerFactory.getLogger(ChannelFactory.class); - protected final InternalDriverContext internalDriverContext; + protected final InternalDriverContext context; /** either set from the configuration, or null and will be negotiated */ @VisibleForTesting ProtocolVersion protocolVersion; @VisibleForTesting volatile String clusterName; - public ChannelFactory(InternalDriverContext internalDriverContext) { - this.internalDriverContext = internalDriverContext; + public ChannelFactory(InternalDriverContext context) { + this.context = context; - DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); + DriverConfigProfile defaultConfig = context.config().defaultProfile(); if (defaultConfig.isDefined(CoreDriverOption.PROTOCOL_VERSION)) { String versionName = defaultConfig.getString(CoreDriverOption.PROTOCOL_VERSION); - this.protocolVersion = internalDriverContext.protocolVersionRegistry().fromName(versionName); + this.protocolVersion = context.protocolVersionRegistry().fromName(versionName); } // else it will be negotiated with the first opened connection } @@ -75,7 +76,7 @@ public CompletionStage connect( currentVersion = protocolVersion; isNegotiating = false; } else { - currentVersion = internalDriverContext.protocolVersionRegistry().highestNonBeta(); + currentVersion = context.protocolVersionRegistry().highestNonBeta(); isNegotiating = true; } @@ -91,7 +92,7 @@ private void connect( List attemptedVersions, CompletableFuture resultFuture) { - NettyOptions nettyOptions = internalDriverContext.nettyOptions(); + NettyOptions nettyOptions = context.nettyOptions(); Bootstrap bootstrap = new Bootstrap() @@ -107,8 +108,8 @@ private void connect( connectFuture.addListener( cf -> { if (connectFuture.isSuccess()) { - DriverChannel driverChannel = - new DriverChannel(connectFuture.channel(), internalDriverContext.writeCoalescer()); + Channel channel = connectFuture.channel(); + DriverChannel driverChannel = new DriverChannel(channel, context.writeCoalescer()); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -118,12 +119,17 @@ private void connect( ChannelFactory.this.clusterName = driverChannel.getClusterName(); } resultFuture.complete(driverChannel); + + context.eventBus().fire(new ChannelEvent(Type.OPENED, address)); + channel + .closeFuture() + .addListener(f -> context.eventBus().fire(new ChannelEvent(Type.CLOSED, address))); } else { Throwable error = connectFuture.cause(); if (error instanceof UnsupportedProtocolVersionException && isNegotiating) { attemptedVersions.add(currentVersion); Optional downgraded = - internalDriverContext.protocolVersionRegistry().downgrade(currentVersion); + context.protocolVersionRegistry().downgrade(currentVersion); if (downgraded.isPresent()) { LOG.info( "Failed to connect with protocol {}, retrying with {}", @@ -147,7 +153,7 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = internalDriverContext.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile.getDuration( @@ -164,22 +170,21 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis); ProtocolInitHandler initHandler = - new ProtocolInitHandler(internalDriverContext, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); ChannelPipeline pipeline = channel.pipeline(); - internalDriverContext + context .sslHandlerFactory() .map(f -> f.newSslHandler(channel, address)) .map(h -> pipeline.addLast("ssl", h)); pipeline - .addLast("encoder", new FrameEncoder(internalDriverContext.frameCodec())) - .addLast( - "decoder", new FrameDecoder(internalDriverContext.frameCodec(), maxFrameLength)) + .addLast("encoder", new FrameEncoder(context.frameCodec())) + .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) .addLast("inflight", inFlightHandler) .addLast("heartbeat", new HeartbeatHandler(defaultConfigProfile)) .addLast("init", initHandler); - internalDriverContext.nettyOptions().afterChannelInitialized(channel); + context.nettyOptions().afterChannelInitialized(channel); } }; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 41db2a86592..f215a246707 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -58,7 +58,11 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) promise.setFailure(new IllegalStateException("Channel is closing")); return; } else if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { - closingGracefully = true; + if (inFlight.isEmpty()) { + ctx.channel().close(); + } else { + closingGracefully = true; + } return; } else if (in == DriverChannel.FORCEFUL_CLOSE_MESSAGE) { abortAllInFlight(new ConnectionException("Channel was force-closed")); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index c313b180f9d..b7ef6cffc07 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -63,6 +63,9 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("authProvider", this::buildAuthProvider, cycleDetector); private final LazyReference> sslEngineFactoryRef = new LazyReference<>("sslEngineFactory", this::buildSslEngineFactory, cycleDetector); + + private final LazyReference eventBusRef = + new LazyReference<>("eventBus", this::buildEventBus, cycleDetector); private final LazyReference> compressorRef = new LazyReference<>("compressor", this::buildCompressor, cycleDetector); private final LazyReference> frameCodecRef = @@ -92,6 +95,10 @@ protected Optional buildSslEngineFactory() { this, CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); } + private EventBus buildEventBus() { + return new EventBus(); + } + private Compressor buildCompressor() { // TODO build alternate implementation if specified in conf return Compressor.none(); @@ -137,6 +144,11 @@ public Optional sslEngineFactory() { return sslEngineFactoryRef.get(); } + @Override + public EventBus eventBus() { + return eventBusRef.get(); + } + @Override public Compressor compressor() { return compressorRef.get(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java new file mode 100644 index 00000000000..b81d214c03f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.context; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Barebones event bus implementation, that allows components to communicate without knowing about + * each other. + * + *

This is intended for administrative events (topology changes, new connections, etc.), which + * are comparatively rare in the driver. Do not use it for anything on the request path, because it + * relies on synchronization. + * + *

We don't use Guava's implementation because Guava is shaded in the driver, and the event bus + * needs to be accessible from low-level 3rd party customizations. + */ +public class EventBus { + private static final Logger LOG = LoggerFactory.getLogger(EventBus.class); + + private final SetMultimap, Consumer> listeners = + Multimaps.synchronizedSetMultimap(HashMultimap.create()); + + /** + * Registers a listener for an event type. + * + * @return a key that is needed to unregister later. + */ + public Object register(Class eventClass, Consumer listener) { + LOG.debug("Registering {} for {}", listener, eventClass); + listeners.put(eventClass, listener); + // The reason for the key mechanism is that this will often be used with method references, + // and you get a different object every time you reference a method, so register(Foo::bar) + // followed by unregister(Foo::bar) wouldn't work as expected. + return listener; + } + + /** + * Unregisters a listener. + * + * @param key the key that was returned by {@link #register(Class, Consumer)} + */ + public boolean unregister(Object key, Class eventClass) { + LOG.debug("Unregistering {} for {}", key, eventClass); + return listeners.remove(eventClass, key); + } + + /** + * Sends an event that will notify any registered listener for that class. + * + *

Listeners are looked up by an exact match on the class of the object, as returned by + * {@code event.getClass()}. Listeners of a supertype won't be notified. + * + *

The listeners are invoked on the calling thread. It's their responsibility to schedule event + * processing asynchronously if needed. + */ + public void fire(Object event) { + // if the exact match thing gets too cumbersome, we can reconsider, but I'd like to avoid + // scanning all the keys with instanceof checks. + Class eventClass = event.getClass(); + for (Consumer l : listeners.get(eventClass)) { + @SuppressWarnings("unchecked") + Consumer listener = (Consumer) l; + LOG.trace("Notifying {} of {}", listener, event); + listener.accept(event); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 22b5c522ce0..420fa412416 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -27,6 +27,8 @@ /** Extends the driver context with additional components that are not exposed by our public API. */ public interface InternalDriverContext extends DriverContext { + EventBus eventBus(); + Compressor compressor(); FrameCodec frameCodec(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java new file mode 100644 index 00000000000..d18c6de3ac0 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.TestResponses; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.response.Ready; +import io.netty.channel.local.LocalAddress; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; + +public class ChannelFactoryEventsTest extends ChannelFactoryTestBase { + + @Test + public void should_not_fire_open_event_until_initialization_complete() { + connect(SERVER_ADDRESS); + Mockito.verify(eventBus, never()).fire(any(ChannelEvent.class)); + + // Clean up + completeChannelInit(); + } + + @Test + public void should_fire_open_event_when_initialization_completes() { + CompletionStage connectFuture = connect(SERVER_ADDRESS); + completeChannelInit(); + assertThat(connectFuture) + .isSuccess( + channel -> + Mockito.verify(eventBus, timeout(100)) + .fire(new ChannelEvent(ChannelEvent.Type.OPENED, SERVER_ADDRESS))); + } + + @Test + public void should_fire_close_event_when_channel_closes() { + CompletionStage connectFuture = connect(SERVER_ADDRESS); + completeChannelInit(); + assertThat(connectFuture) + .isSuccess( + channel -> + assertThat(channel.close()) + .isSuccess( + (v) -> + Mockito.verify(eventBus, timeout(100)) + .fire(new ChannelEvent(ChannelEvent.Type.CLOSED, SERVER_ADDRESS)))); + } + + private CompletionStage connect(LocalAddress address) { + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn("V4"); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + + return newChannelFactory().connect(address, null); + } + + private void completeChannelInit() { + Frame requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index dacdcb001ad..414d6825e6d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; @@ -75,11 +76,12 @@ abstract class ChannelFactoryTestBase { DefaultEventLoopGroup serverGroup; DefaultEventLoopGroup clientGroup; - @Mock InternalDriverContext internalDriverContext; + @Mock InternalDriverContext context; @Mock DriverConfig driverConfig; @Mock DriverConfigProfile defaultConfigProfile; @Mock NettyOptions nettyOptions; @Mock ProtocolVersionRegistry protocolVersionRegistry; + @Mock EventBus eventBus; // The server's I/O thread will store the last received request here, and block until the test // thread retrieves it. This assumes readOutboundFrame() is called for each actual request, else @@ -98,7 +100,7 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); - Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); + Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) .thenReturn(false); @@ -113,17 +115,18 @@ public void setup() throws InterruptedException { Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); - Mockito.when(internalDriverContext.protocolVersionRegistry()) - .thenReturn(protocolVersionRegistry); - Mockito.when(internalDriverContext.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); Mockito.when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); Mockito.when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); - Mockito.when(internalDriverContext.frameCodec()) + Mockito.when(context.frameCodec()) .thenReturn( FrameCodec.defaultClient( new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); - Mockito.when(internalDriverContext.sslHandlerFactory()).thenReturn(Optional.empty()); + Mockito.when(context.sslHandlerFactory()).thenReturn(Optional.empty()); + Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.writeCoalescer()).thenReturn(new DefaultWriteCoalescer(5)); // Start local server ServerBootstrap serverBootstrap = @@ -185,7 +188,7 @@ private void writeInboundFrame(Frame requestFrame, Message response, int protoco } ChannelFactory newChannelFactory() { - return new TestChannelFactory(internalDriverContext); + return new TestChannelFactory(context); } // A simplified channel factory to use in the tests. @@ -203,8 +206,7 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = - internalDriverContext.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile.getDuration( @@ -218,8 +220,7 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis); ProtocolInitHandler initHandler = - new ProtocolInitHandler( - internalDriverContext, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; @@ -230,7 +231,7 @@ protected void initChannel(Channel channel) throws Exception { public void tearDown() throws InterruptedException { serverAcceptChannel.close(); - serverGroup.shutdownGracefully(100, 100, TimeUnit.MILLISECONDS).sync(); - clientGroup.shutdownGracefully(100, 100, TimeUnit.MILLISECONDS).sync(); + serverGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS).sync(); + clientGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS).sync(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 4b49ffed833..f14f550d7d4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -126,7 +126,7 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl } @Test - public void should_delay_close_until_all_pending_complete() { + public void should_delay_graceful_close_until_all_pending_complete() { // Given Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); @@ -150,7 +150,16 @@ public void should_delay_close_until_all_pending_complete() { } @Test - public void should_refuse_new_writes_during_orderly_close() { + public void should_graceful_close_immediately_if_no_pending() { + // When + channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); + + // Then + assertThat(channel.closeFuture()).isSuccess(); + } + + @Test + public void should_refuse_new_writes_during_graceful_close() { // Given Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java new file mode 100644 index 00000000000..634f25fb683 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.context.bus; + +import com.datastax.oss.driver.internal.core.context.EventBus; +import java.util.HashMap; +import java.util.Map; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EventBusTest { + + private EventBus bus; + private Map results; + private ChildEvent event = new ChildEvent(); + + @BeforeMethod + public void setup() { + bus = new EventBus(); + results = new HashMap<>(); + } + + @Test + public void should_notify_registered_listeners() { + // Given + bus.register(ChildEvent.class, (e) -> results.put("listener1", e)); + bus.register(ChildEvent.class, (e) -> results.put("listener2", e)); + + // When + bus.fire(event); + + // Then + assertThat(results) + .hasSize(2) + .containsEntry("listener1", event) + .containsEntry("listener2", event); + } + + @Test + public void should_unregister_listener() { + // Given + Object key1 = bus.register(ChildEvent.class, (e) -> results.put("listener1", e)); + bus.register(ChildEvent.class, (e) -> results.put("listener2", e)); + bus.unregister(key1, ChildEvent.class); + + // When + bus.fire(event); + + // Then + assertThat(results).hasSize(1).containsEntry("listener2", event); + } + + @Test + public void should_use_exact_class() { + // Given + bus.register(ChildEvent.class, (e) -> results.put("listener1", e)); + bus.register(ParentEvent.class, (e) -> results.put("listener2", e)); + + // When + bus.fire(event); + + // Then + assertThat(results).hasSize(1).containsEntry("listener1", event); + + // When + results.clear(); + ParentEvent parentEvent = new ParentEvent(); + bus.fire(parentEvent); + + // Then + assertThat(results).hasSize(1).containsEntry("listener2", parentEvent); + } + + private static class ParentEvent {} + + private static class ChildEvent extends ParentEvent {} +} From 88a91ff5cc9c35f6508194e694498eeb4d805e33 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Apr 2017 09:09:58 -0700 Subject: [PATCH 015/742] Add channel pool --- .../core/auth/AuthenticationException.java | 6 +- .../api/core/config/CoreDriverOption.java | 6 +- .../ExponentialReconnectionPolicy.java | 111 +++++ .../core/connection/HeartbeatException.java | 27 ++ .../api/core/context/DriverContext.java | 4 + .../core/channel/AvailableIdsHolder.java | 25 ++ .../internal/core/channel/ChannelFactory.java | 37 +- .../internal/core/channel/DriverChannel.java | 35 +- .../core/channel/HeartbeatHandler.java | 17 +- .../core/channel/InFlightHandler.java | 64 ++- .../core/channel/InternalRequest.java | 30 +- .../core/channel/ProtocolInitHandler.java | 28 +- .../core/context/DefaultDriverContext.java | 33 +- .../core/context/DefaultNettyOptions.java | 18 +- .../core/context/InternalDriverContext.java | 3 + .../internal/core/context/NettyOptions.java | 10 + .../internal/core/pool/ChannelPool.java | 311 +++++++++++++- .../driver/internal/core/pool/ChannelSet.java | 115 ++++++ .../util/concurrent/CompletableFutures.java | 58 +++ .../core/util/concurrent/Reconnection.java | 123 ++++++ .../core/util/concurrent/RunOrSchedule.java | 83 ++++ .../util/concurrent/UncaughtExceptions.java | 56 +++ core/src/main/resources/reference.conf | 8 + .../ChannelFactoryAvailableIdsTest.java | 111 +++++ .../ChannelFactoryClusterNameTest.java | 8 +- .../channel/ChannelFactoryEventsTest.java | 19 +- ...ChannelFactoryProtocolNegotiationTest.java | 16 +- .../core/channel/ChannelFactoryTestBase.java | 22 +- .../core/channel/DriverChannelTest.java | 5 +- .../core/channel/InFlightHandlerTest.java | 3 +- .../core/channel/ProtocolInitHandlerTest.java | 2 +- .../internal/core/pool/ChannelPoolTest.java | 378 ++++++++++++++++++ .../internal/core/pool/ChannelSetTest.java | 114 ++++++ .../util/concurrent/ReconnectionTest.java | 173 ++++++++ 34 files changed, 1973 insertions(+), 86 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java index 049e1ddddb9..b8c6e6597f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java @@ -24,7 +24,11 @@ public class AuthenticationException extends RuntimeException { private final SocketAddress address; public AuthenticationException(SocketAddress address, String message) { - super(String.format("Authentication error on host %s: %s", address, message)); + this(address, message, null); + } + + public AuthenticationException(SocketAddress address, String message, Throwable cause) { + super(String.format("Authentication error on host %s: %s", address, message), cause); this.address = address; } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index d0caa365947..59260b0f64d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -23,7 +23,11 @@ public enum CoreDriverOption implements DriverOption { CONNECTION_MAX_FRAME_LENGTH("connection.max-frame-length", true), CONNECTION_MAX_REQUESTS("connection.max-requests-per-connection", true), CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), - CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.interval", true), + CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), + + RECONNECTION_POLICY_CLASS("connection.reconnection-policy.provider-class", true), + RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection-policy.config.base-delay", true), + RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection-policy.config.max-delay", true), AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java new file mode 100644 index 00000000000..e853da9b932 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.google.common.base.Preconditions; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * A reconnection policy that waits exponentially longer between each reconnection attempt (but + * keeps a constant delay once a maximum delay is reached). + */ +public class ExponentialReconnectionPolicy implements ReconnectionPolicy { + + private final long baseDelayMs; + private final long maxDelayMs; + private final long maxAttempts; + + /** Builds a new instance. */ + public ExponentialReconnectionPolicy(DriverContext context) { + DriverConfigProfile config = context.config().defaultProfile(); + this.baseDelayMs = + config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY, TimeUnit.MILLISECONDS); + this.maxDelayMs = + config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY, TimeUnit.MILLISECONDS); + + Preconditions.checkArgument( + baseDelayMs > 0, + "%s must be strictly positive (got %s)", + CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY.getPath(), + baseDelayMs); + Preconditions.checkArgument( + maxDelayMs >= 0, + "%s must be positive (got %s)", + CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY.getPath(), + maxDelayMs); + Preconditions.checkArgument( + maxDelayMs >= baseDelayMs, + "%s must be bigger than %s (got %s, %s)", + CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY.getPath(), + CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY.getPath(), + maxDelayMs, + baseDelayMs); + + // Maximum number of attempts after which we overflow + int ceil = (baseDelayMs & (baseDelayMs - 1)) == 0 ? 0 : 1; + this.maxAttempts = 64 - Long.numberOfLeadingZeros(Long.MAX_VALUE / baseDelayMs) - ceil; + } + + /** + * The base delay in milliseconds for this policy (e.g. the delay before the first reconnection + * attempt). + * + * @return the base delay in milliseconds for this policy. + */ + public long getBaseDelayMs() { + return baseDelayMs; + } + + /** + * The maximum delay in milliseconds between reconnection attempts for this policy. + * + * @return the maximum delay in milliseconds between reconnection attempts for this policy. + */ + public long getMaxDelayMs() { + return maxDelayMs; + } + + /** + * A new schedule that used an exponentially growing delay between reconnection attempts. + * + *

For this schedule, reconnection attempt {@code i} will be tried {@code Math.min(2^(i-1) * + * getBaseDelayMs(), getMaxDelayMs())} milliseconds after the previous one. + * + * @return the newly created schedule. + */ + @Override + public ReconnectionSchedule newSchedule() { + return new ExponentialSchedule(); + } + + private class ExponentialSchedule implements ReconnectionSchedule { + + private int attempts; + + @Override + public Duration nextDelay() { + long delay = + (attempts > maxAttempts) + ? maxDelayMs + : Math.min(baseDelayMs * (1L << attempts++), maxDelayMs); + return Duration.ofMillis(delay); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java new file mode 100644 index 00000000000..36620f784d1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +public class HeartbeatException extends ConnectionException { + + public HeartbeatException(String message) { + super(message); + } + + public HeartbeatException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index d19fceb85c7..dc7cd93ffca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import java.util.Optional; @@ -26,6 +27,9 @@ public interface DriverContext { /** The driver's configuration. */ DriverConfig config(); + /** The configured reconnection policy. */ + ReconnectionPolicy reconnectionPolicy(); + /** The authentication provider, if authentication was configured. */ Optional authProvider(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java new file mode 100644 index 00000000000..60de47771b0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +/** + * Hack to expose the number of available ids on a channel. We want to access it on the {@link + * DriverChannel} instance, but it's updated from {@link InFlightHandler}, and we want volatile for + * efficiency. + */ +class AvailableIdsHolder { + volatile int value; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 681d3151a04..7af5f25284c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -65,10 +65,13 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } + /** @param reportAvailableIds whether {@link DriverChannel#availableIds()} should be maintained */ public CompletionStage connect( - final SocketAddress address, CqlIdentifier keyspace) { + final SocketAddress address, CqlIdentifier keyspace, boolean reportAvailableIds) { CompletableFuture resultFuture = new CompletableFuture<>(); + AvailableIdsHolder availableIdsHolder = reportAvailableIds ? new AvailableIdsHolder() : null; + ProtocolVersion currentVersion; boolean isNegotiating; List attemptedVersions = new CopyOnWriteArrayList<>(); @@ -80,13 +83,21 @@ public CompletionStage connect( isNegotiating = true; } - connect(address, keyspace, currentVersion, isNegotiating, attemptedVersions, resultFuture); + connect( + address, + keyspace, + availableIdsHolder, + currentVersion, + isNegotiating, + attemptedVersions, + resultFuture); return resultFuture; } private void connect( SocketAddress address, CqlIdentifier keyspace, + AvailableIdsHolder availableIdsHolder, final ProtocolVersion currentVersion, boolean isNegotiating, List attemptedVersions, @@ -99,7 +110,7 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(address, currentVersion, keyspace)); + .handler(initializer(address, currentVersion, keyspace, availableIdsHolder)); nettyOptions.afterBootstrapInitialized(bootstrap); @@ -109,7 +120,8 @@ private void connect( cf -> { if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); - DriverChannel driverChannel = new DriverChannel(channel, context.writeCoalescer()); + DriverChannel driverChannel = + new DriverChannel(channel, context.writeCoalescer(), availableIdsHolder); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -135,7 +147,14 @@ private void connect( "Failed to connect with protocol {}, retrying with {}", currentVersion, downgraded.get()); - connect(address, keyspace, downgraded.get(), true, attemptedVersions, resultFuture); + connect( + address, + keyspace, + availableIdsHolder, + downgraded.get(), + true, + attemptedVersions, + resultFuture); } else { resultFuture.completeExceptionally( UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); @@ -149,7 +168,10 @@ private void connect( @VisibleForTesting ChannelInitializer initializer( - SocketAddress address, final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) { + SocketAddress address, + final ProtocolVersion protocolVersion, + final CqlIdentifier keyspace, + AvailableIdsHolder availableIdsHolder) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { @@ -168,7 +190,8 @@ protected void initChannel(Channel channel) throws Exception { new InFlightHandler( protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), - setKeyspaceTimeoutMillis); + setKeyspaceTimeoutMillis, + availableIdsHolder); ProtocolInitHandler initHandler = new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 53c64447e8f..2904a25a554 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; @@ -31,16 +32,24 @@ */ public class DriverChannel { static final AttributeKey CLUSTER_NAME_KEY = AttributeKey.newInstance("cluster_name"); - static final Object GRACEFUL_CLOSE_MESSAGE = new Object(); - static final Object FORCEFUL_CLOSE_MESSAGE = new Object(); + + @SuppressWarnings("RedundantStringConstructorCall") + static final Object GRACEFUL_CLOSE_MESSAGE = new String("GRACEFUL_CLOSE_MESSAGE"); + + @SuppressWarnings("RedundantStringConstructorCall") + static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); private final Channel channel; private final WriteCoalescer writeCoalescer; + private final AvailableIdsHolder availableIdsHolder; private final AtomicBoolean closing = new AtomicBoolean(); + private final AtomicBoolean forceClosing = new AtomicBoolean(); - DriverChannel(Channel channel, WriteCoalescer writeCoalescer) { + DriverChannel( + Channel channel, WriteCoalescer writeCoalescer, AvailableIdsHolder availableIdsHolder) { this.channel = channel; this.writeCoalescer = writeCoalescer; + this.availableIdsHolder = availableIdsHolder; } /** @@ -93,6 +102,15 @@ public String getClusterName() { return channel.attr(CLUSTER_NAME_KEY).get(); } + /** + * @return the number of available stream ids on the channel. This is used to weigh channels in + * the pool. Note that for performance reasons this is only maintained if the channel is part + * of a pool that has a size bigger than 1, otherwise it will always return -1. + */ + public int availableIds() { + return (availableIdsHolder == null) ? -1 : availableIdsHolder.value; + } + /** * Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will * be allowed to complete before the underlying channel is closed. @@ -111,8 +129,15 @@ public Future close() { * will be closed. */ public Future forceClose() { - closing.set(true); - writeCoalescer.writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE); + this.close(); + if (forceClosing.compareAndSet(false, true)) { + writeCoalescer.writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE); + } + return channel.closeFuture(); + } + + /** Does not close the channel, but returns a future that will complete when it does. */ + public ChannelFuture closeFuture() { return channel.closeFuture(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index 48dd8630005..5d949cbac47 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Options; import com.datastax.oss.protocol.internal.response.Supported; @@ -56,6 +57,10 @@ protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL.getPath(), CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT.getPath()); } else { + LOG.debug( + "Connection was inactive for {} seconds, sending heartbeat", + defaultConfigProfile.getDuration( + CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL, TimeUnit.SECONDS)); long timeoutMillis = defaultConfigProfile.getDuration( CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT, TimeUnit.MILLISECONDS); @@ -92,8 +97,16 @@ void onResponse(Message response) { } @Override - void fail(Throwable cause) { - ctx.fireExceptionCaught(cause); + void fail(String message, Throwable cause) { + if (cause instanceof HeartbeatException) { + // Ignore: this happens when the heartbeat query times out and the inflight handler aborts + // all queries (including the heartbeat query itself) + return; + } + LOG.debug(ctx.channel().toString() + " Heartbeat query failed: " + message, cause); + // Notify InFlightHandler (fireExceptionCaught wouldn't work because the error has to go downstream) + ctx.write(new HeartbeatException(message, cause)); + HeartbeatHandler.this.request = null; } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index f215a246707..7dc53eeaf25 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; @@ -34,30 +35,37 @@ import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Promise; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Manages requests that are currently executing on a channel. */ public class InFlightHandler extends ChannelDuplexHandler { + private static final Logger LOG = LoggerFactory.getLogger(InFlightHandler.class); + private final ProtocolVersion protocolVersion; private final StreamIdGenerator streamIds; private final Map inFlight; private final long setKeyspaceTimeoutMillis; + private final AvailableIdsHolder availableIdsHolder; private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; InFlightHandler( - ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis) { + ProtocolVersion protocolVersion, + StreamIdGenerator streamIds, + long setKeyspaceTimeoutMillis, + AvailableIdsHolder availableIdsHolder) { this.protocolVersion = protocolVersion; this.streamIds = streamIds; + reportAvailableIds(); this.inFlight = Maps.newHashMapWithExpectedSize(streamIds.getMaxAvailableIds()); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; + this.availableIdsHolder = availableIdsHolder; } @Override public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) throws Exception { - if (closingGracefully) { - promise.setFailure(new IllegalStateException("Channel is closing")); - return; - } else if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { + if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { if (inFlight.isEmpty()) { ctx.channel().close(); } else { @@ -68,8 +76,16 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) abortAllInFlight(new ConnectionException("Channel was force-closed")); ctx.channel().close(); return; + } else if (in instanceof HeartbeatException) { + abortAllInFlight((HeartbeatException) in); + ctx.close(); } + assert in instanceof RequestMessage; + if (closingGracefully) { + promise.setFailure(new IllegalStateException("Channel is closing")); + return; + } int streamId = streamIds.acquire(); if (streamId < 0) { promise.setFailure(new BusyConnectionException(streamIds.getMaxAvailableIds())); @@ -82,6 +98,8 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) return; } + reportAvailableIds(); + RequestMessage message = (RequestMessage) in; Frame frame = Frame.forRequest( @@ -143,8 +161,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws E if (this.setKeyspaceRequest != null) { setKeyspaceEvent.promise.setFailure( new IllegalStateException( - "Got a keyspace change request while another one was already in progress. " - + "This is generally a sign that your application issues USE queries too rapidly.")); + "Can't call setKeyspace while a keyspace switch is already in progress")); } else { this.setKeyspaceRequest = new SetKeyspaceRequest(ctx, setKeyspaceEvent); this.setKeyspaceRequest.send(); @@ -157,6 +174,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws E private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { ResponseCallback responseCallback = inFlight.remove(streamId); streamIds.release(streamId); + reportAvailableIds(); // If we're in the middle of an orderly close and this was the last request, actually close // the channel now if (closingGracefully && inFlight.isEmpty()) { @@ -166,14 +184,30 @@ private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { } private void abortAllInFlight(Throwable cause) { + abortAllInFlight(cause, null); + } + + /** + * @param ignore the ResponseCallback that called this method, if applicable (avoids a recursive + * loop) + */ + private void abortAllInFlight(Throwable cause, ResponseCallback ignore) { for (ResponseCallback responseCallback : inFlight.values()) { - responseCallback.onFailure(cause); + if (responseCallback != ignore) { + responseCallback.onFailure(cause); + } } inFlight.clear(); // It's not necessary to release the stream ids, since we always call this method right before // closing the channel } + private void reportAvailableIds() { + if (availableIdsHolder != null) { + availableIdsHolder.value = streamIds.getAvailableIds(); + } + } + private class SetKeyspaceRequest extends InternalRequest { private final CqlIdentifier keyspaceName; @@ -207,9 +241,19 @@ void onResponse(Message response) { } @Override - void fail(Throwable cause) { - if (promise.tryFailure(cause)) { + void fail(String message, Throwable cause) { + Throwable setKeyspaceException = + (message == null) ? cause : new ConnectionException(message, cause); + if (promise.tryFailure(setKeyspaceException)) { InFlightHandler.this.setKeyspaceRequest = null; + // setKeyspace queries are not triggered directly by the user, but only as a response to a + // successful "USE... query", so the keyspace name should generally be valid. If the + // keyspace switch fails, this could be due to a schema disagreement or a more serious + // error. Rescheduling the switch is impractical, we can't do much better than closing the + // channel and letting it reconnect. + LOG.warn("Unexpected error while switching keyspace", setKeyspaceException); + abortAllInFlight(setKeyspaceException, this); + ctx.channel().close(); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java index 4e062b2d310..2d9db51826f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -49,7 +48,16 @@ abstract class InternalRequest implements ResponseCallback { abstract void onResponse(Message response); - abstract void fail(Throwable cause); + /** either message or cause can be null */ + abstract void fail(String message, Throwable cause); + + void fail(String message) { + fail(message, null); + } + + void fail(Throwable cause) { + fail(null, cause); + } void send() { assert channel.eventLoop().inEventLoop(); @@ -64,7 +72,7 @@ private void writeListener(Future writeFuture) { timeoutFuture = channel.eventLoop().schedule(this::onTimeout, timeoutMillis, TimeUnit.MILLISECONDS); } else { - fail(new ConnectionException(describe() + ": error writing ", writeFuture.cause())); + fail(describe() + ": error writing ", writeFuture.cause()); } } @@ -77,7 +85,7 @@ public final void onResponse(Frame responseFrame) { @Override public final void onFailure(Throwable error) { timeoutFuture.cancel(true); - fail(new ConnectionException(describe() + ": unexpected failure", error)); + fail(describe() + ": unexpected failure", error); } private void onTimeout() { @@ -88,16 +96,14 @@ void failOnUnexpected(Message response) { if (response instanceof Error) { Error error = (Error) response; fail( - new ConnectionException( - String.format( - "%s: unexpected server error [%s] %s", - describe(), ProtocolUtils.errorCodeString(error.code), error.message))); + String.format( + "%s: unexpected server error [%s] %s", + describe(), ProtocolUtils.errorCodeString(error.code), error.message)); } else { fail( - new ConnectionException( - String.format( - "%s: unexpected server response opcode=%s", - describe(), ProtocolUtils.opcodeString(response.opcode)))); + String.format( + "%s: unexpected server response opcode=%s", + describe(), ProtocolUtils.opcodeString(response.opcode))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index cb2c7217eb0..1f0a1a7ba6e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; @@ -145,14 +146,17 @@ void onResponse(Message response) { .whenCompleteAsync( (token, error) -> { if (error != null) { - fail(new ConnectionException("authenticator threw an exception", error)); + fail( + new AuthenticationException( + channel.remoteAddress(), "authenticator threw an exception", error)); } else { step = Step.AUTH_RESPONSE; authReponseToken = token; send(); } }, - channel.eventLoop()); + channel.eventLoop()) + .exceptionally(UncaughtExceptions::log); } else if (step == Step.AUTH_RESPONSE && response instanceof AuthChallenge) { ByteBuffer challenge = ((AuthChallenge) response).token; authenticator @@ -160,14 +164,17 @@ void onResponse(Message response) { .whenCompleteAsync( (token, error) -> { if (error != null) { - fail(new ConnectionException("authenticator threw an exception", error)); + fail( + new AuthenticationException( + channel.remoteAddress(), "authenticator threw an exception", error)); } else { step = Step.AUTH_RESPONSE; authReponseToken = token; send(); } }, - channel.eventLoop()); + channel.eventLoop()) + .exceptionally(UncaughtExceptions::log); } else if (step == Step.AUTH_RESPONSE && response instanceof AuthSuccess) { ByteBuffer token = ((AuthSuccess) response).token; authenticator @@ -175,13 +182,16 @@ void onResponse(Message response) { .whenCompleteAsync( (ignored, error) -> { if (error != null) { - fail(new ConnectionException("authenticator threw an exception", error)); + fail( + new AuthenticationException( + channel.remoteAddress(), "authenticator threw an exception", error)); } else { step = Step.GET_CLUSTER_NAME; send(); } }, - channel.eventLoop()); + channel.eventLoop()) + .exceptionally(UncaughtExceptions::log); } else if (step == Step.AUTH_RESPONSE && response instanceof Error && ((Error) response).code == ProtocolConstants.ErrorCode.AUTH_ERROR) { @@ -235,8 +245,10 @@ void onResponse(Message response) { } @Override - void fail(Throwable cause) { - setConnectFailure(cause); + void fail(String message, Throwable cause) { + Throwable finalException = + (message == null) ? cause : new ConnectionException(message, cause); + setConnectFailure(finalException); } private Authenticator buildAuthenticator(SocketAddress address, String authenticator) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index b7ef6cffc07..754de2ff779 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -18,8 +18,10 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; @@ -59,6 +61,8 @@ public class DefaultDriverContext implements InternalDriverContext { private final LazyReference configRef = new LazyReference<>("config", this::buildDriverConfig, cycleDetector); + private final LazyReference reconnectionPolicyRef = + new LazyReference<>("reconnectionPolicy", this::buildReconnectionPolicy, cycleDetector); private final LazyReference> authProviderRef = new LazyReference<>("authProvider", this::buildAuthProvider, cycleDetector); private final LazyReference> sslEngineFactoryRef = @@ -79,12 +83,25 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("writeCoalescer", this::buildWriteCoalescer, cycleDetector); private final LazyReference> sslHandlerFactoryRef = new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory, cycleDetector); + private final LazyReference channelFactoryRef = + new LazyReference<>("channelFactory", this::buildChannelFactory, cycleDetector); - private DriverConfig buildDriverConfig() { + protected DriverConfig buildDriverConfig() { return new TypeSafeDriverConfig( ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); } + protected ReconnectionPolicy buildReconnectionPolicy() { + CoreDriverOption classOption = CoreDriverOption.RECONNECTION_POLICY_CLASS; + return Reflection.buildFromConfig(this, classOption, ReconnectionPolicy.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing reconnection policy, check your configuration (%s)", + classOption))); + } + protected Optional buildAuthProvider() { return Reflection.buildFromConfig( this, CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, AuthProvider.class); @@ -129,11 +146,20 @@ private WriteCoalescer buildWriteCoalescer() { return new DefaultWriteCoalescer(5); } + private ChannelFactory buildChannelFactory() { + return new ChannelFactory(this); + } + @Override public DriverConfig config() { return configRef.get(); } + @Override + public ReconnectionPolicy reconnectionPolicy() { + return reconnectionPolicyRef.get(); + } + @Override public Optional authProvider() { return authProviderRef.get(); @@ -178,4 +204,9 @@ public WriteCoalescer writeCoalescer() { public Optional sslHandlerFactory() { return sslHandlerFactoryRef.get(); } + + @Override + public ChannelFactory channelFactory() { + return channelFactoryRef.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 1ffe7760ad6..5d50e67723d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -19,19 +19,26 @@ import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; +import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import java.util.concurrent.ThreadFactory; public class DefaultNettyOptions implements NettyOptions { - private final NioEventLoopGroup ioEventLoopGroup; + private final EventLoopGroup ioEventLoopGroup; + private final EventLoopGroup adminEventLoopGroup; public DefaultNettyOptions() { - // TODO use the driver instance's name - ThreadFactory ioThreadFactory = new ThreadFactoryBuilder().build(); + // TODO inject the cluster name in thread names + ThreadFactory ioThreadFactory = new ThreadFactoryBuilder().setNameFormat("io-%d").build(); this.ioEventLoopGroup = new NioEventLoopGroup(0, ioThreadFactory); + + ThreadFactory adminThreadFactory = new ThreadFactoryBuilder().setNameFormat("admin-%d").build(); + int adminThreadCount = Math.min(2, Runtime.getRuntime().availableProcessors()); + this.adminEventLoopGroup = new DefaultEventLoopGroup(adminThreadCount, adminThreadFactory); } @Override @@ -39,6 +46,11 @@ public EventLoopGroup ioEventLoopGroup() { return ioEventLoopGroup; } + @Override + public EventExecutorGroup adminEventExecutorGroup() { + return adminEventLoopGroup; + } + @Override public Class channelClass() { return NioSocketChannel.class; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 420fa412416..a06e6ec2329 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; @@ -40,4 +41,6 @@ public interface InternalDriverContext extends DriverContext { WriteCoalescer writeCoalescer(); Optional sslHandlerFactory(); + + ChannelFactory channelFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 9e52d19ed95..477d7b2445c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; /** Low-level hooks to control certain aspects of Netty usage in the driver. */ @@ -33,6 +34,15 @@ public interface NettyOptions { */ Class channelClass(); + /** + * An event executor group that will be used to schedule all tasks not related to request I/O: + * cluster events, refreshing metadata, reconnection, etc. + * + *

This must always return the same instance (it can be the same object as {@link + * #ioEventLoopGroup()}). + */ + EventExecutorGroup adminEventExecutorGroup(); + /** * The byte buffer allocator to use. This must always return the same instance. Note that this is * also used by the default implementation of {@link InternalDriverContext#frameCodec()}, and the diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 660d6dc3651..6df08ab18e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -15,4 +15,313 @@ */ package com.datastax.oss.driver.internal.core.pool; -public class ChannelPool {} +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.google.common.annotations.VisibleForTesting; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The channel pool maintains a set of {@link DriverChannel} instances connected to a given node. + * + *

It allows clients to obtain a channel to execute their requests. + * + *

If one or more channels go down, a reconnection process starts in order to replace them; it + * runs until the channel count is back to its intended target. + */ +public class ChannelPool { + private static final Logger LOG = LoggerFactory.getLogger(ChannelPool.class); + + /** + * Initializes a new pool. + * + *

The returned completion stage will complete when all the underlying channels have finished + * their initialization. If one or more channels fail, a reconnection will be started immediately. + * Note that this method succeeds even if all channels fail, so you might get a pool that has no + * channels (i.e. {@link #next()} return {@code null}) and is reconnecting. + */ + public static CompletionStage init( + SocketAddress address, + CqlIdentifier keyspaceName, + int channelCount, + InternalDriverContext context) { + ChannelPool pool = new ChannelPool(address, keyspaceName, channelCount, context); + return pool.connect(); + } + + // This is read concurrently, but only mutated on adminExecutor (by methods in SingleThreaded) + @VisibleForTesting final ChannelSet channels = new ChannelSet(); + + private final EventExecutor adminExecutor; + private final SingleThreaded singleThreaded; + + private ChannelPool( + SocketAddress address, + CqlIdentifier keyspaceName, + int channelCount, + InternalDriverContext context) { + + adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.singleThreaded = new SingleThreaded(address, keyspaceName, channelCount, context); + } + + private CompletionStage connect() { + RunOrSchedule.on(adminExecutor, singleThreaded::connect); + return singleThreaded.connectFuture; + } + + /** + * @return the channel that has the most available stream ids. This is called on the direct + * request path, and we want to avoid complex check-then-act semantics; therefore this might + * race and return a channel that is already closed, or {@code null}. In those cases, it is up + * to the caller to fail fast and move to the next node. + *

There is no need to return the channel. + */ + public DriverChannel next() { + return channels.next(); + } + + /** + * Changes the keyspace name on all the channels in this pool. + * + *

Note that this is not called directly by the user, but happens only on a SetKeypsace + * response after a successful "USE ..." query, so the name should be valid. If the keyspace + * switch fails on any channel, that channel is closed and a reconnection is started. + */ + public CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { + return RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspaceName)); + } + + /** + * Closes the pool gracefully: subsequent calls to {@link #next()} will fail, but the pool's + * channels will be closed gracefully (allowing pending requests to complete). + */ + public CompletionStage close() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; + } + + /** + * Closes the pool forcefully: subsequent calls to {@link #next()} will fail, and the pool's + * channels will be closed forcefully (aborting pending requests). + */ + public CompletionStage forceClose() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + return singleThreaded.closeFuture; + } + + /** Does not close the pool, but returns a completion stage that will complete when it does. */ + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } + + /** Holds all administration tasks, that are confined to the admin executor. */ + private class SingleThreaded { + + private final SocketAddress address; + private final int wantedCount; + private final ChannelFactory channelFactory; + // The channels that are currently connecting + private final List> pendingChannels = new ArrayList<>(); + private final Reconnection reconnection; + + private CompletableFuture connectFuture = new CompletableFuture<>(); + private boolean isConnecting; + private CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean isClosing; + private CompletableFuture setKeyspaceFuture; + + private CqlIdentifier keyspaceName; + + private SingleThreaded( + SocketAddress address, + CqlIdentifier keyspaceName, + int wantedCount, + InternalDriverContext context) { + this.address = address; + this.keyspaceName = keyspaceName; + this.wantedCount = wantedCount; + this.channelFactory = context.channelFactory(); + this.reconnection = + new Reconnection(adminExecutor, context.reconnectionPolicy(), this::addMissingChannels); + } + + private void connect() { + assert adminExecutor.inEventLoop(); + if (isConnecting) { + return; + } + isConnecting = true; + CompletionStage initialChannels = + addMissingChannels() + .thenApply( + allConnected -> { + if (!allConnected) { + reconnection.start(); + } + return ChannelPool.this; + }); + CompletableFutures.completeFrom(initialChannels, connectFuture); + } + + private CompletionStage addMissingChannels() { + assert adminExecutor.inEventLoop(); + // We always wait for all attempts to succeed or fail before scheduling a reconnection + assert pendingChannels.isEmpty(); + + int missing = wantedCount - channels.size(); + LOG.debug("{} trying to create {} missing channels", ChannelPool.this, missing); + for (int i = 0; i < missing; i++) { + CompletionStage channelFuture = + channelFactory.connect(address, keyspaceName, wantedCount > 1); + pendingChannels.add(channelFuture); + } + return CompletableFutures.whenAllDone(pendingChannels) + .thenApplyAsync(this::onAllConnected, adminExecutor); + } + + private boolean onAllConnected(@SuppressWarnings("unused") Void v) { + assert adminExecutor.inEventLoop(); + for (CompletionStage pendingChannel : pendingChannels) { + CompletableFuture future = pendingChannel.toCompletableFuture(); + assert future.isDone(); + try { + DriverChannel channel = future.get(); + if (isClosing) { + LOG.debug( + "{} new channel added ({}) but the pool was closed, closing it", + ChannelPool.this, + channel); + channel.forceClose(); + } else { + LOG.debug("{} new channel added {}", ChannelPool.this, channel); + channels.add(channel); + channel + .closeFuture() + .addListener( + f -> + adminExecutor + .submit(() -> onChannelClosed(channel)) + .addListener(UncaughtExceptions::log)); + } + } catch (InterruptedException e) { + // can't happen, the future is done + } catch (ExecutionException e) { + // TODO handle ClusterNameMismatchException + LOG.debug(ChannelPool.this + " error while opening new channel", e.getCause()); + // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) + } + } + pendingChannels.clear(); + + int currentCount = channels.size(); + LOG.debug( + "{} reconnection attempt complete, {}/{} channels", + ChannelPool.this, + currentCount, + wantedCount); + // Stop reconnecting if we have the wanted count + return currentCount >= wantedCount; + } + + private void onChannelClosed(DriverChannel channel) { + assert adminExecutor.inEventLoop(); + LOG.debug("{} lost channel {}", ChannelPool.this, channel); + channels.remove(channel); + if (!isClosing && !reconnection.isRunning()) { + reconnection.start(); + } + } + + private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { + assert adminExecutor.inEventLoop(); + if (setKeyspaceFuture != null && !setKeyspaceFuture.isDone()) { + return CompletableFutures.failedFuture( + new IllegalStateException( + "Can't call setKeyspace while a keyspace switch is already in progress")); + } + keyspaceName = newKeyspaceName; + setKeyspaceFuture = new CompletableFuture<>(); + // Note that we don't handle errors; if the keyspace switch fails, the channel closes + forAllChannels( + ch -> ch.setKeyspace(newKeyspaceName), () -> setKeyspaceFuture.complete(null), null); + + // pending channels were scheduled with the old keyspace name, ensure they eventually switch + for (CompletionStage channelFuture : pendingChannels) { + // errors are swallowed here, this is fine because a setkeyspace error will close the + // channel, so it will eventually get reported + channelFuture.thenAccept(channel -> channel.setKeyspace(newKeyspaceName)); + } + + return setKeyspaceFuture; + } + + private void close() { + assert adminExecutor.inEventLoop(); + if (isClosing) { + return; + } + isClosing = true; + + reconnection.stop(); + + forAllChannels( + DriverChannel::close, + () -> closeFuture.complete(ChannelPool.this), + (channel, error) -> + LOG.warn(ChannelPool.this + " error closing channel " + channel, error)); + } + + private void forAllChannels( + Function> task, + Runnable whenAllDone, + BiConsumer onError) { + assert adminExecutor.inEventLoop(); + // we can read the size before iterating because it's only mutated from this thread + int todo = channels.size(); + if (todo == 0) { + whenAllDone.run(); + } else { + AtomicInteger done = new AtomicInteger(); + for (DriverChannel channel : channels) { + task.apply(channel) + .addListener( + f -> { + if (!f.isSuccess() && onError != null) { + onError.accept(channel, f.cause()); + } else if (done.incrementAndGet() == todo) { + whenAllDone.run(); + } + }); + } + } + } + + private void forceClose() { + assert adminExecutor.inEventLoop(); + if (!isClosing) { + close(); + } + for (DriverChannel channel : channels) { + channel.forceClose(); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java new file mode 100644 index 00000000000..9b2ed0b84db --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterators; +import java.util.Arrays; +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Concurrent structure used to store the channels of a pool. + * + *

Its write semantics are similar to "copy-on-write" JDK collections, selection operations are + * expected to vastly outnumber mutations. + */ +class ChannelSet implements Iterable { + private volatile DriverChannel[] channels; + private ReentrantLock lock = new ReentrantLock(); // must be held when mutating the array + + ChannelSet() { + this.channels = new DriverChannel[] {}; + } + + void add(DriverChannel toAdd) { + Preconditions.checkNotNull(toAdd); + lock.lock(); + try { + assert indexOf(channels, toAdd) < 0; + DriverChannel[] newChannels = Arrays.copyOf(channels, channels.length + 1); + newChannels[newChannels.length - 1] = toAdd; + channels = newChannels; + } finally { + lock.unlock(); + } + } + + boolean remove(DriverChannel toRemove) { + Preconditions.checkNotNull(toRemove); + lock.lock(); + try { + int index = indexOf(channels, toRemove); + if (index < 0) { + return false; + } else { + DriverChannel[] newChannels = new DriverChannel[channels.length - 1]; + int newI = 0; + for (int i = 0; i < channels.length; i++) { + if (i != index) { + newChannels[newI] = channels[i]; + newI += 1; + } + } + channels = newChannels; + return true; + } + } finally { + lock.unlock(); + } + } + + /** @return null if the set is empty or all are full */ + DriverChannel next() { + DriverChannel[] snapshot = this.channels; + switch (snapshot.length) { + case 0: + return null; + case 1: + return snapshot[0]; + default: + DriverChannel best = null; + int bestScore = 0; + for (DriverChannel channel : snapshot) { + int score = channel.availableIds(); + if (score > bestScore) { + bestScore = score; + best = channel; + } + } + return best; + } + } + + int size() { + return this.channels.length; + } + + @Override + public Iterator iterator() { + return Iterators.forArray(this.channels); + } + + private static int indexOf(DriverChannel[] channels, DriverChannel key) { + for (int i = 0; i < channels.length; i++) { + if (channels[i] == key) { + return i; + } + } + return -1; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java new file mode 100644 index 00000000000..7304e34963e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +public class CompletableFutures { + + public static CompletableFuture failedFuture(Throwable cause) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(cause); + return future; + } + + /** Completes {@code target} with the outcome of {@code source} */ + public static void completeFrom(CompletionStage source, CompletableFuture target) { + source.whenComplete( + (t, error) -> { + if (error != null) { + target.completeExceptionally(error); + } else { + target.complete(t); + } + }); + } + + /** @return a completion stage that completes when all inputs are done (success or failure). */ + public static CompletionStage whenAllDone(List> inputs) { + CompletableFuture result = new CompletableFuture<>(); + final int todo = inputs.size(); + final AtomicInteger done = new AtomicInteger(); + for (CompletionStage input : inputs) { + input.whenComplete( + (v, error) -> { + if (done.incrementAndGet() == todo) { + result.complete(null); + } + }); + } + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java new file mode 100644 index 00000000000..cdc02ecce10 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.google.common.base.Preconditions; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ScheduledFuture; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A reconnection process that, if failed, is retried periodically according to the intervals + * defined by a policy. + * + *

All the tasks run on a Netty event executor that is provided at construction time. Clients are + * also expected to call the public methods on that thread. + */ +public class Reconnection { + private static final Logger LOG = LoggerFactory.getLogger(Reconnection.class); + + private final EventExecutor executor; + private final ReconnectionPolicy reconnectionPolicy; + private final Callable> reconnectionTask; + + private ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; + private ScheduledFuture> nextAttempt; + + /** + * @param reconnectionTask the actual thing to try on a reconnection, returns if it succeeded or + * not. + */ + public Reconnection( + EventExecutor executor, + ReconnectionPolicy reconnectionPolicy, + Callable> reconnectionTask) { + this.executor = executor; + this.reconnectionPolicy = reconnectionPolicy; + this.reconnectionTask = reconnectionTask; + } + + public boolean isRunning() { + assert executor.inEventLoop(); + return nextAttempt != null; + } + + /** @throws IllegalStateException if the reconnection is already running */ + public void start() { + assert executor.inEventLoop(); + Preconditions.checkState(nextAttempt == null, "Already running"); + reconnectionSchedule = reconnectionPolicy.newSchedule(); + + scheduleNextAttempt(); + } + + private void scheduleNextAttempt() { + assert executor.inEventLoop(); + Duration nextInterval = reconnectionSchedule.nextDelay(); + LOG.debug("{} scheduling next reconnection in {}", this, nextInterval); + nextAttempt = executor.schedule(reconnectionTask, nextInterval.toNanos(), TimeUnit.NANOSECONDS); + nextAttempt.addListener( + (Future> f) -> { + if (f.isSuccess()) { + onNextAttemptStarted(f.getNow()); + } else { + LOG.warn("Uncaught error while starting reconnection attempt", f.cause()); + scheduleNextAttempt(); + } + }); + } + + // When the Callable runs this means the caller has started the attempt, we have yet to wait on + // the CompletableFuture to find out if that succeeded or not. + private void onNextAttemptStarted(CompletionStage futureOutcome) { + assert executor.inEventLoop(); + futureOutcome + .whenCompleteAsync(this::onNextAttemptCompleted, executor) + .exceptionally(UncaughtExceptions::log); + } + + private void onNextAttemptCompleted(Boolean success, Throwable error) { + assert executor.inEventLoop(); + if (success) { + LOG.debug("{} reconnection successful", this); + stop(); + } else { + if (error != null) { + LOG.warn("Uncaught error while starting reconnection attempt", error); + } + if (isRunning()) { + scheduleNextAttempt(); + } + } + } + + public void stop() { + assert executor.inEventLoop(); + LOG.debug("{} stopping reconnection", this); + if (nextAttempt != null) { + nextAttempt.cancel(true); + } + nextAttempt = null; + reconnectionSchedule = null; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java new file mode 100644 index 00000000000..97bbe17db84 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * Utility to run a task on a Netty event executor (i.e. thread). If we're already on the executor, + * the task is submitted, otherwise it's scheduled. + * + *

Be careful when using this, always keep in mind that the task might be executed synchronously. + * This can lead to subtle bugs when both the calling code and the callback manipulate a collection: + * + *

{@code
+ * List> futureFoos;
+ *
+ * // Scheduled on eventExecutor:
+ * for (int i = 0; i < count; i++) {
+ *   CompletionStage futureFoo = FooFactory.init();
+ *   futureFoos.add(futureFoo);
+ *   // futureFoo happens to be complete by now, so callback gets executed immediately
+ *   futureFoo.whenComplete(RunOrSchedule.on(eventExecutor, () -> callback(futureFoo)));
+ * }
+ *
+ * private void callback(CompletionStage futureFoo) {
+ *    futureFoos.remove(futureFoo); // ConcurrentModificationException!!!
+ * }
+ * }
+ * + * For that kind of situation, it's better to use {@code futureFoo.whenCompleteAsync(theTask, + * eventExecutor)}, so that the task is always scheduled. + */ +public class RunOrSchedule { + + public static void on(EventExecutor executor, Runnable task) { + if (executor.inEventLoop()) { + task.run(); + } else { + executor.submit(task).addListener(UncaughtExceptions::log); + } + } + + public static CompletionStage on( + EventExecutor executor, Callable> task) { + if (executor.inEventLoop()) { + try { + return task.call(); + } catch (Exception e) { + return CompletableFutures.failedFuture(e); + } + } else { + CompletableFuture result = new CompletableFuture<>(); + executor + .submit(task) + .addListener( + ((Future> f) -> { + if (f.isSuccess()) { + CompletableFutures.completeFrom(f.getNow(), result); + } else { + result.completeExceptionally(f.cause()); + } + })); + return result; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java new file mode 100644 index 00000000000..038c30bec30 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import io.netty.util.concurrent.Future; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods to log unexpected exceptions in asynchronous tasks. + * + *

Use this whenever you execute a future callback to apply side effects, but throw away the + * future itself: + * + *

{@code
+ * CompletionStage futureFoo = FooFactory.build();
+ *
+ * futureFoo
+ *   .whenComplete((f, error) -> { handler code with side effects })
+ *   // futureFoo is not propagated, do this or any unexpected error in the handler will be
+ *   // swallowed
+ *   .exceptionally(UncaughtExceptions::log);
+ *
+ * // If you return the future, you don't need it (but it's up to the caller to handle a failed
+ * // future)
+ * return futureFoo.whenComplete(...)
+ * }
+ */ +public class UncaughtExceptions { + + private static final Logger LOG = LoggerFactory.getLogger(UncaughtExceptions.class); + + public static void log(Future future) { + if (!future.isSuccess()) { + LOG.warn("Uncaught exception in scheduled task", future.cause()); + } + } + + public static T log(Throwable t) { + LOG.warn("Uncaught exception in scheduled task", t); + return null; + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 79514be7c18..fecb908a711 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -46,6 +46,14 @@ datastax-java-driver { # The maximum length of the frames supported by the driver. Beyond that limit, requests will # fail with an exception max-frame-length = 256 MB + + reconnection-policy { + provider-class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy + config { + base-delay = 1 second + max-delay = 60 seconds + } + } } authentication { # The auth provider class to use. The driver expects this class to have a public constructor diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java new file mode 100644 index 00000000000..f6f13c0fdfd --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.result.Void; +import io.netty.util.concurrent.Future; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.timeout; + +public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { + + @Mock private ResponseCallback responseCallback; + + @BeforeMethod + public void setup() throws InterruptedException { + super.setup(); + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn("V4"); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) + .thenReturn(128); + } + + @Test + public void should_report_available_ids_if_requested() { + // Given + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, true); + completeSimpleChannelInit(); + + // Then + assertThat(channelFuture) + .isSuccess( + channel -> { + assertThat(channel.availableIds()).isEqualTo(128); + + // Write a request, should decrease the count + Future writeFuture = + channel.write(new Query("test"), false, Frame.NO_PAYLOAD, responseCallback); + assertThat(writeFuture) + .isSuccess( + v -> { + assertThat(channel.availableIds()).isEqualTo(127); + + // Complete the request, should increase again + writeInboundFrame(readOutboundFrame(), Void.INSTANCE); + Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + assertThat(channel.availableIds()).isEqualTo(128); + }); + }); + } + + @Test + public void should_not_report_available_ids_if_not_requested() { + // Given + ChannelFactory factory = newChannelFactory(); + + // When + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + completeSimpleChannelInit(); + + // Then + assertThat(channelFuture) + .isSuccess( + channel -> { + assertThat(channel.availableIds()).isEqualTo(-1); + + // Write a request, complete it, count should never be updated + Future writeFuture = + channel.write(new Query("test"), false, Frame.NO_PAYLOAD, responseCallback); + assertThat(writeFuture) + .isSuccess( + v -> { + assertThat(channel.availableIds()).isEqualTo(-1); + + writeInboundFrame(readOutboundFrame(), Void.INSTANCE); + Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + assertThat(channel.availableIds()).isEqualTo(-1); + }); + }); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index e3361cde259..7b41cb381f5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -36,7 +36,7 @@ public void should_set_cluster_name_from_first_connection() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -55,13 +55,13 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); // open a first connection that will define the cluster name writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); assertThat(channelFuture).isSuccess(); // open a second connection that returns the same cluster name - channelFuture = factory.connect(SERVER_ADDRESS, null); + channelFuture = factory.connect(SERVER_ADDRESS, null, false); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -70,7 +70,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When // open a third connection that returns a different cluster name - channelFuture = factory.connect(SERVER_ADDRESS, null); + channelFuture = factory.connect(SERVER_ADDRESS, null, false); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java index d18c6de3ac0..f0144377e24 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java @@ -17,9 +17,6 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.internal.core.TestResponses; -import com.datastax.oss.protocol.internal.Frame; -import com.datastax.oss.protocol.internal.response.Ready; import io.netty.channel.local.LocalAddress; import java.util.concurrent.CompletionStage; import org.mockito.Mockito; @@ -38,13 +35,13 @@ public void should_not_fire_open_event_until_initialization_complete() { Mockito.verify(eventBus, never()).fire(any(ChannelEvent.class)); // Clean up - completeChannelInit(); + completeSimpleChannelInit(); } @Test public void should_fire_open_event_when_initialization_completes() { CompletionStage connectFuture = connect(SERVER_ADDRESS); - completeChannelInit(); + completeSimpleChannelInit(); assertThat(connectFuture) .isSuccess( channel -> @@ -55,7 +52,7 @@ public void should_fire_open_event_when_initialization_completes() { @Test public void should_fire_close_event_when_channel_closes() { CompletionStage connectFuture = connect(SERVER_ADDRESS); - completeChannelInit(); + completeSimpleChannelInit(); assertThat(connectFuture) .isSuccess( channel -> @@ -73,14 +70,6 @@ private CompletionStage connect(LocalAddress address) { .thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); - return newChannelFactory().connect(address, null); - } - - private void completeChannelInit() { - Frame requestFrame = readOutboundFrame(); - writeInboundFrame(requestFrame, new Ready()); - - requestFrame = readOutboundFrame(); - writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + return newChannelFactory().connect(address, null, false); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index 2d74ebb8f4b..dbb24b75cf9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -44,13 +44,9 @@ public void should_succeed_if_version_specified_and_supported_by_server() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); - Frame requestFrame = readOutboundFrame(); - writeInboundFrame(requestFrame, new Ready()); - - requestFrame = readOutboundFrame(); - writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + completeSimpleChannelInit(); // Then assertThat(channelFuture) @@ -69,7 +65,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -98,7 +94,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -124,7 +120,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -158,7 +154,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null); + CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 414d6825e6d..df353083158 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -29,6 +30,7 @@ import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.response.Ready; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; @@ -187,6 +189,18 @@ private void writeInboundFrame(Frame requestFrame, Message response, int protoco response)); } + /** + * Simulate the sequence of roundtrips to initialize a simple channel without authentication or + * keyspace (avoids repeating it in subclasses). + */ + protected void completeSimpleChannelInit() { + Frame requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, new Ready()); + + requestFrame = readOutboundFrame(); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); + } + ChannelFactory newChannelFactory() { return new TestChannelFactory(context); } @@ -202,7 +216,10 @@ private TestChannelFactory(InternalDriverContext internalDriverContext) { @Override ChannelInitializer initializer( - SocketAddress address, ProtocolVersion protocolVersion, CqlIdentifier keyspace) { + SocketAddress address, + ProtocolVersion protocolVersion, + CqlIdentifier keyspace, + AvailableIdsHolder availableIdsHolder) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { @@ -218,7 +235,8 @@ protected void initChannel(Channel channel) throws Exception { new InFlightHandler( protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), - setKeyspaceTimeoutMillis); + setKeyspaceTimeoutMillis, + availableIdsHolder); ProtocolInitHandler initHandler = new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 47beba2cc8b..65e59da270b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -51,9 +51,10 @@ public void setup() { channel .pipeline() .addLast( - new InFlightHandler(CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS)); + new InFlightHandler( + CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null)); writeCoalescer = new MockWriteCoalescer(); - driverChannel = new DriverChannel(channel, writeCoalescer); + driverChannel = new DriverChannel(channel, writeCoalescer, null); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index f14f550d7d4..cc78862827a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -51,7 +51,8 @@ public void setup() { channel .pipeline() .addLast( - new InFlightHandler(CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS)); + new InFlightHandler( + CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null)); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 40deb3c7af5..e252b53fafd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -76,7 +76,7 @@ public void setup() { .pipeline() .addLast( "inflight", - new InFlightHandler(CoreProtocolVersion.V4, new StreamIdGenerator(100), 100)); + new InFlightHandler(CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null)); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java new file mode 100644 index 00000000000..47f533aa1dc --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultChannelPromise; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.EventLoop; +import io.netty.channel.local.LocalAddress; +import io.netty.util.concurrent.Future; +import java.net.SocketAddress; +import java.time.Duration; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; + +public class ChannelPoolTest { + private static final SocketAddress ADDRESS = new LocalAddress("test"); + + private @Mock InternalDriverContext context; + private @Mock ReconnectionPolicy reconnectionPolicy; + private @Mock ReconnectionSchedule reconnectionSchedule; + private @Mock NettyOptions nettyOptions; + private @Mock ChannelFactory channelFactory; + private BlockingQueue> channelFactoryFutures; + private DefaultEventLoopGroup adminEventLoopGroup; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + adminEventLoopGroup = new DefaultEventLoopGroup(1); + + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.channelFactory()).thenReturn(channelFactory); + + channelFactoryFutures = new ArrayBlockingQueue<>(10); + Mockito.when(channelFactory.connect(eq(ADDRESS), isNull(), anyBoolean())) + .thenAnswer( + invocation -> { + CompletableFuture channelFuture = new CompletableFuture<>(); + channelFactoryFutures.offer(channelFuture); + return channelFuture; + }); + + Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + // By default, set a large reconnection delay. Tests that care about reconnection will override + // it. + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + } + + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + + @Test + public void should_initialize_when_all_channels_succeed() throws Exception { + int channelCount = 3; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + // Pool init should have called the channel factory. Complete the responses: + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + DriverChannel channel3 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel3); + + waitForPendingAdminTasks(); + + assertThat(poolFuture) + .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); + } + + @Test + public void should_initialize_when_all_channels_fail() throws Exception { + int channelCount = 3; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + for (int i = 0; i < channelCount; i++) { + channelFactoryFutures + .take() + .completeExceptionally(new Exception("mock channel init failure")); + } + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); + } + + @Test + public void should_reconnect_when_init_incomplete() throws Exception { + // Short delay so we don't have to wait in the test + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + // 1 channel fails, the other succeeds + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + + // A reconnection should have been scheduled + Mockito.verify(reconnectionSchedule).nextDelay(); + + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel1, channel2); + } + + @Test + public void should_reconnect_when_channel_dies() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + // The 2 channels succeed + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Simulate fatal error on channel2 + ((ChannelPromise) channel2.closeFuture()) + .setFailure(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + Mockito.verify(reconnectionSchedule).nextDelay(); + + DriverChannel channel3 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel3); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel1, channel3); + } + + @Test + public void should_switch_keyspace_on_existing_channels() throws Exception { + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + // The 2 channels succeed + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); + CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + + Mockito.verify(channel1).setKeyspace(newKeyspace); + Mockito.verify(channel2).setKeyspace(newKeyspace); + + assertThat(setKeyspaceFuture).isSuccess(); + } + + @Test + public void should_switch_keyspace_on_pending_channels() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + + // Check that reconnection has kicked in, but do not complete it yet + Mockito.verify(reconnectionSchedule).nextDelay(); + + // Switch keyspace, it succeeds immediately since there is no active channel + CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); + CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + assertThat(setKeyspaceFuture).isSuccess(); + + // Now let the two channels succeed to complete the reconnection + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + Mockito.verify(channel1).setKeyspace(newKeyspace); + Mockito.verify(channel2).setKeyspace(newKeyspace); + } + + @Test + public void should_close_all_channels_when_closed() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 3; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + + Mockito.verify(reconnectionSchedule).nextDelay(); + + CompletionStage closeFuture = pool.close(); + + // Complete the reconnection after the pool was closed + DriverChannel channel3 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel3); + + waitForPendingAdminTasks(); + + // The two original channels were closed normally + Mockito.verify(channel1).close(); + Mockito.verify(channel2).close(); + // The reconnection channel was force-closed when it succeeded and we found out the pool was + // closed + Mockito.verify(channel3).forceClose(); + + // Note that we don't wait for reconnected channels to close, so the pool only depends on + // channel 1 and 2 + ((ChannelPromise) channel1.closeFuture()).setSuccess(); + ((ChannelPromise) channel2.closeFuture()).setSuccess(); + + assertThat(closeFuture).isSuccess(); + } + + @Test + public void should_force_close_all_channels_when_force_closed() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 3; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel2); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + + Mockito.verify(reconnectionSchedule).nextDelay(); + + CompletionStage closeFuture = pool.forceClose(); + + // Complete the reconnection after the pool was closed + DriverChannel channel3 = newMockDriverChannel(); + channelFactoryFutures.take().complete(channel3); + + waitForPendingAdminTasks(); + + // The two original channels were force-closed + Mockito.verify(channel1).forceClose(); + Mockito.verify(channel2).forceClose(); + // The reconnection channel was force-closed when it succeeded and we found out the pool was + // closed + Mockito.verify(channel3).forceClose(); + + // Note that we don't wait for reconnected channels to close, so the pool only depends on + // channel 1 and 2 + ((ChannelPromise) channel1.closeFuture()).setSuccess(); + ((ChannelPromise) channel2.closeFuture()).setSuccess(); + + assertThat(closeFuture).isSuccess(); + } + + private DriverChannel newMockDriverChannel() { + DriverChannel channel = Mockito.mock(DriverChannel.class); + EventLoop adminExecutor = adminEventLoopGroup.next(); + DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); + Mockito.when(channel.close()).thenReturn(closeFuture); + Mockito.when(channel.forceClose()).thenReturn(closeFuture); + Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) + .thenReturn(adminExecutor.newSucceededFuture(null)); + return channel; + } + + // Wait for all the tasks on the pool's admin executor to complete. + private void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java new file mode 100644 index 00000000000..e8a3a84d12f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + +public class ChannelSetTest { + @Mock private DriverChannel channel1, channel2, channel3; + private ChannelSet set; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + set = new ChannelSet(); + } + + @Test + public void should_return_null_when_empty() { + assertThat(set.size()).isEqualTo(0); + assertThat(set.next()).isNull(); + } + + @Test + public void should_return_element_when_single() { + // When + set.add(channel1); + + // Then + assertThat(set.size()).isEqualTo(1); + assertThat(set.next()).isEqualTo(channel1); + Mockito.verify(channel1, never()).availableIds(); + } + + @Test + public void should_return_most_available_when_multiple() { + // Given + Mockito.when(channel1.availableIds()).thenReturn(2); + Mockito.when(channel2.availableIds()).thenReturn(12); + Mockito.when(channel3.availableIds()).thenReturn(8); + + // When + set.add(channel1); + set.add(channel2); + set.add(channel3); + + // Then + assertThat(set.size()).isEqualTo(3); + assertThat(set.next()).isEqualTo(channel2); + Mockito.verify(channel1).availableIds(); + Mockito.verify(channel2).availableIds(); + Mockito.verify(channel3).availableIds(); + + // When + Mockito.when(channel1.availableIds()).thenReturn(15); + + // Then + assertThat(set.next()).isEqualTo(channel1); + } + + @Test + public void should_remove_channels() { + // Given + Mockito.when(channel1.availableIds()).thenReturn(2); + Mockito.when(channel2.availableIds()).thenReturn(12); + Mockito.when(channel3.availableIds()).thenReturn(8); + + set.add(channel1); + set.add(channel2); + set.add(channel3); + assertThat(set.next()).isEqualTo(channel2); + + // When + set.remove(channel2); + + // Then + assertThat(set.size()).isEqualTo(2); + assertThat(set.next()).isEqualTo(channel3); + + // When + set.remove(channel3); + + // Then + assertThat(set.size()).isEqualTo(1); + assertThat(set.next()).isEqualTo(channel1); + + // When + set.remove(channel1); + + // Then + assertThat(set.size()).isEqualTo(0); + assertThat(set.next()).isNull(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java new file mode 100644 index 00000000000..b0f50e40e5a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.concurrent.EventExecutor; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; + +public class ReconnectionTest { + + @Mock private ReconnectionPolicy reconnectionPolicy; + @Mock private ReconnectionSchedule reconnectionSchedule; + private EmbeddedChannel channel; + + private MockReconnectionTask reconnectionTask; + private Reconnection reconnection; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + + // Unfortunately Netty does not expose EmbeddedEventLoop, so we have to go through a channel + channel = new EmbeddedChannel(); + EventExecutor eventExecutor = channel.eventLoop(); + + reconnectionTask = new MockReconnectionTask(); + reconnection = new Reconnection(eventExecutor, reconnectionPolicy, reconnectionTask); + } + + @Test + public void should_start_out_not_running() { + assertThat(reconnection.isRunning()).isFalse(); + } + + @Test + public void should_schedule_first_attempt_on_start() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); + + // When + reconnection.start(); + + // Then + Mockito.verify(reconnectionSchedule).nextDelay(); + assertThat(reconnection.isRunning()).isTrue(); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void should_fail_if_started_twice() { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); + reconnection.start(); + reconnection.start(); + } + + @Test + public void should_stop_if_first_attempt_succeeds() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + reconnection.start(); + Mockito.verify(reconnectionSchedule).nextDelay(); + + // When + // the reconnection task is scheduled: + runPendingTasks(); + assertThat(reconnectionTask.wasCalled()).isTrue(); + // the reconnection task completes: + reconnectionTask.complete(true); + runPendingTasks(); + + // Then + assertThat(reconnection.isRunning()).isFalse(); + } + + @Test + public void should_reschedule_if_first_attempt_fails() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + reconnection.start(); + Mockito.verify(reconnectionSchedule).nextDelay(); + + // When + // the reconnection task is scheduled: + runPendingTasks(); + assertThat(reconnectionTask.wasCalled()).isTrue(); + // the reconnection task completes: + reconnectionTask.complete(false); + runPendingTasks(); + + // Then + // schedule was called again + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + runPendingTasks(); + // task was called again + assertThat(reconnectionTask.wasCalled()).isTrue(); + // still running + assertThat(reconnection.isRunning()).isTrue(); + + // When + // second attempt completes + reconnectionTask.complete(true); + runPendingTasks(); + + // Then + assertThat(reconnection.isRunning()).isFalse(); + } + + @Test + public void should_stop_between_attempts_if_requested() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); + reconnection.start(); + Mockito.verify(reconnectionSchedule).nextDelay(); + + // When + reconnection.stop(); + + // Then + assertThat(reconnection.isRunning()).isFalse(); + } + + private void runPendingTasks() { + channel.runPendingTasks(); + } + + private static class MockReconnectionTask implements Callable> { + private volatile CompletableFuture nextResult; + + @Override + public CompletionStage call() throws Exception { + assertThat(nextResult == null || nextResult.isDone()).isTrue(); + nextResult = new CompletableFuture<>(); + return nextResult; + } + + private void complete(boolean outcome) { + assertThat(nextResult != null || !nextResult.isDone()).isTrue(); + nextResult.complete(outcome); + nextResult = null; + } + + private boolean wasCalled() { + return nextResult != null; + } + } +} From d971b32d2c094edd7c2aed2aae39729f9fc0ab9a Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 10 Apr 2017 18:37:17 -0700 Subject: [PATCH 016/742] Allow pool to resize --- .../internal/core/pool/ChannelPool.java | 47 ++++- .../internal/core/pool/ChannelPoolTest.java | 187 ++++++++++++++++-- 2 files changed, 214 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 6df08ab18e7..9524970a004 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -24,11 +24,13 @@ import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Sets; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; @@ -98,6 +100,10 @@ public DriverChannel next() { return channels.next(); } + public void resize(int newChannelCount) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newChannelCount)); + } + /** * Changes the keyspace name on all the channels in this pool. * @@ -136,12 +142,12 @@ public CompletionStage closeFuture() { private class SingleThreaded { private final SocketAddress address; - private final int wantedCount; private final ChannelFactory channelFactory; // The channels that are currently connecting private final List> pendingChannels = new ArrayList<>(); private final Reconnection reconnection; + private int wantedCount; private CompletableFuture connectFuture = new CompletableFuture<>(); private boolean isConnecting; private CompletableFuture closeFuture = new CompletableFuture<>(); @@ -231,6 +237,8 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } pendingChannels.clear(); + shrinkIfTooManyChannels(); // Can happen if the pool was shrinked during the reconnection + int currentCount = channels.size(); LOG.debug( "{} reconnection attempt complete, {}/{} channels", @@ -250,6 +258,43 @@ private void onChannelClosed(DriverChannel channel) { } } + public void resize(int newChannelCount) { + assert adminExecutor.inEventLoop(); + if (newChannelCount > wantedCount) { + LOG.debug("{} growing ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); + wantedCount = newChannelCount; + if (!reconnection.isRunning()) { + reconnection.start(); + } + } else if (newChannelCount < wantedCount) { + LOG.debug( + "{} shrinking ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); + wantedCount = newChannelCount; + if (!reconnection.isRunning()) { + shrinkIfTooManyChannels(); + } // else it will be handled at the end of the reconnection attempt + } + } + + private void shrinkIfTooManyChannels() { + assert adminExecutor.inEventLoop(); + int extraCount = channels.size() - wantedCount; + if (extraCount > 0) { + LOG.debug("{} closing {} extra channels", ChannelPool.this, extraCount); + Set toRemove = Sets.newHashSetWithExpectedSize(extraCount); + for (DriverChannel channel : channels) { + toRemove.add(channel); + if (--extraCount == 0) { + break; + } + } + for (DriverChannel channel : toRemove) { + channels.remove(channel); + channel.close(); + } + } + } + private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { assert adminExecutor.inEventLoop(); if (setKeyspaceFuture != null && !setKeyspaceFuture.isDone()) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 47f533aa1dc..52f3ecf1589 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -51,6 +51,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.times; public class ChannelPoolTest { private static final SocketAddress ADDRESS = new LocalAddress("test"); @@ -101,11 +102,11 @@ public void should_initialize_when_all_channels_succeed() throws Exception { ChannelPool.init(ADDRESS, null, channelCount, context); // Pool init should have called the channel factory. Complete the responses: - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); - DriverChannel channel3 = newMockDriverChannel(); + DriverChannel channel3 = newMockDriverChannel(3); channelFactoryFutures.take().complete(channel3); waitForPendingAdminTasks(); @@ -142,7 +143,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { // 1 channel fails, the other succeeds channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); waitForPendingAdminTasks(); @@ -154,7 +155,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { // A reconnection should have been scheduled Mockito.verify(reconnectionSchedule).nextDelay(); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); waitForPendingAdminTasks(); @@ -171,9 +172,9 @@ public void should_reconnect_when_channel_dies() throws Exception { ChannelPool.init(ADDRESS, null, channelCount, context); // The 2 channels succeed - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); waitForPendingAdminTasks(); @@ -190,7 +191,7 @@ public void should_reconnect_when_channel_dies() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); - DriverChannel channel3 = newMockDriverChannel(); + DriverChannel channel3 = newMockDriverChannel(3); channelFactoryFutures.take().complete(channel3); waitForPendingAdminTasks(); @@ -198,6 +199,153 @@ public void should_reconnect_when_channel_dies() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel3); } + @Test + public void should_shrink_outside_of_reconnection() throws Exception { + int channelCount = 4; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(1); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(2); + channelFactoryFutures.take().complete(channel2); + DriverChannel channel3 = newMockDriverChannel(3); + channelFactoryFutures.take().complete(channel3); + DriverChannel channel4 = newMockDriverChannel(4); + channelFactoryFutures.take().complete(channel4); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + pool.resize(2); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel3, channel4); + } + + @Test + public void should_shrink_during_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + int channelCount = 4; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(1); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(2); + channelFactoryFutures.take().complete(channel2); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // A reconnection should have been scheduled to add the missing channels, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + + pool.resize(2); + + waitForPendingAdminTasks(); + + // Now allow the reconnected channel to complete initialization + DriverChannel channel3 = newMockDriverChannel(3); + channelFactoryFutures.take().complete(channel3); + DriverChannel channel4 = newMockDriverChannel(4); + channelFactoryFutures.take().complete(channel4); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel3, channel4); + } + + @Test + public void should_grow_outside_of_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(1); + channelFactoryFutures.take().complete(channel1); + DriverChannel channel2 = newMockDriverChannel(2); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + pool.resize(4); + + waitForPendingAdminTasks(); + + // The resizing should have triggered a reconnection + Mockito.verify(reconnectionSchedule).nextDelay(); + + DriverChannel channel3 = newMockDriverChannel(3); + channelFactoryFutures.take().complete(channel3); + DriverChannel channel4 = newMockDriverChannel(4); + channelFactoryFutures.take().complete(channel4); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + } + + @Test + public void should_grow_during_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + int channelCount = 2; + CompletionStage poolFuture = + ChannelPool.init(ADDRESS, null, channelCount, context); + + DriverChannel channel1 = newMockDriverChannel(1); + channelFactoryFutures.take().complete(channel1); + channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + + // A reconnection should have been scheduled to add the missing channel, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + + pool.resize(4); + + waitForPendingAdminTasks(); + + // Complete the channel for the first reconnection, bringing the count to 2 + DriverChannel channel2 = newMockDriverChannel(2); + channelFactoryFutures.take().complete(channel2); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Another reconnection should have been scheduled when evaluating the first attempt + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + + DriverChannel channel3 = newMockDriverChannel(3); + channelFactoryFutures.take().complete(channel3); + DriverChannel channel4 = newMockDriverChannel(4); + channelFactoryFutures.take().complete(channel4); + + waitForPendingAdminTasks(); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + } + @Test public void should_switch_keyspace_on_existing_channels() throws Exception { int channelCount = 2; @@ -205,9 +353,9 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { ChannelPool.init(ADDRESS, null, channelCount, context); // The 2 channels succeed - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); waitForPendingAdminTasks(); @@ -252,9 +400,9 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { assertThat(setKeyspaceFuture).isSuccess(); // Now let the two channels succeed to complete the reconnection - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); waitForPendingAdminTasks(); @@ -271,9 +419,9 @@ public void should_close_all_channels_when_closed() throws Exception { CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, channelCount, context); - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); @@ -287,7 +435,7 @@ public void should_close_all_channels_when_closed() throws Exception { CompletionStage closeFuture = pool.close(); // Complete the reconnection after the pool was closed - DriverChannel channel3 = newMockDriverChannel(); + DriverChannel channel3 = newMockDriverChannel(3); channelFactoryFutures.take().complete(channel3); waitForPendingAdminTasks(); @@ -315,9 +463,9 @@ public void should_force_close_all_channels_when_force_closed() throws Exception CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, channelCount, context); - DriverChannel channel1 = newMockDriverChannel(); + DriverChannel channel1 = newMockDriverChannel(1); channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(); + DriverChannel channel2 = newMockDriverChannel(2); channelFactoryFutures.take().complete(channel2); channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); @@ -331,7 +479,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception CompletionStage closeFuture = pool.forceClose(); // Complete the reconnection after the pool was closed - DriverChannel channel3 = newMockDriverChannel(); + DriverChannel channel3 = newMockDriverChannel(3); channelFactoryFutures.take().complete(channel3); waitForPendingAdminTasks(); @@ -351,7 +499,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception assertThat(closeFuture).isSuccess(); } - private DriverChannel newMockDriverChannel() { + private DriverChannel newMockDriverChannel(int id) { DriverChannel channel = Mockito.mock(DriverChannel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); @@ -360,6 +508,7 @@ private DriverChannel newMockDriverChannel() { Mockito.when(channel.closeFuture()).thenReturn(closeFuture); Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) .thenReturn(adminExecutor.newSucceededFuture(null)); + Mockito.when(channel.toString()).thenReturn("channel" + id); return channel; } From a74dcb5530d28febe38f24986913d1151e70a429 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 15 Apr 2017 12:16:48 -0700 Subject: [PATCH 017/742] Allow driver channels to register for events --- .../internal/core/channel/ChannelFactory.java | 22 ++--- .../core/channel/DriverChannelOptions.java | 85 ++++++++++++++++++ .../internal/core/channel/EventCallback.java | 23 +++++ .../core/channel/InFlightHandler.java | 25 ++++-- .../core/channel/ProtocolInitHandler.java | 31 +++++-- .../internal/core/pool/ChannelPool.java | 9 +- core/src/test/java/Tmp.java.bak | 34 +++++++ .../ChannelFactoryAvailableIdsTest.java | 7 +- .../ChannelFactoryClusterNameTest.java | 10 ++- .../channel/ChannelFactoryEventsTest.java | 2 +- ...ChannelFactoryProtocolNegotiationTest.java | 15 ++-- .../core/channel/ChannelFactoryTestBase.java | 8 +- .../core/channel/DriverChannelTest.java | 2 +- .../core/channel/InFlightHandlerTest.java | 68 ++++++++++++-- .../core/channel/ProtocolInitHandlerTest.java | 88 +++++++++++++++++-- .../internal/core/pool/ChannelPoolTest.java | 5 +- 16 files changed, 373 insertions(+), 61 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/EventCallback.java create mode 100644 core/src/test/java/Tmp.java.bak diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 7af5f25284c..88cdab8b21a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -65,12 +64,12 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } - /** @param reportAvailableIds whether {@link DriverChannel#availableIds()} should be maintained */ public CompletionStage connect( - final SocketAddress address, CqlIdentifier keyspace, boolean reportAvailableIds) { + final SocketAddress address, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); - AvailableIdsHolder availableIdsHolder = reportAvailableIds ? new AvailableIdsHolder() : null; + AvailableIdsHolder availableIdsHolder = + options.reportAvailableIds ? new AvailableIdsHolder() : null; ProtocolVersion currentVersion; boolean isNegotiating; @@ -85,7 +84,7 @@ public CompletionStage connect( connect( address, - keyspace, + options, availableIdsHolder, currentVersion, isNegotiating, @@ -96,7 +95,7 @@ public CompletionStage connect( private void connect( SocketAddress address, - CqlIdentifier keyspace, + DriverChannelOptions options, AvailableIdsHolder availableIdsHolder, final ProtocolVersion currentVersion, boolean isNegotiating, @@ -110,7 +109,7 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(address, currentVersion, keyspace, availableIdsHolder)); + .handler(initializer(address, currentVersion, options, availableIdsHolder)); nettyOptions.afterBootstrapInitialized(bootstrap); @@ -149,7 +148,7 @@ private void connect( downgraded.get()); connect( address, - keyspace, + options, availableIdsHolder, downgraded.get(), true, @@ -170,7 +169,7 @@ private void connect( ChannelInitializer initializer( SocketAddress address, final ProtocolVersion protocolVersion, - final CqlIdentifier keyspace, + final DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { return new ChannelInitializer() { @Override @@ -191,9 +190,10 @@ protected void initChannel(Channel channel) throws Exception { protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, - availableIdsHolder); + availableIdsHolder, + options.eventCallback); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler(context, protocolVersion, clusterName, options); ChannelPipeline pipeline = channel.pipeline(); context diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java new file mode 100644 index 00000000000..d978c95f447 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.List; + +/** Options for the creation of a driver channel. */ +public class DriverChannelOptions { + + /** No keyspace, no events, don't report available stream ids. */ + public static DriverChannelOptions DEFAULT = builder().build(); + + public static Builder builder() { + return new Builder(); + } + + public final CqlIdentifier keyspace; + /** Whether {@link DriverChannel#availableIds()} should be maintained */ + public final boolean reportAvailableIds; + + /** + * What kind of protocol events to listen for. + * + * @see com.datastax.oss.protocol.internal.ProtocolConstants.EventType + */ + public final List eventTypes; + + public final EventCallback eventCallback; + + private DriverChannelOptions( + CqlIdentifier keyspace, + boolean reportAvailableIds, + List eventTypes, + EventCallback eventCallback) { + this.keyspace = keyspace; + this.reportAvailableIds = reportAvailableIds; + this.eventTypes = eventTypes; + this.eventCallback = eventCallback; + } + + public static class Builder { + private CqlIdentifier keyspace = null; + private boolean reportAvailableIds = false; + private List eventTypes = Collections.emptyList(); + private EventCallback eventCallback = null; + + public Builder withKeyspace(CqlIdentifier keyspace) { + this.keyspace = keyspace; + return this; + } + + public Builder reportAvailableIds(boolean reportAvailableIds) { + this.reportAvailableIds = reportAvailableIds; + return this; + } + + public Builder withEvents(List eventTypes, EventCallback eventCallback) { + Preconditions.checkArgument(eventTypes != null && !eventTypes.isEmpty()); + Preconditions.checkNotNull(eventCallback); + this.eventTypes = eventTypes; + this.eventCallback = eventCallback; + return this; + } + + public DriverChannelOptions build() { + return new DriverChannelOptions(keyspace, reportAvailableIds, eventTypes, eventCallback); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/EventCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/EventCallback.java new file mode 100644 index 00000000000..024c3cfeccb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/EventCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.protocol.internal.Message; + +public interface EventCallback { + /** Invoked when a protocol event is received. */ + void onEvent(Message event); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 7dc53eeaf25..57caaebbba8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -47,6 +47,7 @@ public class InFlightHandler extends ChannelDuplexHandler { private final Map inFlight; private final long setKeyspaceTimeoutMillis; private final AvailableIdsHolder availableIdsHolder; + private final EventCallback eventCallback; private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; @@ -54,13 +55,15 @@ public class InFlightHandler extends ChannelDuplexHandler { ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis, - AvailableIdsHolder availableIdsHolder) { + AvailableIdsHolder availableIdsHolder, + EventCallback eventCallback) { this.protocolVersion = protocolVersion; this.streamIds = streamIds; reportAvailableIds(); this.inFlight = Maps.newHashMapWithExpectedSize(streamIds.getMaxAvailableIds()); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; this.availableIdsHolder = availableIdsHolder; + this.eventCallback = eventCallback; } @Override @@ -126,12 +129,22 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception Frame responseFrame = (Frame) msg; int streamId = responseFrame.streamId; - ResponseCallback responseCallback = inFlight.get(streamId); - if (responseCallback != null) { - if (!responseCallback.holdStreamId()) { - release(streamId, ctx); + if (streamId < 0) { + Message event = responseFrame.message; + if (eventCallback == null) { + LOG.debug("Received event {} but no callback was registered", event); + } else { + LOG.debug("Received event {}, notifying callback", event); + eventCallback.onEvent(event); + } + } else { + ResponseCallback responseCallback = inFlight.get(streamId); + if (responseCallback != null) { + if (!responseCallback.holdStreamId()) { + release(streamId, ctx); + } + responseCallback.onResponse(responseFrame); } - responseCallback.onResponse(responseFrame); } super.channelRead(ctx, msg); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 1f0a1a7ba6e..2dfdb1685c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.auth.AuthenticationException; @@ -30,6 +29,7 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.Register; import com.datastax.oss.protocol.internal.request.Startup; import com.datastax.oss.protocol.internal.response.AuthChallenge; import com.datastax.oss.protocol.internal.response.AuthSuccess; @@ -60,7 +60,7 @@ class ProtocolInitHandler extends ConnectInitHandler { private final InternalDriverContext internalDriverContext; private final long timeoutMillis; private final ProtocolVersion initialProtocolVersion; - private final CqlIdentifier keyspaceName; + private final DriverChannelOptions options; // might be null if this is the first channel to this cluster private final String expectedClusterName; @@ -68,7 +68,7 @@ class ProtocolInitHandler extends ConnectInitHandler { InternalDriverContext internalDriverContext, ProtocolVersion protocolVersion, String expectedClusterName, - CqlIdentifier keyspaceName) { + DriverChannelOptions options) { this.internalDriverContext = internalDriverContext; @@ -79,7 +79,7 @@ class ProtocolInitHandler extends ConnectInitHandler { CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS); this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; - this.keyspaceName = keyspaceName; + this.options = options; } @Override @@ -91,7 +91,8 @@ private enum Step { STARTUP, GET_CLUSTER_NAME, SET_KEYSPACE, - AUTH_RESPONSE + AUTH_RESPONSE, + REGISTER, } private class InitRequest extends InternalRequest { @@ -119,9 +120,11 @@ Message getRequest() { case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: - return new Query("USE " + keyspaceName.asCql()); + return new Query("USE " + options.keyspace.asCql()); case AUTH_RESPONSE: return new AuthResponse(authReponseToken); + case REGISTER: + return new Register(options.eventTypes); default: throw new AssertionError("unhandled step: " + step); } @@ -212,14 +215,24 @@ void onResponse(Message response) { // Store the actual name so that it can be retrieved from the factory channel.attr(DriverChannel.CLUSTER_NAME_KEY).set(actualClusterName); } - if (keyspaceName == null) { - setConnectSuccess(); - } else { + if (options.keyspace != null) { step = Step.SET_KEYSPACE; send(); + } else if (!options.eventTypes.isEmpty()) { + step = Step.REGISTER; + send(); + } else { + setConnectSuccess(); } } } else if (step == Step.SET_KEYSPACE && response instanceof SetKeyspace) { + if (!options.eventTypes.isEmpty()) { + step = Step.REGISTER; + send(); + } else { + setConnectSuccess(); + } + } else if (step == Step.REGISTER && response instanceof Ready) { setConnectSuccess(); } else if (response instanceof Error) { Error error = (Error) response; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 9524970a004..1174870c03e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; @@ -194,9 +195,13 @@ private CompletionStage addMissingChannels() { int missing = wantedCount - channels.size(); LOG.debug("{} trying to create {} missing channels", ChannelPool.this, missing); + DriverChannelOptions options = + DriverChannelOptions.builder() + .withKeyspace(keyspaceName) + .reportAvailableIds(wantedCount > 1) + .build(); for (int i = 0; i < missing; i++) { - CompletionStage channelFuture = - channelFactory.connect(address, keyspaceName, wantedCount > 1); + CompletionStage channelFuture = channelFactory.connect(address, options); pendingChannels.add(channelFuture); } return CompletableFutures.whenAllDone(pendingChannels) diff --git a/core/src/test/java/Tmp.java.bak b/core/src/test/java/Tmp.java.bak new file mode 100644 index 00000000000..f8908dd49cf --- /dev/null +++ b/core/src/test/java/Tmp.java.bak @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import com.datastax.oss.driver.api.core.Cluster; +import com.google.common.collect.ImmutableSet; +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class Tmp { + public static void main(String[] args) throws InterruptedException, ExecutionException { + Cluster cluster = + Cluster.builder() + .withContactPoints(ImmutableSet.of(new InetSocketAddress("127.0.0.1", 9042))) + .build(); + + System.out.println("Cluster initialized"); + + System.out.println(cluster.getMetadata().getNodes()); + TimeUnit.HOURS.sleep(1); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index f6f13c0fdfd..83b9d0202f7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -54,7 +54,9 @@ public void should_report_available_ids_if_requested() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, true); + CompletionStage channelFuture = + factory.connect( + SERVER_ADDRESS, DriverChannelOptions.builder().reportAvailableIds(true).build()); completeSimpleChannelInit(); // Then @@ -85,7 +87,8 @@ public void should_not_report_available_ids_if_not_requested() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); // Then diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 7b41cb381f5..0754095dd1b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -36,7 +36,8 @@ public void should_set_cluster_name_from_first_connection() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -55,13 +56,14 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); // open a first connection that will define the cluster name writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); assertThat(channelFuture).isSuccess(); // open a second connection that returns the same cluster name - channelFuture = factory.connect(SERVER_ADDRESS, null, false); + channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -70,7 +72,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When // open a third connection that returns a different cluster name - channelFuture = factory.connect(SERVER_ADDRESS, null, false); + channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java index f0144377e24..17a14ee678e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java @@ -70,6 +70,6 @@ private CompletionStage connect(LocalAddress address) { .thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); - return newChannelFactory().connect(address, null, false); + return newChannelFactory().connect(address, DriverChannelOptions.DEFAULT); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index dbb24b75cf9..dc72bea11bc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -44,7 +44,8 @@ public void should_succeed_if_version_specified_and_supported_by_server() { ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); @@ -65,7 +66,8 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -94,7 +96,8 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -120,7 +123,8 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -154,7 +158,8 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) ChannelFactory factory = newChannelFactory(); // When - CompletionStage channelFuture = factory.connect(SERVER_ADDRESS, null, false); + CompletionStage channelFuture = + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index df353083158..7a26ddeba73 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -218,7 +217,7 @@ private TestChannelFactory(InternalDriverContext internalDriverContext) { ChannelInitializer initializer( SocketAddress address, ProtocolVersion protocolVersion, - CqlIdentifier keyspace, + DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { return new ChannelInitializer() { @Override @@ -236,9 +235,10 @@ protected void initChannel(Channel channel) throws Exception { protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, - availableIdsHolder); + availableIdsHolder, + null); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, keyspace); + new ProtocolInitHandler(context, protocolVersion, clusterName, options); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 65e59da270b..bd02dbadd9c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -52,7 +52,7 @@ public void setup() { .pipeline() .addLast( new InFlightHandler( - CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null)); + CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); writeCoalescer = new MockWriteCoalescer(); driverChannel = new DriverChannel(channel, writeCoalescer, null); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index cc78862827a..99d7b94322a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -21,13 +21,18 @@ import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.event.StatusChangeEvent; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.response.result.Void; import com.google.common.collect.ImmutableList; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPromise; +import java.net.InetSocketAddress; +import java.util.Collections; import java.util.concurrent.TimeUnit; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -39,7 +44,7 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { private static final Query QUERY = new Query("select * from foo"); - public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; @Mock private StreamIdGenerator streamIds; @@ -48,16 +53,12 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { public void setup() { super.setup(); MockitoAnnotations.initMocks(this); - channel - .pipeline() - .addLast( - new InFlightHandler( - CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null)); } @Test public void should_fail_if_connection_busy() throws Throwable { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(-1); // When @@ -74,6 +75,7 @@ public void should_fail_if_connection_busy() throws Throwable { @Test public void should_assign_streamid_and_send_frame() { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); @@ -94,6 +96,7 @@ public void should_assign_streamid_and_send_frame() { @Test public void should_notify_callback_of_response() { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( @@ -112,6 +115,7 @@ public void should_notify_callback_of_response() { @Test public void should_notify_response_promise_when_decoding_fails() throws Throwable { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( @@ -129,6 +133,7 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl @Test public void should_delay_graceful_close_until_all_pending_complete() { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( @@ -152,6 +157,9 @@ public void should_delay_graceful_close_until_all_pending_complete() { @Test public void should_graceful_close_immediately_if_no_pending() { + // Given + addToPipeline(); + // When channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); @@ -162,6 +170,7 @@ public void should_graceful_close_immediately_if_no_pending() { @Test public void should_refuse_new_writes_during_graceful_close() { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( @@ -188,6 +197,7 @@ public void should_refuse_new_writes_during_graceful_close() { @Test public void should_fail_all_pending_when_force_closed() throws Throwable { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); @@ -211,6 +221,7 @@ public void should_fail_all_pending_when_force_closed() throws Throwable { @Test public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() throws Throwable { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); @@ -235,6 +246,7 @@ public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() @Test public void should_hold_stream_id_if_required() { // Given + addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(true); @@ -274,6 +286,7 @@ public void should_hold_stream_id_if_required() { @Test public void should_set_keyspace() { // Given + addToPipeline(); ChannelPromise setKeyspacePromise = channel.newPromise(); DriverChannel.SetKeyspaceEvent setKeyspaceEvent = new DriverChannel.SetKeyspaceEvent(CqlIdentifier.fromCql("ks"), setKeyspacePromise); @@ -291,6 +304,7 @@ public void should_set_keyspace() { @Test public void should_fail_to_set_keyspace_if_query_times_out() throws InterruptedException { // Given + addToPipeline(); ChannelPromise setKeyspacePromise = channel.newPromise(); DriverChannel.SetKeyspaceEvent setKeyspaceEvent = new DriverChannel.SetKeyspaceEvent(CqlIdentifier.fromCql("ks"), setKeyspacePromise); @@ -303,4 +317,46 @@ public void should_fail_to_set_keyspace_if_query_times_out() throws InterruptedE // Then assertThat(setKeyspacePromise).isFailed(); } + + @Test + public void should_notify_callback_of_events() { + // Given + EventCallback eventCallback = Mockito.mock(EventCallback.class); + addToPipelineWithEventCallback(eventCallback); + + // When + StatusChangeEvent event = + new StatusChangeEvent( + ProtocolConstants.StatusChangeType.UP, new InetSocketAddress("127.0.0.1", 9042)); + Frame eventFrame = + Frame.forResponse( + CoreProtocolVersion.V3.getCode(), + -1, + null, + Collections.emptyMap(), + Collections.emptyList(), + event); + writeInboundFrame(eventFrame); + + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(StatusChangeEvent.class); + Mockito.verify(eventCallback).onEvent(captor.capture()); + assertThat(captor.getValue()).isSameAs(event); + } + + private void addToPipeline() { + addToPipelineWithEventCallback(null); + } + + private void addToPipelineWithEventCallback(EventCallback eventCallback) { + channel + .pipeline() + .addLast( + new InFlightHandler( + CoreProtocolVersion.V3, + streamIds, + SET_KEYSPACE_TIMEOUT_MILLIS, + null, + eventCallback)); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index e252b53fafd..59595235b09 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -29,6 +29,7 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.Register; import com.datastax.oss.protocol.internal.request.Startup; import com.datastax.oss.protocol.internal.response.AuthChallenge; import com.datastax.oss.protocol.internal.response.AuthSuccess; @@ -37,8 +38,10 @@ import com.datastax.oss.protocol.internal.response.Ready; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; +import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.mockito.Mock; @@ -76,7 +79,8 @@ public void setup() { .pipeline() .addLast( "inflight", - new InFlightHandler(CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null)); + new InFlightHandler( + CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); } @Test @@ -85,7 +89,8 @@ public void should_initialize_without_authentication() { .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -133,7 +138,8 @@ public void should_initialize_with_authentication() { .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -190,7 +196,8 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -230,7 +237,10 @@ public void should_check_cluster_name_if_provided() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + internalDriverContext, + CoreProtocolVersion.V4, + "expectedClusterName", + DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -255,7 +265,10 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", null)); + internalDriverContext, + CoreProtocolVersion.V4, + "expectedClusterName", + DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -274,12 +287,68 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th @Test public void should_initialize_with_keyspace() { + DriverChannelOptions options = + DriverChannelOptions.builder().withKeyspace(CqlIdentifier.fromCql("ks")).build(); + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, options)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + assertThat(((Query) requestFrame.message).query).isEqualTo("USE \"ks\""); + writeInboundFrame(requestFrame, new SetKeyspace("ks")); + + assertThat(connectFuture).isSuccess(); + } + + @Test + public void should_initialize_with_events() { + List eventTypes = ImmutableList.of("foo", "bar"); + EventCallback eventCallback = Mockito.mock(EventCallback.class); + DriverChannelOptions driverChannelOptions = + DriverChannelOptions.builder().withEvents(eventTypes, eventCallback).build(); channel .pipeline() .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, CqlIdentifier.fromCql("ks"))); + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Register.class); + assertThat(((Register) requestFrame.message).eventTypes).containsExactly("foo", "bar"); + writeInboundFrame(requestFrame, new Ready()); + + assertThat(connectFuture).isSuccess(); + } + + @Test + public void should_initialize_with_keyspace_and_events() { + List eventTypes = ImmutableList.of("foo", "bar"); + EventCallback eventCallback = Mockito.mock(EventCallback.class); + DriverChannelOptions driverChannelOptions = + DriverChannelOptions.builder() + .withKeyspace(CqlIdentifier.fromCql("ks")) + .withEvents(eventTypes, eventCallback) + .build(); + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -291,6 +360,11 @@ public void should_initialize_with_keyspace() { assertThat(((Query) requestFrame.message).query).isEqualTo("USE \"ks\""); writeInboundFrame(requestFrame, new SetKeyspace("ks")); + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Register.class); + assertThat(((Register) requestFrame.message).eventTypes).containsExactly("foo", "bar"); + writeInboundFrame(requestFrame, new Ready()); + assertThat(connectFuture).isSuccess(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 52f3ecf1589..a6a81b5c7cf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.google.common.util.concurrent.Uninterruptibles; @@ -48,9 +49,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.times; public class ChannelPoolTest { @@ -75,7 +74,7 @@ public void setup() { Mockito.when(context.channelFactory()).thenReturn(channelFactory); channelFactoryFutures = new ArrayBlockingQueue<>(10); - Mockito.when(channelFactory.connect(eq(ADDRESS), isNull(), anyBoolean())) + Mockito.when(channelFactory.connect(eq(ADDRESS), any(DriverChannelOptions.class))) .thenAnswer( invocation -> { CompletableFuture channelFuture = new CompletableFuture<>(); From dc2dd48bb9c3f1fdfa4b85d98df79f0820325885 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Apr 2017 09:11:15 -0700 Subject: [PATCH 018/742] Set up metadata infrastructure This commit still contains a lot of work in progress, but it adds the beginning of the initialization sequence, where the driver connects to the cluster and retrieves the list of nodes. The control connection should be close to its final implementation, and is fully tested. --- .../api/core/AllNodesFailedException.java | 62 +++ .../datastax/oss/driver/api/core/Cluster.java | 31 ++ .../oss/driver/api/core/ClusterBuilder.java | 130 +++++ .../oss/driver/api/core/DriverException.java | 30 ++ .../api/core/NoNodeAvailableException.java | 30 ++ .../addresstranslation/AddressTranslator.java | 47 ++ .../PassThroughAddressTranslator.java} | 29 +- .../api/core/config/CoreDriverOption.java | 23 +- .../api/core/config/DriverConfigProfile.java | 5 + .../api/core/context/DriverContext.java | 8 +- .../loadbalancing/LoadBalancingPolicy.java | 56 ++ .../api/core/loadbalancing/NodeDistance.java | 42 ++ .../RoundRobinLoadBalancingPolicy.java | 61 +++ .../driver/api/core/metadata/Metadata.java | 35 ++ .../oss/driver/api/core/metadata/Node.java | 46 ++ .../driver/api/core/metadata/NodeState.java | 58 ++ .../driver/internal/core/ContactPoints.java | 87 +++ .../driver/internal/core/DefaultCluster.java | 113 ++++ .../adminrequest/AdminRequestHandler.java | 184 +++++++ .../core/adminrequest/AdminResult.java | 154 ++++++ .../core/adminrequest/codec/BlobCodec.java | 31 ++ .../core/adminrequest/codec/BooleanCodec.java | 42 ++ .../core/adminrequest/codec/DoubleCodec.java | 41 ++ .../core/adminrequest/codec/InetCodec.java | 41 ++ .../core/adminrequest/codec/IntCodec.java | 45 ++ .../core/adminrequest/codec/ListCodec.java | 80 +++ .../core/adminrequest/codec/MapCodec.java | 97 ++++ .../core/adminrequest/codec/SetCodec.java | 78 +++ .../core/adminrequest/codec/TypeCodec.java | 30 ++ .../core/adminrequest/codec/TypeCodecs.java | 43 ++ .../core/adminrequest/codec/UuidCodec.java | 42 ++ .../core/adminrequest/codec/VarcharCodec.java | 40 ++ .../core/adminrequest/package-info.java | 34 ++ .../internal/core/channel/ChannelEvent.java | 31 +- .../internal/core/channel/ChannelFactory.java | 11 +- .../channel/ClusterNameMismatchException.java | 3 +- .../internal/core/channel/DriverChannel.java | 23 +- .../core/channel/InFlightHandler.java | 31 +- .../typesafe/TypesafeDriverConfigProfile.java | 11 + .../core/context/DefaultDriverContext.java | 108 +++- .../core/context/DefaultNettyOptions.java | 8 +- .../core/context/InternalDriverContext.java | 12 + .../internal/core/context/NettyOptions.java | 13 +- .../core/control/ControlConnection.java | 324 +++++++++++ .../core/metadata/DefaultMetadata.java | 137 +++++ .../internal/core/metadata/DefaultNode.java | 66 +++ .../core/metadata/DefaultNodeInfo.java | 152 ++++++ .../core/metadata/DefaultTopologyMonitor.java | 125 +++++ .../internal/core/metadata/DistanceEvent.java | 53 ++ .../metadata/LoadBalancingPolicyWrapper.java | 80 +++ .../core/metadata/MetadataManager.java | 126 +++++ .../core/metadata/NodeStateEvent.java | 52 ++ .../core/metadata/NodeStateManager.java | 204 +++++++ .../core/metadata/SchemaElementKind.java | 54 ++ .../internal/core/metadata/TopologyEvent.java | 165 ++++++ .../core/metadata/TopologyMonitor.java | 112 ++++ .../internal/core/pool/ChannelPool.java | 50 +- .../core/protocol/ByteBufPrimitiveCodec.java | 16 - .../util/concurrent/BlockingOperation.java | 66 +++ .../util/concurrent/CompletableFutures.java | 27 +- .../core/util/concurrent/Debouncer.java | 126 +++++ .../core/util/concurrent/Reconnection.java | 80 ++- .../core/util/concurrent/RunOrSchedule.java | 11 + .../util/concurrent/UncaughtExceptions.java | 2 +- core/src/main/resources/reference.conf | 63 ++- .../driver/internal/core/TestResponses.java | 1 + .../channel/ChannelFactoryEventsTest.java | 75 --- .../core/channel/DriverChannelTest.java | 2 +- .../channel/MockChannelFactoryHelper.java | 175 ++++++ .../core/channel/ProtocolInitHandlerTest.java | 4 +- .../control/ControlConnectionEventsTest.java | 151 ++++++ .../core/control/ControlConnectionTest.java | 387 ++++++++++++++ .../control/ControlConnectionTestBase.java | 165 ++++++ .../core/metadata/NodeStateManagerTest.java | 503 ++++++++++++++++++ .../internal/core/pool/ChannelPoolTest.java | 479 +++++++++++------ .../core/util/concurrent/DebouncerTest.java | 165 ++++++ .../util/concurrent/ReconnectionTest.java | 62 ++- core/src/test/resources/logback-test.xml | 2 +- 78 files changed, 6060 insertions(+), 328 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java rename core/src/{test/java/Tmp.java.bak => main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java} (52%) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/NodeDistance.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/package-info.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java delete mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java new file mode 100644 index 00000000000..6241d71296d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.util.Map; + +/** + * Thrown when a query failed on all the coordinators it was tried on. This exception may wrap + * multiple errors, use {@link #getErrors()} to inspect the individual problem on each node. + */ +public class AllNodesFailedException extends DriverException { + public static AllNodesFailedException fromErrors(Map errors) { + if (errors == null || errors.size() == 0) { + return new NoNodeAvailableException(); + } else { + return new AllNodesFailedException(ImmutableMap.copyOf(errors)); + } + } + + private final Map errors; + + protected AllNodesFailedException(String message, Map errors) { + super(message); + this.errors = errors; + } + + private AllNodesFailedException(Map errors) { + this(buildMessage(errors), errors); + } + + private static String buildMessage(Map errors) { + int limit = Math.min(errors.size(), 3); + String details = + Joiner.on(", ").withKeyValueSeparator(": ").join(Iterables.limit(errors.entrySet(), limit)); + + return String.format( + "All %d node tried for the query failed (showing first %d, use getErrors() for more: %s)", + errors.size(), limit, details); + } + + /** The details of the individual error on each node. */ + public Map getErrors() { + return errors; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java new file mode 100644 index 00000000000..0b6823a789f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Metadata; + +/** An instance of the driver, that connects to a Cassandra cluster. */ +public interface Cluster { + /** Returns a builder to create a new instance of the default implementation. */ + static ClusterBuilder builder() { + return new ClusterBuilder(); + } + + Metadata getMetadata(); + + DriverContext getContext(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java new file mode 100644 index 00000000000..36f89297176 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.ContactPoints; +import com.datastax.oss.driver.internal.core.DefaultCluster; +import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.typesafe.config.ConfigFactory; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +/** Helper class to build an instance of the default {@link Cluster} implementation. */ +public class ClusterBuilder { + private DriverConfig config; + private Set programmaticContactPoints = Collections.emptySet(); + + /** + * Sets the configuration to use. + * + *

If you don't call this method, the builder will try to build an instance of the default + * implementation, based on the Typesafe config library. More precisely: + * + *

    + *
  • configuration properties are loaded and merged from the following (first-listed are + * higher priority): + *
      + *
    • system properties + *
    • {@code application.conf} (all resources on classpath with this name) + *
    • {@code application.json} (all resources on classpath with this name) + *
    • {@code application.properties} (all resources on classpath with this name) + *
    • {@code reference.conf} (all resources on classpath with this name) + *
    + * + *
  • the resulting configuration is expected to contain a {@code datastax-java-driver} + * section. + *
  • that section is validated against the {@link CoreDriverOption core driver options}. + *
+ * + * The core driver JAR includes a {@code reference.conf} file with sensible defaults for all + * mandatory options, except the contact points. + * + * @see Typesafe config's + * standard loading behavior + */ + public ClusterBuilder withConfig(DriverConfig config) { + this.config = config; + return this; + } + + private static DriverConfig defaultConfig() { + return new TypeSafeDriverConfig( + ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + } + + /** + * Sets the contact points to use for the initial connection to the cluster. + * + *

These are addresses of Cassandra nodes that the driver uses to discover the cluster + * topology. Only one contact point is required (the driver will retrieve the address of the other + * nodes automatically), but it is usually a good idea to provide more than one contact point, + * because if that single contact point is unavailable, the driver cannot initialize itself + * correctly. + * + *

Contact points can also be provided statically in the configuration. If both are specified, + * they will be merged. + * + *

Contrary to the configuration, DNS names with multiple A-records will not be handled here. + * If you need that, call {@link java.net.InetAddress#getAllByName(String)} before calling this + * method. + */ + public ClusterBuilder withContactPoints(Set contactPoints) { + this.programmaticContactPoints = contactPoints; + return this; + } + + /** + * Creates the cluster with the options set by this builder. + * + * @return a completion stage that completes with the cluster when it is fully initialized. + */ + public CompletionStage buildAsync() { + DriverConfig config = buildIfNull(this.config, ClusterBuilder::defaultConfig); + + DriverConfigProfile defaultConfig = config.defaultProfile(); + List configContactPoints = + defaultConfig.isDefined(CoreDriverOption.CONTACT_POINTS) + ? defaultConfig.getStringList(CoreDriverOption.CONTACT_POINTS) + : Collections.emptyList(); + + Set contactPoints = + ContactPoints.merge(programmaticContactPoints, configContactPoints); + + InternalDriverContext context = new DefaultDriverContext(config); + return DefaultCluster.init(context, contactPoints); + } + + /** Convenience method to call {@link #buildAsync()} and block on the result. */ + public Cluster build() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(buildAsync().toCompletableFuture()); + } + + private static T buildIfNull(T value, Supplier builder) { + return (value == null) ? builder.get() : value; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java new file mode 100644 index 00000000000..57d793148a9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +public class DriverException extends RuntimeException { + public DriverException(String message) { + super(message); + } + + public DriverException(String message, Throwable cause) { + super(message, cause); + } + + public DriverException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java new file mode 100644 index 00000000000..0ffdd8ef567 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import java.util.Collections; + +/** + * Specialization of {@code AllNodesFailedException} when no coordinators were tried. + * + *

This can happen if all nodes are down, or if all the contact points provided at startup were + * invalid. + */ +public class NoNodeAvailableException extends AllNodesFailedException { + public NoNodeAvailableException() { + super("No node was available to execute the query", Collections.emptyMap()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java new file mode 100644 index 00000000000..c73d36c9d2b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.addresstranslation; + +import java.net.InetSocketAddress; + +/** + * Translates IP addresses received from Cassandra nodes into locally queriable addresses. + * + *

The driver auto-detects new Cassandra nodes added to the cluster through server side pushed + * notifications and system table queries. For each node, the address the driver will receive will + * correspond to the address set as {@code broadcast_rpc_address} in the node's YAML file. In most + * cases, this is the correct address to use by the driver, and that is what is used by default. + * However, sometimes the addresses received through this mechanism will either not be reachable + * directly by the driver, or should not be the preferred address to use to reach the node (for + * instance, the {@code broadcast_rpc_address} set on Cassandra nodes might be a private IP, but + * some clients may have to use a public IP, or go through a router to reach that node). This + * interface addresses such cases, by allowing to translate an address as sent by a Cassandra node + * into another address to be used by the driver for connection. + * + *

The contact point addresses provided at driver initialization are considered translated + * already; in other words, they will be used as-is, without being processed by this component. + */ +public interface AddressTranslator { + + /** + * Translates an address reported by a Cassandra node into the address that the driver will use to + * connect. + */ + InetSocketAddress translate(InetSocketAddress address); + + /** Called when the associated driver instance shuts down. */ + void close(); +} diff --git a/core/src/test/java/Tmp.java.bak b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java similarity index 52% rename from core/src/test/java/Tmp.java.bak rename to core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java index f8908dd49cf..35363144321 100644 --- a/core/src/test/java/Tmp.java.bak +++ b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java @@ -13,22 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import com.datastax.oss.driver.api.core.Cluster; -import com.google.common.collect.ImmutableSet; +package com.datastax.oss.driver.api.core.addresstranslation; + +import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -public class Tmp { - public static void main(String[] args) throws InterruptedException, ExecutionException { - Cluster cluster = - Cluster.builder() - .withContactPoints(ImmutableSet.of(new InetSocketAddress("127.0.0.1", 9042))) - .build(); +/** An address translator that always returns the same address unchanged. */ +public class PassThroughAddressTranslator implements AddressTranslator { + + public PassThroughAddressTranslator(@SuppressWarnings("unused") DriverContext context) { + // nothing to do + } - System.out.println("Cluster initialized"); + @Override + public InetSocketAddress translate(InetSocketAddress address) { + return address; + } - System.out.println(cluster.getMetadata().getNodes()); - TimeUnit.HOURS.sleep(1); + @Override + public void close() { + // nothing to do } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 59260b0f64d..206b3030061 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -15,7 +15,14 @@ */ package com.datastax.oss.driver.api.core.config; +/** + * Built-in driver options for the core driver. + * + *

Refer to {@code reference.conf} in the driver codebase for a full description of each option. + */ public enum CoreDriverOption implements DriverOption { + CONTACT_POINTS("contact-points", false), + PROTOCOL_VERSION("protocol.version", false), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), @@ -25,9 +32,16 @@ public enum CoreDriverOption implements DriverOption { CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), - RECONNECTION_POLICY_CLASS("connection.reconnection-policy.provider-class", true), - RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection-policy.config.base-delay", true), - RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection-policy.config.max-delay", true), + CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), + CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), + + LOAD_BALANCING_POLICY_CLASS("load-balancing.policy-class", true), + + RECONNECTION_POLICY_CLASS("connection.reconnection.policy-class", true), + RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection.config.base-delay", true), + RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), + + ADDRESS_TRANSLATOR_CLASS("address-translation.translator-class", true), AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false), @@ -35,6 +49,9 @@ public enum CoreDriverOption implements DriverOption { SSL_FACTORY_CLASS("ssl.factory-class", false), SSL_CONFIG_CIPHER_SUITES("ssl.config.cipher-suites", false), + + METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), + METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 7541677fb00..e4a12fab8d0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.config; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -26,6 +27,8 @@ public interface DriverConfigProfile { boolean isDefined(DriverOption option); + boolean getBoolean(DriverOption option); + int getInt(DriverOption option); String getString(DriverOption option); @@ -34,5 +37,7 @@ public interface DriverConfigProfile { long getBytes(DriverOption option); + Duration getDuration(DriverOption option); + long getDuration(DriverOption option, TimeUnit targetUnit); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index dc7cd93ffca..c0267622119 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -15,21 +15,25 @@ */ package com.datastax.oss.driver.api.core.context; +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import java.util.Optional; /** Holds common components that are shared throughout a driver instance. */ public interface DriverContext { - /** The driver's configuration. */ DriverConfig config(); - /** The configured reconnection policy. */ + LoadBalancingPolicy loadBalancingPolicy(); + ReconnectionPolicy reconnectionPolicy(); + AddressTranslator addressTranslator(); + /** The authentication provider, if authentication was configured. */ Optional authProvider(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java new file mode 100644 index 00000000000..3edfee6295a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.loadbalancing; + +import com.datastax.oss.driver.api.core.metadata.Node; +import java.util.Queue; +import java.util.Set; + +/** Decides which Cassandra nodes to contact for each query. */ +public interface LoadBalancingPolicy { + + /** + * Initializes this policy with the nodes discovered during driver initialization. + * + *

This method is guaranteed to be called exactly once per instance, and before any other + * method in this class. + */ + void init(Set nodes, DistanceReporter distanceReporter); + + /** + * Returns the coordinators to use for a new query. + * + *

Each new query will call this method, and try the returned nodes sequentially. + * + * @return the list of coordinators to try. This must be a concurrent queue; {@link + * java.util.concurrent.ConcurrentLinkedQueue} is a good choice. + */ + Queue newQueryPlan(/*TODO keyspace, statement*/ ); + + // TODO node state methods (onUp, etc.) + // TODO way to emit distance events + + /** + * Invoked at cluster shutdown. This gives the policy the opportunity to perform some cleanup, for + * instance stop threads that it might have started. + */ + void close(); + + /** An object that the policy uses to push the decisions it makes about node distances. */ + interface DistanceReporter { + void setDistance(Node node, NodeDistance distance); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/NodeDistance.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/NodeDistance.java new file mode 100644 index 00000000000..e8cadf11e1c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/NodeDistance.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.loadbalancing; +/** + * Determines how the driver will manage connections to a Cassandra node. + * + *

The distance is assigned by a {@link LoadBalancingPolicy}. + */ +public enum NodeDistance { + /** + * An "active" distance that, indicates that the driver should maintain connections to the node; + * it also marks it as "preferred", meaning that the number or capacity of the connections may be + * higher, and that the node may also have priority for some tasks (for example, being chosen as + * the control host). + */ + LOCAL, + /** + * An "active" distance that, indicates that the driver should maintain connections to the node; + * it also marks it as "less preferred", meaning that the number or capacity of the connections + * may be lower, and that other nodes may have a higher priority for some tasks (for example, + * being chosen as the control host). + */ + REMOTE, + /** + * An "inactive" distance, that indicates that the driver will not open any connection to the + * node. + */ + IGNORED, +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java new file mode 100644 index 00000000000..c17bcd0e9b1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.loadbalancing; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableList; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntUnaryOperator; + +public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { + + private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; + + private final AtomicInteger startIndex = new AtomicInteger(); + private ImmutableList nodes; + + public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { + // nothing to do + } + + @Override + public void init(Set nodes, DistanceReporter distanceReporter) { + // TODO handle node states (this is a temporary impl. to kickstart things) + this.nodes = ImmutableList.copyOf(nodes); + for (Node node : this.nodes) { + distanceReporter.setDistance(node, NodeDistance.LOCAL); + } + } + + @Override + public Queue newQueryPlan() { + int myStartIndex = startIndex.getAndUpdate(INCREMENT); + ConcurrentLinkedQueue plan = new ConcurrentLinkedQueue<>(); + for (int i = 0; i < nodes.size(); i++) { + plan.offer(nodes.get((myStartIndex + i) % nodes.size())); + } + return plan; + } + + @Override + public void close() { + // nothing to do + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java new file mode 100644 index 00000000000..75ff2e3b5ff --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import java.net.InetSocketAddress; +import java.util.Map; + +/** + * The metadata of the Cassandra cluster that this driver instance is connected to. + * + *

Updates to this object are guaranteed to be atomic: the node list, schema, and token metadata + * are immutable, and will always be consistent for a given metadata instance. The node instances + * are the only mutable objects in the hierarchy, and some of their fields will be modified + * dynamically (in particular the node state). + */ +public interface Metadata { + /** + * The nodes known to the driver, indexed by the address that it uses to connect to them. This + * might include nodes that are currently viewed as down, or ignored by the load balancing policy. + */ + Map getNodes(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java new file mode 100644 index 00000000000..d7e8541433e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import java.net.InetSocketAddress; + +/** Metadata about a Cassandra node in the cluster. */ +public interface Node { + /** + * The address that the driver uses to connect to the node. This is the node's broadcast RPC + * address, transformed by the address translator if one is configured. + * + *

The driver also uses this to uniquely identify a node. + */ + InetSocketAddress getConnectAddress(); + + NodeState getState(); + + /** + * @return the total number of active connections currently open by this driver instance to the + * node. This can be either pooled connections, or the control connection. + */ + int getOpenConnections(); + + /** + * @return whether the driver is currently trying to reconnect to this node. That is, whether the + * active connection count is below the value mandated by the configuration. This does not + * mean that the node is down, there could be some active connections but not enough. + */ + boolean isReconnecting(); + + // TODO expose distance (set by LBP) +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java new file mode 100644 index 00000000000..e8b033e9f69 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +/** The state of a node, as viewed from the driver. */ +public enum NodeState { + /** + * The driver has never tried to connect to the node, nor received any topology events about it. + * + *

This happens if your load balancing policy ignores some of the nodes. Since the driver does + * not connect to them, the only way it can assess their states is from topology events. + */ + UNKNOWN, + /** + * A node is considered up in either of the following situations: + * + *

    + *
  • the driver has at least one active connection to the node. + *
  • the driver is not actively trying to connect to the node (because it's ignored by the + * load balancing policy), but it has received a topology event indicating that the node is + * up. + *
+ */ + UP, + /** + * A node is considered down in either of the following situations: + * + *
    + *
  • the driver has lost all connections to the node (and is currently trying to reconnect). + *
  • the driver is not actively trying to connect to the node (because it's ignored by the + * load balancing policy), but it has received a topology event indicating that the node is + * down. + *
+ */ + DOWN, + /** + * The node was forced down externally, the driver will never try to reconnect to it, whatever the + * load balancing policy says. + * + *

This is used for edge error cases, for example when the driver detects that it's trying to + * connect to a node that does not belong to the Cassandra cluster (e.g. a wrong address was + * provided in the contact points). + */ + FORCED_DOWN, +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java new file mode 100644 index 00000000000..308bb66a8f3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Utility class to handle the initial contact points passed to the driver. */ +public class ContactPoints { + private static final Logger LOG = LoggerFactory.getLogger(ContactPoints.class); + + public static Set merge( + Set programmaticContactPoints, List configContactPoints) { + + Set result = Sets.newHashSet(programmaticContactPoints); + for (String spec : configContactPoints) { + for (InetSocketAddress address : extract(spec)) { + boolean wasNew = result.add(address); + if (!wasNew) { + LOG.warn("Duplicate contact point {}", address); + } + } + } + return result; + } + + private static Set extract(String spec) { + List hostAndPort = Splitter.on(":").splitToList(spec); + if (hostAndPort.size() != 2) { + LOG.warn("Ignoring invalid contact point {} (expecting host:port)", spec); + return Collections.emptySet(); + } + String host = hostAndPort.get(0); + int port; + try { + port = Integer.parseInt(hostAndPort.get(1)); + } catch (NumberFormatException e) { + LOG.warn( + "Ignoring invalid contact point {} (expecting a number, got {})", + spec, + hostAndPort.get(1)); + return Collections.emptySet(); + } + try { + InetAddress[] inetAddresses = InetAddress.getAllByName(host); + if (inetAddresses.length > 1) { + LOG.info( + "Contact point {} resolves to multiple addresses, will use them all ({})", + spec, + Arrays.deepToString(inetAddresses)); + } + Set result = new HashSet<>(); + for (InetAddress inetAddress : inetAddresses) { + result.add(new InetSocketAddress(inetAddress, port)); + } + return result; + } catch (UnknownHostException e) { + LOG.warn("Ignoring invalid contact point {} (unknown host {})", spec, host); + return Collections.emptySet(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java new file mode 100644 index 00000000000..148451d5038 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import io.netty.util.concurrent.EventExecutor; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultCluster implements Cluster { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultCluster.class); + + public static CompletableFuture init( + InternalDriverContext context, Set contactPoints) { + DefaultCluster cluster = new DefaultCluster(context, contactPoints); + return cluster.init(); + } + + private final InternalDriverContext context; + private final EventExecutor adminExecutor; + private final SingleThreaded singleThreaded; + private final MetadataManager metadataManager; + + private DefaultCluster(InternalDriverContext context, Set contactPoints) { + this.context = context; + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.singleThreaded = new SingleThreaded(context, contactPoints); + this.metadataManager = context.metadataManager(); + } + + private CompletableFuture init() { + RunOrSchedule.on(adminExecutor, singleThreaded::init); + return singleThreaded.initFuture; + } + + @Override + public Metadata getMetadata() { + return metadataManager.getMetadata(); + } + + @Override + public DriverContext getContext() { + return context; + } + + private class SingleThreaded { + + private final InternalDriverContext context; + private final Set initialContactPoints; + private final CompletableFuture initFuture = new CompletableFuture<>(); + private boolean initWasCalled; + + private SingleThreaded(InternalDriverContext context, Set contactPoints) { + this.context = context; + this.initialContactPoints = contactPoints; + } + + private void init() { + assert adminExecutor.inEventLoop(); + if (initWasCalled) { + return; + } + initWasCalled = true; + + new NodeStateManager(context); + + // If any contact points were provided, store them in the metadata right away (the + // control connection will need them if it has to initialize) + MetadataManager metadataManager = context.metadataManager(); + metadataManager + .addContactPoints(initialContactPoints) + // Then initialize the topology monitor + .thenCompose(v -> context.topologyMonitor().init()) + // If that succeeds, Cluster init is considered successful + .thenAccept( + v -> { + initFuture.complete(DefaultCluster.this); + + // Launch a full refresh asynchronously + metadataManager.refreshNodes(); + // TODO schedule full schema refresh + }) + .exceptionally( + error -> { + initFuture.completeExceptionally(error); + return null; + }); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java new file mode 100644 index 00000000000..9cd08d35adf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Prepare; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.query.QueryOptions; +import com.datastax.oss.protocol.internal.response.result.Prepared; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.google.common.collect.Maps; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ScheduledFuture; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Handles the lifecyle of an admin request. */ +public class AdminRequestHandler implements ResponseCallback { + private static final Logger LOG = LoggerFactory.getLogger(AdminRequestHandler.class); + + public static AdminRequestHandler query( + DriverChannel channel, + String query, + Map parameters, + Duration timeout, + int pageSize) { + Query message = + new Query( + query, + buildQueryOptions(pageSize, serialize(parameters, channel.protocolVersion()), null)); + return new AdminRequestHandler(channel, message, timeout); + } + + public static AdminRequestHandler query( + DriverChannel channel, String query, Duration timeout, int pageSize) { + return query(channel, query, Collections.emptyMap(), timeout, pageSize); + } + + public static AdminRequestHandler prepare(DriverChannel channel, String query, Duration timeout) { + return new AdminRequestHandler(channel, new Prepare(query), timeout); + } + + private final DriverChannel channel; + private final Message message; + private final Duration timeout; + private final CompletableFuture result = new CompletableFuture<>(); + + // This is only ever accessed on the channel's event loop, so it doesn't need to be volatile + private ScheduledFuture timeoutFuture; + + private AdminRequestHandler(DriverChannel channel, Message message, Duration timeout) { + this.channel = channel; + this.message = message; + this.timeout = timeout; + } + + public CompletionStage start() { + if (LOG.isDebugEnabled()) { + if (message instanceof Query) { + Query query = (Query) this.message; + LOG.debug("Executing query '{}' with values {}", query.query, query.options.namedValues); + } else if (message instanceof Prepare) { + LOG.debug("Preparing {}", ((Prepare) message).cqlQuery); + } + } + channel.write(message, false, Frame.NO_PAYLOAD, this).addListener(this::onWriteComplete); + return result; + } + + private void onWriteComplete(Future future) { + if (future.isSuccess()) { + timeoutFuture = + channel.eventLoop().schedule(this::fireTimeout, timeout.toNanos(), TimeUnit.NANOSECONDS); + timeoutFuture.addListener(UncaughtExceptions::log); + } else { + result.completeExceptionally(future.cause()); + } + } + + private void fireTimeout() { + result.completeExceptionally(new DriverException("Query timed out")); + } + + @Override + public void onFailure(Throwable error) { + timeoutFuture.cancel(true); + result.completeExceptionally(error); + } + + @Override + public void onResponse(Frame responseFrame) { + timeoutFuture.cancel(true); + Message message = responseFrame.message; + if (message instanceof Rows) { + Rows rows = (Rows) message; + ByteBuffer pagingState = rows.metadata.pagingState; + AdminRequestHandler nextHandler = (pagingState == null) ? null : this.copy(pagingState); + result.complete(new AdminResult(rows, nextHandler, channel.protocolVersion())); + } else if (message instanceof Prepared) { + // Internal prepares are only "reprepare on up" types of queries, where we only care about + // success, not the actual result, so this is good enough: + result.complete(null); + } else { + result.completeExceptionally( + new DriverException("Unexpected response to control query: " + message)); + } + } + + private AdminRequestHandler copy(ByteBuffer pagingState) { + assert message instanceof Query; + Query current = (Query) this.message; + QueryOptions currentOptions = current.options; + QueryOptions newOptions = + buildQueryOptions(currentOptions.pageSize, currentOptions.namedValues, pagingState); + return new AdminRequestHandler(channel, new Query(current.query, newOptions), timeout); + } + + private static QueryOptions buildQueryOptions( + int pageSize, Map serialize, ByteBuffer pagingState) { + return new QueryOptions( + ProtocolConstants.ConsistencyLevel.ONE, + Collections.emptyList(), + serialize, + false, + pageSize, + pagingState, + ProtocolConstants.ConsistencyLevel.SERIAL, + Long.MIN_VALUE); + } + + private static Map serialize( + Map parameters, ProtocolVersion protocolVersion) { + Map result = Maps.newHashMapWithExpectedSize(parameters.size()); + for (Map.Entry entry : parameters.entrySet()) { + result.put(entry.getKey(), serialize(entry.getValue(), protocolVersion)); + } + return result; + } + + private static ByteBuffer serialize(Object parameter, ProtocolVersion protocolVersion) { + if (parameter instanceof String) { + return TypeCodecs.VARCHAR.encode((String) parameter, protocolVersion); + } else if (parameter instanceof InetAddress) { + return TypeCodecs.INET.encode((InetAddress) parameter, protocolVersion); + } else if (parameter instanceof List && ((List) parameter).get(0) instanceof String) { + @SuppressWarnings("unchecked") + List l = (List) parameter; + return TypeCodecs.LIST_OF_VARCHAR.encode(l, protocolVersion); + } else { + throw new IllegalArgumentException( + "Unsupported variable type for admin query: " + parameter.getClass()); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java new file mode 100644 index 00000000000..0f7a9bc0e22 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodec; +import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableMap; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +public class AdminResult implements Iterable { + + private final Queue> data; + private final Map columnSpecs; + private final AdminRequestHandler nextHandler; + private final ProtocolVersion protocolVersion; + + public AdminResult(Rows rows, AdminRequestHandler nextHandler, ProtocolVersion protocolVersion) { + this.data = rows.data; + + ImmutableMap.Builder columnSpecsBuilder = ImmutableMap.builder(); + for (ColumnSpec spec : rows.metadata.columnSpecs) { + columnSpecsBuilder.put(spec.name, spec); + } + // Admin queries are simple selects only, so there are no duplicate names (if that ever + // changes, build() will fail and we'll have to do things differently) + this.columnSpecs = columnSpecsBuilder.build(); + + this.nextHandler = nextHandler; + this.protocolVersion = protocolVersion; + } + + /** This consumes the result's data and can be called only once. */ + @Override + public Iterator iterator() { + return new AbstractIterator() { + @Override + protected Row computeNext() { + List rowData = data.poll(); + return (rowData == null) ? endOfData() : new Row(columnSpecs, rowData, protocolVersion); + } + }; + } + + public boolean hasNextPage() { + return nextHandler != null; + } + + public CompletionStage nextPage() { + return (nextHandler == null) + ? CompletableFutures.failedFuture( + new AssertionError("No next page, use hasNextPage() before you call this method")) + : nextHandler.start(); + } + + public static class Row { + private final Map columnSpecs; + private final List data; + private final ProtocolVersion protocolVersion; + + private Row( + Map columnSpecs, + List data, + ProtocolVersion protocolVersion) { + this.columnSpecs = columnSpecs; + this.data = data; + this.protocolVersion = protocolVersion; + } + + public Boolean getBoolean(String columnName) { + return get(columnName, TypeCodecs.BOOLEAN); + } + + public Integer getInt(String columnName) { + return get(columnName, TypeCodecs.INT); + } + + public Double getDouble(String columnName) { + return get(columnName, TypeCodecs.DOUBLE); + } + + public String getVarchar(String columnName) { + return get(columnName, TypeCodecs.VARCHAR); + } + + public UUID getUuid(String columnName) { + return get(columnName, TypeCodecs.UUID); + } + + public ByteBuffer getBlob(String columnName) { + return get(columnName, TypeCodecs.BLOB); + } + + public InetAddress getInet(String columnName) { + return get(columnName, TypeCodecs.INET); + } + + public List getListOfVarchar(String columnName) { + return get(columnName, TypeCodecs.LIST_OF_VARCHAR); + } + + public Set getSetOfVarchar(String columnName) { + return get(columnName, TypeCodecs.SET_OF_VARCHAR); + } + + public Map getMapOfVarcharToVarchar(String columnName) { + return get(columnName, TypeCodecs.MAP_OF_VARCHAR_TO_VARCHAR); + } + + public Map getMapOfVarcharToBlob(String columnName) { + return get(columnName, TypeCodecs.MAP_OF_VARCHAR_TO_BLOB); + } + + public Map getMapOfUuidToBlob(String columnName) { + return get(columnName, TypeCodecs.MAP_OF_UUID_TO_BLOB); + } + + private T get(String columnName, TypeCodec codec) { + // Minimal checks here: this is for internal use, so the caller should know what they're + // doing + if (!columnSpecs.containsKey(columnName)) { + return null; + } else { + int index = columnSpecs.get(columnName).index; + return codec.decode(data.get(index), protocolVersion); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java new file mode 100644 index 00000000000..0e95123d101 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +public class BlobCodec implements TypeCodec { + @Override + public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) { + return (value == null) ? null : value.duplicate(); + } + + @Override + public ByteBuffer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null) ? null : bytes.duplicate(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java new file mode 100644 index 00000000000..69d1a54c6ff --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +public class BooleanCodec implements TypeCodec { + private static final ByteBuffer TRUE = ByteBuffer.wrap(new byte[] {1}); + private static final ByteBuffer FALSE = ByteBuffer.wrap(new byte[] {0}); + + @Override + public ByteBuffer encode(Boolean value, ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } else { + return value ? TRUE.duplicate() : FALSE.duplicate(); + } + } + + @Override + public Boolean decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + if (bytes == null || bytes.remaining() == 0) { + return null; + } else { + return (bytes.get(bytes.position()) == 0) ? Boolean.FALSE : Boolean.TRUE; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java new file mode 100644 index 00000000000..91b73a9426e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +public class DoubleCodec implements TypeCodec { + @Override + public ByteBuffer encode(Double value, ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } else { + ByteBuffer bytes = ByteBuffer.allocate(8); + bytes.putDouble(0, value); + return bytes; + } + } + + @Override + public Double decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + if (bytes == null || bytes.remaining() == 0) { + return null; + } else { + return bytes.getDouble(bytes.position()); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java new file mode 100644 index 00000000000..219ad6b14c2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +public class InetCodec implements TypeCodec { + + @Override + public ByteBuffer encode(InetAddress value, ProtocolVersion protocolVersion) { + return (value == null) ? null : ByteBuffer.wrap(value.getAddress()); + } + + @Override + public InetAddress decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + if (bytes == null || bytes.remaining() == 0) return null; + try { + return InetAddress.getByAddress(Bytes.getArray(bytes)); + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + "Invalid bytes for inet value, got " + bytes.remaining() + " bytes"); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java new file mode 100644 index 00000000000..544ac9c4710 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +public class IntCodec implements TypeCodec { + + @Override + public ByteBuffer encode(Integer value, ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } else { + ByteBuffer bytes = ByteBuffer.allocate(4); + bytes.putInt(0, value); + return bytes; + } + } + + @Override + public Integer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + if (bytes == null || bytes.remaining() == 0) { + return null; + } else if (bytes.remaining() != 4) { + throw new IllegalArgumentException( + "Invalid 32-bits integer value, expecting 4 bytes but got " + bytes.remaining()); + } else { + return bytes.getInt(bytes.position()); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java new file mode 100644 index 00000000000..95f540ccc87 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class ListCodec implements TypeCodec> { + + private final TypeCodec elementCodec; + + public ListCodec(TypeCodec elementCodec) { + this.elementCodec = elementCodec; + } + + @Override + public ByteBuffer encode(List value, ProtocolVersion protocolVersion) { + // An int indicating the number of elements in the list, followed by the elements. Each element + // is a byte array representing the serialized value, preceded by an int indicating its size. + if (value == null) { + return null; + } else { + int i = 0; + ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; + int toAllocate = 4; // initialize with number of elements + for (T element : value) { + if (element == null) { + throw new NullPointerException("Collection elements cannot be null"); + } + ByteBuffer encodedElement; + try { + encodedElement = elementCodec.encode(element, protocolVersion); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid type for element: " + element.getClass()); + } + encodedElements[i++] = encodedElement; + toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size + } + ByteBuffer result = ByteBuffer.allocate(toAllocate); + result.putInt(value.size()); + for (ByteBuffer encodedElement : encodedElements) { + result.putInt(encodedElement.remaining()); + result.put(encodedElement); + } + return result; + } + } + + @Override + public List decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + List result = new ArrayList<>(); + if (bytes != null && bytes.remaining() != 0) { + int size = bytes.getInt(); + for (int i = 0; i < size; i++) { + int elementSize = bytes.getInt(); + ByteBuffer encodedElement = bytes.slice(); + encodedElement.limit(elementSize); + result.add(elementCodec.decode(encodedElement, protocolVersion)); + } + } + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java new file mode 100644 index 00000000000..3d7a015a7f3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class MapCodec implements TypeCodec> { + + private final TypeCodec keyCodec; + private final TypeCodec valueCodec; + + public MapCodec(TypeCodec keyCodec, TypeCodec valueCodec) { + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; + } + + @Override + public ByteBuffer encode(Map value, ProtocolVersion protocolVersion) { + // An int indicating the number of key/value pairs in the map, followed by the pairs. Each pair + // is a byte array representing the serialized key, preceded by an int indicating its size, + // followed by the value in the same format. + if (value == null) { + return null; + } else { + int i = 0; + ByteBuffer[] encodedElements = new ByteBuffer[value.size() * 2]; + int toAllocate = 4; // initialize with number of elements + for (Map.Entry entry : value.entrySet()) { + if (entry.getValue() == null) { + throw new NullPointerException("Collection elements cannot be null"); + } + ByteBuffer encodedKey; + try { + encodedKey = keyCodec.encode(entry.getKey(), protocolVersion); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid type for key: " + entry.getKey().getClass()); + } + encodedElements[i++] = encodedKey; + toAllocate += 4 + encodedKey.remaining(); // the key preceded by its size + ByteBuffer encodedValue; + try { + encodedValue = valueCodec.encode(entry.getValue(), protocolVersion); + } catch (ClassCastException e) { + throw new IllegalArgumentException( + "Invalid type for value: " + entry.getValue().getClass()); + } + encodedElements[i++] = encodedValue; + toAllocate += 4 + encodedValue.remaining(); // the value preceded by its size + } + ByteBuffer result = ByteBuffer.allocate(toAllocate); + result.putInt(value.size()); + for (ByteBuffer encodedElement : encodedElements) { + result.putInt(encodedElement.remaining()); + result.put(encodedElement); + } + return result; + } + } + + @Override + public Map decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + Map result = new LinkedHashMap<>(); + if (bytes != null && bytes.remaining() != 0) { + int size = bytes.getInt(); + for (int i = 0; i < size; i++) { + int keySize = bytes.getInt(); + ByteBuffer encodedKey = bytes.slice(); + encodedKey.limit(keySize); + K key = keyCodec.decode(encodedKey, protocolVersion); + int valueSize = bytes.getInt(); + ByteBuffer encodedValue = bytes.slice(); + encodedValue.limit(valueSize); + V value = valueCodec.decode(encodedValue, protocolVersion); + result.put(key, value); + } + } + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java new file mode 100644 index 00000000000..fc3c960c9d9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; +import java.util.LinkedHashSet; +import java.util.Set; + +public class SetCodec implements TypeCodec> { + + private final TypeCodec elementCodec; + + public SetCodec(TypeCodec elementCodec) { + this.elementCodec = elementCodec; + } + + @Override + public ByteBuffer encode(Set value, ProtocolVersion protocolVersion) { + // An int indicating the number of elements in the set, followed by the elements. Each element + // is a byte array representing the serialized value, preceded by an int indicating its size. + if (value == null) { + return null; + } else { + int i = 0; + ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; + int toAllocate = 4; // initialize with number of elements + for (T element : value) { + if (element == null) { + throw new NullPointerException("Collection elements cannot be null"); + } + ByteBuffer encodedElement; + try { + encodedElement = elementCodec.encode(element, protocolVersion); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid type for element: " + element.getClass()); + } + encodedElements[i++] = encodedElement; + toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size + } + ByteBuffer result = ByteBuffer.allocate(toAllocate); + result.putInt(value.size()); + for (ByteBuffer encodedElement : encodedElements) { + result.putInt(encodedElement.remaining()); + result.put(encodedElement); + } + return result; + } + } + + @Override + public Set decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + Set result = new LinkedHashSet<>(); + if (bytes != null && bytes.remaining() != 0) { + int size = bytes.getInt(); + for (int i = 0; i < size; i++) { + int elementSize = bytes.getInt(); + ByteBuffer encodedElement = bytes.slice(); + encodedElement.limit(elementSize); + result.add(elementCodec.decode(encodedElement, protocolVersion)); + } + } + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java new file mode 100644 index 00000000000..6a96547a01f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * Converts query values to/from the protocol format. + * + *

TODO this will probably be wrapped or superseded by the codecs of the public query API. + */ +public interface TypeCodec { + ByteBuffer encode(T value, ProtocolVersion protocolVersion); + + T decode(ByteBuffer bytes, ProtocolVersion protocolVersion); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java new file mode 100644 index 00000000000..8b42f23163d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** The codecs we need for admin queries. */ +public class TypeCodecs { + public static final TypeCodec BOOLEAN = new BooleanCodec(); + public static final TypeCodec INT = new IntCodec(); + public static final TypeCodec DOUBLE = new DoubleCodec(); + public static final TypeCodec VARCHAR = new VarcharCodec(); + public static final TypeCodec UUID = new UuidCodec(); + public static final TypeCodec BLOB = new BlobCodec(); + public static final TypeCodec INET = new InetCodec(); + public static final TypeCodec> LIST_OF_VARCHAR = new ListCodec<>(VARCHAR); + public static final TypeCodec> SET_OF_VARCHAR = new SetCodec<>(VARCHAR); + public static final TypeCodec> MAP_OF_VARCHAR_TO_VARCHAR = + new MapCodec<>(VARCHAR, VARCHAR); + public static final TypeCodec> MAP_OF_VARCHAR_TO_BLOB = + new MapCodec<>(VARCHAR, BLOB); + public static final TypeCodec> MAP_OF_UUID_TO_BLOB = + new MapCodec<>(UUID, BLOB); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java new file mode 100644 index 00000000000..6d4052688a7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UuidCodec implements TypeCodec { + + @Override + public ByteBuffer encode(UUID value, ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } else { + ByteBuffer bytes = ByteBuffer.allocate(16); + bytes.putLong(0, value.getMostSignificantBits()); + bytes.putLong(8, value.getLeastSignificantBits()); + return bytes; + } + } + + @Override + public UUID decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : new UUID(bytes.getLong(bytes.position()), bytes.getLong(bytes.position() + 8)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java new file mode 100644 index 00000000000..5e4b1b59640 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.base.Charsets; +import java.nio.ByteBuffer; + +public class VarcharCodec implements TypeCodec { + + @Override + public ByteBuffer encode(String value, ProtocolVersion protocolVersion) { + return (value == null) ? null : ByteBuffer.wrap(value.getBytes(Charsets.UTF_8)); + } + + @Override + public String decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + if (bytes == null) { + return null; + } else if (bytes.remaining() == 0) { + return ""; + } else { + return new String(Bytes.getArray(bytes), Charsets.UTF_8); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/package-info.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/package-info.java new file mode 100644 index 00000000000..2a7e0c854bb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/package-info.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Infrastructure to execute internal requests in the driver, for example control connection + * queries, or automatic statement preparation. + * + *

This is a stripped-down version of the public API, with the bare minimum for our needs: + * + *

    + *
  • async mode only. + *
  • execution on a given channel, without retries. + *
  • {@code QUERY} and {@code PREPARE} messages only. + *
  • paging is possible, but only on the same channel. If the channel gets closed between pages, + * the query fails. + *
  • values can only be bound by name, and it is assumed that the target type can always be + * inferred unambiguously (i.e. the only integer type is {@code int}, etc). + *
  • limited result API: getters by internal name only, no custom codecs. + *
  • codecs are only implemented for the types we actually need for admin queries. + *
+ */ +package com.datastax.oss.driver.internal.core.adminrequest; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java index 6f78a6a22b4..06b04285739 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java @@ -18,14 +18,36 @@ import java.net.SocketAddress; import java.util.Objects; -/** An event to notify other driver components when a channel has been opened or closed. */ +/** Events relating to driver channels. */ public class ChannelEvent { public enum Type { OPENED, - CLOSED + CLOSED, + RECONNECTION_STARTED, + RECONNECTION_STOPPED + } + + public static ChannelEvent channelOpened(SocketAddress address) { + return new ChannelEvent(Type.OPENED, address); + } + + public static ChannelEvent channelClosed(SocketAddress address) { + return new ChannelEvent(Type.CLOSED, address); + } + + public static ChannelEvent reconnectionStarted(SocketAddress address) { + return new ChannelEvent(Type.RECONNECTION_STARTED, address); + } + + public static ChannelEvent reconnectionStopped(SocketAddress address) { + return new ChannelEvent(Type.RECONNECTION_STOPPED, address); } public final Type type; + /** + * We use SocketAddress because some of our tests use the local Netty transport, but in production + * it will always be InetSocketAddress. + */ public final SocketAddress address; public ChannelEvent(Type type, SocketAddress address) { @@ -49,4 +71,9 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(type, address); } + + @Override + public String toString() { + return "ChannelEvent(" + type + ", " + address + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 88cdab8b21a..7b8b4f6af94 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.internal.core.channel.ChannelEvent.Type; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; @@ -31,6 +30,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -120,7 +120,8 @@ private void connect( if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); DriverChannel driverChannel = - new DriverChannel(channel, context.writeCoalescer(), availableIdsHolder); + new DriverChannel( + channel, context.writeCoalescer(), availableIdsHolder, currentVersion); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -130,11 +131,6 @@ private void connect( ChannelFactory.this.clusterName = driverChannel.getClusterName(); } resultFuture.complete(driverChannel); - - context.eventBus().fire(new ChannelEvent(Type.OPENED, address)); - channel - .closeFuture() - .addListener(f -> context.eventBus().fire(new ChannelEvent(Type.CLOSED, address))); } else { Throwable error = connectFuture.cause(); if (error instanceof UnsupportedProtocolVersionException && isNegotiating) { @@ -184,7 +180,6 @@ protected void initChannel(Channel channel) throws Exception { int maxRequestsPerConnection = defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); - // TODO SSL InFlightHandler inFlightHandler = new InFlightHandler( protocolVersion, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java index 989dc35cc06..2837593d9a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -43,7 +43,8 @@ public ClusterNameMismatchException( SocketAddress address, String actualClusterName, String expectedClusterName) { super( String.format( - "Host %s reports cluster name '%s' that doesn't match our cluster name '%s'.", + "Node %s reports cluster name '%s' that doesn't match our cluster name '%s'. " + + "It will be forced down.", address, actualClusterName, expectedClusterName)); this.address = address; this.expectedClusterName = expectedClusterName; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 2904a25a554..6f3cc550f54 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -16,12 +16,16 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoop; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,14 +46,19 @@ public class DriverChannel { private final Channel channel; private final WriteCoalescer writeCoalescer; private final AvailableIdsHolder availableIdsHolder; + private final ProtocolVersion protocolVersion; private final AtomicBoolean closing = new AtomicBoolean(); private final AtomicBoolean forceClosing = new AtomicBoolean(); DriverChannel( - Channel channel, WriteCoalescer writeCoalescer, AvailableIdsHolder availableIdsHolder) { + Channel channel, + WriteCoalescer writeCoalescer, + AvailableIdsHolder availableIdsHolder, + ProtocolVersion protocolVersion) { this.channel = channel; this.writeCoalescer = writeCoalescer; this.availableIdsHolder = availableIdsHolder; + this.protocolVersion = protocolVersion; } /** @@ -111,6 +120,18 @@ public int availableIds() { return (availableIdsHolder == null) ? -1 : availableIdsHolder.value; } + public EventLoop eventLoop() { + return channel.eventLoop(); + } + + public ProtocolVersion protocolVersion() { + return protocolVersion; + } + + public SocketAddress address() { + return channel.remoteAddress(); + } + /** * Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will * be allowed to complete before the underlying channel is closed. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 57caaebbba8..1dd120b7f3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -135,7 +135,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception LOG.debug("Received event {} but no callback was registered", event); } else { LOG.debug("Received event {}, notifying callback", event); - eventCallback.onEvent(event); + try { + eventCallback.onEvent(event); + } catch (Throwable t) { + LOG.warn("Unexpected error while invoking event handler", t); + } } } else { ResponseCallback responseCallback = inFlight.get(streamId); @@ -143,7 +147,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (!responseCallback.holdStreamId()) { release(streamId, ctx); } - responseCallback.onResponse(responseFrame); + try { + responseCallback.onResponse(responseFrame); + } catch (Throwable t) { + LOG.warn("Unexpected error while invoking response handler", t); + } } } super.channelRead(ctx, msg); @@ -152,12 +160,19 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception /** Called if an exception was thrown while processing an inbound event (i.e. a response). */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - int streamId; - if (cause instanceof FrameDecodingException - && (streamId = ((FrameDecodingException) cause).streamId) >= 0) { - // We know which request matches the failing response, fail that one only - ResponseCallback responseCallback = release(streamId, ctx); - responseCallback.onFailure(cause.getCause()); + if (cause instanceof FrameDecodingException) { + int streamId = ((FrameDecodingException) cause).streamId; + if (streamId >= 0) { + // We know which request matches the failing response, fail that one only + ResponseCallback responseCallback = release(streamId, ctx); + try { + responseCallback.onFailure(cause.getCause()); + } catch (Throwable t) { + LOG.warn("Unexpected error while invoking failure handler", t); + } + } else { + LOG.warn("Unexpected error while decoding incoming event frame", cause.getCause()); + } } else { // Otherwise fail all pending requests abortAllInFlight(new ConnectionException("Unexpected error on channel", cause)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 956ced8f05f..1d3e8c3980e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.typesafe.config.Config; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -33,11 +34,21 @@ public boolean isDefined(DriverOption option) { return config.hasPath(option.getPath()); } + @Override + public boolean getBoolean(DriverOption option) { + return config.getBoolean(option.getPath()); + } + @Override public int getInt(DriverOption option) { return config.getInt(option.getPath()); } + @Override + public Duration getDuration(DriverOption option) { + return config.getDuration(option.getPath()); + } + @Override public long getDuration(DriverOption option, TimeUnit targetUnit) { return config.getDuration(option.getPath(), targetUnit); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 754de2ff779..ce9961b2767 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -15,16 +15,22 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; -import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.DefaultTopologyMonitor; +import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -33,7 +39,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; -import com.typesafe.config.ConfigFactory; import io.netty.buffer.ByteBuf; import java.util.Optional; @@ -59,10 +64,12 @@ public class DefaultDriverContext implements InternalDriverContext { private final CycleDetector cycleDetector = new CycleDetector("Detected cycle in context initialization"); - private final LazyReference configRef = - new LazyReference<>("config", this::buildDriverConfig, cycleDetector); + private final LazyReference loadBalancingPolicyRef = + new LazyReference<>("loadBalancingPolicy", this::buildLoadBalancingPolicy, cycleDetector); private final LazyReference reconnectionPolicyRef = new LazyReference<>("reconnectionPolicy", this::buildReconnectionPolicy, cycleDetector); + private final LazyReference addressTranslatorRef = + new LazyReference<>("addressTranslator", this::buildAddressTranslator, cycleDetector); private final LazyReference> authProviderRef = new LazyReference<>("authProvider", this::buildAuthProvider, cycleDetector); private final LazyReference> sslEngineFactoryRef = @@ -85,10 +92,31 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory, cycleDetector); private final LazyReference channelFactoryRef = new LazyReference<>("channelFactory", this::buildChannelFactory, cycleDetector); + private final LazyReference topologyMonitorRef = + new LazyReference<>("topologyMonitor", this::buildTopologyMonitor, cycleDetector); + private final LazyReference metadataManagerRef = + new LazyReference<>("metadataManager", this::buildMetadataManager, cycleDetector); + private final LazyReference loadBalancingPolicyWrapperRef = + new LazyReference<>( + "loadBalancingPolicyWrapper", this::buildLoadBalancingPolicyWrapper, cycleDetector); + private final LazyReference controlConnectionRef = + new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); + + private final DriverConfig config; - protected DriverConfig buildDriverConfig() { - return new TypeSafeDriverConfig( - ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + public DefaultDriverContext(DriverConfig config) { + this.config = config; + } + + protected LoadBalancingPolicy buildLoadBalancingPolicy() { + CoreDriverOption classOption = CoreDriverOption.LOAD_BALANCING_POLICY_CLASS; + return Reflection.buildFromConfig(this, classOption, LoadBalancingPolicy.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing load balancing policy, check your configuration (%s)", + classOption))); } protected ReconnectionPolicy buildReconnectionPolicy() { @@ -102,6 +130,16 @@ protected ReconnectionPolicy buildReconnectionPolicy() { classOption))); } + protected AddressTranslator buildAddressTranslator() { + CoreDriverOption classOption = CoreDriverOption.ADDRESS_TRANSLATOR_CLASS; + return Reflection.buildFromConfig(this, classOption, AddressTranslator.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing address translator, check your configuration (%s)", classOption))); + } + protected Optional buildAuthProvider() { return Reflection.buildFromConfig( this, CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, AuthProvider.class); @@ -112,11 +150,11 @@ protected Optional buildSslEngineFactory() { this, CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); } - private EventBus buildEventBus() { + protected EventBus buildEventBus() { return new EventBus(); } - private Compressor buildCompressor() { + protected Compressor buildCompressor() { // TODO build alternate implementation if specified in conf return Compressor.none(); } @@ -142,17 +180,38 @@ protected Optional buildSslHandlerFactory() { // extend DefaultDriverContext and override this method } - private WriteCoalescer buildWriteCoalescer() { + protected WriteCoalescer buildWriteCoalescer() { return new DefaultWriteCoalescer(5); } - private ChannelFactory buildChannelFactory() { + protected ChannelFactory buildChannelFactory() { return new ChannelFactory(this); } + protected TopologyMonitor buildTopologyMonitor() { + return new DefaultTopologyMonitor(this); + } + + protected MetadataManager buildMetadataManager() { + return new MetadataManager(this); + } + + protected LoadBalancingPolicyWrapper buildLoadBalancingPolicyWrapper() { + return new LoadBalancingPolicyWrapper(this, loadBalancingPolicy()); + } + + protected ControlConnection buildControlConnection() { + return new ControlConnection(this); + } + @Override public DriverConfig config() { - return configRef.get(); + return config; + } + + @Override + public LoadBalancingPolicy loadBalancingPolicy() { + return loadBalancingPolicyRef.get(); } @Override @@ -160,6 +219,11 @@ public ReconnectionPolicy reconnectionPolicy() { return reconnectionPolicyRef.get(); } + @Override + public AddressTranslator addressTranslator() { + return addressTranslatorRef.get(); + } + @Override public Optional authProvider() { return authProviderRef.get(); @@ -209,4 +273,24 @@ public Optional sslHandlerFactory() { public ChannelFactory channelFactory() { return channelFactoryRef.get(); } + + @Override + public TopologyMonitor topologyMonitor() { + return topologyMonitorRef.get(); + } + + @Override + public MetadataManager metadataManager() { + return metadataManagerRef.get(); + } + + @Override + public LoadBalancingPolicyWrapper loadBalancingPolicyWrapper() { + return loadBalancingPolicyWrapperRef.get(); + } + + @Override + public ControlConnection controlConnection() { + return controlConnectionRef.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 5d50e67723d..c3bf98c5f92 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; @@ -33,10 +34,13 @@ public class DefaultNettyOptions implements NettyOptions { public DefaultNettyOptions() { // TODO inject the cluster name in thread names - ThreadFactory ioThreadFactory = new ThreadFactoryBuilder().setNameFormat("io-%d").build(); + ThreadFactory safeFactory = new BlockingOperation.SafeThreadFactory(); + ThreadFactory ioThreadFactory = + new ThreadFactoryBuilder().setThreadFactory(safeFactory).setNameFormat("io-%d").build(); this.ioEventLoopGroup = new NioEventLoopGroup(0, ioThreadFactory); - ThreadFactory adminThreadFactory = new ThreadFactoryBuilder().setNameFormat("admin-%d").build(); + ThreadFactory adminThreadFactory = + new ThreadFactoryBuilder().setThreadFactory(safeFactory).setNameFormat("admin-%d").build(); int adminThreadCount = Math.min(2, Runtime.getRuntime().availableProcessors()); this.adminEventLoopGroup = new DefaultEventLoopGroup(adminThreadCount, adminThreadFactory); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index a06e6ec2329..adb73b465b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -19,6 +19,10 @@ import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; @@ -43,4 +47,12 @@ public interface InternalDriverContext extends DriverContext { Optional sslHandlerFactory(); ChannelFactory channelFactory(); + + TopologyMonitor topologyMonitor(); + + MetadataManager metadataManager(); + + LoadBalancingPolicyWrapper loadBalancingPolicyWrapper(); + + ControlConnection controlConnection(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 477d7b2445c..5186ae2244d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; @@ -25,7 +26,13 @@ /** Low-level hooks to control certain aspects of Netty usage in the driver. */ public interface NettyOptions { - /** The event loop group that will be used for I/O. This must always return the same instance. */ + /** + * The event loop group that will be used for I/O. This must always return the same instance. + * + *

It is highly recommended that the threads in this event loop group be created by a {@link + * BlockingOperation.SafeThreadFactory}, so that the driver can protect against deadlocks + * introduced by bad client code. + */ EventLoopGroup ioEventLoopGroup(); /** @@ -40,6 +47,10 @@ public interface NettyOptions { * *

This must always return the same instance (it can be the same object as {@link * #ioEventLoopGroup()}). + * + *

It is highly recommended that the threads in this event loop group be created by a {@link + * BlockingOperation.SafeThreadFactory}, so that the driver can protect against deadlocks + * introduced by bad client code. */ EventExecutorGroup adminEventExecutorGroup(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java new file mode 100644 index 00000000000..05f8d47aa76 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.control; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.channel.EventCallback; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Event; +import com.datastax.oss.protocol.internal.response.event.SchemaChangeEvent; +import com.datastax.oss.protocol.internal.response.event.StatusChangeEvent; +import com.datastax.oss.protocol.internal.response.event.TopologyChangeEvent; +import com.google.common.collect.ImmutableList; +import io.netty.util.concurrent.EventExecutor; +import java.net.InetSocketAddress; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Maintains a dedicated connection to a Cassandra node for administrative queries: schema + * refreshes, and cluster topology queries and events. + * + *

If the control node goes down, a reconnection is triggered. The control node is chosen + * randomly among the contact points at startup, or according to the load balancing policy for later + * reconnections. + * + *

If a custom {@link TopologyMonitor} is used, the control connection is used only for schema + * refreshes; if schema metadata is also disabled, the control connection never initializes. + */ +public class ControlConnection implements EventCallback { + private static final Logger LOG = LoggerFactory.getLogger(ControlConnection.class); + + private final InternalDriverContext context; + private final EventExecutor adminExecutor; + private final SingleThreaded singleThreaded; + + // The single channel used by this connection. This field is accessed currently, but only + // mutated on adminExecutor (by SingleThreaded methods) + private volatile DriverChannel channel; + + public ControlConnection(InternalDriverContext context) { + this.context = context; + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.singleThreaded = new SingleThreaded(context); + } + + /** + * @param listenToClusterEvents whether to register for TOPOLOGY_CHANGE and STATUS_CHANGE events. + * If the control connection has already initialized with another value, this is ignored. + * SCHEMA_CHANGE events are always registered. + */ + public CompletionStage init(boolean listenToClusterEvents) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.init(listenToClusterEvents)); + return singleThreaded.initFuture; + } + + /** + * The channel currently used by this control connection. This is modified concurrently in the + * event of a reconnection, so it may occasionally return a closed channel (clients should be + * ready to deal with that). + */ + public DriverChannel channel() { + return channel; + } + + /** + * Forces an immediate reconnect: if we were connected to a node, that connection will be closed; + * if we were already reconnecting, the next attempt is started immediately, without waiting for + * the next scheduled interval; in all cases, a new query plan is fetched from the load balancing + * policy, and each node in it will be tried in sequence. + */ + public void reconnectNow() { + RunOrSchedule.on(adminExecutor, singleThreaded::reconnectNow); + } + + /** Note: control queries are never critical, so there is no graceful close. */ + public CompletionStage forceClose() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + return singleThreaded.closeFuture; + } + + @Override + public void onEvent(Message eventMessage) { + if (!(eventMessage instanceof Event)) { + LOG.warn("Unsupported event class: {}", eventMessage.getClass().getName()); + } else { + LOG.debug("Processing incoming event {}", eventMessage); + Event event = (Event) eventMessage; + switch (event.type) { + case ProtocolConstants.EventType.TOPOLOGY_CHANGE: + processTopologyChange(event); + break; + case ProtocolConstants.EventType.STATUS_CHANGE: + processStatusChange(event); + break; + case ProtocolConstants.EventType.SCHEMA_CHANGE: + processSchemaChange(event); + break; + default: + LOG.warn("Unsupported event type: {}", event.type); + } + } + } + + private void processTopologyChange(Event event) { + TopologyChangeEvent tce = (TopologyChangeEvent) event; + InetSocketAddress address = context.addressTranslator().translate(tce.address); + switch (tce.changeType) { + case ProtocolConstants.TopologyChangeType.NEW_NODE: + context.eventBus().fire(TopologyEvent.suggestAdded(address)); + break; + case ProtocolConstants.TopologyChangeType.REMOVED_NODE: + context.eventBus().fire(TopologyEvent.suggestRemoved(address)); + break; + default: + LOG.warn("Unsupported topology change type: {}", tce.changeType); + } + } + + private void processStatusChange(Event event) { + StatusChangeEvent sce = (StatusChangeEvent) event; + InetSocketAddress address = context.addressTranslator().translate(sce.address); + switch (sce.changeType) { + case ProtocolConstants.StatusChangeType.UP: + context.eventBus().fire(TopologyEvent.suggestUp(address)); + break; + case ProtocolConstants.StatusChangeType.DOWN: + context.eventBus().fire(TopologyEvent.suggestDown(address)); + break; + default: + LOG.warn("Unsupported status change type: {}", sce.changeType); + } + } + + private void processSchemaChange(Event event) { + SchemaChangeEvent sce = (SchemaChangeEvent) event; + context + .metadataManager() + .refreshSchema( + SchemaElementKind.fromProtocolString(sce.target), + sce.keyspace, + sce.object, + sce.arguments); + } + + private class SingleThreaded { + private final InternalDriverContext context; + private final CompletableFuture initFuture = new CompletableFuture<>(); + private boolean initWasCalled; + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; + private final Reconnection reconnection; + private DriverChannelOptions channelOptions; + + private SingleThreaded(InternalDriverContext context) { + this.context = context; + this.reconnection = + new Reconnection(adminExecutor, context.reconnectionPolicy(), this::reconnect); + } + + private void init(boolean listenToClusterEvents) { + assert adminExecutor.inEventLoop(); + if (initWasCalled) { + return; + } + initWasCalled = true; + ImmutableList eventTypes = buildEventTypes(listenToClusterEvents); + LOG.debug("Initializing with event types {}", eventTypes); + channelOptions = + DriverChannelOptions.builder().withEvents(eventTypes, ControlConnection.this).build(); + + Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); + + connect(nodes, null, () -> initFuture.complete(null), initFuture::completeExceptionally); + } + + private CompletionStage reconnect() { + assert adminExecutor.inEventLoop(); + Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); + CompletableFuture result = new CompletableFuture<>(); + connect(nodes, null, () -> result.complete(true), error -> result.complete(false)); + return result; + } + + private void connect( + Queue nodes, + Map errors, + Runnable onSuccess, + Consumer onFailure) { + assert adminExecutor.inEventLoop(); + Node node = nodes.poll(); + if (node == null) { + onFailure.accept(AllNodesFailedException.fromErrors(errors)); + } else { + LOG.debug("Trying to establish a connection to {}", node); + context + .channelFactory() + .connect(node.getConnectAddress(), channelOptions) + .whenCompleteAsync( + (channel, error) -> { + try { + if (error != null) { + if (closeWasCalled) { + onSuccess.run(); // abort, we don't really care about the result + } else { + LOG.debug("Error connecting to " + node + ", trying next node", error); + Map newErrors = + (errors == null) ? new LinkedHashMap<>() : errors; + newErrors.put(node, error); + connect(nodes, newErrors, onSuccess, onFailure); + } + } else if (closeWasCalled) { + LOG.debug( + "New channel opened ({}) but the control connection was closed, closing it", + channel); + channel.forceClose(); + onSuccess.run(); + } else { + LOG.debug("Connection established to {}", node); + // Make sure previous channel gets closed (it may still be open if reconnection was forced) + DriverChannel previousChannel = ControlConnection.this.channel; + if (previousChannel != null) { + previousChannel.forceClose(); + } + ControlConnection.this.channel = channel; + context.eventBus().fire(ChannelEvent.channelOpened(node.getConnectAddress())); + channel + .closeFuture() + .addListener( + f -> + adminExecutor + .submit(() -> onChannelClosed(channel)) + .addListener(UncaughtExceptions::log)); + onSuccess.run(); + } + } catch (Exception e) { + LOG.warn("Unexpected exception while processing channel init result", e); + } + }, + adminExecutor); + } + } + + private void onChannelClosed(DriverChannel channel) { + assert adminExecutor.inEventLoop(); + LOG.debug("Lost channel {}", channel); + context.eventBus().fire(ChannelEvent.channelClosed(channel.address())); + if (!closeWasCalled && !reconnection.isRunning()) { + reconnection.start(); + } + } + + private void reconnectNow() { + assert adminExecutor.inEventLoop(); + if (initWasCalled && !closeWasCalled) { + reconnection.reconnectNow(true); + } + } + + private void forceClose() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + reconnection.stop(); + if (channel == null) { + closeFuture.complete(null); + } else { + channel + .forceClose() + .addListener( + f -> { + if (f.isSuccess()) { + closeFuture.complete(null); + } else { + closeFuture.completeExceptionally(f.cause()); + } + }); + } + } + } + + private static ImmutableList buildEventTypes(boolean listenClusterEvents) { + ImmutableList.Builder builder = ImmutableList.builder(); + builder.add(ProtocolConstants.EventType.SCHEMA_CHANGE); + if (listenClusterEvents) { + builder + .add(ProtocolConstants.EventType.STATUS_CHANGE) + .add(ProtocolConstants.EventType.TOPOLOGY_CHANGE); + } + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java new file mode 100644 index 00000000000..4c3ab8f6c3f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is immutable, so that metadata changes are atomic for the client. Every mutation + * operation must return a new instance, that will replace the existing one in {@link + * MetadataManager}'s volatile field. + */ +public class DefaultMetadata implements Metadata { + private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadata.class); + + public static final DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); + + private final Map nodes; + // TODO add metadata and schema + + public DefaultMetadata(Map nodes) { + this.nodes = ImmutableMap.copyOf(nodes); + } + + @Override + public Map getNodes() { + return nodes; + } + + /** Create minimal node info about the contact points, before the first connection. */ + public DefaultMetadata initNodes(Set addresses) { + assert nodes.isEmpty(); + ImmutableMap.Builder newNodes = ImmutableMap.builder(); + for (InetSocketAddress address : addresses) { + newNodes.put(address, new DefaultNode(address)); + } + return new DefaultMetadata(newNodes.build()); + } + + public DefaultMetadata refreshNodes(Iterable nodeInfos) { + Map added = new HashMap<>(); + Set seen = new HashSet<>(); + + for (TopologyMonitor.NodeInfo nodeInfo : nodeInfos) { + InetSocketAddress address = nodeInfo.getConnectAddress(); + if (address == null) { + // TODO more advanced row validation (see 3.x), here or in TopologyMonitor? + LOG.debug("Ignoring node info with missing connect address"); + continue; + } + seen.add(address); + DefaultNode node = (DefaultNode) nodes.get(address); + if (node == null) { + node = new DefaultNode(address); + LOG.debug("Adding new node {}", node); + added.put(address, node); + } + copyInfos(nodeInfo, node); + } + + Set removed = Sets.difference(nodes.keySet(), seen); + + if (added.isEmpty() && removed.isEmpty()) { + return this; + } else { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(added); + for (Map.Entry entry : nodes.entrySet()) { + if (!removed.contains(entry.getKey())) { + builder.put(entry.getKey(), entry.getValue()); + } + } + // TODO fire node added/removed events + // TODO recompute token map + return new DefaultMetadata(builder.build()); + } + } + + private void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { + // TODO add new properties in Node, fill them + } + + public DefaultMetadata addNode(Node toAdd) { + Map newNodes; + if (nodes.containsKey(toAdd.getConnectAddress())) { + return this; + } else { + newNodes = + ImmutableMap.builder() + .putAll(nodes) + .put(toAdd.getConnectAddress(), toAdd) + .build(); + // TODO recompute token map + return new DefaultMetadata(newNodes); + } + } + + public DefaultMetadata removeNode(InetSocketAddress toRemove) { + Map newNodes; + if (!nodes.containsKey(toRemove)) { + return this; + } else { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map.Entry entry : nodes.entrySet()) { + if (!entry.getKey().equals(toRemove)) { + builder.put(entry.getKey(), entry.getValue()); + } + } + newNodes = builder.build(); + // TODO recompute token map + return new DefaultMetadata(newNodes); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java new file mode 100644 index 00000000000..050a39caee5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import java.net.InetSocketAddress; + +/** + * Implementation note: all the mutable state in this class is read concurrently, but only mutated + * from {@link MetadataManager}'s admin thread. + */ +public class DefaultNode implements Node { + + private final InetSocketAddress connectAddress; + + // These 3 fields are read concurrently, but only mutated on NodeStateManager's admin thread + volatile NodeState state; + volatile int openConnections; + volatile int reconnections; + volatile NodeDistance distance; + + public DefaultNode(InetSocketAddress connectAddress) { + this.connectAddress = connectAddress; + this.state = NodeState.UNKNOWN; + this.distance = NodeDistance.IGNORED; + } + + @Override + public InetSocketAddress getConnectAddress() { + return connectAddress; + } + + @Override + public NodeState getState() { + return state; + } + + public int getOpenConnections() { + return openConnections; + } + + @Override + public boolean isReconnecting() { + return reconnections > 0; + } + + @Override + public String toString() { + return connectAddress.toString(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java new file mode 100644 index 00000000000..c467056254a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class DefaultNodeInfo implements TopologyMonitor.NodeInfo { + public static Builder builder() { + return new Builder(); + } + + private final InetSocketAddress connectAddress; + private final Optional broadcastAddress; + private final Optional listenAddress; + private final String datacenter; + private final String rack; + private final String cassandraVersion; + private final Set tokens; + private final Map extras; + + private DefaultNodeInfo(Builder builder) { + this.connectAddress = builder.connectAddress; + this.broadcastAddress = builder.broadcastAddress; + this.listenAddress = builder.listenAddress; + this.datacenter = builder.datacenter; + this.rack = builder.rack; + this.cassandraVersion = builder.cassandraVersion; + this.tokens = (builder.tokens == null) ? Collections.emptySet() : builder.tokens; + this.extras = (builder.extras == null) ? Collections.emptyMap() : builder.extras; + } + + @Override + public InetSocketAddress getConnectAddress() { + return connectAddress; + } + + @Override + public Optional getBroadcastAddress() { + return broadcastAddress; + } + + @Override + public Optional getListenAddress() { + return listenAddress; + } + + @Override + public String getDatacenter() { + return datacenter; + } + + @Override + public String getRack() { + return rack; + } + + @Override + public String getCassandraVersion() { + return cassandraVersion; + } + + @Override + public Set getTokens() { + return tokens; + } + + @Override + public Map getExtras() { + return extras; + } + + public static class Builder { + private InetSocketAddress connectAddress; + private Optional broadcastAddress = Optional.empty(); + private Optional listenAddress = Optional.empty(); + private String datacenter; + private String rack; + private String cassandraVersion; + private Set tokens; + private Map extras; + + public Builder withConnectAddress(InetSocketAddress address) { + this.connectAddress = address; + return this; + } + + public Builder withBroadcastAddress(InetAddress address) { + if (address != null) { + this.broadcastAddress = Optional.of(address); + } + return this; + } + + public Builder withListenAddress(InetAddress address) { + if (address != null) { + this.listenAddress = Optional.of(address); + } + return this; + } + + public Builder withDatacenter(String datacenter) { + this.datacenter = datacenter; + return this; + } + + public Builder withRack(String rack) { + this.rack = rack; + return this; + } + + public Builder withCassandraVersion(String cassandraVersion) { + this.cassandraVersion = cassandraVersion; + return this; + } + + public Builder withTokens(Set tokens) { + this.tokens = tokens; + return this; + } + + public Builder withExtra(String key, Object value) { + if (this.extras == null) { + this.extras = new HashMap<>(); + } + this.extras.put(key, value); + return this; + } + + public DefaultNodeInfo build() { + return new DefaultNodeInfo(this); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java new file mode 100644 index 00000000000..8bf6bf2bae0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The default topology monitor, based on the control connection. */ +public class DefaultTopologyMonitor implements TopologyMonitor { + private static final Logger LOG = LoggerFactory.getLogger(DefaultTopologyMonitor.class); + + // Assume topology queries never need paging + private static final int INFINITE_PAGE_SIZE = -1; + + private final ControlConnection controlConnection; + private final AddressTranslator addressTranslator; + private final Duration timeout; + + private volatile int port = -1; + + public DefaultTopologyMonitor(InternalDriverContext context) { + this.controlConnection = context.controlConnection(); + addressTranslator = context.addressTranslator(); + DriverConfigProfile config = context.config().defaultProfile(); + this.timeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); + } + + @Override + public CompletionStage init() { + return controlConnection.init(true); + } + + @Override + public CompletionStage refreshNode(InetSocketAddress address) { + return CompletableFutures.failedFuture(new UnsupportedOperationException("TODO")); + } + + @Override + public CompletionStage> refreshNodeList() { + LOG.debug("Refreshing node list"); + DriverChannel channel = controlConnection.channel(); + savePort(channel); + + CompletionStage controlNodeStage = + AdminRequestHandler.query( + channel, "SELECT * FROM system.local", timeout, INFINITE_PAGE_SIZE) + .start(); + CompletionStage peersStage = + AdminRequestHandler.query( + channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) + .start(); + + return controlNodeStage.thenCombine( + peersStage, + (controlNodeResult, peersResult) -> { + List nodeInfos = new ArrayList<>(); + nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next())); + for (AdminResult.Row row : peersResult) { + nodeInfos.add(buildNodeInfo(row)); + } + return nodeInfos; + }); + } + + private NodeInfo buildNodeInfo(AdminResult.Row row) { + DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder(); + + InetAddress broadcastRpcAddress = row.getInet("rpc_address"); + if (broadcastRpcAddress != null) { + builder.withConnectAddress( + addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port))); + } + + InetAddress broadcastAddress = row.getInet("broadcast_address"); // in system.local + if (broadcastAddress == null) { + broadcastAddress = row.getInet("peer"); // in system.peers + } + builder.withBroadcastAddress(broadcastAddress); + + builder.withListenAddress(row.getInet("listen")); + builder.withDatacenter(row.getVarchar("data_center")); + builder.withRack(row.getVarchar("rack")); + builder.withCassandraVersion(row.getVarchar("release_version")); + builder.withTokens(row.getSetOfVarchar("tokens")); + + return builder.build(); + } + + // Current versions of Cassandra (3.11 at the time of writing), require the same port for all + // nodes. As a consequence, the port is not stored in system tables. + // We save it the first time we get a control connection channel. + private void savePort(DriverChannel channel) { + if (port < 0 && channel.address() instanceof InetSocketAddress) { + port = ((InetSocketAddress) channel.address()).getPort(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java new file mode 100644 index 00000000000..d758a09fdc7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import java.util.Objects; + +/** Fired when the load balancing policy assigns a new distance to a host. */ +public class DistanceEvent { + public final NodeDistance distance; + public final DefaultNode node; + + public DistanceEvent(NodeDistance distance, DefaultNode node) { + this.distance = distance; + this.node = node; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof DistanceEvent) { + DistanceEvent that = (DistanceEvent) other; + return this.distance == that.distance + && Objects.equals(this.node.getConnectAddress(), that.node.getConnectAddress()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.distance, this.node.getConnectAddress()); + } + + @Override + public String toString() { + return "DistanceEvent(" + distance + ", " + node.getConnectAddress() + ")"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java new file mode 100644 index 00000000000..df031ca53fe --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps the user-provided LBP for internal use. This serves multiple purposes: + * + *

    + *
  • help enforce the guarantee that init is called exactly once, and before any other method. + *
  • handle the early stages of initialization (before first actual connect), where the LBP is not + * ready yet. + *
  • process distance events. + *
+ */ +public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceReporter { + private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicyWrapper.class); + + private final InternalDriverContext context; + private final LoadBalancingPolicy policy; + private AtomicBoolean isInit = new AtomicBoolean(); + + public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPolicy policy) { + this.context = context; + this.policy = policy; + } + + public void init(Set nodes) { + if (isInit.compareAndSet(false, true)) { + policy.init(nodes, this); + } + } + + public Queue newQueryPlan() { + if (isInit.get()) { + return policy.newQueryPlan(); + } else { + // Still in early initialization: retrieve nodes from the metadata (at this stage it's the + // contact points). + List nodes = new ArrayList<>(); + nodes.addAll(context.metadataManager().getMetadata().getNodes().values()); + Collections.shuffle(nodes); + return new ConcurrentLinkedQueue<>(nodes); + } + } + + @Override + public void setDistance(Node node, NodeDistance distance) { + LOG.debug("LBP changed distance of {} to {}", node, distance); + DefaultNode defaultNode = (DefaultNode) node; + defaultNode.distance = distance; + context.eventBus().fire(new DistanceEvent(distance, defaultNode)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java new file mode 100644 index 00000000000..1c8d1e13988 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import io.netty.util.concurrent.EventExecutor; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Holds the immutable instance of the {@link Metadata}, and handles requests to update it. */ +public class MetadataManager { + private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class); + + private final InternalDriverContext context; + private final EventExecutor adminExecutor; + private final SingleThreaded singleThreaded; + private volatile DefaultMetadata metadata; // must be updated on adminExecutor only + + public MetadataManager(InternalDriverContext context) { + this.context = context; + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.singleThreaded = new SingleThreaded(context); + this.metadata = DefaultMetadata.EMPTY; + } + + public Metadata getMetadata() { + return this.metadata; + } + + public CompletionStage addContactPoints(Set contactPoints) { + if (contactPoints == null || contactPoints.isEmpty()) { + return CompletableFuture.completedFuture(null); + } else { + LOG.debug("Adding initial contact points {}", contactPoints); + CompletableFuture initNodesFuture = new CompletableFuture<>(); + RunOrSchedule.on( + adminExecutor, () -> singleThreaded.initNodes(contactPoints, initNodesFuture)); + return initNodesFuture; + } + } + + public CompletionStage refreshNodes() { + return context + .topologyMonitor() + .refreshNodeList() + .thenApplyAsync(singleThreaded::refreshNodes, adminExecutor); + } + + public void addNode(InetSocketAddress address) { + context + .topologyMonitor() + .refreshNode(address) + .thenApplyAsync(singleThreaded::addNode, adminExecutor) + .exceptionally( + e -> { + LOG.debug( + "Error adding node " + + address + + ", this will be retried on the next full refresh", + e); + return null; + }); + } + + public void removeNode(InetSocketAddress address) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.removeNode(address)); + } + + public void refreshSchema( + SchemaElementKind kind, String keyspace, String object, List arguments) { + // TODO refresh schema metadata + } + + // TODO user-controlled refresh, shutdown? + + private class SingleThreaded { + private final InternalDriverContext context; + + private SingleThreaded(InternalDriverContext context) { + this.context = context; + } + + private void initNodes( + Set addresses, CompletableFuture initNodesFuture) { + assert adminExecutor.inEventLoop(); + metadata = metadata.initNodes(addresses); + initNodesFuture.complete(null); + } + + private Void refreshNodes(Iterable nodeInfos) { + metadata = metadata.refreshNodes(nodeInfos); + // TODO init LBP if needed + return null; + } + + private Void addNode(TopologyMonitor.NodeInfo nodeInfo) { + //TODO + return null; + } + + private void removeNode(InetSocketAddress address) { + LOG.debug("Removing node {}", address); + metadata = metadata.removeNode(address); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java new file mode 100644 index 00000000000..bcb4a28b39c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.NodeState; +import java.util.Objects; + +public class NodeStateEvent { + public final NodeState newState; + public final DefaultNode node; + + public NodeStateEvent(NodeState newState, DefaultNode node) { + this.node = node; + this.newState = newState; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof NodeStateEvent) { + NodeStateEvent that = (NodeStateEvent) other; + return this.newState == that.newState + && Objects.equals(this.node.getConnectAddress(), that.node.getConnectAddress()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(newState, node.getConnectAddress()); + } + + @Override + public String toString() { + return "NodeStateEvent(" + newState + ", " + node.getConnectAddress() + ")"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java new file mode 100644 index 00000000000..9271f228ffa --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.Debouncer; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.google.common.collect.Maps; +import io.netty.util.concurrent.EventExecutor; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Maintains the state of the Cassandra nodes, based on the events received from other components of + * the driver. + * + *

See {@link NodeState} and {@link TopologyEvent} for a description of the state change rules. + */ +public class NodeStateManager { + private static final Logger LOG = LoggerFactory.getLogger(NodeStateManager.class); + + private final EventExecutor adminExecutor; + private final MetadataManager metadataManager; + private final EventBus eventBus; + private final Debouncer> topologyEventDebouncer; + + public NodeStateManager(InternalDriverContext context) { + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.metadataManager = context.metadataManager(); + + DriverConfigProfile config = context.config().defaultProfile(); + this.topologyEventDebouncer = + new Debouncer<>( + adminExecutor, + this::coalesceTopologyEvents, + this::flushTopologyEvents, + config.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW), + config.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); + + this.eventBus = context.eventBus(); + this.eventBus.register( + ChannelEvent.class, RunOrSchedule.on(adminExecutor, this::onChannelEvent)); + this.eventBus.register( + TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); + // Note: this component exists for the whole life of the driver instance, so don't worry about + // unregistering the listeners. + } + + private void onChannelEvent(ChannelEvent event) { + assert adminExecutor.inEventLoop(); + LOG.debug("Processing {}", event); + @SuppressWarnings("SuspiciousMethodCalls") + DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + assert node != null; + switch (event.type) { + case OPENED: + node.openConnections += 1; + if (node.state == NodeState.DOWN || node.state == NodeState.UNKNOWN) { + setState(node, NodeState.UP, "a new connection was opened to it"); + } + break; + case CLOSED: + node.openConnections -= 1; + break; + case RECONNECTION_STARTED: + node.reconnections += 1; + if (node.openConnections == 0) { + setState(node, NodeState.DOWN, "it has no connections and started reconnecting"); + } + break; + case RECONNECTION_STOPPED: + node.reconnections -= 1; + break; + } + } + + private void onDebouncedTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + LOG.debug("Processing {}", event); + DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + switch (event.type) { + case SUGGEST_UP: + if (node == null) { + LOG.debug("Received UP event for unknown node {}, adding it", event.address); + metadataManager.addNode(event.address); + } else if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("Not setting {} UP because it is FORCED_DOWN", node); + } else { + setState(node, NodeState.UP, "an UP topology event was received"); + } + break; + case SUGGEST_DOWN: + if (node == null) { + LOG.debug("Received DOWN event for unknown node {}, ignoring it", event.address); + } else if (node.openConnections > 0) { + LOG.debug("Not setting {} DOWN because it still has active connections", node); + } else if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("Not setting {} DOWN because it is FORCED_DOWN", node); + } else { + setState(node, NodeState.DOWN, "a DOWN topology event was received"); + } + break; + case FORCE_UP: + if (node == null) { + LOG.debug("Received FORCE_UP event for unknown node {}, adding it", event.address); + metadataManager.addNode(event.address); + } else { + setState(node, NodeState.UP, "a FORCE_UP topology event was received"); + } + break; + case FORCE_DOWN: + if (node == null) { + LOG.debug("Received FORCE_DOWN event for unknown node {}, ignoring it", event.address); + } else { + setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); + } + break; + case SUGGEST_ADDED: + if (node != null) { + LOG.debug( + "Received ADDED event for {} but it is already in our metadata, ignoring", node); + } else { + metadataManager.addNode(event.address); + } + break; + case SUGGEST_REMOVED: + if (node == null) { + LOG.debug( + "Received REMOVED event for {} but it is not in our metadata, ignoring", + event.address); + } else { + metadataManager.removeNode(event.address); + } + break; + } + } + + // Called by the event bus, needs debouncing + private void onTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + topologyEventDebouncer.receive(event); + } + + // Called to process debounced events before flushing + private Collection coalesceTopologyEvents(List events) { + assert adminExecutor.inEventLoop(); + Collection result; + if (events.size() == 1) { + result = events; + } else { + // Keep the last FORCE* event for each node, or if there is none the last normal event + Map last = Maps.newHashMapWithExpectedSize(events.size()); + for (TopologyEvent event : events) { + if (event.isForceEvent() + || !last.containsKey(event.address) + || !last.get(event.address).isForceEvent()) { + last.put(event.address, event); + } + } + result = last.values(); + } + LOG.debug("Coalesced topology events: {} => {}", events, result); + return result; + } + + // Called when the debouncer flushes + private void flushTopologyEvents(Collection events) { + assert adminExecutor.inEventLoop(); + for (TopologyEvent event : events) { + onDebouncedTopologyEvent(event); + } + } + + private void setState(DefaultNode node, NodeState newState, String reason) { + NodeState oldState = node.state; + if (oldState != newState) { + LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); + node.state = newState; + eventBus.fire(new NodeStateEvent(newState, node)); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java new file mode 100644 index 00000000000..3ed9ee396d4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +/** The different kinds of objects in a schema. */ +public enum SchemaElementKind { + KEYSPACE(ProtocolConstants.SchemaChangeTarget.KEYSPACE), + TABLE(ProtocolConstants.SchemaChangeTarget.TABLE), + TYPE(ProtocolConstants.SchemaChangeTarget.TYPE), + FUNCTION(ProtocolConstants.SchemaChangeTarget.FUNCTION), + AGGREGATE(ProtocolConstants.SchemaChangeTarget.AGGREGATE), + ; + + private final String protocolString; + + SchemaElementKind(String protocolString) { + this.protocolString = protocolString; + } + + private static final Map BY_PROTOCOL_STRING; + + static { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (SchemaElementKind kind : values()) { + builder.put(kind.protocolString, kind); + } + BY_PROTOCOL_STRING = builder.build(); + } + + public static SchemaElementKind fromProtocolString(String protocolString) { + SchemaElementKind kind = BY_PROTOCOL_STRING.get(protocolString); + if (kind == null) { + throw new IllegalArgumentException("Unsupported schema type: " + protocolString); + } + return kind; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java new file mode 100644 index 00000000000..e0e6ae5e80d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import java.net.InetSocketAddress; +import java.util.Objects; + +/** + * An event emitted from the {@link TopologyMonitor}, indicating a change in the topology of the + * Cassandra cluster. + * + *

As shown by the names, most of these events are mere suggestions, that the driver might choose + * to ignore if they contradict other information it has about the nodes; see the documentation of + * each factory method for detailed explanations. + */ +public class TopologyEvent { + + public enum Type { + SUGGEST_UP, + SUGGEST_DOWN, + FORCE_UP, + FORCE_DOWN, + SUGGEST_ADDED, + SUGGEST_REMOVED, + } + + /** + * Suggests that a node is up. + * + *

    + *
  • if the node is currently ignored by the driver's load balancing policy, this is reflected + * in the driver metadata's corresponding {@link Node}, for information purposes only. + *
  • otherwise: + *
      + *
    • if the driver already had active connections to that node, this has no effect. + *
    • if the driver was currently reconnecting to the node, this causes the current + * {@link + * com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule} + * to be reset, and the next reconnection attempt to happen immediately. + *
    + * + *
+ */ + public static TopologyEvent suggestUp(InetSocketAddress address) { + return new TopologyEvent(Type.SUGGEST_UP, address); + } + + /** + * Suggests that a node is down. + * + *
    + *
  • if the node is currently ignored by the driver's load balancing policy, this is reflected + * in the driver metadata's corresponding {@link Node}, for information purposes only. + *
  • otherwise, if the driver still has at least one active connection to that node, this is + * ignored. In other words, a functioning connection is considered a more reliable + * indication than a topology event. + *

    If you want to bypass that behavior and force the node down, use {@link + * #forceDown(InetSocketAddress)}. + *

+ */ + public static TopologyEvent suggestDown(InetSocketAddress address) { + return new TopologyEvent(Type.SUGGEST_DOWN, address); + } + + /** + * Forces the driver to set a node down. + * + *
    + *
  • if the node is currently ignored by the driver's load balancing policy, this is reflected + * in the driver metadata, for information purposes only. + *
  • otherwise, all active connections to the node are closed, and any active reconnection is + * cancelled. + *
+ * + * In all cases, the driver will never try to reconnect to the node again. If you decide to + * reconnect to it later, use {@link #forceUp(InetSocketAddress)}. + * + *

This is intended for deployments that use a custom {@link TopologyMonitor} (for example if + * you do some kind of maintenance on a live node). This is also used internally by the driver + * when it detects an unrecoverable error, such as a node that does not support the current + * protocol version. + */ + public static TopologyEvent forceDown(InetSocketAddress address) { + return new TopologyEvent(Type.FORCE_DOWN, address); + } + + /** + * Cancels a previous {@link #forceDown(InetSocketAddress)} event for the node. + * + *

The node will be set back UP. If it is not ignored by the load balancing policy, a + * connection pool will be reopened. + */ + public static TopologyEvent forceUp(InetSocketAddress address) { + return new TopologyEvent(Type.FORCE_UP, address); + } + + /** + * Suggests that a new node was added in the cluster. + * + *

The driver will ignore this event if the node is already present in its metadata, or if + * information about the node can't be refreshed (i.e. {@link + * TopologyMonitor#refreshNode(InetSocketAddress)} fails). + */ + public static TopologyEvent suggestAdded(InetSocketAddress address) { + return new TopologyEvent(Type.SUGGEST_ADDED, address); + } + + /** + * Suggests that a node was removed from the cluster. + * + *

The driver ignore this event if the node does not exist in its metadata. + */ + public static TopologyEvent suggestRemoved(InetSocketAddress address) { + return new TopologyEvent(Type.SUGGEST_REMOVED, address); + } + + public final Type type; + public final InetSocketAddress address; + + /** Builds a new instance (the static methods in this class are a preferred alternative). */ + public TopologyEvent(Type type, InetSocketAddress address) { + this.type = type; + this.address = address; + } + + public boolean isForceEvent() { + return type == Type.FORCE_DOWN || type == Type.FORCE_UP; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TopologyEvent) { + TopologyEvent that = (TopologyEvent) other; + return this.type == that.type && Objects.equals(this.address, that.address); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.address); + } + + @Override + public String toString() { + return "TopologyEvent(" + type + ", " + address + ")"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java new file mode 100644 index 00000000000..cc79946d095 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletionStage; + +/** + * Monitors the state of the Cassandra cluster. + * + *

It can either push {@link TopologyEvent topology events} to the rest of the driver (to do + * that, retrieve the {@link EventBus}) from the {@link InternalDriverContext}), or receive requests + * to refresh data about the nodes. + * + *

The default implementation uses the control connection: {@code TOPOLOGY_CHANGE} and {@code + * STATUS_CHANGE} events on the connection are converted into {@code TopologyEvent}s, and node + * refreshes are done with queries to system tables. If you prefer to rely on an external monitoring + * tool, this can be completely overridden. + */ +public interface TopologyMonitor { + + /** + * Triggers the initialization of the monitor. + * + *

This will be invoked at startup, and is how the driver determines when it is "successfully + * connected" to the Cassandra cluster. In particular, the initialization of the {@link Cluster} + * instance depends on the result of this method. + */ + CompletionStage init(); + + /** + * Invoked when the driver needs to refresh information about a node. + * + *

This will be invoked directly from a driver's internal thread; if the refresh involves + * blocking I/O or heavy computations, it should be scheduled on a separate thread. + * + * @param address the address that the driver uses to connect to the node. This is the node's + * broadcast RPC address, transformed by the address translator if one is configured. + * @return a future that completes with the information. + */ + CompletionStage refreshNode(InetSocketAddress address); + + /** + * Invoked when the driver needs to refresh information about all the nodes. + * + *

This will be invoked directly from a driver's internal thread; if the refresh involves + * blocking I/O or heavy computations, it should be scheduled on a separate thread. + * + *

Implementation note: as shown by the signature, it is assumed that the full node list will + * always be returned in a single message (no paging). + * + * @return a future that completes with the information. + */ + CompletionStage> refreshNodeList(); + + /** + * Information about a node, as it will be returned by the monitor. + * + *

This is distinct from what we expose in the public driver metadata. + */ + interface NodeInfo { + /** + * The address that the driver uses to connect to the node. This is the node's broadcast RPC + * address, transformed by the address translator if one is configured. + */ + InetSocketAddress getConnectAddress(); + + /** + * The node's broadcast address. That is, the address that other nodes use to communicate with + * that node. + */ + Optional getBroadcastAddress(); + + /** The node's listen address. That is, the address that the Cassandra process binds to. */ + Optional getListenAddress(); + + String getDatacenter(); + + String getRack(); + + String getCassandraVersion(); + + Set getTokens(); + + /** + * An additional map of free-form properties, that can be used by custom implementations. They + * will be copied as-is into the driver metadata's {@link Node}. + */ + Map getExtras(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 1174870c03e..d359a0333af 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -16,10 +16,14 @@ package com.datastax.oss.driver.internal.core.pool; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; @@ -28,7 +32,7 @@ import com.google.common.collect.Sets; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; -import java.net.SocketAddress; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -61,7 +65,7 @@ public class ChannelPool { * channels (i.e. {@link #next()} return {@code null}) and is reconnecting. */ public static CompletionStage init( - SocketAddress address, + InetSocketAddress address, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { @@ -76,7 +80,7 @@ public static CompletionStage init( private final SingleThreaded singleThreaded; private ChannelPool( - SocketAddress address, + InetSocketAddress address, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { @@ -142,8 +146,9 @@ public CompletionStage closeFuture() { /** Holds all administration tasks, that are confined to the admin executor. */ private class SingleThreaded { - private final SocketAddress address; + private final InetSocketAddress address; private final ChannelFactory channelFactory; + private final EventBus eventBus; // The channels that are currently connecting private final List> pendingChannels = new ArrayList<>(); private final Reconnection reconnection; @@ -158,7 +163,7 @@ private class SingleThreaded { private CqlIdentifier keyspaceName; private SingleThreaded( - SocketAddress address, + InetSocketAddress address, CqlIdentifier keyspaceName, int wantedCount, InternalDriverContext context) { @@ -166,8 +171,14 @@ private SingleThreaded( this.keyspaceName = keyspaceName; this.wantedCount = wantedCount; this.channelFactory = context.channelFactory(); + this.eventBus = context.eventBus(); this.reconnection = - new Reconnection(adminExecutor, context.reconnectionPolicy(), this::addMissingChannels); + new Reconnection( + adminExecutor, + context.reconnectionPolicy(), + this::addMissingChannels, + () -> eventBus.fire(ChannelEvent.reconnectionStarted(this.address)), + () -> eventBus.fire(ChannelEvent.reconnectionStopped(this.address))); } private void connect() { @@ -210,6 +221,7 @@ private CompletionStage addMissingChannels() { private boolean onAllConnected(@SuppressWarnings("unused") Void v) { assert adminExecutor.inEventLoop(); + ClusterNameMismatchException clusterNameMismatch = null; for (CompletionStage pendingChannel : pendingChannels) { CompletableFuture future = pendingChannel.toCompletableFuture(); assert future.isDone(); @@ -224,6 +236,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } else { LOG.debug("{} new channel added {}", ChannelPool.this, channel); channels.add(channel); + eventBus.fire(ChannelEvent.channelOpened(address)); channel .closeFuture() .addListener( @@ -235,13 +248,27 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } catch (InterruptedException e) { // can't happen, the future is done } catch (ExecutionException e) { - // TODO handle ClusterNameMismatchException LOG.debug(ChannelPool.this + " error while opening new channel", e.getCause()); // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) + + // TODO auth exception => WARN and keep reconnecting + // TODO protocol error => WARN and force down + + if (e.getCause() instanceof ClusterNameMismatchException) { + // This will likely be thrown by all channels, but finish the loop cleanly + clusterNameMismatch = (ClusterNameMismatchException) e.getCause(); + } } } pendingChannels.clear(); + if (clusterNameMismatch != null) { + LOG.warn(clusterNameMismatch.getMessage()); + eventBus.fire(TopologyEvent.forceDown(address)); + // Don't bother continuing, the pool will get shut down soon anyway + return true; + } + shrinkIfTooManyChannels(); // Can happen if the pool was shrinked during the reconnection int currentCount = channels.size(); @@ -258,12 +285,13 @@ private void onChannelClosed(DriverChannel channel) { assert adminExecutor.inEventLoop(); LOG.debug("{} lost channel {}", ChannelPool.this, channel); channels.remove(channel); + eventBus.fire(ChannelEvent.channelClosed(address)); if (!isClosing && !reconnection.isRunning()) { reconnection.start(); } } - public void resize(int newChannelCount) { + private void resize(int newChannelCount) { assert adminExecutor.inEventLoop(); if (newChannelCount > wantedCount) { LOG.debug("{} growing ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); @@ -296,6 +324,7 @@ private void shrinkIfTooManyChannels() { for (DriverChannel channel : toRemove) { channels.remove(channel); channel.close(); + eventBus.fire(ChannelEvent.channelClosed(address)); } } } @@ -333,7 +362,10 @@ private void close() { reconnection.stop(); forAllChannels( - DriverChannel::close, + channel -> { + eventBus.fire(ChannelEvent.channelClosed(address)); + return channel.close(); + }, () -> closeFuture.complete(ChannelPool.this), (channel, error) -> LOG.warn(ChannelPool.this + " error closing channel " + channel, error)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java index 818f74a6c55..26bde030534 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java @@ -21,7 +21,6 @@ import io.netty.buffer.CompositeByteBuf; import io.netty.util.CharsetUtil; import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; @@ -75,15 +74,6 @@ public int readInt(ByteBuf source) { return source.readInt(); } - @Override - public InetSocketAddress readInet(ByteBuf source) { - int length = readByte(source) & 0xFF; - byte[] bytes = new byte[length]; - source.readBytes(bytes); - int port = source.readInt(); - return new InetSocketAddress(newInetAddress(bytes), port); - } - @Override public InetAddress readInetAddr(ByteBuf source) { int length = readByte(source) & 0xFF; @@ -145,12 +135,6 @@ public void writeInt(int i, ByteBuf dest) { dest.writeInt(i); } - @Override - public void writeInet(InetSocketAddress inet, ByteBuf dest) { - writeInetAddr(inet.getAddress(), dest); - writeInt(inet.getPort(), dest); - } - @Override public void writeInetAddr(InetAddress inetAddr, ByteBuf dest) { byte[] bytes = inetAddr.getAddress(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java new file mode 100644 index 00000000000..c69616aca78 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import java.util.concurrent.ThreadFactory; + +/** + * Safeguards against bad usage patterns in client code that could introduce deadlocks in the + * driver. + * + *

The driver internals are fully asynchronous, nothing should ever block. On the other hand, our + * API exposes synchronous wrappers, that call async methods and wait on the result (as a + * convenience for clients that don't want to do async). These methods should never be called on a + * driver thread, because this can lead to deadlocks. This can happen from client code if it uses + * callbacks. + */ +public class BlockingOperation { + private static ThreadLocal isDriverThread = ThreadLocal.withInitial(() -> false); + + /** + * This method is invoked from each synchronous driver method, and checks that we are not on a + * driver thread. + * + *

For this to work, all driver threads must be created by {@link SafeThreadFactory} (which is + * the case by default). + * + * @throws IllegalStateException if a driver thread is executing this. + */ + public static void checkNotDriverThread() { + if (isDriverThread.get()) { + throw new IllegalStateException( + "Detected a synchronous API call on a driver thread, " + + "failing because this can cause deadlocks."); + } + } + + /** + * Marks threads as driver threads, so that they will be detected by {@link + * #checkNotDriverThread()} + */ + public static class SafeThreadFactory implements ThreadFactory { + @Override + public Thread newThread(Runnable r) { + return new Thread(r) { + @Override + public void run() { + isDriverThread.set(true); + super.run(); + } + }; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 7304e34963e..6d1dbcc1e25 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -15,10 +15,14 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import com.datastax.oss.driver.api.core.DriverException; +import com.google.common.base.Throwables; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; public class CompletableFutures { @@ -28,7 +32,7 @@ public static CompletableFuture failedFuture(Throwable cause) { return future; } - /** Completes {@code target} with the outcome of {@code source} */ + /** Completes {@code target} with the outcome of {@code source}. */ public static void completeFrom(CompletionStage source, CompletableFuture target) { source.whenComplete( (t, error) -> { @@ -55,4 +59,25 @@ public static CompletionStage whenAllDone(List> inp } return result; } + + public static T getUninterruptibly(CompletableFuture future) { + boolean interrupted = false; + try { + while (true) { + try { + return future.get(); + } catch (InterruptedException e) { + interrupted = true; + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + Throwables.throwIfUnchecked(cause); + throw new DriverException(cause); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java new file mode 100644 index 00000000000..6c8ae599a7d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.ScheduledFuture; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Debounces a sequence of events to smoothen temporary oscillations. + * + *

When a first event is received, the debouncer starts a time window. If no other event is + * received within that window, the initial event is flushed. However, if another event arrives, the + * window is reset, and the next flush will now contain both events. If the window keeps getting + * reset, the debouncer will flush after a given number of accumulated events. + * + * @param the type of event. + * @param the resulting type after the events of a batch have been coalesced. + */ +public class Debouncer { + private static final Logger LOG = LoggerFactory.getLogger(Debouncer.class); + + private final EventExecutor adminExecutor; + private final Consumer onFlush; + private final Duration window; + private final long maxEvents; + private final Function, R> coalescer; + + private List currentBatch = new ArrayList<>(); + private ScheduledFuture nextFlush; + + /** + * Creates a new instance. + * + * @param adminExecutor the executor that will be used to schedule all tasks. + * @param coalescer how to transform a batch of events into a result. + * @param onFlush what to do with a result. + * @param window the time window. + * @param maxEvents the maximum number of accumulated events before a flush is forced. + */ + public Debouncer( + EventExecutor adminExecutor, + Function, R> coalescer, + Consumer onFlush, + Duration window, + long maxEvents) { + this.coalescer = coalescer; + Preconditions.checkArgument(maxEvents >= 1, "maxEvents should be at least 1"); + this.adminExecutor = adminExecutor; + this.onFlush = onFlush; + this.window = window; + this.maxEvents = maxEvents; + } + + /** This must be called on eventExecutor too. */ + public void receive(T element) { + assert adminExecutor.inEventLoop(); + if (window.isZero() || maxEvents == 1) { + LOG.debug( + "Received {}, flushing immediately (window = {}, maxEvents = {})", + element, + window, + maxEvents); + onFlush.accept(coalescer.apply(ImmutableList.of(element))); + } else { + currentBatch.add(element); + if (currentBatch.size() == maxEvents) { + LOG.debug( + "Received {}, flushing immediately (because {} accumulated events)", + element, + maxEvents); + flushNow(); + } else { + LOG.debug("Received {}, scheduling next flush in {}", element, window); + scheduleFlush(); + } + } + } + + private void flushNow() { + assert adminExecutor.inEventLoop(); + LOG.debug("Flushing now"); + cancelNextFlush(); + onFlush.accept(coalescer.apply(currentBatch)); + currentBatch = new ArrayList<>(); + } + + private void scheduleFlush() { + assert adminExecutor.inEventLoop(); + cancelNextFlush(); + nextFlush = adminExecutor.schedule(this::flushNow, window.toNanos(), TimeUnit.NANOSECONDS); + nextFlush.addListener(UncaughtExceptions::log); + } + + private void cancelNextFlush() { + assert adminExecutor.inEventLoop(); + if (nextFlush != null && !nextFlush.isDone()) { + boolean cancelled = nextFlush.cancel(true); + if (cancelled) { + LOG.debug("Cancelled existing scheduled flush"); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index cdc02ecce10..17df4e7e655 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -22,6 +22,7 @@ import io.netty.util.concurrent.ScheduledFuture; import java.time.Duration; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -40,7 +41,10 @@ public class Reconnection { private final EventExecutor executor; private final ReconnectionPolicy reconnectionPolicy; private final Callable> reconnectionTask; + private final Runnable onStart; + private final Runnable onStop; + private boolean isRunning; private ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; private ScheduledFuture> nextAttempt; @@ -51,28 +55,80 @@ public class Reconnection { public Reconnection( EventExecutor executor, ReconnectionPolicy reconnectionPolicy, - Callable> reconnectionTask) { + Callable> reconnectionTask, + Runnable onStart, + Runnable onStop) { this.executor = executor; this.reconnectionPolicy = reconnectionPolicy; this.reconnectionTask = reconnectionTask; + this.onStart = onStart; + this.onStop = onStop; + } + + public Reconnection( + EventExecutor executor, + ReconnectionPolicy reconnectionPolicy, + Callable> reconnectionTask) { + this(executor, reconnectionPolicy, reconnectionTask, () -> {}, () -> {}); } public boolean isRunning() { assert executor.inEventLoop(); - return nextAttempt != null; + return isRunning; } /** @throws IllegalStateException if the reconnection is already running */ public void start() { assert executor.inEventLoop(); - Preconditions.checkState(nextAttempt == null, "Already running"); + Preconditions.checkState(!isRunning, "Already running"); reconnectionSchedule = reconnectionPolicy.newSchedule(); - + isRunning = true; + onStart.run(); scheduleNextAttempt(); } + /** + * Forces a reconnection now, without waiting for the next scheduled attempt. + * + * @param forceIfStopped if true and the reconnection is not running, it will get started. If + * false and the reconnection is not running, no attempt is scheduled. + */ + public void reconnectNow(boolean forceIfStopped) { + assert executor.inEventLoop(); + if (isRunning || forceIfStopped) { + LOG.debug("{} forcing next attempt now", this); + isRunning = true; + if (nextAttempt != null) { + nextAttempt.cancel(true); + } + try { + onNextAttemptStarted(reconnectionTask.call()); + } catch (Exception e) { + LOG.warn("Uncaught error while starting reconnection attempt", e); + scheduleNextAttempt(); + } + } + } + + public void stop() { + assert executor.inEventLoop(); + if (isRunning) { + isRunning = false; + LOG.debug("{} stopping reconnection", this); + if (nextAttempt != null) { + nextAttempt.cancel(true); + } + onStop.run(); + nextAttempt = null; + reconnectionSchedule = null; + } + } + private void scheduleNextAttempt() { assert executor.inEventLoop(); + if (reconnectionSchedule == null) { // happens if reconnectNow() while we were stopped + reconnectionSchedule = reconnectionPolicy.newSchedule(); + } Duration nextInterval = reconnectionSchedule.nextDelay(); LOG.debug("{} scheduling next reconnection in {}", this, nextInterval); nextAttempt = executor.schedule(reconnectionTask, nextInterval.toNanos(), TimeUnit.NANOSECONDS); @@ -80,7 +136,7 @@ private void scheduleNextAttempt() { (Future> f) -> { if (f.isSuccess()) { onNextAttemptStarted(f.getNow()); - } else { + } else if (!f.isCancelled()) { LOG.warn("Uncaught error while starting reconnection attempt", f.cause()); scheduleNextAttempt(); } @@ -102,22 +158,12 @@ private void onNextAttemptCompleted(Boolean success, Throwable error) { LOG.debug("{} reconnection successful", this); stop(); } else { - if (error != null) { + if (error != null && !(error instanceof CancellationException)) { LOG.warn("Uncaught error while starting reconnection attempt", error); } - if (isRunning()) { + if (isRunning) { // can be false if stop() was called scheduleNextAttempt(); } } } - - public void stop() { - assert executor.inEventLoop(); - LOG.debug("{} stopping reconnection", this); - if (nextAttempt != null) { - nextAttempt.cancel(true); - } - nextAttempt = null; - reconnectionSchedule = null; - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java index 97bbe17db84..1e7b05b83ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/RunOrSchedule.java @@ -20,6 +20,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; /** * Utility to run a task on a Netty event executor (i.e. thread). If we're already on the executor, @@ -57,6 +58,16 @@ public static void on(EventExecutor executor, Runnable task) { } } + public static Consumer on(EventExecutor executor, Consumer task) { + return (t) -> { + if (executor.inEventLoop()) { + task.accept(t); + } else { + executor.submit(() -> task.accept(t)).addListener(UncaughtExceptions::log); + } + }; + } + public static CompletionStage on( EventExecutor executor, Callable> task) { if (executor.inEventLoop()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java index 038c30bec30..05c0d992e6d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java @@ -44,7 +44,7 @@ public class UncaughtExceptions { private static final Logger LOG = LoggerFactory.getLogger(UncaughtExceptions.class); public static void log(Future future) { - if (!future.isSuccess()) { + if (!future.isSuccess() && !future.isCancelled()) { LOG.warn("Uncaught exception in scheduled task", future.cause()); } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index fecb908a711..ae97c2e12ae 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -5,6 +5,25 @@ # # This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md. datastax-java-driver { + # The contact points to use for the initial connection to the cluster. + # + # These are addresses of Cassandra nodes that the driver uses to discover the cluster topology. + # Only one contact point is required (the driver will retrieve the address of the other nodes + # automatically), but it is usually a good idea to provide more than one contact point, because + # if that single contact point is unavailable, the driver cannot initialize itself correctly. + # + # This must be a list of strings with each contact point specified as "host:port". If the host is + # a DNS name that resolves to multiple A-records, all the corresponding addressess will be used. + # Do not use "localhost" as the host name (since it resolves to both IPv4 and IPv6 addresses on + # some platforms). + # + # Note that the current version of Cassandra (3.11) requires all nodes in a cluster to share the + # same port. + # + # Contact points can also be provided programmatically when you build a cluster instance. If both + # are specified, they will be merged. + // contact-points = [ "127.0.0.1:9042", "127.0.0.2:9042" ] + protocol { # The native protocol version to use. # @@ -18,6 +37,11 @@ datastax-java-driver { # in that case. // version = V4 } + + load-balancing { + policy-class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy + } + connection { # The timeout to use for internal queries that run as part of the initialization process, just # after we open a connection. If this timeout fires, the initialization of the connection will @@ -47,13 +71,48 @@ datastax-java-driver { # fail with an exception max-frame-length = 256 MB - reconnection-policy { - provider-class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy + reconnection { + policy-class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy config { base-delay = 1 second max-delay = 60 seconds } } + + control-connection { + # How long the driver waits for responses to control queries (e.g. fetching the list of + # nodes, refreshing the schema). + timeout = 5 seconds + # The page size used for control queries. If a query returns more than this number of + # results, it will be fetched in multiple requests. + page-size = 5000 + } + } + metadata { + # Topology events are external signals that inform the driver of the state of Cassandra nodes + # (by default, they correspond to gossip events received on the control connection). + # The debouncer helps smoothen out oscillations if conflicting events are sent out in short + # bursts. + # Debouncing may be disabled by setting the window to 0 or max-events to 1 (this is not + # recommended). + topology-event-debouncer { + # How long the driver waits to propagate an event. If another event is received within that + # time, the window is reset and a batch of accumulated events will be delivered. + window = 1 second + # The maximum number of events that can accumulate. If this count is reached, the events are + # delivered immediately and the time window is reset. This avoids holding events indefinitely + # if the window keeps getting reset. + max-events = 20 + } + } + address-translation { + # The address translator to use to convert the addresses sent by Cassandra nodes into ones that + # the driver uses to connect. + # This is only needed if the nodes are not directly reachable from the driver (for example, the + # driver is in a different network region and needs to use a public IP, or it connects through + # a proxy). + # The default implementation always returns the same address unchanged. + translator-class = com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator } authentication { # The auth provider class to use. The driver expects this class to have a public constructor diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java index 7a684f047f0..f7bf9cf0b4c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -35,6 +35,7 @@ public static Rows clusterNameResponse(String actualClusterName) { "system", "local", "cluster_name", + 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR)); RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); Queue> data = Lists.newLinkedList(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java deleted file mode 100644 index 17a14ee678e..00000000000 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryEventsTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.channel; - -import com.datastax.oss.driver.api.core.CoreProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import io.netty.channel.local.LocalAddress; -import java.util.concurrent.CompletionStage; -import org.mockito.Mockito; -import org.testng.annotations.Test; - -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; - -public class ChannelFactoryEventsTest extends ChannelFactoryTestBase { - - @Test - public void should_not_fire_open_event_until_initialization_complete() { - connect(SERVER_ADDRESS); - Mockito.verify(eventBus, never()).fire(any(ChannelEvent.class)); - - // Clean up - completeSimpleChannelInit(); - } - - @Test - public void should_fire_open_event_when_initialization_completes() { - CompletionStage connectFuture = connect(SERVER_ADDRESS); - completeSimpleChannelInit(); - assertThat(connectFuture) - .isSuccess( - channel -> - Mockito.verify(eventBus, timeout(100)) - .fire(new ChannelEvent(ChannelEvent.Type.OPENED, SERVER_ADDRESS))); - } - - @Test - public void should_fire_close_event_when_channel_closes() { - CompletionStage connectFuture = connect(SERVER_ADDRESS); - completeSimpleChannelInit(); - assertThat(connectFuture) - .isSuccess( - channel -> - assertThat(channel.close()) - .isSuccess( - (v) -> - Mockito.verify(eventBus, timeout(100)) - .fire(new ChannelEvent(ChannelEvent.Type.CLOSED, SERVER_ADDRESS)))); - } - - private CompletionStage connect(LocalAddress address) { - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) - .thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); - - return newChannelFactory().connect(address, DriverChannelOptions.DEFAULT); - } -} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index bd02dbadd9c..351929aace2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -54,7 +54,7 @@ public void setup() { new InFlightHandler( CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); writeCoalescer = new MockWriteCoalescer(); - driverChannel = new DriverChannel(channel, writeCoalescer, null); + driverChannel = new DriverChannel(channel, writeCoalescer, null, CoreProtocolVersion.V3); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java new file mode 100644 index 00000000000..5244a63d7b6 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Sets; +import java.net.SocketAddress; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.internal.util.MockUtil; +import org.mockito.stubbing.OngoingStubbing; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; + +/** + * Helper class to set up and verify a sequence of invocations on a ChannelFactory mock. + * + *

Use the builder at the beginning of the test to stub expected calls. Then call the verify + * methods throughout the test to check that each call has been performed. + * + *

This class handles asynchronous calls to the thread factory, but it must be used from a single + * thread (see {@link #waitForCalls(SocketAddress, int)}). + */ +public class MockChannelFactoryHelper { + + public static Builder builder(ChannelFactory channelFactory) { + return new Builder(channelFactory); + } + + private final ChannelFactory channelFactory; + private final InOrder inOrder; + // If waitForCalls sees more invocations than expected, the difference is stored here + private final Map previous = new HashMap<>(); + + public MockChannelFactoryHelper(ChannelFactory channelFactory) { + this.channelFactory = channelFactory; + this.inOrder = Mockito.inOrder(channelFactory); + } + + public void waitForCall(SocketAddress address) { + waitForCalls(address, 1); + } + + /** + * Waits for a given number of calls to {@code ChannelFactory.connect()}. + * + *

Because we test asynchronous, non-blocking code, there might already be more calls than + * expected when this method is called. If so, the extra calls are stored and stored and will be + * taken into account next time. + */ + public void waitForCalls(SocketAddress address, int expected) { + int fromLastTime = previous.getOrDefault(address, 0); + if (fromLastTime >= expected) { + previous.put(address, fromLastTime - expected); + return; + } + expected -= fromLastTime; + + // Because we test asynchronous, non-blocking code, there might have been already more + // invocations than expected. Use `atLeast` and a captor to find out. + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + inOrder + .verify(channelFactory, timeout(100).atLeast(expected)) + .connect(eq(address), optionsCaptor.capture()); + int actual = optionsCaptor.getAllValues().size(); + + int extras = actual - expected; + if (extras > 0) { + previous.compute(address, (k, v) -> (v == null) ? extras : v + extras); + } + } + + public void verifyNoMoreCalls() { + inOrder + .verify(channelFactory, timeout(100).times(0)) + .connect(any(SocketAddress.class), any(DriverChannelOptions.class)); + + Set counts = Sets.newHashSet(previous.values()); + if (!counts.isEmpty()) { + assertThat(counts).containsExactly(0); + } + } + + public static class Builder { + private final ChannelFactory channelFactory; + private final ListMultimap invocations = + MultimapBuilder.hashKeys().arrayListValues().build(); + + public Builder(ChannelFactory channelFactory) { + assertThat(MockUtil.isMock(channelFactory)).isTrue().as("expected a mock"); + Mockito.verifyZeroInteractions(channelFactory); + this.channelFactory = channelFactory; + } + + public Builder success(SocketAddress address, DriverChannel channel) { + invocations.put(address, channel); + return this; + } + + public Builder failure(SocketAddress address, String error) { + invocations.put(address, new Exception(error)); + return this; + } + + public Builder failure(SocketAddress address, Throwable error) { + invocations.put(address, error); + return this; + } + + public Builder pending(SocketAddress address, CompletableFuture future) { + invocations.put(address, future); + return this; + } + + public MockChannelFactoryHelper build() { + stub(); + return new MockChannelFactoryHelper(channelFactory); + } + + private void stub() { + for (SocketAddress address : invocations.keySet()) { + LinkedList> results = new LinkedList<>(); + for (Object object : invocations.get(address)) { + if (object instanceof DriverChannel) { + results.add(CompletableFuture.completedFuture(((DriverChannel) object))); + } else if (object instanceof Throwable) { + results.add(CompletableFutures.failedFuture(((Throwable) object))); + } else if (object instanceof CompletableFuture) { + @SuppressWarnings("unchecked") + CompletionStage future = (CompletionStage) object; + results.add(future); + } else { + fail("unexpected type: " + object.getClass()); + } + } + if (results.size() > 0) { + CompletionStage first = results.poll(); + OngoingStubbing> ongoingStubbing = + Mockito.when(channelFactory.connect(eq(address), any(DriverChannelOptions.class))) + .thenReturn(first); + for (CompletionStage result : results) { + ongoingStubbing.thenReturn(result); + } + } + } + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 59595235b09..53bcc498e0d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -281,8 +281,8 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th e -> assertThat(e) .isInstanceOf(ClusterNameMismatchException.class) - .hasMessage( - "Host embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); + .hasMessageContaining( + "Node embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java new file mode 100644 index 00000000000..92434360c0c --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.control; + +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.channel.EventCallback; +import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.event.SchemaChangeEvent; +import com.datastax.oss.protocol.internal.response.event.StatusChangeEvent; +import com.datastax.oss.protocol.internal.response.event.TopologyChangeEvent; +import com.google.common.collect.ImmutableList; +import java.util.concurrent.CompletableFuture; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; + +public class ControlConnectionEventsTest extends ControlConnectionTestBase { + + @Test + public void should_register_for_all_events_if_topology_requested() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + .thenReturn(CompletableFuture.completedFuture(channel1)); + + // When + controlConnection.init(true); + waitForPendingAdminTasks(); + DriverChannelOptions channelOptions = optionsCaptor.getValue(); + + // Then + assertThat(channelOptions.eventTypes) + .containsExactly( + ProtocolConstants.EventType.SCHEMA_CHANGE, + ProtocolConstants.EventType.STATUS_CHANGE, + ProtocolConstants.EventType.TOPOLOGY_CHANGE); + assertThat(channelOptions.eventCallback).isEqualTo(controlConnection); + } + + @Test + public void should_register_for_schema_events_only_if_topology_not_requested() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + .thenReturn(CompletableFuture.completedFuture(channel1)); + + // When + controlConnection.init(false); + waitForPendingAdminTasks(); + DriverChannelOptions channelOptions = optionsCaptor.getValue(); + + // Then + assertThat(channelOptions.eventTypes) + .containsExactly(ProtocolConstants.EventType.SCHEMA_CHANGE); + assertThat(channelOptions.eventCallback).isEqualTo(controlConnection); + } + + @Test + public void should_process_status_change_events() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + .thenReturn(CompletableFuture.completedFuture(channel1)); + controlConnection.init(true); + waitForPendingAdminTasks(); + EventCallback callback = optionsCaptor.getValue().eventCallback; + StatusChangeEvent event = + new StatusChangeEvent(ProtocolConstants.StatusChangeType.UP, ADDRESS1); + + // When + callback.onEvent(event); + + // Then + Mockito.verify(addressTranslator).translate(ADDRESS1); + Mockito.verify(eventBus).fire(TopologyEvent.suggestUp(ADDRESS1)); + } + + @Test + public void should_process_topology_change_events() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + .thenReturn(CompletableFuture.completedFuture(channel1)); + controlConnection.init(true); + waitForPendingAdminTasks(); + EventCallback callback = optionsCaptor.getValue().eventCallback; + TopologyChangeEvent event = + new TopologyChangeEvent(ProtocolConstants.TopologyChangeType.NEW_NODE, ADDRESS1); + + // When + callback.onEvent(event); + + // Then + Mockito.verify(addressTranslator).translate(ADDRESS1); + Mockito.verify(eventBus).fire(TopologyEvent.suggestAdded(ADDRESS1)); + } + + @Test + public void should_process_schema_change_events() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(DriverChannelOptions.class); + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + .thenReturn(CompletableFuture.completedFuture(channel1)); + controlConnection.init(false); + waitForPendingAdminTasks(); + EventCallback callback = optionsCaptor.getValue().eventCallback; + SchemaChangeEvent event = + new SchemaChangeEvent( + ProtocolConstants.SchemaChangeType.CREATED, + ProtocolConstants.SchemaChangeTarget.FUNCTION, + "ks", + "fn", + ImmutableList.of("text", "text")); + + // When + callback.onEvent(event); + + // Then + Mockito.verify(metadataManager) + .refreshSchema(SchemaElementKind.FUNCTION, "ks", "fn", ImmutableList.of("text", "text")); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java new file mode 100644 index 00000000000..12d7cd42114 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.control; + +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; + +public class ControlConnectionTest extends ControlConnectionTestBase { + + @Test + public void should_close_successfully_if_it_was_never_init() { + // When + CompletionStage closeFuture = controlConnection.forceClose(); + + // Then + assertThat(closeFuture).isSuccess(); + } + + @Test + public void should_init_with_first_contact_point_if_reachable() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + + // When + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + + // Then + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_always_return_same_init_future() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + + // When + CompletionStage initFuture1 = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + CompletionStage initFuture2 = controlConnection.init(false); + + // Then + assertThat(initFuture1).isEqualTo(initFuture2); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_init_with_second_contact_point_if_first_one_fails() { + // Given + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) + .build(); + + // When + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + + // Then + assertThat(initFuture) + .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + // each attempt tries all nodes, so there is no reconnection + Mockito.verify(reconnectionPolicy, never()).newSchedule(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_fail_to_init_if_all_contact_points_fail() { + // Given + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS1, "mock failure") + .failure(ADDRESS2, "mock failure") + .build(); + + // When + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + + // Then + assertThat(initFuture).isFailed(); + Mockito.verify(eventBus, never()).fire(any(ChannelEvent.class)); + // no reconnections at init + Mockito.verify(reconnectionPolicy, never()).newSchedule(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_reconnect_if_channel_goes_down() throws Exception { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + // When + failChannel(channel1, "mock channel failure"); + waitForPendingAdminTasks(); + + // Then + // a reconnection was started + Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + assertThat(controlConnection.channel()).isEqualTo(channel2); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_force_reconnection_if_pending() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + // the channel fails and a reconnection is scheduled for later + failChannel(channel1, "mock channel failure"); + waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(reconnectionSchedule).nextDelay(); + + // When + controlConnection.reconnectNow(); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + + // Then + assertThat(controlConnection.channel()).isEqualTo(channel2); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_force_reconnection_even_if_connected() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + // When + controlConnection.reconnectNow(); + + // Then + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + assertThat(controlConnection.channel()).isEqualTo(channel2); + Mockito.verify(channel1).forceClose(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_not_force_reconnection_if_not_init() { + // When + controlConnection.reconnectNow(); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + } + + @Test + public void should_not_force_reconnection_if_closed() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CompletionStage closeFuture = controlConnection.forceClose(); + assertThat(closeFuture).isSuccess(); + + // When + controlConnection.reconnectNow(); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_close_channel_when_closing() { + // Given + DriverChannel channel1 = newMockDriverChannel(1); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + + // When + CompletionStage closeFuture = controlConnection.forceClose(); + waitForPendingAdminTasks(); + + // Then + assertThat(closeFuture).isSuccess(); + Mockito.verify(channel1).forceClose(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_close_channel_if_closed_during_reconnection() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .pending(ADDRESS2, channel2Future) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + // the channel fails and a reconnection is scheduled + failChannel(channel1, "mock channel failure"); + waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCall(ADDRESS1); + // channel2 starts initializing (but the future is not completed yet) + factoryHelper.waitForCall(ADDRESS2); + + // When + // the control connection gets closed before channel2 initialization is complete + controlConnection.forceClose(); + waitForPendingAdminTasks(); + channel2Future.complete(channel2); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(channel2).forceClose(); + // no event because the control connection never "owned" the channel + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS2)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_handle_channel_failure_if_closed_during_reconnection() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel1Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .pending(ADDRESS1, channel1Future) + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + + // the channel fails and a reconnection is scheduled + failChannel(channel1, "mock channel failure"); + waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(reconnectionSchedule).nextDelay(); + // channel1 starts initializing (but the future is not completed yet) + factoryHelper.waitForCall(ADDRESS1); + + // When + // the control connection gets closed before channel1 initialization fails + controlConnection.forceClose(); + channel1Future.completeExceptionally(new Exception("mock failure")); + waitForPendingAdminTasks(); + + // Then + // should never try channel2 because the reconnection has detected that it can stop after the + // first failure + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java new file mode 100644 index 00000000000..e1e97d55283 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.control; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.DefaultChannelPromise; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.Future; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Exchanger; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.MockUtil; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; + +abstract class ControlConnectionTestBase { + protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + + @Mock protected InternalDriverContext context; + @Mock protected ReconnectionPolicy reconnectionPolicy; + @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; + @Mock protected NettyOptions nettyOptions; + protected DefaultEventLoopGroup adminEventLoopGroup; + @Mock protected EventBus eventBus; + @Mock protected ChannelFactory channelFactory; + protected Exchanger> channelFactoryFuture; + @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; + @Mock protected MetadataManager metadataManager; + protected AddressTranslator addressTranslator; + + protected ControlConnection controlConnection; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + adminEventLoopGroup = new DefaultEventLoopGroup(1); + + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.channelFactory()).thenReturn(channelFactory); + + channelFactoryFuture = new Exchanger<>(); + Mockito.when(channelFactory.connect(any(SocketAddress.class), any(DriverChannelOptions.class))) + .thenAnswer( + invocation -> { + CompletableFuture channelFuture = new CompletableFuture<>(); + channelFactoryFuture.exchange(channelFuture, 100, TimeUnit.MILLISECONDS); + return channelFuture; + }); + + Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + // By default, set a large reconnection delay. Tests that care about reconnection will override + // it. + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + + DefaultNode node1 = new DefaultNode(ADDRESS1); + DefaultNode node2 = new DefaultNode(ADDRESS2); + + Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()) + .thenAnswer( + i -> { + ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); + queryPlan.offer(node1); + queryPlan.offer(node2); + return queryPlan; + }); + + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + + addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); + Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + + controlConnection = new ControlConnection(context); + } + + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + + protected DriverChannel newMockDriverChannel(int id) { + DriverChannel channel = Mockito.mock(DriverChannel.class); + EventLoop adminExecutor = adminEventLoopGroup.next(); + DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); + Mockito.when(channel.close()) + .thenAnswer( + i -> { + closeFuture.trySuccess(null); + return closeFuture; + }); + Mockito.when(channel.forceClose()) + .thenAnswer( + i -> { + closeFuture.trySuccess(null); + return closeFuture; + }); + Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + Mockito.when(channel.toString()).thenReturn("channel" + id); + Mockito.when(channel.address()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); + return channel; + } + + protected static void failChannel(DriverChannel channel, String message) { + assertThat(MockUtil.isMock(channel)).isTrue(); + ((DefaultChannelPromise) channel.closeFuture()).setFailure(new Exception(message)); + } + + // Wait for all the tasks on the admin executor to complete. + protected void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java new file mode 100644 index 00000000000..bb75ae442ba --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.util.concurrent.Future; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class NodeStateManagerTest { + private static final InetSocketAddress NEW_ADDRESS = new InetSocketAddress("127.0.0.3", 9042); + + @Mock private InternalDriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private NettyOptions nettyOptions; + @Mock private MetadataManager metadataManager; + @Mock private Metadata metadata; + private DefaultNode node1, node2; + private EventBus eventBus; + private DefaultEventLoopGroup adminEventLoopGroup; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + // Disable debouncing by default, tests that need it will override + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + .thenReturn(Duration.ofSeconds(0)); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + .thenReturn(1); + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(context.config()).thenReturn(config); + + this.eventBus = Mockito.spy(new EventBus()); + Mockito.when(context.eventBus()).thenReturn(eventBus); + + adminEventLoopGroup = new DefaultEventLoopGroup(1, new BlockingOperation.SafeThreadFactory()); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + + node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042)); + node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042)); + ImmutableMap nodes = + ImmutableMap.builder() + .put(node1.getConnectAddress(), node1) + .put(node2.getConnectAddress(), node2) + .build(); + Mockito.when(metadata.getNodes()).thenReturn(nodes); + Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + } + + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + + @Test + public void should_ignore_up_event_if_node_is_already_up_or_forced_down() { + new NodeStateManager(context); + + for (NodeState oldState : ImmutableList.of(NodeState.UP, NodeState.FORCED_DOWN)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(TopologyEvent.suggestUp(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(oldState); + } + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_apply_up_event_if_node_is_unknown_or_down() { + new NodeStateManager(context); + + int i = 0; + for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(TopologyEvent.suggestUp(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + } + } + + @Test + public void should_add_node_if_up_event_and_not_in_metadata() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.suggestUp(NEW_ADDRESS)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + Mockito.verify(metadataManager).addNode(NEW_ADDRESS); + } + + @Test + public void should_ignore_down_event_if_node_is_down_or_forced_down() { + new NodeStateManager(context); + + for (NodeState oldState : ImmutableList.of(NodeState.DOWN, NodeState.FORCED_DOWN)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(oldState); + } + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_ignore_down_event_if_node_has_active_connections() { + // Given + new NodeStateManager(context); + node1.state = NodeState.UP; + eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + waitForPendingAdminTasks(); + assertThat(node1.openConnections).isEqualTo(1); + + // When + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_apply_down_event_if_node_has_no_active_connections() { + // Given + new NodeStateManager(context); + node1.state = NodeState.UP; + assertThat(node1.openConnections).isEqualTo(0); + + // When + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.DOWN); + Mockito.verify(eventBus).fire(new NodeStateEvent(NodeState.DOWN, node1)); + } + + @Test + public void should_ignore_down_event_if_not_in_metadata() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.suggestDown(NEW_ADDRESS)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + Mockito.verify(metadataManager, never()).addNode(NEW_ADDRESS); + } + + @Test + public void should_ignore_force_down_event_if_already_forced_down() { + // Given + new NodeStateManager(context); + node1.state = NodeState.FORCED_DOWN; + + // When + eventBus.fire(TopologyEvent.forceDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_apply_force_down_event_over_any_other_state() { + new NodeStateManager(context); + + int i = 0; + for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN, NodeState.UP)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(TopologyEvent.forceDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); + Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.FORCED_DOWN, node1)); + } + } + + @Test + public void should_ignore_force_down_event_if_not_in_metadata() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.forceDown(NEW_ADDRESS)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + Mockito.verify(metadataManager, never()).addNode(NEW_ADDRESS); + } + + @Test + public void should_ignore_force_up_event_if_node_is_already_up() { + // Given + new NodeStateManager(context); + node1.state = NodeState.UP; + + // When + eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_apply_force_up_event_if_node_is_not_up() { + new NodeStateManager(context); + + int i = 0; + for (NodeState oldState : + ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN, NodeState.FORCED_DOWN)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + } + } + + @Test + public void should_add_node_if_force_up_and_not_in_metadata() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.forceUp(NEW_ADDRESS)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + Mockito.verify(metadataManager).addNode(NEW_ADDRESS); + } + + @Test + public void should_notify_metadata_of_node_addition() { + // Given + new NodeStateManager(context); + InetSocketAddress newAddress = NEW_ADDRESS; + + // When + eventBus.fire(TopologyEvent.suggestAdded(newAddress)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(metadataManager).addNode(newAddress); + } + + @Test + public void should_ignore_addition_of_existing_node() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.suggestAdded(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(metadataManager, never()).addNode(any(InetSocketAddress.class)); + } + + @Test + public void should_notify_metadata_of_node_removal() { + // Given + new NodeStateManager(context); + + // When + eventBus.fire(TopologyEvent.suggestRemoved(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(metadataManager).removeNode(node1.getConnectAddress()); + } + + @Test + public void should_ignore_removal_of_nonexistent_node() { + // Given + new NodeStateManager(context); + InetSocketAddress newAddress = NEW_ADDRESS; + + // When + eventBus.fire(TopologyEvent.suggestRemoved(newAddress)); + waitForPendingAdminTasks(); + + // Then + Mockito.verify(metadataManager, never()).removeNode(any(InetSocketAddress.class)); + } + + @Test + public void should_coalesce_topology_events() { + // Given + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + .thenReturn(Duration.ofDays(1)); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + .thenReturn(5); + new NodeStateManager(context); + node1.state = NodeState.FORCED_DOWN; + node2.state = NodeState.DOWN; + + // When + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node2.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestUp(node2.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + // down / forceUp / down => keep the last forced event => forceUp + assertThat(node1.state).isEqualTo(NodeState.UP); + // down / up => keep the last => up + assertThat(node2.state).isEqualTo(NodeState.UP); + } + + @Test + public void should_track_open_connections() { + new NodeStateManager(context); + + assertThat(node1.openConnections).isEqualTo(0); + + eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + waitForPendingAdminTasks(); + assertThat(node1.openConnections).isEqualTo(2); + + eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); + waitForPendingAdminTasks(); + assertThat(node1.openConnections).isEqualTo(1); + } + + @Test + public void should_mark_node_up_if_down_or_unknown_and_connection_opened() { + new NodeStateManager(context); + + int i = 0; + for (NodeState oldState : ImmutableList.of(NodeState.DOWN, NodeState.UNKNOWN)) { + // Given + node1.state = oldState; + + // When + eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + } + } + + @Test + public void should_not_mark_node_up_if_forced_down_and_connection_opened() { + // Given + new NodeStateManager(context); + node1.state = NodeState.FORCED_DOWN; + + // When + eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + @Test + public void should_track_reconnections() { + new NodeStateManager(context); + + assertThat(node1.reconnections).isEqualTo(0); + + eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + waitForPendingAdminTasks(); + assertThat(node1.reconnections).isEqualTo(2); + + eventBus.fire(ChannelEvent.reconnectionStopped(node1.getConnectAddress())); + waitForPendingAdminTasks(); + assertThat(node1.reconnections).isEqualTo(1); + } + + @Test + public void should_mark_node_down_if_reconnection_starts_with_no_connections() { + new NodeStateManager(context); + + node1.state = NodeState.UP; + node1.openConnections = 1; + + eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + assertThat(node1.state).isEqualTo(NodeState.DOWN); + Mockito.verify(eventBus).fire(new NodeStateEvent(NodeState.DOWN, node1)); + } + + @Test + public void should_keep_node_up_if_reconnection_starts_with_some_connections() { + new NodeStateManager(context); + + node1.state = NodeState.UP; + node1.openConnections = 2; + + eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + } + + // Wait for all the tasks on the pool's admin executor to complete. + private void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index a6a81b5c7cf..f99efa513fe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -18,27 +18,29 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; -import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoop; -import io.netty.channel.local.LocalAddress; import io.netty.util.concurrent.Future; -import java.net.SocketAddress; +import java.net.InetSocketAddress; import java.time.Duration; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -49,18 +51,18 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; public class ChannelPoolTest { - private static final SocketAddress ADDRESS = new LocalAddress("test"); + private static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); private @Mock InternalDriverContext context; private @Mock ReconnectionPolicy reconnectionPolicy; private @Mock ReconnectionSchedule reconnectionSchedule; private @Mock NettyOptions nettyOptions; + private @Mock EventBus eventBus; private @Mock ChannelFactory channelFactory; - private BlockingQueue> channelFactoryFutures; private DefaultEventLoopGroup adminEventLoopGroup; @BeforeMethod @@ -71,17 +73,9 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.channelFactory()).thenReturn(channelFactory); - channelFactoryFutures = new ArrayBlockingQueue<>(10); - Mockito.when(channelFactory.connect(eq(ADDRESS), any(DriverChannelOptions.class))) - .thenAnswer( - invocation -> { - CompletableFuture channelFuture = new CompletableFuture<>(); - channelFactoryFutures.offer(channelFuture); - return channelFuture; - }); - Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override @@ -96,39 +90,74 @@ public void teardown() { @Test public void should_initialize_when_all_channels_succeed() throws Exception { - int channelCount = 3; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 3; - // Pool init should have called the channel factory. Complete the responses: DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + .build(); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture) .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); + Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(ADDRESS)); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_initialize_when_all_channels_fail() throws Exception { - int channelCount = 3; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); - - for (int i = 0; i < channelCount; i++) { - channelFactoryFutures - .take() - .completeExceptionally(new Exception("mock channel init failure")); - } + int poolSize = 3; + + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .build(); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { + int poolSize = 3; + + ClusterNameMismatchException error = + new ClusterNameMismatchException(ADDRESS, "actual", "expected"); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, error) + .failure(ADDRESS, error) + .failure(ADDRESS, error) + .build(); + + ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + + Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + + factoryHelper.verifyNoMoreCalls(); } @Test @@ -136,182 +165,271 @@ public void should_reconnect_when_init_incomplete() throws Exception { // Short delay so we don't have to wait in the test Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 2; - // 1 channel fails, the other succeeds - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); - + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // Init: 1 channel fails, the other succeeds + .failure(ADDRESS, "mock channel init failure") + .success(ADDRESS, channel1) + // 1st reconnection + .pending(ADDRESS, channel2Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); // A reconnection should have been scheduled Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); - DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - + channel2Future.complete(channel2); + factoryHelper.waitForCalls(ADDRESS, 1); waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); assertThat(pool.channels).containsOnly(channel1, channel2); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_reconnect_when_channel_dies() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 2; - // The 2 channels succeed DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); // Simulate fatal error on channel2 ((ChannelPromise) channel2.closeFuture()) .setFailure(new Exception("mock channel init failure")); - waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS)); Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + factoryHelper.waitForCall(ADDRESS); - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); - + channel3Future.complete(channel3); waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); assertThat(pool.channels).containsOnly(channel1, channel3); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_shrink_outside_of_reconnection() throws Exception { - int channelCount = 4; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 4; DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); DriverChannel channel4 = newMockDriverChannel(4); - channelFactoryFutures.take().complete(channel4); - + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(ADDRESS)); pool.resize(2); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); assertThat(pool.channels).containsOnly(channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_shrink_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 4; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 4; DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); // A reconnection should have been scheduled to add the missing channels, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); pool.resize(2); waitForPendingAdminTasks(); - // Now allow the reconnected channel to complete initialization - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); - DriverChannel channel4 = newMockDriverChannel(4); - channelFactoryFutures.take().complete(channel4); + // Now allow the reconnected channels to complete initialization + channel3Future.complete(channel3); + channel4Future.complete(channel4); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); + // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); assertThat(pool.channels).containsOnly(channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_grow_outside_of_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + + int poolSize = 2; DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // growth attempt + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); pool.resize(4); - waitForPendingAdminTasks(); // The resizing should have triggered a reconnection Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); - DriverChannel channel4 = newMockDriverChannel(4); - channelFactoryFutures.take().complete(channel4); - + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_grow_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); - DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + int poolSize = 2; + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .failure(ADDRESS, "mock channel init failure") + // first reconnection attempt + .pending(ADDRESS, channel2Future) + // extra reconnection attempt after we realize the pool must grow + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -319,44 +437,55 @@ public void should_grow_during_reconnection() throws Exception { // A reconnection should have been scheduled to add the missing channel, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); pool.resize(4); waitForPendingAdminTasks(); // Complete the channel for the first reconnection, bringing the count to 2 - DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - + channel2Future.complete(channel2); + factoryHelper.waitForCall(ADDRESS); waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); assertThat(pool.channels).containsOnly(channel1, channel2); - // Another reconnection should have been scheduled when evaluating the first attempt + // A second attempt should have been scheduled since we're now still under the target size Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); - - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); - DriverChannel channel4 = newMockDriverChannel(4); - channelFactoryFutures.take().complete(channel4); - + // Same reconnection is still running, no additional events + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + + // Two more channels get opened, bringing us to the target count + factoryHelper.waitForCalls(ADDRESS, 2); + channel3Future.complete(channel3); + channel4Future.complete(channel4); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_switch_keyspace_on_existing_channels() throws Exception { - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 2; - // The 2 channels succeed DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .build(); + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -371,19 +500,33 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { Mockito.verify(channel2).setKeyspace(newKeyspace); assertThat(setKeyspaceFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_switch_keyspace_on_pending_channels() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 2; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); - - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); + int poolSize = 2; + DriverChannel channel1 = newMockDriverChannel(1); + CompletableFuture channel1Future = new CompletableFuture<>(); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel1Future) + .pending(ADDRESS, channel2Future) + .build(); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -391,6 +534,8 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { // Check that reconnection has kicked in, but do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + factoryHelper.waitForCalls(ADDRESS, 2); // Switch keyspace, it succeeds immediately since there is no active channel CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); @@ -399,52 +544,68 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { assertThat(setKeyspaceFuture).isSuccess(); // Now let the two channels succeed to complete the reconnection - DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); - DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - + channel1Future.complete(channel1); + channel2Future.complete(channel2); waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); Mockito.verify(channel1).setKeyspace(newKeyspace); Mockito.verify(channel2).setKeyspace(newKeyspace); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_close_all_channels_when_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 3; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 3; DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - + DriverChannel channel3 = newMockDriverChannel(2); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); + // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCalls(ADDRESS, 1); CompletionStage closeFuture = pool.close(); - - // Complete the reconnection after the pool was closed - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); - waitForPendingAdminTasks(); // The two original channels were closed normally Mockito.verify(channel1).close(); Mockito.verify(channel2).close(); - // The reconnection channel was force-closed when it succeeded and we found out the pool was - // closed + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + + // Complete the reconnecting channel + channel3Future.complete(channel3); + waitForPendingAdminTasks(); + + // It should be force-closed once we find out the pool was closed Mockito.verify(channel3).forceClose(); + // No events because the channel was never really associated to the pool + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS)); // Note that we don't wait for reconnected channels to close, so the pool only depends on // channel 1 and 2 @@ -452,43 +613,61 @@ public void should_close_all_channels_when_closed() throws Exception { ((ChannelPromise) channel2.closeFuture()).setSuccess(); assertThat(closeFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); } @Test public void should_force_close_all_channels_when_force_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int channelCount = 3; - CompletionStage poolFuture = - ChannelPool.init(ADDRESS, null, channelCount, context); + int poolSize = 3; DriverChannel channel1 = newMockDriverChannel(1); - channelFactoryFutures.take().complete(channel1); DriverChannel channel2 = newMockDriverChannel(2); - channelFactoryFutures.take().complete(channel2); - channelFactoryFutures.take().completeExceptionally(new Exception("mock channel init failure")); - + DriverChannel channel3 = newMockDriverChannel(2); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCalls(ADDRESS, 1); CompletionStage closeFuture = pool.forceClose(); + waitForPendingAdminTasks(); - // Complete the reconnection after the pool was closed - DriverChannel channel3 = newMockDriverChannel(3); - channelFactoryFutures.take().complete(channel3); + // The two original channels were force-closed + Mockito.verify(channel1).close(); + Mockito.verify(channel2).close(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + // Complete the reconnecting channel + channel3Future.complete(channel3); waitForPendingAdminTasks(); - // The two original channels were force-closed - Mockito.verify(channel1).forceClose(); - Mockito.verify(channel2).forceClose(); - // The reconnection channel was force-closed when it succeeded and we found out the pool was - // closed + // It should be force-closed once we find out the pool was closed Mockito.verify(channel3).forceClose(); + // No events because the channel was never really associated to the pool + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS)); // Note that we don't wait for reconnected channels to close, so the pool only depends on // channel 1 and 2 @@ -496,6 +675,8 @@ public void should_force_close_all_channels_when_force_closed() throws Exception ((ChannelPromise) channel2.closeFuture()).setSuccess(); assertThat(closeFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); } private DriverChannel newMockDriverChannel(int id) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java new file mode 100644 index 00000000000..338a741cd93 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.google.common.base.Joiner; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.ScheduledFuture; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class DebouncerTest { + + private static final Duration DEFAULT_WINDOW = Duration.ofSeconds(1); + private static final int DEFAULT_MAX_EVENTS = 10; + + @Mock private EventExecutor adminExecutor; + @Mock private ScheduledFuture scheduledFuture; + private List results; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + Mockito.when(adminExecutor.inEventLoop()).thenReturn(true); + Mockito.when( + adminExecutor.schedule( + Mockito.any(Runnable.class), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS))) + .thenAnswer((i) -> scheduledFuture); + results = new ArrayList<>(); + } + + private String coalesce(List events) { + return Joiner.on(",").join(events); + } + + private void flush(String result) { + results.add(result); + } + + @Test + public void should_flush_synchronously_if_window_is_zero() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, Duration.ZERO, DEFAULT_MAX_EVENTS); + + debouncer.receive(1); + debouncer.receive(2); + + Mockito.verify(adminExecutor, never()) + .schedule(Mockito.any(Runnable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class)); + + assertThat(results).containsExactly("1", "2"); + } + + @Test + public void should_flush_synchronously_if_max_events_is_one() { + Debouncer debouncer = + new Debouncer<>(adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, 1); + + debouncer.receive(1); + debouncer.receive(2); + + Mockito.verify(adminExecutor, never()) + .schedule(Mockito.any(Runnable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class)); + + assertThat(results).containsExactly("1", "2"); + } + + @Test + public void should_debounce_after_time_window_if_no_other_event() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); + debouncer.receive(1); + + // a task should have been scheduled, run it + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + Mockito.verify(adminExecutor) + .schedule( + captor.capture(), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + captor.getValue().run(); + + // the element should have been flushed + assertThat(results).containsExactly("1"); + } + + @Test + public void should_reset_time_window_when_new_event() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); + debouncer.receive(1); + debouncer.receive(2); + + InOrder inOrder = Mockito.inOrder(adminExecutor, scheduledFuture); + + // a first task should have been scheduled, and then cancelled + inOrder + .verify(adminExecutor) + .schedule( + Mockito.any(Runnable.class), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + inOrder.verify(scheduledFuture).cancel(true); + + // a second task should have been scheduled, run it + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + inOrder + .verify(adminExecutor) + .schedule( + captor.capture(), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + captor.getValue().run(); + + // both elements should have been flushed together + assertThat(results).containsExactly("1,2"); + } + + @Test + public void should_force_flush_after_max_events() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); + for (int i = 0; i < 10; i++) { + debouncer.receive(i); + } + Mockito.verify(adminExecutor, times(9)) + .schedule( + Mockito.any(Runnable.class), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + Mockito.verify(scheduledFuture, times(9)).cancel(true); + assertThat(results).containsExactly("0,1,2,3,4,5,6,7,8,9"); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index b0f50e40e5a..6d8271f26df 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -36,6 +36,8 @@ public class ReconnectionTest { @Mock private ReconnectionPolicy reconnectionPolicy; @Mock private ReconnectionSchedule reconnectionSchedule; + @Mock private Runnable onStartCallback; + @Mock private Runnable onStopCallback; private EmbeddedChannel channel; private MockReconnectionTask reconnectionTask; @@ -52,7 +54,9 @@ public void setup() { EventExecutor eventExecutor = channel.eventLoop(); reconnectionTask = new MockReconnectionTask(); - reconnection = new Reconnection(eventExecutor, reconnectionPolicy, reconnectionTask); + reconnection = + new Reconnection( + eventExecutor, reconnectionPolicy, reconnectionTask, onStartCallback, onStopCallback); } @Test @@ -71,6 +75,7 @@ public void should_schedule_first_attempt_on_start() { // Then Mockito.verify(reconnectionSchedule).nextDelay(); assertThat(reconnection.isRunning()).isTrue(); + Mockito.verify(onStartCallback).run(); } @Test(expectedExceptions = IllegalStateException.class) @@ -97,6 +102,7 @@ public void should_stop_if_first_attempt_succeeds() { // Then assertThat(reconnection.isRunning()).isFalse(); + Mockito.verify(onStopCallback).run(); } @Test @@ -130,6 +136,60 @@ public void should_reschedule_if_first_attempt_fails() { // Then assertThat(reconnection.isRunning()).isFalse(); + Mockito.verify(onStopCallback).run(); + } + + @Test + public void should_reconnect_now_if_running() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + reconnection.start(); + Mockito.verify(reconnectionSchedule).nextDelay(); + + // When + reconnection.reconnectNow(false); + runPendingTasks(); + + // Then + // reconnection task was run immediately + assertThat(reconnectionTask.wasCalled()).isTrue(); + // if that attempt failed, another reconnection was scheduled + reconnectionTask.complete(false); + runPendingTasks(); + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + } + + @Test + public void should_reconnect_now_if_stopped_and_forced() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + assertThat(reconnection.isRunning()).isFalse(); + + // When + reconnection.reconnectNow(true); + runPendingTasks(); + + // Then + // reconnection task was run immediately + assertThat(reconnectionTask.wasCalled()).isTrue(); + // if that attempt failed, another reconnection was scheduled + reconnectionTask.complete(false); + runPendingTasks(); + Mockito.verify(reconnectionSchedule).nextDelay(); + } + + @Test + public void should_not_reconnect_now_if_stopped_and_not_forced() { + // Given + assertThat(reconnection.isRunning()).isFalse(); + + // When + reconnection.reconnectNow(false); + runPendingTasks(); + + // Then + // reconnection task was run immediately + assertThat(reconnectionTask.wasCalled()).isFalse(); } @Test diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 945e1d8a3c1..98b11c52e44 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -24,5 +24,5 @@ - + \ No newline at end of file From d708d9ccf93e41fb2afb035cc293b9d91103e144 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 18 Apr 2017 17:16:02 -0700 Subject: [PATCH 019/742] Initialize load balancing policy at startup --- .../loadbalancing/LoadBalancingPolicy.java | 32 ++- .../RoundRobinLoadBalancingPolicy.java | 45 +++- .../driver/internal/core/DefaultCluster.java | 17 +- .../core/control/ControlConnection.java | 31 ++- .../metadata/LoadBalancingPolicyWrapper.java | 57 ++++- .../core/metadata/NodeStateEvent.java | 26 ++- .../core/metadata/NodeStateManager.java | 2 +- .../core/metadata/TopologyMonitor.java | 10 +- .../util/concurrent/ReplayingEventFilter.java | 107 +++++++++ .../core/control/ControlConnectionTest.java | 2 + .../control/ControlConnectionTestBase.java | 2 + .../LoadBalancingPolicyWrapperTest.java | 211 ++++++++++++++++++ .../core/metadata/NodeStateManagerTest.java | 37 ++- .../concurrent/ReplayingEventFilterTest.java | 65 ++++++ 14 files changed, 597 insertions(+), 47 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index 3edfee6295a..45b3cc387a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.loadbalancing; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import java.util.Queue; import java.util.Set; @@ -27,6 +28,11 @@ public interface LoadBalancingPolicy { * *

This method is guaranteed to be called exactly once per instance, and before any other * method in this class. + * + * @param nodes the nodes discovered by the driver when it connected to the cluster. When this + * method is invoked, their state is guaranteed to be either {@link NodeState#UP} or {@link + * NodeState#UNKNOWN}. Node states may be updated concurrently while this method executes, but + * if so you will receive a notification */ void init(Set nodes, DistanceReporter distanceReporter); @@ -40,8 +46,28 @@ public interface LoadBalancingPolicy { */ Queue newQueryPlan(/*TODO keyspace, statement*/ ); - // TODO node state methods (onUp, etc.) - // TODO way to emit distance events + /** + * Called when a node is added to the cluster. + * + *

The new node will have the state {@link NodeState#UNKNOWN}. The actual state will be known + * when: + * + *

    + *
  • the load balancing policy signals an active distance for the node, and the driver tries + * to connect to it. + *
  • or a topology event is received from the cluster. + *
+ */ + void onAdd(Node node); + + /** Called when a node is determined to be up. */ + void onUp(Node node); + + /** Called when a node is determined to be down. */ + void onDown(Node node); + + /** Called when a node is removed from the cluster. */ + void onRemove(Node node); /** * Invoked at cluster shutdown. This gives the policy the opportunity to perform some cleanup, for @@ -49,7 +75,7 @@ public interface LoadBalancingPolicy { */ void close(); - /** An object that the policy uses to push the decisions it makes about node distances. */ + /** An object that the policy uses to signal decisions it makes about node distances. */ interface DistanceReporter { void setDistance(Node node, NodeDistance distance); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index c17bcd0e9b1..ae1bb59ce77 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -17,19 +17,28 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; -import com.google.common.collect.ImmutableList; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntUnaryOperator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * A round-robin load balancing policy. + * + *

It assigns distance {@link NodeDistance#LOCAL} to all up nodes. Each query plan returns all + * the nodes, starting at an incrementing index and traversing the list in a circular fashion. + */ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { + private static final Logger LOG = LoggerFactory.getLogger(RoundRobinLoadBalancingPolicy.class); private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; private final AtomicInteger startIndex = new AtomicInteger(); - private ImmutableList nodes; + private final CopyOnWriteArrayList liveNodes = new CopyOnWriteArrayList<>(); public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { // nothing to do @@ -37,23 +46,45 @@ public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext c @Override public void init(Set nodes, DistanceReporter distanceReporter) { - // TODO handle node states (this is a temporary impl. to kickstart things) - this.nodes = ImmutableList.copyOf(nodes); - for (Node node : this.nodes) { + LOG.debug("Initializing with {}", nodes); + this.liveNodes.addAll(nodes); + for (Node node : nodes) { distanceReporter.setDistance(node, NodeDistance.LOCAL); } } @Override public Queue newQueryPlan() { + Object[] snapshot = liveNodes.toArray(); int myStartIndex = startIndex.getAndUpdate(INCREMENT); ConcurrentLinkedQueue plan = new ConcurrentLinkedQueue<>(); - for (int i = 0; i < nodes.size(); i++) { - plan.offer(nodes.get((myStartIndex + i) % nodes.size())); + for (int i = 0; i < snapshot.length; i++) { + Node node = (Node) snapshot[(myStartIndex + i) % liveNodes.size()]; + plan.offer(node); } return plan; } + @Override + public void onAdd(Node node) { + onUp(node); + } + + @Override + public void onUp(Node node) { + liveNodes.add(node); + } + + @Override + public void onDown(Node node) { + liveNodes.remove(node); + } + + @Override + public void onRemove(Node node) { + onDown(node); + } + @Override public void close() { // nothing to do diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 148451d5038..0f9eb9da933 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -100,7 +100,22 @@ private void init() { initFuture.complete(DefaultCluster.this); // Launch a full refresh asynchronously - metadataManager.refreshNodes(); + metadataManager + .refreshNodes() + .whenComplete( + (result, error) -> { + if (error != null) { + LOG.debug("Error while refreshing node list", error); + } else { + try { + context.loadBalancingPolicyWrapper().init(); + } catch (Throwable t) { + LOG.warn( + "Unexpected error while initializing load balancing policy", t); + } + } + }); + // TODO schedule full schema refresh }) .exceptionally( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 05f8d47aa76..6ade97ee58e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -208,7 +208,14 @@ private CompletionStage reconnect() { assert adminExecutor.inEventLoop(); Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); CompletableFuture result = new CompletableFuture<>(); - connect(nodes, null, () -> result.complete(true), error -> result.complete(false)); + connect( + nodes, + null, + () -> { + result.complete(true); + onSuccessfulReconnect(); + }, + error -> result.complete(false)); return result; } @@ -271,6 +278,28 @@ private void connect( } } + private void onSuccessfulReconnect() { + // Always perform a full refresh (we don't know how long we were disconnected) + context + .metadataManager() + .refreshNodes() + .whenComplete( + (result, error) -> { + if (error != null) { + LOG.debug("Error while refreshing node list", error); + } else { + try { + // This does nothing if the LBP is initialized already + context.loadBalancingPolicyWrapper().init(); + } catch (Throwable t) { + LOG.warn("Unexpected error while initializing load balancing policy", t); + } + } + }); + + // TODO refresh schema metadata + } + private void onChannelClosed(DriverChannel channel) { assert adminExecutor.inEventLoop(); LOG.debug("Lost channel {}", channel); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index df031ca53fe..be9c514c67b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -17,13 +17,16 @@ import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; +import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Queue; -import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; @@ -33,10 +36,11 @@ * Wraps the user-provided LBP for internal use. This serves multiple purposes: * *

    - *
  • help enforce the guarantee that init is called exactly once, and before any other method. - *
  • handle the early stages of initialization (before first actual connect), where the LBP is not - * ready yet. - *
  • process distance events. + *
  • help enforce the guarantee that init is called exactly once, and before any other method. + *
  • handle the early stages of initialization (before first actual connect), where the LBP is + * not ready yet. + *
  • handle incoming node state events from the outside world and propagate them to the policy. + *
  • process distance decisions from the policy and propagate them to the outside world. *
*/ public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceReporter { @@ -44,16 +48,24 @@ public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceR private final InternalDriverContext context; private final LoadBalancingPolicy policy; + private final ReplayingEventFilter eventFilter = + new ReplayingEventFilter<>(this::processNodeStateEvent); private AtomicBoolean isInit = new AtomicBoolean(); public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPolicy policy) { this.context = context; this.policy = policy; + context.eventBus().register(NodeStateEvent.class, this::onNodeStateEvent); } - public void init(Set nodes) { + public void init() { if (isInit.compareAndSet(false, true)) { - policy.init(nodes, this); + // State events can happen concurrently with init, so we must record them and replay once the + // policy is initialized. + eventFilter.start(); + Metadata metadata = context.metadataManager().getMetadata(); + policy.init(excludeDownHosts(metadata), this); + eventFilter.markReady(); } } @@ -77,4 +89,35 @@ public void setDistance(Node node, NodeDistance distance) { defaultNode.distance = distance; context.eventBus().fire(new DistanceEvent(distance, defaultNode)); } + + // when it comes in from the outside + private void onNodeStateEvent(NodeStateEvent event) { + eventFilter.accept(event); + } + + // once it has gone through the filter + private void processNodeStateEvent(NodeStateEvent event) { + assert isInit.get(); + if (event.newState == NodeState.UP) { + policy.onUp(event.node); + } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { + policy.onDown(event.node); + } else if (event.newState == NodeState.UNKNOWN) { + policy.onAdd(event.node); + } else if (event.newState == null) { + policy.onDown(event.node); + } else { + LOG.warn("Unsupported event: " + event); + } + } + + private static ImmutableSet excludeDownHosts(Metadata metadata) { + ImmutableSet.Builder nodes = ImmutableSet.builder(); + for (Node node : metadata.getNodes().values()) { + if (node.getState() == NodeState.UP || node.getState() == NodeState.UNKNOWN) { + nodes.add(node); + } + } + return nodes.build(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java index bcb4a28b39c..60922e7e32a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java @@ -16,14 +16,31 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.google.common.base.Preconditions; import java.util.Objects; public class NodeStateEvent { + public static NodeStateEvent changed(NodeState oldState, NodeState newState, DefaultNode node) { + Preconditions.checkNotNull(oldState); + Preconditions.checkNotNull(newState); + return new NodeStateEvent(oldState, newState, node); + } + + public static NodeStateEvent added(DefaultNode node) { + return new NodeStateEvent(null, NodeState.UNKNOWN, node); + } + + public static NodeStateEvent removed(DefaultNode node) { + return new NodeStateEvent(null, null, node); + } + + public final NodeState oldState; public final NodeState newState; public final DefaultNode node; - public NodeStateEvent(NodeState newState, DefaultNode node) { + private NodeStateEvent(NodeState oldState, NodeState newState, DefaultNode node) { this.node = node; + this.oldState = oldState; this.newState = newState; } @@ -33,7 +50,8 @@ public boolean equals(Object other) { return true; } else if (other instanceof NodeStateEvent) { NodeStateEvent that = (NodeStateEvent) other; - return this.newState == that.newState + return this.oldState == that.oldState + && this.newState == that.newState && Objects.equals(this.node.getConnectAddress(), that.node.getConnectAddress()); } else { return false; @@ -42,11 +60,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(newState, node.getConnectAddress()); + return Objects.hash(oldState, newState, node.getConnectAddress()); } @Override public String toString() { - return "NodeStateEvent(" + newState + ", " + node.getConnectAddress() + ")"; + return "NodeStateEvent(" + oldState + "=>" + newState + ", " + node.getConnectAddress() + ")"; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 9271f228ffa..e80d0559ba4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -198,7 +198,7 @@ private void setState(DefaultNode node, NodeState newState, String reason) { if (oldState != newState) { LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); node.state = newState; - eventBus.fire(new NodeStateEvent(newState, node)); + eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index cc79946d095..b34ad8dba8b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.control.ControlConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Map; @@ -67,10 +68,13 @@ public interface TopologyMonitor { *

This will be invoked directly from a driver's internal thread; if the refresh involves * blocking I/O or heavy computations, it should be scheduled on a separate thread. * - *

Implementation note: as shown by the signature, it is assumed that the full node list will - * always be returned in a single message (no paging). + *

The driver calls this at initialization; if that initial call fails, the load balancing + * policy is not initialized, and the driver is unable to execute queries. You should schedule + * retries to ensure that the call eventually succeeds (see how the default implementation does it + * in {@link ControlConnection.SingleThreaded#onSuccessfulReconnect()}). * - * @return a future that completes with the information. + * @return a future that completes with the information. We assume that the full node list will + * always be returned in a single message (no paging). */ CompletionStage> refreshNodeList(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java new file mode 100644 index 00000000000..a7bfad9c67e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; + +/** + * Filters a list of events, accumulating them during an initialization period. + * + *

It has three states: + * + *

    + *
  • Not started: events are discarded. + *
  • Started: events accumulate but are not propagated to the end consumer yet. + *
  • Ready: all accumulated events are flushed to the end consumer; subsequent events are + * propagated directly. The order of events is preserved at all times. + *
+ */ +public class ReplayingEventFilter { + + private enum State { + NEW, + STARTED, + READY + } + + private final Consumer consumer; + + // Exceptionally, we use a lock: it will rarely be contended, and if so for only a short period. + private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); + private State state; + private List recordedEvents; + + public ReplayingEventFilter(Consumer consumer) { + this.consumer = consumer; + this.state = State.NEW; + this.recordedEvents = new ArrayList<>(); + } + + public void start() { + stateLock.writeLock().lock(); + try { + state = State.STARTED; + } finally { + stateLock.writeLock().unlock(); + } + } + + public void markReady() { + stateLock.writeLock().lock(); + try { + state = State.READY; + for (T event : recordedEvents) { + consumer.accept(event); + } + } finally { + stateLock.writeLock().unlock(); + } + } + + public void accept(T event) { + stateLock.readLock().lock(); + try { + switch (state) { + case NEW: + break; + case STARTED: + recordedEvents.add(event); + break; + case READY: + consumer.accept(event); + break; + } + } finally { + stateLock.readLock().unlock(); + } + } + + @VisibleForTesting + public List recordedEvents() { + stateLock.readLock().lock(); + try { + return ImmutableList.copyOf(recordedEvents); + } finally { + stateLock.readLock().unlock(); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 12d7cd42114..48ce8ce602f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -161,6 +161,8 @@ public void should_reconnect_if_channel_goes_down() throws Exception { assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(metadataManager).refreshNodes(); + Mockito.verify(loadBalancingPolicyWrapper).init(); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index e1e97d55283..ddb899bbfa2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -110,6 +110,8 @@ public void setup() { return queryPlan; }); + Mockito.when(metadataManager.refreshNodes()) + .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(context.metadataManager()).thenReturn(metadataManager); addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java new file mode 100644 index 00000000000..e60033f3c68 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.never; + +public class LoadBalancingPolicyWrapperTest { + + private DefaultNode node1; + private DefaultNode node2; + private DefaultNode node3; + + private Map contactPointsMap; + private Queue policysQueryPlan; + + @Mock private InternalDriverContext context; + @Mock private LoadBalancingPolicy loadBalancingPolicy; + private EventBus eventBus; + @Mock private MetadataManager metadataManager; + @Mock private Metadata metadata; + + private LoadBalancingPolicyWrapper wrapper; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042)); + node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042)); + node3 = new DefaultNode(new InetSocketAddress("127.0.0.3", 9042)); + + contactPointsMap = + ImmutableMap.builder() + .put(node1.getConnectAddress(), node1) + .put(node2.getConnectAddress(), node2) + .build(); + Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap); + Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + + policysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); + Mockito.when(loadBalancingPolicy.newQueryPlan()).thenReturn(policysQueryPlan); + + eventBus = Mockito.spy(new EventBus()); + Mockito.when(context.eventBus()).thenReturn(eventBus); + + wrapper = new LoadBalancingPolicyWrapper(context, loadBalancingPolicy); + } + + @Test + public void should_build_query_plan_from_contact_points_before_init() { + // When + Queue queryPlan = wrapper.newQueryPlan(); + + // Then + Mockito.verify(loadBalancingPolicy, never()).newQueryPlan(); + assertThat(queryPlan).containsOnlyElementsOf(contactPointsMap.values()); + } + + @Test + public void should_fetch_query_plan_from_policy_after_init() { + // Given + wrapper.init(); + Mockito.verify(loadBalancingPolicy).init(anySet(), any(DistanceReporter.class)); + + // When + Queue queryPlan = wrapper.newQueryPlan(); + + // Then + Mockito.verify(loadBalancingPolicy).newQueryPlan(); + assertThat(queryPlan).isEqualTo(policysQueryPlan); + } + + @Test + public void should_init_policy_with_up_or_unknown_nodes() { + // Given + node1.state = NodeState.UP; + node2.state = NodeState.UNKNOWN; + node3.state = NodeState.DOWN; + Map contactPointsMap2 = + ImmutableMap.builder() + .put(node1.getConnectAddress(), node1) + .put(node2.getConnectAddress(), node2) + .put(node3.getConnectAddress(), node3) + .build(); + Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap2); + + // When + wrapper.init(); + + // Then + @SuppressWarnings("unchecked") + ArgumentCaptor> captor = ArgumentCaptor.forClass(Set.class); + Mockito.verify(loadBalancingPolicy).init(captor.capture(), any(DistanceReporter.class)); + Set initNodes = captor.getValue(); + assertThat(initNodes).containsOnly(node1, node2); + } + + @Test + public void should_propagate_distance_from_policy() { + // Given + wrapper.init(); + ArgumentCaptor captor = ArgumentCaptor.forClass(DistanceReporter.class); + Mockito.verify(loadBalancingPolicy).init(anySet(), captor.capture()); + DistanceReporter distanceReporter = captor.getValue(); + + // When + distanceReporter.setDistance(node1, NodeDistance.LOCAL); + + // Then + Mockito.verify(eventBus).fire(new DistanceEvent(NodeDistance.LOCAL, node1)); + } + + @Test + public void should_not_propagate_node_states_to_policy_until_init() { + // When + eventBus.fire(NodeStateEvent.changed(NodeState.UNKNOWN, NodeState.UP, node1)); + + // Then + Mockito.verify(loadBalancingPolicy, never()).onUp(node1); + } + + @Test + public void should_propagate_node_states_to_policy_after_init() { + // Given + wrapper.init(); + + // When + eventBus.fire(NodeStateEvent.changed(NodeState.UNKNOWN, NodeState.UP, node1)); + + // Then + Mockito.verify(loadBalancingPolicy).onUp(node1); + } + + @Test + public void should_accumulate_events_during_init_and_replay() { + // Given + // Hack to obtain concurrency: the main thread blocks in init, while another thread fires an + // event on the bus + CountDownLatch eventLatch = new CountDownLatch(1); + CountDownLatch initLatch = new CountDownLatch(1); + Answer mockInit = + i -> { + eventLatch.countDown(); + initLatch.await(100, TimeUnit.MILLISECONDS); + return null; + }; + Mockito.doAnswer(mockInit) + .when(loadBalancingPolicy) + .init(anySet(), any(DistanceReporter.class)); + + // When + Runnable runnable = + () -> { + try { + eventLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + eventBus.fire(NodeStateEvent.changed(NodeState.UNKNOWN, NodeState.DOWN, node1)); + }; + Thread thread = new Thread(runnable); + thread.start(); + wrapper.init(); + + // Then + assertThat(thread.isAlive()).isFalse(); + Mockito.verify(loadBalancingPolicy).onDown(node1); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index bb75ae442ba..4e30d7ad9f1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -47,7 +47,6 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; public class NodeStateManagerTest { private static final InetSocketAddress NEW_ADDRESS = new InetSocketAddress("127.0.0.3", 9042); @@ -120,7 +119,6 @@ public void should_ignore_up_event_if_node_is_already_up_or_forced_down() { public void should_apply_up_event_if_node_is_unknown_or_down() { new NodeStateManager(context); - int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN)) { // Given node1.state = oldState; @@ -131,7 +129,7 @@ public void should_apply_up_event_if_node_is_unknown_or_down() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -169,7 +167,6 @@ public void should_ignore_down_event_if_node_is_down_or_forced_down() { @Test public void should_ignore_down_event_if_node_has_active_connections() { - // Given new NodeStateManager(context); node1.state = NodeState.UP; eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); @@ -187,18 +184,21 @@ public void should_ignore_down_event_if_node_has_active_connections() { @Test public void should_apply_down_event_if_node_has_no_active_connections() { - // Given new NodeStateManager(context); - node1.state = NodeState.UP; - assertThat(node1.openConnections).isEqualTo(0); - // When - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); - waitForPendingAdminTasks(); + for (NodeState oldState : ImmutableList.of(NodeState.UP, NodeState.UNKNOWN)) { + // Given + node1.state = oldState; + assertThat(node1.openConnections).isEqualTo(0); - // Then - assertThat(node1.state).isEqualTo(NodeState.DOWN); - Mockito.verify(eventBus).fire(new NodeStateEvent(NodeState.DOWN, node1)); + // When + eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + // Then + assertThat(node1.state).isEqualTo(NodeState.DOWN); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.DOWN, node1)); + } } @Test @@ -234,7 +234,6 @@ public void should_ignore_force_down_event_if_already_forced_down() { public void should_apply_force_down_event_over_any_other_state() { new NodeStateManager(context); - int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN, NodeState.UP)) { // Given node1.state = oldState; @@ -245,7 +244,7 @@ public void should_apply_force_down_event_over_any_other_state() { // Then assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); - Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.FORCED_DOWN, node1)); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.FORCED_DOWN, node1)); } } @@ -282,7 +281,6 @@ public void should_ignore_force_up_event_if_node_is_already_up() { public void should_apply_force_up_event_if_node_is_not_up() { new NodeStateManager(context); - int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN, NodeState.FORCED_DOWN)) { // Given @@ -294,7 +292,7 @@ public void should_apply_force_up_event_if_node_is_not_up() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -412,7 +410,6 @@ public void should_track_open_connections() { public void should_mark_node_up_if_down_or_unknown_and_connection_opened() { new NodeStateManager(context); - int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.DOWN, NodeState.UNKNOWN)) { // Given node1.state = oldState; @@ -423,7 +420,7 @@ public void should_mark_node_up_if_down_or_unknown_and_connection_opened() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, times(++i)).fire(new NodeStateEvent(NodeState.UP, node1)); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -470,7 +467,7 @@ public void should_mark_node_down_if_reconnection_starts_with_no_connections() { waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.DOWN); - Mockito.verify(eventBus).fire(new NodeStateEvent(NodeState.DOWN, node1)); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java new file mode 100644 index 00000000000..5cc121c09f0 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import java.util.ArrayList; +import java.util.List; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ReplayingEventFilterTest { + private ReplayingEventFilter filter; + private List filteredEvents; + + @BeforeMethod + public void setup() { + filteredEvents = new ArrayList<>(); + filter = new ReplayingEventFilter<>(filteredEvents::add); + } + + @Test + public void should_discard_events_until_started() { + filter.accept(1); + filter.accept(2); + assertThat(filteredEvents).isEmpty(); + } + + @Test + public void should_accumulate_events_when_started() { + filter.accept(1); + filter.accept(2); + filter.start(); + filter.accept(3); + filter.accept(4); + assertThat(filter.recordedEvents()).containsExactly(3, 4); + } + + @Test + public void should_flush_accumulated_events_when_ready() { + filter.accept(1); + filter.accept(2); + filter.start(); + filter.accept(3); + filter.accept(4); + filter.markReady(); + assertThat(filteredEvents).containsExactly(3, 4); + filter.accept(5); + filter.accept(6); + assertThat(filteredEvents).containsExactly(3, 4, 5, 6); + } +} From 3294e299157f2ae5bcf0c74452e8a54b02b5bcb8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Apr 2017 10:46:42 -0700 Subject: [PATCH 020/742] Add Scala console to the build --- core/console.scala | 24 ++++++++++++++++++++++++ pom.xml | 13 +++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 core/console.scala diff --git a/core/console.scala b/core/console.scala new file mode 100644 index 00000000000..277bb1c97bb --- /dev/null +++ b/core/console.scala @@ -0,0 +1,24 @@ +/* + * Allows quick manual tests from the Scala console: + * + * cd core/ + * mvn scala:console + * + * The script below is run at init, then you can do `val cluster = builder.build()` and play with + * it. + * + * Note: on MacOS, the Scala plugin seems to break the terminal if you exit the console with `:q`. + * Use Ctrl+C instead. + */ +import com.datastax.oss.driver.api.core._ +import java.net.InetSocketAddress +import scala.collection.JavaConversions._ + +val address1 = new InetSocketAddress("127.0.0.1", 9042) +val address2 = new InetSocketAddress("127.0.0.2", 9042) +val address3 = new InetSocketAddress("127.0.0.3", 9042) +val address4 = new InetSocketAddress("127.0.0.4", 9042) +val address5 = new InetSocketAddress("127.0.0.5", 9042) +val address6 = new InetSocketAddress("127.0.0.6", 9042) + +val builder = Cluster.builder().withContactPoints(Set(address1)) diff --git a/pom.xml b/pom.xml index a655740f2cc..256c7aeee18 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,19 @@ maven-javadoc-plugin 2.10.4 + + + net.alchim31.maven + scala-maven-plugin + 3.2.1 + + 2.11 + + -i + console.scala + + + From c172bf6d9bb99eb319e4a1748182abee935123aa Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Apr 2017 11:50:58 -0700 Subject: [PATCH 021/742] Add more node information to the metadata --- .../oss/driver/api/core/CassandraVersion.java | 290 ++++++++++++++++++ .../datastax/oss/driver/api/core/Cluster.java | 17 + .../driver/api/core/metadata/Metadata.java | 10 +- .../oss/driver/api/core/metadata/Node.java | 65 +++- .../core/metadata/DefaultMetadata.java | 64 +--- .../internal/core/metadata/DefaultNode.java | 47 +++ .../core/metadata/FullNodeListRefresh.java | 87 ++++++ .../metadata/InitContactPointsRefresh.java | 48 +++ .../core/metadata/MetadataManager.java | 18 +- .../core/metadata/MetadataRefresh.java | 46 +++ .../core/metadata/NodeListRefresh.java | 61 ++++ .../com/datastax/oss/driver/Assertions.java | 6 + .../api/core/CassandraVersionAssert.java | 66 ++++ .../driver/api/core/CassandraVersionTest.java | 104 +++++++ .../metadata/FullNodeListRefreshTest.java | 85 +++++ .../InitContactPointsRefreshTest.java | 42 +++ 16 files changed, 978 insertions(+), 78 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java new file mode 100644 index 00000000000..bc7659eea4a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The version of a Cassandra release. + * + *

It is in the form X.Y.Z, with optional pre-release labels and build metadata. + * + *

Version numbers compare the usual way, the major number (X) is compared first, then the minor + * one (Y) and then the patch level one (Z). Lastly, versions with pre-release sorts before the + * versions that don't have one, and labels are sorted alphabetically if necessary. Build metadata + * are ignored for sorting versions. + */ +public class CassandraVersion implements Comparable { + + private static final String VERSION_REGEXP = + "(\\d+)\\.(\\d+)(\\.\\d+)?(\\.\\d+)?([~\\-]\\w[.\\w]*(?:\\-\\w[.\\w]*)*)?(\\+[.\\w]+)?"; + private static final Pattern pattern = Pattern.compile(VERSION_REGEXP); + + private final int major; + private final int minor; + private final int patch; + private final int dsePatch; + + private final String[] preReleases; + private final String build; + + private CassandraVersion( + int major, int minor, int patch, int dsePatch, String[] preReleases, String build) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.dsePatch = dsePatch; + this.preReleases = preReleases; + this.build = build; + } + + /** + * Parses a version from a string. + * + *

The version string should have primarily the form X.Y.Z to which can be appended one or more + * pre-release label after dashes (2.0.1-beta1, 2.1.4-rc1-SNAPSHOT) and an optional build label + * (2.1.0-beta1+a20ba.sha). Out of convenience, the "patch" version number, Z, can be omitted, in + * which case it is assumed to be 0. + * + * @param version the string to parse. + * @return the parsed version number. + * @throws IllegalArgumentException if the provided string does not represent a valid version. + */ + public static CassandraVersion parse(String version) { + if (version == null) { + return null; + } + + Matcher matcher = pattern.matcher(version); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid version number: " + version); + } + + try { + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + + String pa = matcher.group(3); + int patch = + pa == null || pa.isEmpty() + ? 0 + : Integer.parseInt( + pa.substring(1)); // dropping the initial '.' since it's included this time + + String dse = matcher.group(4); + int dsePatch = + dse == null || dse.isEmpty() + ? -1 + : Integer.parseInt( + dse.substring(1)); // dropping the initial '.' since it's included this time + + String pr = matcher.group(5); + String[] preReleases = + pr == null || pr.isEmpty() + ? null + : pr.substring(1) + .split("\\-"); // drop initial '-' or '~' then split on the remaining ones + + String bl = matcher.group(6); + String build = bl == null || bl.isEmpty() ? null : bl.substring(1); // drop the initial '+' + + return new CassandraVersion(major, minor, patch, dsePatch, preReleases, build); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid version number: " + version); + } + } + + /** + * The major version number. + * + * @return the major version number, i.e. X in X.Y.Z. + */ + public int getMajor() { + return major; + } + + /** + * The minor version number. + * + * @return the minor version number, i.e. Y in X.Y.Z. + */ + public int getMinor() { + return minor; + } + + /** + * The patch version number. + * + * @return the patch version number, i.e. Z in X.Y.Z. + */ + public int getPatch() { + return patch; + } + + /** + * The DSE patch version number (will only be present for version of Cassandra in DSE). + * + *

DataStax Entreprise (DSE) adds a fourth number to the version number to track potential hot + * fixes and/or DSE specific patches that may have been applied to the Cassandra version. In that + * case, this method returns that fourth number. + * + * @return the DSE patch version number, i.e. D in X.Y.Z.D, or -1 if the version number is not + * from DSE. + */ + public int getDSEPatch() { + return dsePatch; + } + + /** + * The pre-release labels if relevant, i.e. label1 and label2 in X.Y.Z-label1-lable2. + * + * @return the pre-release labels. The return list will be {@code null} if the version number + * doesn't have any. + */ + public List getPreReleaseLabels() { + return preReleases == null ? null : Collections.unmodifiableList(Arrays.asList(preReleases)); + } + + /** + * The build label if there is one. + * + * @return the build label or {@code null} if the version number doesn't have one. + */ + public String getBuildLabel() { + return build; + } + + /** + * The next stable version, i.e. the version stripped of its pre-release labels and build + * metadata. + * + *

This is mostly used during our development stage, where we test the driver against + * pre-release versions of Cassandra like 2.1.0-rc7-SNAPSHOT, but need to compare to the stable + * version 2.1.0 when testing for native protocol compatibility, etc. + * + * @return the next stable version. + */ + public CassandraVersion nextStable() { + return new CassandraVersion(major, minor, patch, dsePatch, null, null); + } + + @Override + public int compareTo(CassandraVersion other) { + if (major < other.major) { + return -1; + } + if (major > other.major) { + return 1; + } + + if (minor < other.minor) { + return -1; + } + if (minor > other.minor) { + return 1; + } + + if (patch < other.patch) { + return -1; + } + if (patch > other.patch) { + return 1; + } + + if (dsePatch < 0) { + if (other.dsePatch >= 0) { + return -1; + } + } else { + if (other.dsePatch < 0) { + return 1; + } + + // Both are >= 0 + if (dsePatch < other.dsePatch) { + return -1; + } + if (dsePatch > other.dsePatch) { + return 1; + } + } + + if (preReleases == null) { + return other.preReleases == null ? 0 : 1; + } + if (other.preReleases == null) { + return -1; + } + + for (int i = 0; i < Math.min(preReleases.length, other.preReleases.length); i++) { + int cmp = preReleases[i].compareTo(other.preReleases[i]); + if (cmp != 0) { + return cmp; + } + } + + return preReleases.length == other.preReleases.length + ? 0 + : (preReleases.length < other.preReleases.length ? -1 : 1); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof CassandraVersion) { + CassandraVersion that = (CassandraVersion) other; + return this.major == that.major + && this.minor == that.minor + && this.patch == that.patch + && this.dsePatch == that.dsePatch + && (this.preReleases == null + ? that.preReleases == null + : Arrays.equals(this.preReleases, that.preReleases)) + && Objects.equals(this.build, that.build); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch, dsePatch, preReleases, build); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(major).append('.').append(minor).append('.').append(patch); + if (dsePatch >= 0) { + sb.append('.').append(dsePatch); + } + if (preReleases != null) { + for (String preRelease : preReleases) { + sb.append('-').append(preRelease); + } + } + if (build != null) { + sb.append('+').append(build); + } + return sb.toString(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 0b6823a789f..737a6a4b202 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; /** An instance of the driver, that connects to a Cassandra cluster. */ public interface Cluster { @@ -25,6 +26,22 @@ static ClusterBuilder builder() { return new ClusterBuilder(); } + /** + * Returns a snapshot of the Cassandra cluster's topology and schema metadata. + * + *

In order to provide atomic updates, this method returns an immutable object: the node list, + * token map, and schema contained in a given instance will always be consistent with each other + * (but note that {@link Node} itself is not immutable: some of its properties will be updated + * dynamically, in particular {@link Node#getState()}). + * + *

As a consequence of the above, you should call this method each time you need a fresh view + * of the metadata. Do not call it once and store the result, because it is a frozen + * snapshot that will become stale over time. + * + *

If a metadata refresh triggers events (such as node added/removed, or schema events), then + * the new version of the metadata is guaranteed to be visible by the time you receive these + * events. + */ Metadata getMetadata(); DriverContext getContext(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 75ff2e3b5ff..8cd44ed816b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -15,16 +15,18 @@ */ package com.datastax.oss.driver.api.core.metadata; +import com.datastax.oss.driver.api.core.Cluster; import java.net.InetSocketAddress; import java.util.Map; /** * The metadata of the Cassandra cluster that this driver instance is connected to. * - *

Updates to this object are guaranteed to be atomic: the node list, schema, and token metadata - * are immutable, and will always be consistent for a given metadata instance. The node instances - * are the only mutable objects in the hierarchy, and some of their fields will be modified - * dynamically (in particular the node state). + * @see Cluster#getMetadata() + *

Updates to this object are guaranteed to be atomic: the node list, schema, and token + * metadata are immutable, and will always be consistent for a given metadata instance. The node + * instances are the only mutable objects in the hierarchy, and some of their fields will be + * modified dynamically (in particular the node state). */ public interface Metadata { /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index d7e8541433e..b83de4517e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -15,32 +15,83 @@ */ package com.datastax.oss.driver.api.core.metadata; +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; /** Metadata about a Cassandra node in the cluster. */ public interface Node { /** * The address that the driver uses to connect to the node. This is the node's broadcast RPC - * address, transformed by the address translator if one is configured. + * address, transformed by the {@link AddressTranslator} if one is configured. * *

The driver also uses this to uniquely identify a node. */ InetSocketAddress getConnectAddress(); + /** + * The node's broadcast address. That is, the address that other nodes use to communicate with + * that node. This is also the value of the {@code peer} column in {@code system.peers}. + * + *

This may not be known at all times. In particular, some Cassandra versions don't store it in + * the {@code system.local} table, so this will be unknown for the control node, until the control + * connection reconnects to another node. + */ + Optional getBroadcastAddress(); + + /** + * The node's listen address. That is, the address that the Cassandra process binds to. + * + *

This may not be know at all times. In particular, current Cassandra versions (3.10) only + * store it in {@code system.local}, so this will be known only for the control node. + */ + Optional getListenAddress(); + + String getDatacenter(); + + String getRack(); + + CassandraVersion getCassandraVersion(); + + // TODO tokens? (might be better to have a method on TokenMap) + + /** + * An additional map of free-form properties. + * + *

This is intended for future evolution or custom driver extensions. The contents of this map + * are unspecified and may change at any point in time, always check for the existence of a key + * before using it. + * + *

The returned map is immutable. + */ + Map getExtras(); + NodeState getState(); /** - * @return the total number of active connections currently open by this driver instance to the - * node. This can be either pooled connections, or the control connection. + * The total number of active connections currently open by this driver instance to the node. This + * can be either pooled connections, or the control connection. */ int getOpenConnections(); /** - * @return whether the driver is currently trying to reconnect to this node. That is, whether the - * active connection count is below the value mandated by the configuration. This does not - * mean that the node is down, there could be some active connections but not enough. + * Whether the driver is currently trying to reconnect to this node. That is, whether the active + * connection count is below the value mandated by the configuration. This does not mean that the + * node is down, there could be some active connections but not enough. */ boolean isReconnecting(); - // TODO expose distance (set by LBP) + /** + * The distance assigned to this node by the {@link LoadBalancingPolicy}, that controls certain + * aspects of connection management. + * + *

This is exposed here for information only. Distance events are handled internally by the + * driver. + */ + NodeDistance getDistance(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 4c3ab8f6c3f..e17e1c3c7a8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -18,15 +18,9 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; import java.net.InetSocketAddress; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This class is immutable, so that metadata changes are atomic for the client. Every mutation @@ -34,12 +28,11 @@ * MetadataManager}'s volatile field. */ public class DefaultMetadata implements Metadata { - private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadata.class); - public static final DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); private final Map nodes; - // TODO add metadata and schema + // TODO schema + // TODO token map public DefaultMetadata(Map nodes) { this.nodes = ImmutableMap.copyOf(nodes); @@ -50,59 +43,6 @@ public Map getNodes() { return nodes; } - /** Create minimal node info about the contact points, before the first connection. */ - public DefaultMetadata initNodes(Set addresses) { - assert nodes.isEmpty(); - ImmutableMap.Builder newNodes = ImmutableMap.builder(); - for (InetSocketAddress address : addresses) { - newNodes.put(address, new DefaultNode(address)); - } - return new DefaultMetadata(newNodes.build()); - } - - public DefaultMetadata refreshNodes(Iterable nodeInfos) { - Map added = new HashMap<>(); - Set seen = new HashSet<>(); - - for (TopologyMonitor.NodeInfo nodeInfo : nodeInfos) { - InetSocketAddress address = nodeInfo.getConnectAddress(); - if (address == null) { - // TODO more advanced row validation (see 3.x), here or in TopologyMonitor? - LOG.debug("Ignoring node info with missing connect address"); - continue; - } - seen.add(address); - DefaultNode node = (DefaultNode) nodes.get(address); - if (node == null) { - node = new DefaultNode(address); - LOG.debug("Adding new node {}", node); - added.put(address, node); - } - copyInfos(nodeInfo, node); - } - - Set removed = Sets.difference(nodes.keySet(), seen); - - if (added.isEmpty() && removed.isEmpty()) { - return this; - } else { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(added); - for (Map.Entry entry : nodes.entrySet()) { - if (!removed.contains(entry.getKey())) { - builder.put(entry.getKey(), entry.getValue()); - } - } - // TODO fire node added/removed events - // TODO recompute token map - return new DefaultMetadata(builder.build()); - } - } - - private void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { - // TODO add new properties in Node, fill them - } - public DefaultMetadata addNode(Node toAdd) { Map newNodes; if (nodes.containsKey(toAdd.getConnectAddress())) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 050a39caee5..1e64d04ddc1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -15,10 +15,14 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; /** * Implementation note: all the mutable state in this class is read concurrently, but only mutated @@ -28,10 +32,18 @@ public class DefaultNode implements Node { private final InetSocketAddress connectAddress; + volatile Optional broadcastAddress; + volatile Optional listenAddress; + volatile String datacenter; + volatile String rack; + volatile CassandraVersion cassandraVersion; + volatile Map extras; + // These 3 fields are read concurrently, but only mutated on NodeStateManager's admin thread volatile NodeState state; volatile int openConnections; volatile int reconnections; + volatile NodeDistance distance; public DefaultNode(InetSocketAddress connectAddress) { @@ -45,6 +57,36 @@ public InetSocketAddress getConnectAddress() { return connectAddress; } + @Override + public Optional getBroadcastAddress() { + return broadcastAddress; + } + + @Override + public Optional getListenAddress() { + return listenAddress; + } + + @Override + public String getDatacenter() { + return datacenter; + } + + @Override + public String getRack() { + return rack; + } + + @Override + public CassandraVersion getCassandraVersion() { + return cassandraVersion; + } + + @Override + public Map getExtras() { + return extras; + } + @Override public NodeState getState() { return state; @@ -59,6 +101,11 @@ public boolean isReconnecting() { return reconnections > 0; } + @Override + public NodeDistance getDistance() { + return distance; + } + @Override public String toString() { return connectAddress.toString(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java new file mode 100644 index 00000000000..4e886cc6d25 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class FullNodeListRefresh extends NodeListRefresh { + + private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); + private final Iterable nodeInfos; + + FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos) { + super(current); + this.nodeInfos = nodeInfos; + } + + protected Map computeNewNodes() { + + Map oldNodes = oldMetadata.getNodes(); + + Map added = new HashMap<>(); + Set seen = new HashSet<>(); + + for (TopologyMonitor.NodeInfo nodeInfo : nodeInfos) { + InetSocketAddress address = nodeInfo.getConnectAddress(); + if (address == null) { + // TODO more advanced row validation (see 3.x), here or in TopologyMonitor? + LOG.debug("Ignoring node info with missing connect address"); + continue; + } + seen.add(address); + DefaultNode node = (DefaultNode) oldNodes.get(address); + if (node == null) { + node = new DefaultNode(address); + LOG.debug("Adding new node {}", node); + added.put(address, node); + } + copyInfos(nodeInfo, node); + } + + Set removed = Sets.difference(oldNodes.keySet(), seen); + + if (added.isEmpty() && removed.isEmpty()) { + return oldNodes; + } else { + ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + newNodesBuilder.putAll(added); + for (Map.Entry entry : oldNodes.entrySet()) { + if (!removed.contains(entry.getKey())) { + newNodesBuilder.put(entry.getKey(), entry.getValue()); + } + } + + for (Node node : added.values()) { + events.add(NodeStateEvent.added((DefaultNode) node)); + } + for (InetSocketAddress address : removed) { + Node node = oldNodes.get(address); + events.add(NodeStateEvent.removed((DefaultNode) node)); + } + + return newNodesBuilder.build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java new file mode 100644 index 00000000000..d43765d96e8 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Creates minimal node info about the contact points, before the first connection. */ +class InitContactPointsRefresh extends MetadataRefresh { + private static final Logger LOG = LoggerFactory.getLogger(InitContactPointsRefresh.class); + + private final Set contactPoints; + + InitContactPointsRefresh(DefaultMetadata current, Set contactPoints) { + super(current); + this.contactPoints = contactPoints; + } + + @Override + void compute() { + assert oldMetadata == DefaultMetadata.EMPTY; + LOG.debug("Initializing node metadata with contact points {}", contactPoints); + + ImmutableMap.Builder newNodes = ImmutableMap.builder(); + for (InetSocketAddress address : contactPoints) { + newNodes.put(address, new DefaultNode(address)); + } + newMetadata = new DefaultMetadata(newNodes.build()); + // No token map refresh, because we don't have enough information yet + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 1c8d1e13988..07bb16cedd8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -102,15 +102,12 @@ private SingleThreaded(InternalDriverContext context) { private void initNodes( Set addresses, CompletableFuture initNodesFuture) { - assert adminExecutor.inEventLoop(); - metadata = metadata.initNodes(addresses); + refresh(new InitContactPointsRefresh(metadata, addresses)); initNodesFuture.complete(null); } private Void refreshNodes(Iterable nodeInfos) { - metadata = metadata.refreshNodes(nodeInfos); - // TODO init LBP if needed - return null; + return refresh(new FullNodeListRefresh(metadata, nodeInfos)); } private Void addNode(TopologyMonitor.NodeInfo nodeInfo) { @@ -121,6 +118,17 @@ private Void addNode(TopologyMonitor.NodeInfo nodeInfo) { private void removeNode(InetSocketAddress address) { LOG.debug("Removing node {}", address); metadata = metadata.removeNode(address); + // TODO recompute token map + } + + private Void refresh(MetadataRefresh refresh) { + assert adminExecutor.inEventLoop(); + refresh.compute(); + metadata = refresh.newMetadata; + for (Object event : refresh.events) { + context.eventBus().fire(event); + } + return null; } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java new file mode 100644 index 00000000000..f095c56c09c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import java.util.ArrayList; +import java.util.List; + +/** + * Any update to the driver's metadata. It produces a new metadata instance, and may also trigger + * events. + * + *

This is modelled as a separate type for modularity, and because we can't send the events while + * we are doing the refresh (by contract, the new copy of the metadata needs to be visible before + * the events are sent). This also makes unit testing very easy. + * + *

This is only instantiated and called from the metadata manager's admin thread, therefore + * implementations don't need to be thread-safe. + * + * @see Cluster#getMetadata() + */ +abstract class MetadataRefresh { + final DefaultMetadata oldMetadata; + DefaultMetadata newMetadata; + final List events; + + protected MetadataRefresh(DefaultMetadata current) { + this.oldMetadata = current; + this.events = new ArrayList<>(); + } + + abstract void compute(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java new file mode 100644 index 00000000000..129b60f61ee --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class NodeListRefresh extends MetadataRefresh { + + private static final Logger LOG = LoggerFactory.getLogger(NodeListRefresh.class); + + protected NodeListRefresh(DefaultMetadata current) { + super(current); + } + + /** @return null if the nodes haven't changed */ + protected abstract Map computeNewNodes(); + + @Override + void compute() { + Map newNodes = computeNewNodes(); + newMetadata = (newNodes == null) ? oldMetadata : new DefaultMetadata(newNodes); + // TODO recompute token map (even if node list hasn't changed, b/c tokens might have changed) + } + + protected void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { + node.broadcastAddress = nodeInfo.getBroadcastAddress(); + node.listenAddress = nodeInfo.getListenAddress(); + node.datacenter = nodeInfo.getDatacenter(); + node.rack = nodeInfo.getRack(); + String versionString = nodeInfo.getCassandraVersion(); + try { + node.cassandraVersion = CassandraVersion.parse(versionString); + } catch (IllegalArgumentException e) { + LOG.warn("Error converting Cassandra version '{}'", versionString); + } + node.extras = + (nodeInfo.getExtras() == null) + ? Collections.emptyMap() + : ImmutableMap.copyOf(nodeInfo.getExtras()); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/Assertions.java b/core/src/test/java/com/datastax/oss/driver/Assertions.java index 43b4414b754..b9f98838e31 100644 --- a/core/src/test/java/com/datastax/oss/driver/Assertions.java +++ b/core/src/test/java/com/datastax/oss/driver/Assertions.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver; +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.CassandraVersionAssert; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.internal.core.CompletionStageAssert; import com.datastax.oss.driver.internal.core.DriverConfigAssert; @@ -39,4 +41,8 @@ public static NettyFutureAssert assertThat(Future actual) { public static CompletionStageAssert assertThat(CompletionStage actual) { return new CompletionStageAssert<>(actual); } + + public static CassandraVersionAssert assertThat(CassandraVersion actual) { + return new CassandraVersionAssert(actual); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java new file mode 100644 index 00000000000..3dbe1afd6bf --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import org.assertj.core.api.AbstractComparableAssert; +import org.assertj.core.api.Assertions; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class CassandraVersionAssert + extends AbstractComparableAssert { + + public CassandraVersionAssert(CassandraVersion actual) { + super(actual, CassandraVersionAssert.class); + } + + public CassandraVersionAssert hasMajorMinorPatch(int major, int minor, int patch) { + Assertions.assertThat(actual.getMajor()).isEqualTo(major); + Assertions.assertThat(actual.getMinor()).isEqualTo(minor); + Assertions.assertThat(actual.getPatch()).isEqualTo(patch); + return this; + } + + public CassandraVersionAssert hasDsePatch(int dsePatch) { + Assertions.assertThat(actual.getDSEPatch()).isEqualTo(dsePatch); + return this; + } + + public CassandraVersionAssert hasPreReleaseLabels(String... labels) { + Assertions.assertThat(actual.getPreReleaseLabels()).containsExactly(labels); + return this; + } + + public CassandraVersionAssert hasNoPreReleaseLabels() { + Assertions.assertThat(actual.getPreReleaseLabels()).isNull(); + return this; + } + + public CassandraVersionAssert hasBuildLabel(String label) { + Assertions.assertThat(actual.getBuildLabel()).isEqualTo(label); + return this; + } + + public CassandraVersionAssert hasNextStable(String version) { + assertThat(actual.nextStable()).isEqualTo(CassandraVersion.parse(version)); + return this; + } + + public CassandraVersionAssert hasToString(String string) { + Assertions.assertThat(actual.toString()).isEqualTo(string); + return this; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java new file mode 100644 index 00000000000..97d490bd53b --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class CassandraVersionTest { + + @Test + public void should_parse_release_version() { + assertThat(CassandraVersion.parse("1.2.19")) + .hasMajorMinorPatch(1, 2, 19) + .hasDsePatch(-1) + .hasNoPreReleaseLabels() + .hasBuildLabel(null) + .hasNextStable("1.2.19") + .hasToString("1.2.19"); + } + + @Test + public void should_parse_release_without_patch() { + assertThat(CassandraVersion.parse("1.2")).hasMajorMinorPatch(1, 2, 0); + } + + @Test + public void should_parse_pre_release_version() { + assertThat(CassandraVersion.parse("1.2.0-beta1-SNAPSHOT")) + .hasMajorMinorPatch(1, 2, 0) + .hasDsePatch(-1) + .hasPreReleaseLabels("beta1", "SNAPSHOT") + .hasBuildLabel(null) + .hasToString("1.2.0-beta1-SNAPSHOT") + .hasNextStable("1.2.0"); + } + + @Test + public void should_allow_tilde_as_first_pre_release_delimiter() { + assertThat(CassandraVersion.parse("1.2.0~beta1-SNAPSHOT")) + .hasMajorMinorPatch(1, 2, 0) + .hasDsePatch(-1) + .hasPreReleaseLabels("beta1", "SNAPSHOT") + .hasBuildLabel(null) + .hasToString("1.2.0-beta1-SNAPSHOT") + .hasNextStable("1.2.0"); + } + + @Test + public void should_parse_dse_patch() { + assertThat(CassandraVersion.parse("1.2.19.2-SNAPSHOT")) + .hasMajorMinorPatch(1, 2, 19) + .hasDsePatch(2) + .hasToString("1.2.19.2-SNAPSHOT") + .hasNextStable("1.2.19.2"); + } + + @Test + public void should_order_versions() { + // by component + assertOrder("1.2.0", "2.0.0", -1); + assertOrder("2.0.0", "2.1.0", -1); + assertOrder("2.0.1", "2.0.2", -1); + assertOrder("2.0.1.1", "2.0.1.2", -1); + + // shortened vs. longer version + assertOrder("2.0", "2.0.0", 0); + assertOrder("2.0", "2.0.1", -1); + + // any DSE version is higher than no DSE version + assertOrder("2.0.0", "2.0.0.0", -1); + assertOrder("2.0.0", "2.0.0.1", -1); + + // pre-release vs. release + assertOrder("2.0.0-beta1", "2.0.0", -1); + assertOrder("2.0.0-SNAPSHOT", "2.0.0", -1); + assertOrder("2.0.0-beta1-SNAPSHOT", "2.0.0", -1); + + // pre-release vs. pre-release + assertOrder("2.0.0-a-b-c", "2.0.0-a-b-d", -1); + assertOrder("2.0.0-a-b-c", "2.0.0-a-b-c-d", -1); + + // build number ignored + assertOrder("2.0.0+build01", "2.0.0+build02", 0); + } + + private void assertOrder(String version1, String version2, int expected) { + assertThat(CassandraVersion.parse(version1).compareTo(CassandraVersion.parse(version2))) + .isEqualTo(expected); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java new file mode 100644 index 00000000000..dad6d0fb8f9 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class FullNodeListRefreshTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + private static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); + + private static final DefaultNode node1 = new DefaultNode(ADDRESS1); + private static final DefaultNode node2 = new DefaultNode(ADDRESS2); + private static final DefaultNode node3 = new DefaultNode(ADDRESS3); + + @Test + public void should_add_and_remove_nodes() { + // Given + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + Iterable newInfos = + ImmutableList.of( + DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), + DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); + FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); + assertThat(refresh.events) + .containsOnly(NodeStateEvent.removed(node1), NodeStateEvent.added(node3)); + } + + @Test + public void should_update_existing_nodes() { + // Given + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + Iterable newInfos = + ImmutableList.of( + DefaultNodeInfo.builder() + .withConnectAddress(ADDRESS1) + .withDatacenter("dc1") + .withRack("rack1") + .build(), + DefaultNodeInfo.builder() + .withConnectAddress(ADDRESS2) + .withDatacenter("dc1") + .withRack("rack2") + .build()); + FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); + assertThat(node1.getDatacenter()).isEqualTo("dc1"); + assertThat(node1.getRack()).isEqualTo("rack1"); + assertThat(node2.getDatacenter()).isEqualTo("dc1"); + assertThat(node2.getRack()).isEqualTo("rack2"); + assertThat(refresh.events).isEmpty(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java new file mode 100644 index 00000000000..7fef2c55b41 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.google.common.collect.ImmutableSet; +import java.net.InetSocketAddress; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class InitContactPointsRefreshTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + + @Test + public void should_create_nodes() { + // Given + InitContactPointsRefresh refresh = + new InitContactPointsRefresh(DefaultMetadata.EMPTY, ImmutableSet.of(ADDRESS1, ADDRESS2)); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); + assertThat(refresh.events).isEmpty(); + } +} From c1e96c0cb96f3dfe7099c87448d1a3600928059b Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Apr 2017 12:43:29 -0700 Subject: [PATCH 022/742] Don't propagate incoming frames once they've been handled --- .../oss/driver/internal/core/channel/InFlightHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 1dd120b7f3f..2167fd2d96b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -154,7 +154,6 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } } } - super.channelRead(ctx, msg); } /** Called if an exception was thrown while processing an inbound event (i.e. a response). */ From c4b16b23be910d40b966d21e60ae8415c21de068 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Apr 2017 12:46:20 -0700 Subject: [PATCH 023/742] Check that timeout future is not null before cancelling it onResponse can apparently be invoked before onWriteComplete (observed it in tests). --- .../internal/core/adminrequest/AdminRequestHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 9cd08d35adf..5998a10951a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -113,13 +113,17 @@ private void fireTimeout() { @Override public void onFailure(Throwable error) { - timeoutFuture.cancel(true); + if (timeoutFuture != null) { + timeoutFuture.cancel(true); + } result.completeExceptionally(error); } @Override public void onResponse(Frame responseFrame) { - timeoutFuture.cancel(true); + if (timeoutFuture != null) { + timeoutFuture.cancel(true); + } Message message = responseFrame.message; if (message instanceof Rows) { Rows rows = (Rows) message; From f1f37c953744205e54026052e0e641afa4f6323e Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Apr 2017 16:02:33 -0700 Subject: [PATCH 024/742] Add metadata refreshes for single nodes --- .../adminrequest/AdminRequestHandler.java | 31 +++++--- .../core/metadata/AddNodeRefresh.java | 46 +++++++++++ .../core/metadata/DefaultMetadata.java | 17 ---- .../core/metadata/DefaultTopologyMonitor.java | 73 ++++++++++++++++- .../core/metadata/FullNodeListRefresh.java | 2 +- .../core/metadata/MetadataManager.java | 78 ++++++++++++++----- .../core/metadata/NodeStateManager.java | 20 ++++- ...NodeListRefresh.java => NodesRefresh.java} | 8 +- .../core/metadata/RemoveNodeRefresh.java | 56 +++++++++++++ .../internal/core/metadata/TopologyEvent.java | 2 +- .../core/metadata/TopologyMonitor.java | 39 ++++++++-- .../core/metadata/AddNodeRefreshTest.java | 78 +++++++++++++++++++ .../core/metadata/NodeStateManagerTest.java | 8 ++ .../core/metadata/RemoveNodeRefreshTest.java | 60 ++++++++++++++ 14 files changed, 453 insertions(+), 65 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java rename core/src/main/java/com/datastax/oss/driver/internal/core/metadata/{NodeListRefresh.java => NodesRefresh.java} (90%) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 5998a10951a..8aa06e72497 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -58,7 +58,11 @@ public static AdminRequestHandler query( new Query( query, buildQueryOptions(pageSize, serialize(parameters, channel.protocolVersion()), null)); - return new AdminRequestHandler(channel, message, timeout); + String debugString = "query '" + query + "'"; + if (!parameters.isEmpty()) { + debugString += " with parameters " + parameters; + } + return new AdminRequestHandler(channel, message, timeout, debugString); } public static AdminRequestHandler query( @@ -67,32 +71,29 @@ public static AdminRequestHandler query( } public static AdminRequestHandler prepare(DriverChannel channel, String query, Duration timeout) { - return new AdminRequestHandler(channel, new Prepare(query), timeout); + String debugString = "prepare '" + query + "'"; + return new AdminRequestHandler(channel, new Prepare(query), timeout, debugString); } private final DriverChannel channel; private final Message message; private final Duration timeout; + private final String debugString; private final CompletableFuture result = new CompletableFuture<>(); // This is only ever accessed on the channel's event loop, so it doesn't need to be volatile private ScheduledFuture timeoutFuture; - private AdminRequestHandler(DriverChannel channel, Message message, Duration timeout) { + private AdminRequestHandler( + DriverChannel channel, Message message, Duration timeout, String debugString) { this.channel = channel; this.message = message; this.timeout = timeout; + this.debugString = debugString; } public CompletionStage start() { - if (LOG.isDebugEnabled()) { - if (message instanceof Query) { - Query query = (Query) this.message; - LOG.debug("Executing query '{}' with values {}", query.query, query.options.namedValues); - } else if (message instanceof Prepare) { - LOG.debug("Preparing {}", ((Prepare) message).cqlQuery); - } - } + LOG.debug("Executing {}", this); channel.write(message, false, Frame.NO_PAYLOAD, this).addListener(this::onWriteComplete); return result; } @@ -146,7 +147,8 @@ private AdminRequestHandler copy(ByteBuffer pagingState) { QueryOptions currentOptions = current.options; QueryOptions newOptions = buildQueryOptions(currentOptions.pageSize, currentOptions.namedValues, pagingState); - return new AdminRequestHandler(channel, new Query(current.query, newOptions), timeout); + return new AdminRequestHandler( + channel, new Query(current.query, newOptions), timeout, debugString); } private static QueryOptions buildQueryOptions( @@ -185,4 +187,9 @@ private static ByteBuffer serialize(Object parameter, ProtocolVersion protocolVe "Unsupported variable type for admin query: " + parameter.getClass()); } } + + @Override + public String toString() { + return debugString; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java new file mode 100644 index 00000000000..5ef2d154cf2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Map; + +public class AddNodeRefresh extends NodesRefresh { + private final TopologyMonitor.NodeInfo newNodeInfo; + + public AddNodeRefresh(DefaultMetadata oldMetadata, TopologyMonitor.NodeInfo newNodeInfo) { + super(oldMetadata); + this.newNodeInfo = newNodeInfo; + } + + @Override + protected Map computeNewNodes() { + Map oldNodes = oldMetadata.getNodes(); + if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { + return oldNodes; + } else { + DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); + copyInfos(newNodeInfo, newNode); + events.add(NodeStateEvent.added(newNode)); + return ImmutableMap.builder() + .putAll(oldNodes) + .put(newNode.getConnectAddress(), newNode) + .build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index e17e1c3c7a8..49879746cee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -57,21 +57,4 @@ public DefaultMetadata addNode(Node toAdd) { return new DefaultMetadata(newNodes); } } - - public DefaultMetadata removeNode(InetSocketAddress toRemove) { - Map newNodes; - if (!nodes.containsKey(toRemove)) { - return this; - } else { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Map.Entry entry : nodes.entrySet()) { - if (!entry.getKey().equals(toRemove)) { - builder.put(entry.getKey(), entry.getValue()); - } - } - newNodes = builder.build(); - // TODO recompute token map - return new DefaultMetadata(newNodes); - } - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 8bf6bf2bae0..9e617d98c00 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -18,22 +18,30 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.google.common.collect.ImmutableMap; import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** The default topology monitor, based on the control connection. */ +/** + * The default topology monitor, based on {@link ControlConnection}. + * + *

Note that event processing is implemented directly in the control connection, not here. + */ public class DefaultTopologyMonitor implements TopologyMonitor { private static final Logger LOG = LoggerFactory.getLogger(DefaultTopologyMonitor.class); @@ -59,8 +67,40 @@ public CompletionStage init() { } @Override - public CompletionStage refreshNode(InetSocketAddress address) { - return CompletableFutures.failedFuture(new UnsupportedOperationException("TODO")); + public CompletionStage> refreshNode(Node node) { + LOG.debug("Refreshing info for {}", node); + DriverChannel channel = controlConnection.channel(); + if (node.getConnectAddress().equals(channel.address())) { + // refreshNode is called for nodes that just came up. If the control node just came up, it + // means the control connection just reconnected, which means we did a full node refresh. So + // we don't need to process this call. + LOG.debug("Ignoring refresh of control node"); + return CompletableFuture.completedFuture(Optional.empty()); + } else if (node.getBroadcastAddress().isPresent()) { + return AdminRequestHandler.query( + channel, + "SELECT * FROM system.peers WHERE peer = :address", + ImmutableMap.of("address", node.getBroadcastAddress().get()), + timeout, + INFINITE_PAGE_SIZE) + .start() + .thenApply(this::buildNodeInfoFromFirstRow); + } else { + return AdminRequestHandler.query( + channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) + .start() + .thenApply(result -> this.findInPeers(result, node.getConnectAddress())); + } + } + + @Override + public CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress) { + LOG.debug("Fetching info for new node {}", connectAddress); + DriverChannel channel = controlConnection.channel(); + return AdminRequestHandler.query( + channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) + .start() + .thenApply(result -> this.findInPeers(result, connectAddress)); } @Override @@ -114,6 +154,31 @@ private NodeInfo buildNodeInfo(AdminResult.Row row) { return builder.build(); } + private Optional buildNodeInfoFromFirstRow(AdminResult result) { + Iterator iterator = result.iterator(); + if (iterator.hasNext()) { + return Optional.of(buildNodeInfo(iterator.next())); + } else { + return Optional.empty(); + } + } + + private Optional findInPeers(AdminResult result, InetSocketAddress connectAddress) { + // The peers table is keyed by broadcast_address, but we only have the translated + // broadcast_rpc_address, so we have to traverse the whole table and check the rows one by one. + for (AdminResult.Row row : result) { + InetAddress broadcastRpcAddress = row.getInet("rpc_address"); + if (broadcastRpcAddress != null + && addressTranslator + .translate(new InetSocketAddress(broadcastRpcAddress, port)) + .equals(connectAddress)) { + return Optional.of(buildNodeInfo(row)); + } + } + LOG.debug("Could not find any peer row matching {}", connectAddress); + return Optional.empty(); + } + // Current versions of Cassandra (3.11 at the time of writing), require the same port for all // nodes. As a consequence, the port is not stored in system tables. // We save it the first time we get a control connection channel. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 4e886cc6d25..35cd4533754 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class FullNodeListRefresh extends NodeListRefresh { +class FullNodeListRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); private final Iterable nodeInfos; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 07bb16cedd8..ab6ee69cf3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -16,11 +16,14 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -66,22 +69,43 @@ public CompletionStage refreshNodes() { .thenApplyAsync(singleThreaded::refreshNodes, adminExecutor); } - public void addNode(InetSocketAddress address) { - context + public CompletionStage refreshNode(Node node) { + return context .topologyMonitor() - .refreshNode(address) - .thenApplyAsync(singleThreaded::addNode, adminExecutor) - .exceptionally( - e -> { - LOG.debug( - "Error adding node " - + address - + ", this will be retried on the next full refresh", - e); + .refreshNode(node) + // The callback only updates volatile fields so no need to schedule it on adminExecutor + .thenApply( + maybeInfo -> { + if (maybeInfo.isPresent()) { + NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node); + } else { + LOG.debug( + "Topology monitor did not return any info for the refresh of {}, skipping", + node); + } return null; }); } + public void addNode(InetSocketAddress address) { + context + .topologyMonitor() + .getNewNodeInfo(address) + .whenCompleteAsync( + (info, error) -> { + if (error != null) { + LOG.debug( + "Error refreshing node info for " + + address + + ", this will be retried on the next full refresh", + error); + } else { + singleThreaded.addNode(address, info); + } + }, + adminExecutor); + } + public void removeNode(InetSocketAddress address) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.removeNode(address)); } @@ -106,19 +130,37 @@ private void initNodes( initNodesFuture.complete(null); } - private Void refreshNodes(Iterable nodeInfos) { + private Void refreshNodes(Iterable nodeInfos) { return refresh(new FullNodeListRefresh(metadata, nodeInfos)); } - private Void addNode(TopologyMonitor.NodeInfo nodeInfo) { - //TODO - return null; + private void addNode(InetSocketAddress address, Optional maybeInfo) { + try { + if (maybeInfo.isPresent()) { + NodeInfo info = maybeInfo.get(); + if (!address.equals(info.getConnectAddress())) { + // This would be a bug in the TopologyMonitor, protect against it + LOG.warn( + "Received a request to add a node for {}, " + + "but the provided info uses the address {}, ignoring it", + address, + info.getBroadcastAddress()); + } else { + refresh(new AddNodeRefresh(metadata, info)); + } + } else { + LOG.debug( + "Ignoring node addition for {} because the " + + "topology monitor didn't return any information", + address); + } + } catch (Throwable t) { + LOG.warn("Unexpected exception while handling added node", t); + } } private void removeNode(InetSocketAddress address) { - LOG.debug("Removing node {}", address); - metadata = metadata.removeNode(address); - // TODO recompute token map + refresh(new RemoveNodeRefresh(metadata, address)); } private Void refresh(MetadataRefresh refresh) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index e80d0559ba4..6fcd86cd6ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -198,7 +198,25 @@ private void setState(DefaultNode node, NodeState newState, String reason) { if (oldState != newState) { LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); node.state = newState; - eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + if (newState != NodeState.UP) { + // Fire the event immediately + eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + } else { + // Refresh the node first (but still fire event if that fails) + metadataManager + .refreshNode(node) + .whenComplete( + (success, error) -> { + try { + if (error != null) { + LOG.debug("Error while refreshing info for " + node, error); + } + eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + } catch (Throwable t) { + LOG.warn("Unexpected exception", t); + } + }); + } } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java similarity index 90% rename from core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 129b60f61ee..f44e74c36a9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -24,11 +24,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class NodeListRefresh extends MetadataRefresh { +abstract class NodesRefresh extends MetadataRefresh { - private static final Logger LOG = LoggerFactory.getLogger(NodeListRefresh.class); + private static final Logger LOG = LoggerFactory.getLogger(NodesRefresh.class); - protected NodeListRefresh(DefaultMetadata current) { + protected NodesRefresh(DefaultMetadata current) { super(current); } @@ -42,7 +42,7 @@ void compute() { // TODO recompute token map (even if node list hasn't changed, b/c tokens might have changed) } - protected void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { + protected static void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { node.broadcastAddress = nodeInfo.getBroadcastAddress(); node.listenAddress = nodeInfo.getListenAddress(); node.datacenter = nodeInfo.getDatacenter(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java new file mode 100644 index 00000000000..0a509032fa2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RemoveNodeRefresh extends NodesRefresh { + + private static final Logger LOG = LoggerFactory.getLogger(RemoveNodeRefresh.class); + + private final InetSocketAddress toRemove; + + RemoveNodeRefresh(DefaultMetadata current, InetSocketAddress toRemove) { + super(current); + this.toRemove = toRemove; + } + + @Override + protected Map computeNewNodes() { + Map oldNodes = oldMetadata.getNodes(); + Node node = oldNodes.get(toRemove); + if (node == null) { + // Normally this should already be checked before calling MetadataManager, but it doesn't + // hurt to fail gracefully just in case + return null; + } else { + LOG.debug("Removing node {}", node); + events.add(NodeStateEvent.removed((DefaultNode) node)); + ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + for (Map.Entry entry : oldNodes.entrySet()) { + if (!entry.getKey().equals(toRemove)) { + newNodesBuilder.put(entry.getKey(), entry.getValue()); + } + } + return newNodesBuilder.build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java index e0e6ae5e80d..de09ba71c95 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java @@ -113,7 +113,7 @@ public static TopologyEvent forceUp(InetSocketAddress address) { * *

The driver will ignore this event if the node is already present in its metadata, or if * information about the node can't be refreshed (i.e. {@link - * TopologyMonitor#refreshNode(InetSocketAddress)} fails). + * TopologyMonitor#getNewNodeInfo(InetSocketAddress)} fails). */ public static TopologyEvent suggestAdded(InetSocketAddress address) { return new TopologyEvent(Type.SUGGEST_ADDED, address); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index b34ad8dba8b..faa6c4fe66c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -51,16 +52,32 @@ public interface TopologyMonitor { CompletionStage init(); /** - * Invoked when the driver needs to refresh information about a node. + * Invoked when the drive needs to refresh the information about an existing node. This is called + * when the node was back and comes back up. * *

This will be invoked directly from a driver's internal thread; if the refresh involves * blocking I/O or heavy computations, it should be scheduled on a separate thread. * - * @param address the address that the driver uses to connect to the node. This is the node's - * broadcast RPC address, transformed by the address translator if one is configured. - * @return a future that completes with the information. + * @param node the node to refresh. + * @return a future that completes with the information. If the monitor can't fulfill the request + * at this time, it should reply with {@link Optional#empty()}, and the driver will carry on + * with its current information. */ - CompletionStage refreshNode(InetSocketAddress address); + CompletionStage> refreshNode(Node node); + + /** + * Invoked when the driver needs to get information about a newly discovered node. + * + *

This will be invoked directly from a driver's internal thread; if the refresh involves + * blocking I/O or heavy computations, it should be scheduled on a separate thread. + * + * @param connectAddress the address that the driver uses to connect to the node. This is the + * node's broadcast RPC address, transformed by the {@link AddressTranslator} if one is + * configured. + * @return a future that completes with the information. If the monitor doesn't know any node with + * this address, it should reply with {@link Optional#empty()}; the new node will be ignored. + */ + CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress); /** * Invoked when the driver needs to refresh information about all the nodes. @@ -86,17 +103,25 @@ public interface TopologyMonitor { interface NodeInfo { /** * The address that the driver uses to connect to the node. This is the node's broadcast RPC - * address, transformed by the address translator if one is configured. + * address, transformed by the {@link AddressTranslator} if one is configured. */ InetSocketAddress getConnectAddress(); /** * The node's broadcast address. That is, the address that other nodes use to communicate with * that node. + * + *

This is only used by the default topology monitor, so if you are writing a custom one and + * don't need this information, you can leave it empty. */ Optional getBroadcastAddress(); - /** The node's listen address. That is, the address that the Cassandra process binds to. */ + /** + * The node's listen address. That is, the address that the Cassandra process binds to. + * + *

This is currently not used anywhere in the driver. If you write a custom topology monitor + * and don't need this information, you can leave it empty. + */ Optional getListenAddress(); String getDatacenter(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java new file mode 100644 index 00000000000..5a82938a466 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Map; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class AddNodeRefreshTest { + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + + private static final DefaultNode node1 = new DefaultNode(ADDRESS1); + + @Test + public void should_add_new_node() { + // Given + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultNodeInfo newNodeInfo = + DefaultNodeInfo.builder() + .withConnectAddress(ADDRESS2) + .withDatacenter("dc1") + .withRack("rack2") + .build(); + AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo); + + // When + refresh.compute(); + + // Then + Map newNodes = refresh.newMetadata.getNodes(); + assertThat(newNodes).containsOnlyKeys(ADDRESS1, ADDRESS2); + Node node2 = newNodes.get(ADDRESS2); + assertThat(node2.getDatacenter()).isEqualTo("dc1"); + assertThat(node2.getRack()).isEqualTo("rack2"); + assertThat(refresh.events).containsExactly(NodeStateEvent.added((DefaultNode) node2)); + } + + @Test + public void should_not_add_existing_node() { + // Given + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultNodeInfo newNodeInfo = + DefaultNodeInfo.builder() + .withConnectAddress(ADDRESS1) + .withDatacenter("dc1") + .withRack("rack2") + .build(); + AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + // Info is not copied over: + assertThat(node1.getDatacenter()).isNull(); + assertThat(node1.getRack()).isNull(); + assertThat(refresh.events).isEmpty(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 4e30d7ad9f1..f885fbc5eb6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -33,6 +33,7 @@ import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import java.time.Duration; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -47,6 +48,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; public class NodeStateManagerTest { private static final InetSocketAddress NEW_ADDRESS = new InetSocketAddress("127.0.0.3", 9042); @@ -89,6 +91,8 @@ public void setup() { .build(); Mockito.when(metadata.getNodes()).thenReturn(nodes); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(metadataManager.refreshNode(any(Node.class))) + .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(context.metadataManager()).thenReturn(metadataManager); } @@ -119,6 +123,7 @@ public void should_ignore_up_event_if_node_is_already_up_or_forced_down() { public void should_apply_up_event_if_node_is_unknown_or_down() { new NodeStateManager(context); + int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN)) { // Given node1.state = oldState; @@ -129,6 +134,7 @@ public void should_apply_up_event_if_node_is_unknown_or_down() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); + Mockito.verify(metadataManager, times(++i)).refreshNode(node1); Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -281,6 +287,7 @@ public void should_ignore_force_up_event_if_node_is_already_up() { public void should_apply_force_up_event_if_node_is_not_up() { new NodeStateManager(context); + int i = 0; for (NodeState oldState : ImmutableList.of(NodeState.UNKNOWN, NodeState.DOWN, NodeState.FORCED_DOWN)) { // Given @@ -293,6 +300,7 @@ public void should_apply_force_up_event_if_node_is_not_up() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); + Mockito.verify(metadataManager, times(++i)).refreshNode(node1); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java new file mode 100644 index 00000000000..a84637043da --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class RemoveNodeRefreshTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + + private static final DefaultNode node1 = new DefaultNode(ADDRESS1); + private static final DefaultNode node2 = new DefaultNode(ADDRESS2); + + @Test + public void should_remove_existing_node() { + // Given + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(refresh.events).containsExactly(NodeStateEvent.removed(node2)); + } + + @Test + public void should_not_remove_nonexistent_node() { + // Given + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2); + + // When + refresh.compute(); + + // Then + assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(refresh.events).isEmpty(); + } +} From c609b5f9e80ac19d66033364533e40b7c746e8ef Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 07:49:57 -0700 Subject: [PATCH 025/742] Add DefaultTopologyMonitor test --- .../core/metadata/DefaultTopologyMonitor.java | 64 ++-- .../metadata/DefaultTopologyMonitorTest.java | 308 ++++++++++++++++++ 2 files changed, 341 insertions(+), 31 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 9e617d98c00..307d4597587 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -24,13 +24,16 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -52,7 +55,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { private final AddressTranslator addressTranslator; private final Duration timeout; - private volatile int port = -1; + @VisibleForTesting volatile int port = -1; public DefaultTopologyMonitor(InternalDriverContext context) { this.controlConnection = context.controlConnection(); @@ -77,18 +80,13 @@ public CompletionStage> refreshNode(Node node) { LOG.debug("Ignoring refresh of control node"); return CompletableFuture.completedFuture(Optional.empty()); } else if (node.getBroadcastAddress().isPresent()) { - return AdminRequestHandler.query( + return query( channel, "SELECT * FROM system.peers WHERE peer = :address", - ImmutableMap.of("address", node.getBroadcastAddress().get()), - timeout, - INFINITE_PAGE_SIZE) - .start() + ImmutableMap.of("address", node.getBroadcastAddress().get())) .thenApply(this::buildNodeInfoFromFirstRow); } else { - return AdminRequestHandler.query( - channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) - .start() + return query(channel, "SELECT * FROM system.peers") .thenApply(result -> this.findInPeers(result, node.getConnectAddress())); } } @@ -97,9 +95,7 @@ public CompletionStage> refreshNode(Node node) { public CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress) { LOG.debug("Fetching info for new node {}", connectAddress); DriverChannel channel = controlConnection.channel(); - return AdminRequestHandler.query( - channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) - .start() + return query(channel, "SELECT * FROM system.peers") .thenApply(result -> this.findInPeers(result, connectAddress)); } @@ -109,17 +105,11 @@ public CompletionStage> refreshNodeList() { DriverChannel channel = controlConnection.channel(); savePort(channel); - CompletionStage controlNodeStage = - AdminRequestHandler.query( - channel, "SELECT * FROM system.local", timeout, INFINITE_PAGE_SIZE) - .start(); - CompletionStage peersStage = - AdminRequestHandler.query( - channel, "SELECT * FROM system.peers", timeout, INFINITE_PAGE_SIZE) - .start(); - - return controlNodeStage.thenCombine( - peersStage, + CompletionStage localQuery = query(channel, "SELECT * FROM system.local"); + CompletionStage peersQuery = query(channel, "SELECT * FROM system.peers"); + + return localQuery.thenCombine( + peersQuery, (controlNodeResult, peersResult) -> { List nodeInfos = new ArrayList<>(); nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next())); @@ -130,14 +120,26 @@ public CompletionStage> refreshNodeList() { }); } - private NodeInfo buildNodeInfo(AdminResult.Row row) { - DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder(); + @VisibleForTesting + protected CompletionStage query( + DriverChannel channel, String queryString, Map parameters) { + return AdminRequestHandler.query(channel, queryString, parameters, timeout, INFINITE_PAGE_SIZE) + .start(); + } + + private CompletionStage query(DriverChannel channel, String queryString) { + return query(channel, queryString, Collections.emptyMap()); + } + private NodeInfo buildNodeInfo(AdminResult.Row row) { InetAddress broadcastRpcAddress = row.getInet("rpc_address"); - if (broadcastRpcAddress != null) { - builder.withConnectAddress( - addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port))); - } + InetSocketAddress connectAddress = + addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port)); + return buildNodeInfo(row, connectAddress); + } + + private NodeInfo buildNodeInfo(AdminResult.Row row, InetSocketAddress connectAddress) { + DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder().withConnectAddress(connectAddress); InetAddress broadcastAddress = row.getInet("broadcast_address"); // in system.local if (broadcastAddress == null) { @@ -145,7 +147,7 @@ private NodeInfo buildNodeInfo(AdminResult.Row row) { } builder.withBroadcastAddress(broadcastAddress); - builder.withListenAddress(row.getInet("listen")); + builder.withListenAddress(row.getInet("listen_address")); builder.withDatacenter(row.getVarchar("data_center")); builder.withRack(row.getVarchar("rack")); builder.withCassandraVersion(row.getVarchar("release_version")); @@ -172,7 +174,7 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con && addressTranslator .translate(new InetSocketAddress(broadcastRpcAddress, port)) .equals(connectAddress)) { - return Optional.of(buildNodeInfo(row)); + return Optional.of(buildNodeInfo(row, connectAddress)); } } LOG.debug("Could not find any peer row matching {}", connectAddress); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java new file mode 100644 index 00000000000..aa8682b3f3d --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; + +public class DefaultTopologyMonitorTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + + @Mock private InternalDriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfig; + @Mock private ControlConnection controlConnection; + @Mock private DriverChannel channel; + private AddressTranslator addressTranslator; + private DefaultNode node1; + + private TestTopologyMonitor topologyMonitor; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT)) + .thenReturn(Duration.ofSeconds(1)); + Mockito.when(config.defaultProfile()).thenReturn(defaultConfig); + Mockito.when(context.config()).thenReturn(config); + + addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); + Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + + Mockito.when(controlConnection.channel()).thenReturn(channel); + Mockito.when(context.controlConnection()).thenReturn(controlConnection); + + node1 = new DefaultNode(ADDRESS1); + + topologyMonitor = new TestTopologyMonitor(context); + } + + @Test + public void should_initialize_control_connection() { + // When + topologyMonitor.init(); + + // Then + Mockito.verify(controlConnection).init(true); + } + + @Test + public void should_not_refresh_control_node() { + // Given + Mockito.when(channel.address()).thenReturn(ADDRESS1); + + // When + CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); + + // Then + assertThat(futureInfo).isSuccess(maybeInfo -> assertThat(maybeInfo.isPresent()).isFalse()); + } + + @Test + public void should_refresh_node_from_peers_if_broadcast_address_is_present() { + // Given + InetAddress broadcastAddress = ADDRESS1.getAddress(); + node1.broadcastAddress = Optional.of(broadcastAddress); + topologyMonitor.stubQueries( + new StubbedQuery( + "SELECT * FROM system.peers WHERE peer = :address", + ImmutableMap.of("address", broadcastAddress), + mockResult(mockPeersRow(1)))); + + // When + CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); + + // Then + assertThat(futureInfo) + .isSuccess( + maybeInfo -> { + assertThat(maybeInfo.isPresent()).isTrue(); + NodeInfo info = maybeInfo.get(); + assertThat(info.getDatacenter()).isEqualTo("dc1"); + }); + } + + @Test + public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() { + // Given + node1.broadcastAddress = Optional.empty(); + AdminResult.Row peer3 = mockPeersRow(3); + AdminResult.Row peer2 = mockPeersRow(2); + AdminResult.Row peer1 = mockPeersRow(1); + topologyMonitor.stubQueries( + new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2, peer1))); + + // When + CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); + + // Then + assertThat(futureInfo) + .isSuccess( + maybeInfo -> { + assertThat(maybeInfo.isPresent()).isTrue(); + NodeInfo info = maybeInfo.get(); + assertThat(info.getDatacenter()).isEqualTo("dc1"); + }); + // The rpc_address in each row should have been tried, only the last row should have been + // converted + Mockito.verify(peer3).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + Mockito.verify(peer3, never()).getVarchar(anyString()); + + Mockito.verify(peer2).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + Mockito.verify(peer2, never()).getVarchar(anyString()); + + Mockito.verify(peer1).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); + Mockito.verify(peer1).getVarchar("data_center"); + } + + @Test + public void should_get_new_node_from_peers() { + // Given + AdminResult.Row peer3 = mockPeersRow(3); + AdminResult.Row peer2 = mockPeersRow(2); + AdminResult.Row peer1 = mockPeersRow(1); + topologyMonitor.stubQueries( + new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2, peer1))); + + // When + CompletionStage> futureInfo = topologyMonitor.getNewNodeInfo(ADDRESS1); + + // Then + assertThat(futureInfo) + .isSuccess( + maybeInfo -> { + assertThat(maybeInfo.isPresent()).isTrue(); + NodeInfo info = maybeInfo.get(); + assertThat(info.getDatacenter()).isEqualTo("dc1"); + }); + // The rpc_address in each row should have been tried, only the last row should have been + // converted + Mockito.verify(peer3).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + Mockito.verify(peer3, never()).getVarchar(anyString()); + + Mockito.verify(peer2).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + Mockito.verify(peer2, never()).getVarchar(anyString()); + + Mockito.verify(peer1).getInet("rpc_address"); + Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); + Mockito.verify(peer1).getVarchar("data_center"); + } + + @Test + public void should_refresh_node_list_from_local_and_peers() { + // Given + AdminResult.Row peer3 = mockPeersRow(3); + AdminResult.Row peer2 = mockPeersRow(2); + topologyMonitor.stubQueries( + new StubbedQuery("SELECT * FROM system.local", mockResult(mockLocalRow(1))), + new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2))); + + // When + CompletionStage> futureInfos = topologyMonitor.refreshNodeList(); + + // Then + assertThat(futureInfos) + .isSuccess( + infos -> { + Iterator iterator = infos.iterator(); + assertThat(iterator.next().getDatacenter()).isEqualTo("dc1"); + assertThat(iterator.next().getDatacenter()).isEqualTo("dc3"); + assertThat(iterator.next().getDatacenter()).isEqualTo("dc2"); + }); + } + + /** Mocks the query execution logic. */ + private static class TestTopologyMonitor extends DefaultTopologyMonitor { + + private final Queue queries = new LinkedList<>(); + + private TestTopologyMonitor(InternalDriverContext context) { + super(context); + port = 9042; + } + + private void stubQueries(StubbedQuery... queries) { + this.queries.addAll(Arrays.asList(queries)); + } + + @Override + protected CompletionStage query( + DriverChannel channel, String queryString, Map parameters) { + StubbedQuery nextQuery = queries.poll(); + assertThat(nextQuery).isNotNull(); + assertThat(nextQuery.queryString).isEqualTo(queryString); + assertThat(nextQuery.parameters).isEqualTo(parameters); + return CompletableFuture.completedFuture(nextQuery.result); + } + } + + private static class StubbedQuery { + private final String queryString; + private final Map parameters; + private final AdminResult result; + + private StubbedQuery(String queryString, Map parameters, AdminResult result) { + this.queryString = queryString; + this.parameters = parameters; + this.result = result; + } + + private StubbedQuery(String queryString, AdminResult result) { + this(queryString, Collections.emptyMap(), result); + } + } + + private AdminResult.Row mockLocalRow(int i) { + try { + AdminResult.Row row = Mockito.mock(AdminResult.Row.class); + Mockito.when(row.getInet("broadcast_address")) + .thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getVarchar("data_center")).thenReturn("dc" + i); + Mockito.when(row.getInet("listen_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getVarchar("rack")).thenReturn("rack" + i); + Mockito.when(row.getVarchar("release_version")).thenReturn("release_version" + i); + Mockito.when(row.getInet("rpc_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getSetOfVarchar("tokens")).thenReturn(ImmutableSet.of("token" + i)); + return row; + } catch (UnknownHostException e) { + fail("unexpected", e); + return null; + } + } + + private AdminResult.Row mockPeersRow(int i) { + try { + AdminResult.Row row = Mockito.mock(AdminResult.Row.class); + Mockito.when(row.getInet("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getVarchar("data_center")).thenReturn("dc" + i); + Mockito.when(row.getVarchar("rack")).thenReturn("rack" + i); + Mockito.when(row.getVarchar("release_version")).thenReturn("release_version" + i); + Mockito.when(row.getInet("rpc_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getSetOfVarchar("tokens")).thenReturn(ImmutableSet.of("token" + i)); + return row; + } catch (UnknownHostException e) { + fail("unexpected", e); + return null; + } + } + + private AdminResult mockResult(AdminResult.Row... rows) { + AdminResult result = Mockito.mock(AdminResult.class); + Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + return result; + } +} From 630e36acd4a271abce858e06ae3ea9f89de154f8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 09:28:11 -0700 Subject: [PATCH 026/742] Add MetadataManager test --- .../core/metadata/AddNodeRefresh.java | 4 +- .../core/metadata/FullNodeListRefresh.java | 4 +- .../metadata/InitContactPointsRefresh.java | 3 +- .../core/metadata/MetadataManager.java | 18 +- .../core/metadata/RemoveNodeRefresh.java | 3 +- .../core/metadata/MetadataManagerTest.java | 237 ++++++++++++++++++ 6 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 5ef2d154cf2..2b9e666373c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -16,12 +16,14 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; public class AddNodeRefresh extends NodesRefresh { - private final TopologyMonitor.NodeInfo newNodeInfo; + + @VisibleForTesting final TopologyMonitor.NodeInfo newNodeInfo; public AddNodeRefresh(DefaultMetadata oldMetadata, TopologyMonitor.NodeInfo newNodeInfo) { super(oldMetadata); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 35cd4533754..0d9822bb114 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import java.net.InetSocketAddress; @@ -29,7 +30,8 @@ class FullNodeListRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); - private final Iterable nodeInfos; + + @VisibleForTesting final Iterable nodeInfos; FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos) { super(current); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index d43765d96e8..9cf152974f9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Set; @@ -26,7 +27,7 @@ class InitContactPointsRefresh extends MetadataRefresh { private static final Logger LOG = LoggerFactory.getLogger(InitContactPointsRefresh.class); - private final Set contactPoints; + @VisibleForTesting final Set contactPoints; InitContactPointsRefresh(DefaultMetadata current, Set contactPoints) { super(current); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index ab6ee69cf3a..74d7ba81a61 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.List; @@ -162,15 +163,16 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { private void removeNode(InetSocketAddress address) { refresh(new RemoveNodeRefresh(metadata, address)); } + } - private Void refresh(MetadataRefresh refresh) { - assert adminExecutor.inEventLoop(); - refresh.compute(); - metadata = refresh.newMetadata; - for (Object event : refresh.events) { - context.eventBus().fire(event); - } - return null; + @VisibleForTesting + Void refresh(MetadataRefresh refresh) { + assert adminExecutor.inEventLoop(); + refresh.compute(); + metadata = refresh.newMetadata; + for (Object event : refresh.events) { + context.eventBus().fire(event); } + return null; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 0a509032fa2..9056a18ffa6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; @@ -26,7 +27,7 @@ public class RemoveNodeRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(RemoveNodeRefresh.class); - private final InetSocketAddress toRemove; + @VisibleForTesting final InetSocketAddress toRemove; RemoveNodeRefresh(DefaultMetadata current, InetSocketAddress toRemove) { super(current); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java new file mode 100644 index 00000000000..982b6c95854 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.util.concurrent.Future; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.timeout; + +public class MetadataManagerTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + + @Mock private InternalDriverContext context; + @Mock private NettyOptions nettyOptions; + @Mock private TopologyMonitor topologyMonitor; + + private DefaultEventLoopGroup adminEventLoopGroup; + + private TestMetadataManager metadataManager; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + adminEventLoopGroup = new DefaultEventLoopGroup(1); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + + Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); + + metadataManager = new TestMetadataManager(context); + } + + @Test + public void should_add_contact_points() { + // When + CompletionStage addContactPointsFuture = + metadataManager.addContactPoints(ImmutableSet.of(ADDRESS1)); + waitForPendingAdminTasks(); + + // Then + assertThat(addContactPointsFuture).isSuccess(); + assertThat(metadataManager.refreshes).hasSize(1); + InitContactPointsRefresh refresh = + ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); + assertThat(refresh.contactPoints).containsExactlyInAnyOrder(ADDRESS1); + } + + @Test + public void should_refresh_all_nodes() { + // Given + NodeInfo info1 = Mockito.mock(NodeInfo.class); + NodeInfo info2 = Mockito.mock(NodeInfo.class); + List infos = ImmutableList.of(info1, info2); + Mockito.when(topologyMonitor.refreshNodeList()) + .thenReturn(CompletableFuture.completedFuture(infos)); + + // When + CompletionStage refreshNodesFuture = metadataManager.refreshNodes(); + waitForPendingAdminTasks(); + + // Then + assertThat(refreshNodesFuture).isSuccess(); + assertThat(metadataManager.refreshes).hasSize(1); + FullNodeListRefresh refresh = (FullNodeListRefresh) metadataManager.refreshes.get(0); + assertThat(refresh.nodeInfos).containsExactlyInAnyOrder(info1, info2); + } + + @Test + public void should_refresh_single_node() { + // Given + Node node = new DefaultNode(ADDRESS1); + NodeInfo info = Mockito.mock(NodeInfo.class); + Mockito.when(info.getDatacenter()).thenReturn("dc1"); + Mockito.when(topologyMonitor.refreshNode(node)) + .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); + + // When + CompletionStage refreshNodeFuture = metadataManager.refreshNode(node); + + // Then + // the info should have been copied to the node + assertThat(refreshNodeFuture).isSuccess(); + Mockito.verify(info, timeout(100)).getDatacenter(); + assertThat(node.getDatacenter()).isEqualTo("dc1"); + } + + @Test + public void should_ignore_node_refresh_if_topology_monitor_does_not_have_info() { + // Given + Node node = Mockito.mock(Node.class); + Mockito.when(topologyMonitor.refreshNode(node)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // When + CompletionStage refreshNodeFuture = metadataManager.refreshNode(node); + + // Then + assertThat(refreshNodeFuture).isSuccess(); + } + + @Test + public void should_add_node() { + // Given + NodeInfo info = Mockito.mock(NodeInfo.class); + Mockito.when(info.getConnectAddress()).thenReturn(ADDRESS1); + Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); + + // When + metadataManager.addNode(ADDRESS1); + waitForPendingAdminTasks(); + + // Then + assertThat(metadataManager.refreshes).hasSize(1); + AddNodeRefresh refresh = (AddNodeRefresh) metadataManager.refreshes.get(0); + assertThat(refresh.newNodeInfo).isEqualTo(info); + } + + @Test + public void should_not_add_node_if_connect_address_does_not_match() { + // Given + NodeInfo info = Mockito.mock(NodeInfo.class); + Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); + Mockito.when(info.getConnectAddress()) + .thenReturn( + ADDRESS2 // Does not match the address we got the info with + ); + + // When + metadataManager.addNode(ADDRESS1); + waitForPendingAdminTasks(); + + // Then + assertThat(metadataManager.refreshes).isEmpty(); + } + + @Test + public void should_not_add_node_if_topology_monitor_does_not_have_info() { + // Given + Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // When + metadataManager.addNode(ADDRESS1); + waitForPendingAdminTasks(); + + // Then + assertThat(metadataManager.refreshes).isEmpty(); + } + + @Test + public void should_remove_node() { + // When + metadataManager.removeNode(ADDRESS1); + waitForPendingAdminTasks(); + + // Then + assertThat(metadataManager.refreshes).hasSize(1); + RemoveNodeRefresh refresh = (RemoveNodeRefresh) metadataManager.refreshes.get(0); + assertThat(refresh.toRemove).isEqualTo(ADDRESS1); + } + + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + + private class TestMetadataManager extends MetadataManager { + + private List refreshes = new CopyOnWriteArrayList<>(); + + public TestMetadataManager(InternalDriverContext context) { + super(context); + } + + @Override + Void refresh(MetadataRefresh refresh) { + // Do not execute refreshes, just store them for inspection in the test + refreshes.add(refresh); + return null; + } + } + + // Wait for all the tasks on the pool's admin executor to complete. + private void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} From a82fd4e892f388f1abcdb9cf9ee5b54dfa8c7998 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 10:09:57 -0700 Subject: [PATCH 027/742] Make TopologyMonitor.NodeInfo top-level --- .../core/metadata/AddNodeRefresh.java | 4 +- .../core/metadata/DefaultNodeInfo.java | 2 +- .../core/metadata/FullNodeListRefresh.java | 9 +- .../core/metadata/MetadataManager.java | 5 +- .../internal/core/metadata/NodeInfo.java | 102 ++++++++++++++++++ .../internal/core/metadata/NodesRefresh.java | 2 +- .../core/metadata/TopologyMonitor.java | 47 -------- .../metadata/DefaultTopologyMonitorTest.java | 1 - .../metadata/FullNodeListRefreshTest.java | 4 +- .../core/metadata/MetadataManagerTest.java | 1 - 10 files changed, 114 insertions(+), 63 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 2b9e666373c..9f90537a09b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -23,9 +23,9 @@ public class AddNodeRefresh extends NodesRefresh { - @VisibleForTesting final TopologyMonitor.NodeInfo newNodeInfo; + @VisibleForTesting final NodeInfo newNodeInfo; - public AddNodeRefresh(DefaultMetadata oldMetadata, TopologyMonitor.NodeInfo newNodeInfo) { + AddNodeRefresh(DefaultMetadata oldMetadata, NodeInfo newNodeInfo) { super(oldMetadata); this.newNodeInfo = newNodeInfo; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java index c467056254a..d53966b66fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -23,7 +23,7 @@ import java.util.Optional; import java.util.Set; -public class DefaultNodeInfo implements TopologyMonitor.NodeInfo { +public class DefaultNodeInfo implements NodeInfo { public static Builder builder() { return new Builder(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 0d9822bb114..13d9659494f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -31,9 +31,9 @@ class FullNodeListRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); - @VisibleForTesting final Iterable nodeInfos; + @VisibleForTesting final Iterable nodeInfos; - FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos) { + FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos) { super(current); this.nodeInfos = nodeInfos; } @@ -45,11 +45,10 @@ protected Map computeNewNodes() { Map added = new HashMap<>(); Set seen = new HashSet<>(); - for (TopologyMonitor.NodeInfo nodeInfo : nodeInfos) { + for (NodeInfo nodeInfo : nodeInfos) { InetSocketAddress address = nodeInfo.getConnectAddress(); if (address == null) { - // TODO more advanced row validation (see 3.x), here or in TopologyMonitor? - LOG.debug("Ignoring node info with missing connect address"); + LOG.warn("Got node info with no connect address, ignoring"); continue; } seen.add(address); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 74d7ba81a61..061163bc75e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.EventExecutor; @@ -143,9 +142,9 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { // This would be a bug in the TopologyMonitor, protect against it LOG.warn( "Received a request to add a node for {}, " - + "but the provided info uses the address {}, ignoring it", + + "but the provided info uses the connect address {}, ignoring it", address, - info.getBroadcastAddress()); + info.getConnectAddress()); } else { refresh(new AddNodeRefresh(metadata, info)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java new file mode 100644 index 00000000000..315c4882d79 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Information about a node, returned by the {@link TopologyMonitor}. + * + *

This information will be copied to the corresponding {@link Node} in the metadata. + */ +public interface NodeInfo { + /** + * The address that the driver uses to connect to the node. This is the node's broadcast RPC + * address, transformed by the {@link AddressTranslator} if one is configured. + * + *

The driver uses this to uniquely identify a node. + * + *

This must not be null. If this instance is the reponse to a {@link + * TopologyMonitor#refreshNode(Node) refresh node} request, it must also match the address with + * which the request was made, otherwise the new node will be ignored. + */ + InetSocketAddress getConnectAddress(); + + /** + * The node's broadcast address. That is, the address that other nodes use to communicate with + * that node. + * + *

This is only used by the default topology monitor, so if you are writing a custom one and + * don't need this information, you can leave it empty. + */ + Optional getBroadcastAddress(); + + /** + * The node's listen address. That is, the address that the Cassandra process binds to. + * + *

This is currently not used anywhere in the driver. If you write a custom topology monitor + * and don't need this information, you can leave it empty. + */ + Optional getListenAddress(); + + /** + * The data center that this node belongs to, according to the Cassandra snitch. + * + *

This is used by some {@link LoadBalancingPolicy} implementations to compute the {@link + * NodeDistance}. + */ + String getDatacenter(); + + /** + * The rack that this node belongs to, according to the Cassandra snitch. + * + *

This is used by some {@link LoadBalancingPolicy} implementations to compute the {@link + * NodeDistance}. + */ + String getRack(); + + /** + * The Cassandra version that this node runs. + * + *

This is used when parsing the schema (schema tables sometimes change from one version to the + * next, even if the protocol version stays the same). If this is null, schema parsing will use + * the lowest version for the current protocol version, which might lead to inaccuracies. + */ + String getCassandraVersion(); + + /** + * The tokens that this node owns on the ring. + * + *

This is used to compute the driver-side token metadata (in particular, token-aware routing + * relies on this information). If you're not using token metadata in any way, you may return an + * empty set here. + */ + Set getTokens(); + + /** + * An additional map of free-form properties, that can be used by custom implementations. They + * will be copied as-is into {@link Node#getExtras()}. + */ + Map getExtras(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index f44e74c36a9..da7f72e89a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -42,7 +42,7 @@ void compute() { // TODO recompute token map (even if node list hasn't changed, b/c tokens might have changed) } - protected static void copyInfos(TopologyMonitor.NodeInfo nodeInfo, DefaultNode node) { + protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node) { node.broadcastAddress = nodeInfo.getBroadcastAddress(); node.listenAddress = nodeInfo.getListenAddress(); node.datacenter = nodeInfo.getDatacenter(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index faa6c4fe66c..b8a80cb478b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -21,11 +21,8 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; -import java.net.InetAddress; import java.net.InetSocketAddress; -import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletionStage; /** @@ -94,48 +91,4 @@ public interface TopologyMonitor { * always be returned in a single message (no paging). */ CompletionStage> refreshNodeList(); - - /** - * Information about a node, as it will be returned by the monitor. - * - *

This is distinct from what we expose in the public driver metadata. - */ - interface NodeInfo { - /** - * The address that the driver uses to connect to the node. This is the node's broadcast RPC - * address, transformed by the {@link AddressTranslator} if one is configured. - */ - InetSocketAddress getConnectAddress(); - - /** - * The node's broadcast address. That is, the address that other nodes use to communicate with - * that node. - * - *

This is only used by the default topology monitor, so if you are writing a custom one and - * don't need this information, you can leave it empty. - */ - Optional getBroadcastAddress(); - - /** - * The node's listen address. That is, the address that the Cassandra process binds to. - * - *

This is currently not used anywhere in the driver. If you write a custom topology monitor - * and don't need this information, you can leave it empty. - */ - Optional getListenAddress(); - - String getDatacenter(); - - String getRack(); - - String getCassandraVersion(); - - Set getTokens(); - - /** - * An additional map of free-form properties, that can be used by custom implementations. They - * will be copied as-is into the driver metadata's {@link Node}. - */ - Map getExtras(); - } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index aa8682b3f3d..1589e6bf407 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -24,7 +24,6 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; -import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index dad6d0fb8f9..2009da739c3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -37,7 +37,7 @@ public void should_add_and_remove_nodes() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); - Iterable newInfos = + Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); @@ -57,7 +57,7 @@ public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); - Iterable newInfos = + Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder() .withConnectAddress(ADDRESS1) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 982b6c95854..041a7918b4a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; -import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor.NodeInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Uninterruptibles; From 585063101d33dac59852cc6e803c2361d9937803 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 14:36:57 -0700 Subject: [PATCH 028/742] Initialize types module with TypeToken wrapper --- .gitignore | 7 +- core/pom.xml | 33 +++++++ pom.xml | 5 + types/pom.xml | 92 +++++++++++++++++++ .../driver/api/types/reflect/GenericType.java | 90 ++++++++++++++++++ .../api/types/reflect/GenericTypeTest.java | 38 ++++++++ 6 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 types/pom.xml create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java create mode 100644 types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java diff --git a/.gitignore b/.gitignore index e6b8c10a1be..be80b5f6688 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ -target/ .settings -.classpath -.project .DS_Store /.idea *.iml +.classpath +.project .java-version +target/ +dependency-reduced-pom.xml diff --git a/core/pom.xml b/core/pom.xml index 1adcd132db3..4963cdebf9e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -32,6 +32,11 @@ DataStax Java driver for Apache Cassandra® - core + + ${project.groupId} + java-driver-types + ${project.version} + com.datastax.oss native-protocol @@ -73,4 +78,32 @@ test + + + + + maven-shade-plugin + + + + shade + + + + + com.google.guava:guava + + + + + com.google + com.datastax.oss.driver.shaded.guava + + + + + + + + diff --git a/pom.xml b/pom.xml index 256c7aeee18..bf7df9e219e 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ 2017 + types core @@ -117,6 +118,10 @@ maven-javadoc-plugin 2.10.4 + + maven-shade-plugin + 3.0.0 + net.alchim31.maven diff --git a/types/pom.xml b/types/pom.xml new file mode 100644 index 00000000000..4a08807c04c --- /dev/null +++ b/types/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-SNAPSHOT + + + java-driver-types + jar + + DataStax Java driver for Apache Cassandra® - data types + + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + test + + + org.testng + testng + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + + + + + maven-shade-plugin + + + + shade + + + + + com.google.guava:guava + + + + + com.google + com.datastax.oss.driver.shaded.guava + + + + + + + + + diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java b/types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java new file mode 100644 index 00000000000..dcd8dbff848 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types.reflect; + +import com.google.common.reflect.TypeToken; + +/** + * Runtime representation of a generic Java type. + * + *

This is used by type codecs to indicate which Java types they accept, and by generic getters + * and setters in the driver's query API. + * + *

To create an instance, use one of the static factory methods, or create an anonymous class: + * + *

{@code
+ * GenericType> fooBarType = new GenericType>(){};
+ * }
+ * + * You are encouraged to store and reuse these objects. + */ +public abstract class GenericType { + + /** Creates a new instance representing a raw Java class. */ + public static GenericType of(Class type) { + return new SimpleGenericType<>(type); + } + + // This wraps -- and delegates most of the work to -- a Guava type token. The reason we don't + // expose that type directly is because we shade Guava. + private final TypeToken token; + + private GenericType(TypeToken token) { + this.token = token; + } + + protected GenericType() { + this.token = new TypeToken(getClass()) {}; + } + + /** + * This method is for internal use, DO NOT use it from client code. + * + *

It leaks a shaded type. This should be part of the internal API, but due to internal + * implementation details it has to be exposed here. + */ + public TypeToken __getToken() { + return token; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof GenericType) { + GenericType that = (GenericType) other; + return this.token.equals(that.token); + } else { + return false; + } + } + + @Override + public int hashCode() { + return token.hashCode(); + } + + @Override + public String toString() { + return token.toString(); + } + + private static class SimpleGenericType extends GenericType { + SimpleGenericType(Class type) { + super(TypeToken.of(type)); + } + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java b/types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java new file mode 100644 index 00000000000..39c1989a50b --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types.reflect; + +import com.google.common.reflect.TypeToken; +import java.util.List; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GenericTypeTest { + + @Test + public void should_wrap_class() { + GenericType stringType = GenericType.of(String.class); + assertThat(stringType.__getToken()).isEqualTo(TypeToken.of(String.class)); + } + + @Test + public void should_capture_generic_type() { + GenericType> stringListType = new GenericType>() {}; + TypeToken> stringListToken = new TypeToken>() {}; + assertThat(stringListType.__getToken()).isEqualTo(stringListToken); + } +} From 1045e6813c19d333ec1e08286c573c840a885d35 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 17:07:01 -0700 Subject: [PATCH 029/742] Flesh out base API for data types --- types/pom.xml | 4 + .../oss/driver/api/core/CqlIdentifier.java | 0 .../oss/driver/api/types/CustomType.java | 24 +++++ .../oss/driver/api/types/DataType.java | 23 +++++ .../oss/driver/api/types/DataTypes.java | 73 ++++++++++++++ .../oss/driver/api/types/ListType.java | 24 +++++ .../oss/driver/api/types/MapType.java | 26 +++++ .../oss/driver/api/types/SetType.java | 24 +++++ .../oss/driver/api/types/TupleType.java | 22 +++++ .../oss/driver/api/types/UserDefinedType.java | 27 +++++ .../driver/internal/core/util/Strings.java | 0 .../internal/types/DefaultCustomType.java | 55 +++++++++++ .../internal/types/DefaultListType.java | 64 ++++++++++++ .../driver/internal/types/DefaultMapType.java | 73 ++++++++++++++ .../driver/internal/types/DefaultSetType.java | 64 ++++++++++++ .../internal/types/DefaultTupleType.java | 61 ++++++++++++ .../types/DefaultUserDefinedType.java | 79 +++++++++++++++ .../driver/internal/types/PrimitiveType.java | 98 +++++++++++++++++++ .../types/UserDefinedTypeBuilder.java | 57 +++++++++++ .../driver/api/core/CqlIdentifierTest.java | 2 +- 20 files changed, 799 insertions(+), 1 deletion(-) rename {core => types}/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java (100%) create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/DataType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/ListType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/MapType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/SetType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java rename {core => types}/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java (100%) create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java rename {core => types}/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java (98%) diff --git a/types/pom.xml b/types/pom.xml index 4a08807c04c..f486f7fc76f 100644 --- a/types/pom.xml +++ b/types/pom.xml @@ -32,6 +32,10 @@ DataStax Java driver for Apache Cassandra® - data types + + com.datastax.oss + native-protocol + com.google.guava guava diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java similarity index 100% rename from core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java rename to types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java b/types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java new file mode 100644 index 00000000000..f9cbcd9a622 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +public interface CustomType { + /** + * The fully qualified name of the subtype of {@code org.apache.cassandra.db.marshal.AbstractType} + * that represents this type server-side. + */ + String getClassName(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/DataType.java b/types/src/main/java/com/datastax/oss/driver/api/types/DataType.java new file mode 100644 index 00000000000..10772d7f153 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/DataType.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +/** + * The type of a CQL column or function argument. + * + * @see DataTypes + */ +public interface DataType {} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java b/types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java new file mode 100644 index 00000000000..00113af7e63 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import com.datastax.oss.driver.internal.types.DefaultCustomType; +import com.datastax.oss.driver.internal.types.DefaultListType; +import com.datastax.oss.driver.internal.types.DefaultMapType; +import com.datastax.oss.driver.internal.types.DefaultSetType; +import com.datastax.oss.driver.internal.types.DefaultTupleType; +import com.datastax.oss.driver.internal.types.PrimitiveType; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; + +/** Utility class to get or build {@link DataType} instances. */ +public class DataTypes { + + public static final DataType ASCII = new PrimitiveType(ProtocolConstants.DataType.ASCII); + public static final DataType BIGINT = new PrimitiveType(ProtocolConstants.DataType.BIGINT); + public static final DataType BLOB = new PrimitiveType(ProtocolConstants.DataType.BLOB); + public static final DataType BOOLEAN = new PrimitiveType(ProtocolConstants.DataType.BOOLEAN); + public static final DataType COUNTER = new PrimitiveType(ProtocolConstants.DataType.COUNTER); + public static final DataType DECIMAL = new PrimitiveType(ProtocolConstants.DataType.DECIMAL); + public static final DataType DOUBLE = new PrimitiveType(ProtocolConstants.DataType.DOUBLE); + public static final DataType FLOAT = new PrimitiveType(ProtocolConstants.DataType.FLOAT); + public static final DataType INT = new PrimitiveType(ProtocolConstants.DataType.INT); + public static final DataType TIMESTAMP = new PrimitiveType(ProtocolConstants.DataType.TIMESTAMP); + public static final DataType UUID = new PrimitiveType(ProtocolConstants.DataType.UUID); + public static final DataType VARINT = new PrimitiveType(ProtocolConstants.DataType.VARINT); + public static final DataType TIMEUUID = new PrimitiveType(ProtocolConstants.DataType.TIMEUUID); + public static final DataType INET = new PrimitiveType(ProtocolConstants.DataType.INET); + public static final DataType DATE = new PrimitiveType(ProtocolConstants.DataType.DATE); + public static final DataType TEXT = new PrimitiveType(ProtocolConstants.DataType.VARCHAR); + public static final DataType TIME = new PrimitiveType(ProtocolConstants.DataType.TIME); + public static final DataType SMALLINT = new PrimitiveType(ProtocolConstants.DataType.SMALLINT); + public static final DataType TINYINT = new PrimitiveType(ProtocolConstants.DataType.TINYINT); + public static final DataType DURATION = new PrimitiveType(ProtocolConstants.DataType.DURATION); + + public static CustomType custom(String className) { + return new DefaultCustomType(className); + } + + public static ListType listOf(DataType elementType) { + // frozen == true is only used in column definitions, it's unlikely that end users will need to + // create such instances. + return new DefaultListType(elementType, false); + } + + public static SetType setOf(DataType elementType) { + return new DefaultSetType(elementType, false); + } + + public static MapType mapOf(DataType keyType, DataType valueType) { + return new DefaultMapType(keyType, valueType, false); + } + + public static TupleType tupleOf(DataType... componentTypes) { + return new DefaultTupleType(ImmutableList.copyOf(Arrays.asList(componentTypes))); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/ListType.java b/types/src/main/java/com/datastax/oss/driver/api/types/ListType.java new file mode 100644 index 00000000000..f74823d29fb --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/ListType.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import com.datastax.oss.driver.api.types.DataType; + +public interface ListType { + public DataType getElementType(); + + public boolean isFrozen(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/MapType.java b/types/src/main/java/com/datastax/oss/driver/api/types/MapType.java new file mode 100644 index 00000000000..94f77269ab5 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/MapType.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import com.datastax.oss.driver.api.types.DataType; + +public interface MapType { + public DataType getKeyType(); + + public DataType getValueType(); + + public boolean isFrozen(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/SetType.java b/types/src/main/java/com/datastax/oss/driver/api/types/SetType.java new file mode 100644 index 00000000000..f3e52d7f8f9 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/SetType.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import com.datastax.oss.driver.api.types.DataType; + +public interface SetType { + public DataType getElementType(); + + public boolean isFrozen(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java b/types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java new file mode 100644 index 00000000000..dd8ea3ccc6c --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import java.util.List; + +public interface TupleType { + List getComponentTypes(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java new file mode 100644 index 00000000000..b36e66902a1 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.types; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import java.util.Map; + +public interface UserDefinedType { + CqlIdentifier getKeyspace(); + + CqlIdentifier getName(); + + Map getFieldTypes(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java similarity index 100% rename from core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java rename to types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java new file mode 100644 index 00000000000..d7652652fb2 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.CustomType; +import com.google.common.base.Preconditions; + +public class DefaultCustomType implements CustomType { + private final String className; + + public DefaultCustomType(String className) { + Preconditions.checkNotNull(className); + this.className = className; + } + + @Override + public String getClassName() { + return className; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof CustomType) { + CustomType that = (CustomType) other; + return this.className.equals(that.getClassName()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return className.hashCode(); + } + + @Override + public String toString() { + return "Custom(" + className + ")"; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java new file mode 100644 index 00000000000..b6d52887b35 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.ListType; +import com.google.common.base.Preconditions; + +public class DefaultListType implements ListType { + private final DataType elementType; + private final boolean frozen; + + public DefaultListType(DataType elementType, boolean frozen) { + Preconditions.checkNotNull(elementType); + this.elementType = elementType; + this.frozen = frozen; + } + + @Override + public DataType getElementType() { + return elementType; + } + + @Override + public boolean isFrozen() { + return frozen; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ListType) { + ListType that = (ListType) other; + // frozen is not taken into account + return this.elementType.equals(that.getElementType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.elementType.hashCode(); + } + + @Override + public String toString() { + return "List(" + elementType + ", " + (frozen ? "" : "not ") + "frozen)"; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java new file mode 100644 index 00000000000..a6fb8fb5a99 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.MapType; +import com.google.common.base.Preconditions; +import java.util.Objects; + +public class DefaultMapType implements MapType { + private final DataType keyType; + private final DataType valueType; + private final boolean frozen; + + public DefaultMapType(DataType keyType, DataType valueType, boolean frozen) { + Preconditions.checkNotNull(keyType); + Preconditions.checkNotNull(valueType); + this.keyType = keyType; + this.valueType = valueType; + this.frozen = frozen; + } + + @Override + public DataType getKeyType() { + return keyType; + } + + @Override + public DataType getValueType() { + return valueType; + } + + @Override + public boolean isFrozen() { + return frozen; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof MapType) { + MapType that = (MapType) other; + // frozen is not taken into account + return this.keyType.equals(that.getKeyType()) && this.valueType.equals(that.getValueType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(keyType, valueType); + } + + @Override + public String toString() { + return "Map(" + keyType + " => " + valueType + ", " + (frozen ? "" : "not ") + "frozen)"; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java new file mode 100644 index 00000000000..b71c8598bd9 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.SetType; +import com.google.common.base.Preconditions; + +public class DefaultSetType implements SetType { + private final DataType elementType; + private final boolean frozen; + + public DefaultSetType(DataType elementType, boolean frozen) { + Preconditions.checkNotNull(elementType); + this.elementType = elementType; + this.frozen = frozen; + } + + @Override + public DataType getElementType() { + return elementType; + } + + @Override + public boolean isFrozen() { + return frozen; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof SetType) { + SetType that = (SetType) other; + // frozen is not taken into account + return this.elementType.equals(that.getElementType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.elementType.hashCode(); + } + + @Override + public String toString() { + return "Set(" + elementType + ", " + (frozen ? "" : "not ") + "frozen)"; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java new file mode 100644 index 00000000000..c4c45cb83af --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.TupleType; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import java.util.List; + +public class DefaultTupleType implements TupleType { + + private final List componentTypes; + + public DefaultTupleType(List componentTypes) { + Preconditions.checkNotNull(componentTypes); + this.componentTypes = componentTypes; + } + + @Override + public List getComponentTypes() { + return componentTypes; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TupleType) { + TupleType that = (TupleType) other; + return this.componentTypes.equals(that.getComponentTypes()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return componentTypes.hashCode(); + } + + @Override + public String toString() { + return "Tuple(" + WITH_COMMA.join(componentTypes) + ")"; + } + + private static final Joiner WITH_COMMA = Joiner.on(", "); +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java new file mode 100644 index 00000000000..5910edc8d58 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.UserDefinedType; +import com.google.common.base.Preconditions; +import java.util.Map; +import java.util.Objects; + +public class DefaultUserDefinedType implements UserDefinedType { + + private final CqlIdentifier keyspace; + private final CqlIdentifier name; + private final Map fieldTypes; + + public DefaultUserDefinedType( + CqlIdentifier keyspace, CqlIdentifier name, Map fieldTypes) { + Preconditions.checkNotNull(keyspace); + Preconditions.checkNotNull(name); + Preconditions.checkNotNull(fieldTypes); + this.keyspace = keyspace; + this.name = name; + this.fieldTypes = fieldTypes; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public Map getFieldTypes() { + return fieldTypes; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof UserDefinedType) { + UserDefinedType that = (UserDefinedType) other; + return this.keyspace.equals(that.getKeyspace()) + && this.name.equals(that.getName()) + && this.fieldTypes.equals(that.getFieldTypes()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(keyspace, name, fieldTypes); + } + + @Override + public String toString() { + return "UDT(" + keyspace.asPrettyCql() + "." + name.asPrettyCql() + ")"; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java b/types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java new file mode 100644 index 00000000000..2626db32bb5 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.protocol.internal.ProtocolConstants; + +public class PrimitiveType implements DataType { + + public final int protocolCode; + + public PrimitiveType(int protocolCode) { + this.protocolCode = protocolCode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof PrimitiveType) { + PrimitiveType that = (PrimitiveType) other; + return this.protocolCode == that.protocolCode; + } else { + return false; + } + } + + @Override + public int hashCode() { + return protocolCode; + } + + @Override + public String toString() { + return codeName(protocolCode); + } + + private static String codeName(int protocolCode) { + // Reminder: we don't use enums to leave the door open for custom extensions + switch (protocolCode) { + case ProtocolConstants.DataType.ASCII: + return "ASCII"; + case ProtocolConstants.DataType.BIGINT: + return "BIGINT"; + case ProtocolConstants.DataType.BLOB: + return "BLOB"; + case ProtocolConstants.DataType.BOOLEAN: + return "BOOLEAN"; + case ProtocolConstants.DataType.COUNTER: + return "COUNTER"; + case ProtocolConstants.DataType.DECIMAL: + return "DECIMAL"; + case ProtocolConstants.DataType.DOUBLE: + return "DOUBLE"; + case ProtocolConstants.DataType.FLOAT: + return "FLOAT"; + case ProtocolConstants.DataType.INT: + return "INT"; + case ProtocolConstants.DataType.TIMESTAMP: + return "TIMESTAMP"; + case ProtocolConstants.DataType.UUID: + return "UUID"; + case ProtocolConstants.DataType.VARINT: + return "VARINT"; + case ProtocolConstants.DataType.TIMEUUID: + return "TIMEUUID"; + case ProtocolConstants.DataType.INET: + return "INET"; + case ProtocolConstants.DataType.DATE: + return "DATE"; + case ProtocolConstants.DataType.VARCHAR: + return "TEXT"; + case ProtocolConstants.DataType.TIME: + return "TIME"; + case ProtocolConstants.DataType.SMALLINT: + return "SMALLINT"; + case ProtocolConstants.DataType.TINYINT: + return "TINYINT"; + case ProtocolConstants.DataType.DURATION: + return "DURATION"; + default: + return "0x" + Integer.toHexString(protocolCode); + } + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java b/types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java new file mode 100644 index 00000000000..be2d994fa65 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.types; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.types.UserDefinedType; +import com.google.common.collect.ImmutableMap; + +/** + * Helper class to build {@link UserDefinedType} instances. + * + *

+ * + *

This is not part of the public API, because building user defined types manually can be + * tricky: the fields must be defined in the exact same order as the database definition, otherwise + * you will insert corrupt data in your database. If you decide to use this class anyway, make sure + * that you define fields in the correct order, and that the database schema never changes. + */ +public class UserDefinedTypeBuilder { + + private final CqlIdentifier keyspaceName; + private final CqlIdentifier typeName; + private final ImmutableMap.Builder fieldTypesBuilder; + + public UserDefinedTypeBuilder(CqlIdentifier keyspaceName, CqlIdentifier typeName) { + this.keyspaceName = keyspaceName; + this.typeName = typeName; + this.fieldTypesBuilder = ImmutableMap.builder(); + } + + /** + * Adds a new field. The fields in the resulting type will be in the order of the calls to this + * method. + */ + public UserDefinedTypeBuilder withField(CqlIdentifier name, DataType dataType) { + fieldTypesBuilder.put(name, dataType); + return this; + } + + public UserDefinedType build() { + return new DefaultUserDefinedType(keyspaceName, typeName, fieldTypesBuilder.build()); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/types/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java similarity index 98% rename from core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java rename to types/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java index aa679114c90..ab166692ce6 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java +++ b/types/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java @@ -17,7 +17,7 @@ import org.testng.annotations.Test; -import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class CqlIdentifierTest { @Test From 6948449c7cf42c5cca29f9b77b34cf0888fd6331 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 21 Apr 2017 06:25:08 -0700 Subject: [PATCH 030/742] Rename package --- .../oss/driver/api/{types => type}/CustomType.java | 2 +- .../oss/driver/api/{types => type}/DataType.java | 2 +- .../oss/driver/api/{types => type}/DataTypes.java | 14 +++++++------- .../oss/driver/api/{types => type}/ListType.java | 4 +--- .../oss/driver/api/{types => type}/MapType.java | 4 +--- .../oss/driver/api/{types => type}/SetType.java | 4 +--- .../oss/driver/api/{types => type}/TupleType.java | 2 +- .../api/{types => type}/UserDefinedType.java | 2 +- .../api/{types => type}/reflect/GenericType.java | 2 +- .../{types => type}/DefaultCustomType.java | 4 ++-- .../internal/{types => type}/DefaultListType.java | 6 +++--- .../internal/{types => type}/DefaultMapType.java | 6 +++--- .../internal/{types => type}/DefaultSetType.java | 6 +++--- .../internal/{types => type}/DefaultTupleType.java | 6 +++--- .../{types => type}/DefaultUserDefinedType.java | 6 +++--- .../internal/{types => type}/PrimitiveType.java | 4 ++-- .../{types => type}/UserDefinedTypeBuilder.java | 6 +++--- .../{types => type}/reflect/GenericTypeTest.java | 2 +- 18 files changed, 38 insertions(+), 44 deletions(-) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/CustomType.java (95%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/DataType.java (94%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/DataTypes.java (89%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/ListType.java (88%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/MapType.java (88%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/SetType.java (88%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/TupleType.java (94%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/UserDefinedType.java (95%) rename types/src/main/java/com/datastax/oss/driver/api/{types => type}/reflect/GenericType.java (98%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultCustomType.java (93%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultListType.java (91%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultMapType.java (92%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultSetType.java (91%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultTupleType.java (91%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/DefaultUserDefinedType.java (93%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/PrimitiveType.java (96%) rename types/src/main/java/com/datastax/oss/driver/internal/{types => type}/UserDefinedTypeBuilder.java (92%) rename types/src/test/java/com/datastax/oss/driver/api/{types => type}/reflect/GenericTypeTest.java (96%) diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java b/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java index f9cbcd9a622..291a4a84e5d 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/CustomType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; +package com.datastax.oss.driver.api.type; public interface CustomType { /** diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/DataType.java b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/types/DataType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/DataType.java index 10772d7f153..c29c11786bf 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/DataType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; +package com.datastax.oss.driver.api.type; /** * The type of a CQL column or function argument. diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java b/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java similarity index 89% rename from types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java rename to types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java index 00113af7e63..14c3c0cd8ac 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/DataTypes.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; +package com.datastax.oss.driver.api.type; -import com.datastax.oss.driver.internal.types.DefaultCustomType; -import com.datastax.oss.driver.internal.types.DefaultListType; -import com.datastax.oss.driver.internal.types.DefaultMapType; -import com.datastax.oss.driver.internal.types.DefaultSetType; -import com.datastax.oss.driver.internal.types.DefaultTupleType; -import com.datastax.oss.driver.internal.types.PrimitiveType; +import com.datastax.oss.driver.internal.type.DefaultCustomType; +import com.datastax.oss.driver.internal.type.DefaultListType; +import com.datastax.oss.driver.internal.type.DefaultMapType; +import com.datastax.oss.driver.internal.type.DefaultSetType; +import com.datastax.oss.driver.internal.type.DefaultTupleType; +import com.datastax.oss.driver.internal.type.PrimitiveType; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.google.common.collect.ImmutableList; import java.util.Arrays; diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/ListType.java b/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java similarity index 88% rename from types/src/main/java/com/datastax/oss/driver/api/types/ListType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/ListType.java index f74823d29fb..57f84cc3002 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/ListType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; - -import com.datastax.oss.driver.api.types.DataType; +package com.datastax.oss.driver.api.type; public interface ListType { public DataType getElementType(); diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/MapType.java b/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java similarity index 88% rename from types/src/main/java/com/datastax/oss/driver/api/types/MapType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/MapType.java index 94f77269ab5..0678114d78b 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/MapType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; - -import com.datastax.oss.driver.api.types.DataType; +package com.datastax.oss.driver.api.type; public interface MapType { public DataType getKeyType(); diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/SetType.java b/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java similarity index 88% rename from types/src/main/java/com/datastax/oss/driver/api/types/SetType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/SetType.java index f3e52d7f8f9..8bd8a12efff 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/SetType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; - -import com.datastax.oss.driver.api.types.DataType; +package com.datastax.oss.driver.api.type; public interface SetType { public DataType getElementType(); diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java b/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java index dd8ea3ccc6c..4f1f7ac1a48 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/TupleType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; +package com.datastax.oss.driver.api.type; import java.util.List; diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java index b36e66902a1..278238859c4 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/UserDefinedType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types; +package com.datastax.oss.driver.api.type; import com.datastax.oss.driver.api.core.CqlIdentifier; import java.util.Map; diff --git a/types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java b/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java similarity index 98% rename from types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java rename to types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java index dcd8dbff848..e2cbb83b43f 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/types/reflect/GenericType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types.reflect; +package com.datastax.oss.driver.api.type.reflect; import com.google.common.reflect.TypeToken; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java similarity index 93% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java index d7652652fb2..a1d30977988 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultCustomType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.CustomType; +import com.datastax.oss.driver.api.type.CustomType; import com.google.common.base.Preconditions; public class DefaultCustomType implements CustomType { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java similarity index 91% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java index b6d52887b35..3a5616a55ad 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultListType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.ListType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.ListType; import com.google.common.base.Preconditions; public class DefaultListType implements ListType { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java similarity index 92% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java index a6fb8fb5a99..0be851c62f7 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultMapType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.MapType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.MapType; import com.google.common.base.Preconditions; import java.util.Objects; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java similarity index 91% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java index b71c8598bd9..036f61f4917 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultSetType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.SetType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.SetType; import com.google.common.base.Preconditions; public class DefaultSetType implements SetType { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java similarity index 91% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java index c4c45cb83af..f92cfc2d6e8 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultTupleType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.TupleType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.TupleType; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import java.util.List; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java similarity index 93% rename from types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java index 5910edc8d58..9d452aa0bad 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/DefaultUserDefinedType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.UserDefinedType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.UserDefinedType; import com.google.common.base.Preconditions; import java.util.Map; import java.util.Objects; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java index 2626db32bb5..f44b10bc584 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/PrimitiveType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; -import com.datastax.oss.driver.api.types.DataType; +import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.protocol.internal.ProtocolConstants; public class PrimitiveType implements DataType { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java similarity index 92% rename from types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java rename to types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java index be2d994fa65..3eea1ce50cf 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/types/UserDefinedTypeBuilder.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.types; +package com.datastax.oss.driver.internal.type; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.types.DataType; -import com.datastax.oss.driver.api.types.UserDefinedType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.UserDefinedType; import com.google.common.collect.ImmutableMap; /** diff --git a/types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java b/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java similarity index 96% rename from types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java rename to types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java index 39c1989a50b..a8333ad3e13 100644 --- a/types/src/test/java/com/datastax/oss/driver/api/types/reflect/GenericTypeTest.java +++ b/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.types.reflect; +package com.datastax.oss.driver.api.type.reflect; import com.google.common.reflect.TypeToken; import java.util.List; From 260d441f5368ec3acab409fee1707192a5ef3318 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 21 Apr 2017 07:29:25 -0700 Subject: [PATCH 031/742] Make data types serializable --- .../oss/driver/api/type/DataType.java | 4 +++- .../internal/type/DefaultCustomType.java | 11 +++++++++++ .../driver/internal/type/DefaultListType.java | 12 ++++++++++++ .../driver/internal/type/DefaultMapType.java | 14 ++++++++++++++ .../driver/internal/type/DefaultSetType.java | 12 ++++++++++++ .../internal/type/DefaultTupleType.java | 15 +++++++++++++-- .../internal/type/DefaultUserDefinedType.java | 19 +++++++++++++++++-- .../internal/type/UserDefinedTypeBuilder.java | 2 -- 8 files changed, 82 insertions(+), 7 deletions(-) diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java index c29c11786bf..52f677fb304 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.type; +import java.io.Serializable; + /** * The type of a CQL column or function argument. * * @see DataTypes */ -public interface DataType {} +public interface DataType extends Serializable {} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java index a1d30977988..081b681b24c 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java @@ -17,8 +17,14 @@ import com.datastax.oss.driver.api.type.CustomType; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; public class DefaultCustomType implements CustomType { + + private static final long serialVersionUID = 1; + + /** @serial */ private final String className; public DefaultCustomType(String className) { @@ -52,4 +58,9 @@ public int hashCode() { public String toString() { return "Custom(" + className + ")"; } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(className); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java index 3a5616a55ad..b6aacde5508 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java @@ -18,9 +18,16 @@ import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.ListType; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; public class DefaultListType implements ListType { + + private static final long serialVersionUID = 1; + + /** @serial */ private final DataType elementType; + /** @serial */ private final boolean frozen; public DefaultListType(DataType elementType, boolean frozen) { @@ -61,4 +68,9 @@ public int hashCode() { public String toString() { return "List(" + elementType + ", " + (frozen ? "" : "not ") + "frozen)"; } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(elementType); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java index 0be851c62f7..caec0a5638f 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java @@ -18,11 +18,19 @@ import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.MapType; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.Objects; public class DefaultMapType implements MapType { + + private static final long serialVersionUID = 1; + + /** @serial */ private final DataType keyType; + /** @serial */ private final DataType valueType; + /** @serial */ private final boolean frozen; public DefaultMapType(DataType keyType, DataType valueType, boolean frozen) { @@ -70,4 +78,10 @@ public int hashCode() { public String toString() { return "Map(" + keyType + " => " + valueType + ", " + (frozen ? "" : "not ") + "frozen)"; } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(keyType); + Preconditions.checkNotNull(valueType); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java index 036f61f4917..360b80f01f0 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java @@ -18,9 +18,16 @@ import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.SetType; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; public class DefaultSetType implements SetType { + + private static final long serialVersionUID = 1; + + /** @serial */ private final DataType elementType; + /** @serial */ private final boolean frozen; public DefaultSetType(DataType elementType, boolean frozen) { @@ -61,4 +68,9 @@ public int hashCode() { public String toString() { return "Set(" + elementType + ", " + (frozen ? "" : "not ") + "frozen)"; } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(elementType); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java index f92cfc2d6e8..7e03ec4e7b5 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java @@ -19,15 +19,21 @@ import com.datastax.oss.driver.api.type.TupleType; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.List; public class DefaultTupleType implements TupleType { - private final List componentTypes; + private static final long serialVersionUID = 1; + + /** @serial */ + private final ImmutableList componentTypes; public DefaultTupleType(List componentTypes) { Preconditions.checkNotNull(componentTypes); - this.componentTypes = componentTypes; + this.componentTypes = ImmutableList.copyOf(componentTypes); } @Override @@ -58,4 +64,9 @@ public String toString() { } private static final Joiner WITH_COMMA = Joiner.on(", "); + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(componentTypes); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java index 9d452aa0bad..211eddc2983 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java @@ -19,14 +19,22 @@ import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.UserDefinedType; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.Map; import java.util.Objects; public class DefaultUserDefinedType implements UserDefinedType { + private static final long serialVersionUID = 1; + + /** @serial */ private final CqlIdentifier keyspace; + /** @serial */ private final CqlIdentifier name; - private final Map fieldTypes; + /** @serial */ + private final ImmutableMap fieldTypes; public DefaultUserDefinedType( CqlIdentifier keyspace, CqlIdentifier name, Map fieldTypes) { @@ -35,7 +43,7 @@ public DefaultUserDefinedType( Preconditions.checkNotNull(fieldTypes); this.keyspace = keyspace; this.name = name; - this.fieldTypes = fieldTypes; + this.fieldTypes = ImmutableMap.copyOf(fieldTypes); } @Override @@ -76,4 +84,11 @@ public int hashCode() { public String toString() { return "UDT(" + keyspace.asPrettyCql() + "." + name.asPrettyCql() + ")"; } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(keyspace); + Preconditions.checkNotNull(name); + Preconditions.checkNotNull(fieldTypes); + } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java index 3eea1ce50cf..c692ee0bb47 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java @@ -23,8 +23,6 @@ /** * Helper class to build {@link UserDefinedType} instances. * - *

- * *

This is not part of the public API, because building user defined types manually can be * tricky: the fields must be defined in the exact same order as the database definition, otherwise * you will insert corrupt data in your database. If you decide to use this class anyway, make sure From a9c4c03a37a132d73fb2c4c8cd3c07a287ca48df Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 21 Apr 2017 18:31:11 -0700 Subject: [PATCH 032/742] Add type codecs, registry, and UDT and tuple values --- .../adminrequest/AdminRequestHandler.java | 6 +- .../core/adminrequest/AdminResult.java | 47 +- .../core/adminrequest/codec/BooleanCodec.java | 42 -- .../core/adminrequest/codec/IntCodec.java | 45 -- .../core/adminrequest/codec/ListCodec.java | 80 --- .../core/adminrequest/codec/MapCodec.java | 97 --- .../core/adminrequest/codec/SetCodec.java | 78 --- .../core/adminrequest/codec/TypeCodecs.java | 43 -- .../core/adminrequest/codec/VarcharCodec.java | 40 -- .../core/metadata/DefaultTopologyMonitor.java | 18 +- .../metadata/DefaultTopologyMonitorTest.java | 53 +- .../driver/api/core/CoreProtocolVersion.java | 0 .../oss/driver/api/core/CqlIdentifier.java | 13 +- .../oss/driver/api/core/ProtocolVersion.java | 13 +- .../driver/api/core/data/AccessibleById.java | 43 ++ .../api/core/data/AccessibleByIndex.java | 24 +- .../api/core/data/AccessibleByName.java | 59 ++ .../oss/driver/api/core/data/CqlDuration.java | 566 +++++++++++++++++ .../oss/driver/api/core/data/Data.java | 47 ++ .../driver/api/core/data/GettableById.java | 584 ++++++++++++++++++ .../driver/api/core/data/GettableByIndex.java | 450 ++++++++++++++ .../driver/api/core/data/GettableByName.java | 580 +++++++++++++++++ .../driver/api/core/data/SettableById.java | 468 ++++++++++++++ .../driver/api/core/data/SettableByIndex.java | 418 +++++++++++++ .../driver/api/core/data/SettableByName.java | 469 ++++++++++++++ .../oss/driver/api/core/data/TupleValue.java | 31 + .../oss/driver/api/core/data/UdtValue.java | 37 ++ .../api/core/detach/AttachmentPoint.java | 40 +- .../driver/api/core/detach/Detachable.java | 54 ++ .../oss/driver/api/type/CustomType.java | 8 +- .../oss/driver/api/type/DataType.java | 8 +- .../oss/driver/api/type/DataTypes.java | 17 +- .../oss/driver/api/type/ListType.java | 12 +- .../datastax/oss/driver/api/type/MapType.java | 14 +- .../datastax/oss/driver/api/type/SetType.java | 12 +- .../oss/driver/api/type/TupleType.java | 13 +- .../oss/driver/api/type/UserDefinedType.java | 31 +- .../type/codec/CodecNotFoundException.java | 60 ++ .../api/type/codec/PrimitiveBooleanCodec.java | 44 ++ .../api/type/codec/PrimitiveByteCodec.java | 30 +- .../api/type/codec/PrimitiveDoubleCodec.java | 44 ++ .../api/type/codec/PrimitiveFloatCodec.java | 44 ++ .../api/type/codec/PrimitiveIntCodec.java | 44 ++ .../api/type/codec/PrimitiveLongCodec.java | 44 ++ .../api/type/codec/PrimitiveShortCodec.java | 44 ++ .../oss/driver/api/type/codec/TypeCodec.java | 94 +++ .../oss/driver/api/type/codec/TypeCodecs.java | 114 ++++ .../type/codec/registry/CodecRegistry.java | 68 ++ .../driver/api/type/reflect/GenericType.java | 84 ++- .../internal/core/data/DefaultTupleValue.java | 121 ++++ .../internal/core/data/DefaultUdtValue.java | 131 ++++ .../internal/core/data/IdentifierIndex.java | 70 +++ .../driver/internal/core/util/Strings.java | 18 + .../internal/type/DefaultCustomType.java | 11 + .../driver/internal/type/DefaultListType.java | 11 + .../driver/internal/type/DefaultMapType.java | 12 + .../driver/internal/type/DefaultSetType.java | 11 + .../internal/type/DefaultTupleType.java | 36 +- .../internal/type/DefaultUserDefinedType.java | 90 ++- .../driver/internal/type/PrimitiveType.java | 20 +- .../internal/type/UserDefinedTypeBuilder.java | 16 +- .../internal/type/codec/BigIntCodec.java | 71 +++ .../internal/type}/codec/BlobCodec.java | 29 +- .../internal/type/codec/BooleanCodec.java | 79 +++ .../internal/type/codec/CounterCodec.java | 26 + .../internal/type/codec/CqlDurationCodec.java | 96 +++ .../internal/type/codec/CustomCodec.java | 65 ++ .../driver/internal/type/codec/DateCodec.java | 133 ++++ .../internal/type/codec/DecimalCodec.java | 88 +++ .../internal/type/codec/DoubleCodec.java | 71 +++ .../internal/type/codec/FloatCodec.java | 71 +++ .../internal/type}/codec/InetCodec.java | 44 +- .../driver/internal/type/codec/IntCodec.java | 72 +++ .../driver/internal/type/codec/ListCodec.java | 180 ++++++ .../driver/internal/type/codec/MapCodec.java | 234 +++++++ .../internal/type/codec/ParseUtils.java | 141 +++++ .../driver/internal/type/codec/SetCodec.java | 181 ++++++ .../internal/type/codec/SmallIntCodec.java | 71 +++ .../internal/type/codec/StringCodec.java | 79 +++ .../driver/internal/type/codec/TimeCodec.java | 93 +++ .../internal/type/codec/TimeUuidCodec.java | 58 ++ .../internal/type/codec/TimestampCodec.java | 117 ++++ .../internal/type/codec/TinyIntCodec.java | 71 +++ .../internal/type/codec/TupleCodec.java | 201 ++++++ .../driver/internal/type/codec/UdtCodec.java | 231 +++++++ .../driver/internal/type/codec/UuidCodec.java | 76 +++ .../internal/type/codec/VarIntCodec.java | 64 ++ .../codec/registry/CachingCodecRegistry.java | 372 +++++++++++ .../codec/registry/DefaultCodecRegistry.java | 120 ++++ .../driver/internal/type/util/VIntCoding.java | 179 ++++++ .../driver/api/core/data/CqlDurationTest.java | 131 ++++ .../api/type/reflect/GenericTypeTest.java | 14 + .../core/data/AccessibleByIdTestBase.java | 471 ++++++++++++++ .../core/data/AccessibleByIndexTestBase.java | 284 +++++++++ .../core/data/DefaultTupleValueTest.java | 31 + .../core/data/DefaultUdtValueTest.java | 40 ++ .../core/data/IdentifierIndexTest.java | 63 ++ .../internal/type/codec/BigIntCodecTest.java | 71 +++ .../internal/type/codec/BlobCodecTest.java | 85 +++ .../internal/type/codec/BooleanCodecTest.java | 70 +++ .../internal/type/codec/CodecTestBase.java | 59 ++ .../internal/type/codec/CounterCodecTest.java | 71 +++ .../type/codec/CqlDurationCodecTest.java | 75 +++ .../type/codec/CqlIntToStringCodec.java | 66 ++ .../internal/type/codec/CustomCodecTest.java | 86 +++ .../internal/type/codec/DateCodecTest.java | 89 +++ .../internal/type/codec/DecimalCodecTest.java | 81 +++ .../internal/type/codec/DoubleCodecTest.java | 68 ++ .../internal/type/codec/FloatCodecTest.java | 68 ++ .../internal/type/codec/InetCodecTest.java | 97 +++ .../internal/type/codec/IntCodecTest.java | 73 +++ .../internal/type/codec/ListCodecTest.java | 143 +++++ .../internal/type/codec/MapCodecTest.java | 189 ++++++ .../internal/type/codec/SetCodecTest.java | 146 +++++ .../type/codec/SmallIntCodecTest.java | 73 +++ .../internal/type/codec/StringCodecTest.java | 62 ++ .../internal/type/codec/TimeCodecTest.java | 78 +++ .../type/codec/TimeUuidCodecTest.java | 56 ++ .../type/codec/TimestampCodecTest.java | 81 +++ .../internal/type/codec/TinyIntCodecTest.java | 68 ++ .../internal/type/codec/TupleCodecTest.java | 166 +++++ .../internal/type/codec/UdtCodecTest.java | 173 ++++++ .../internal/type/codec/UuidCodecTest.java | 75 +++ .../internal/type/codec/VarintCodecTest.java | 64 ++ .../registry/CachingCodecRegistryTest.java | 443 +++++++++++++ 125 files changed, 12472 insertions(+), 572 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java rename {core => types}/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java (100%) rename {core => types}/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java (67%) create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java rename core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java => types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java (56%) create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java rename core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java => types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java (51%) create mode 100644 types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java rename core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java => types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java (51%) create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java create mode 100644 types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java rename {core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest => types/src/main/java/com/datastax/oss/driver/internal/type}/codec/BlobCodec.java (56%) create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java rename {core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest => types/src/main/java/com/datastax/oss/driver/internal/type}/codec/InetCodec.java (51%) create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java create mode 100644 types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 8aa06e72497..6b566e29e93 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -175,13 +175,13 @@ private static Map serialize( private static ByteBuffer serialize(Object parameter, ProtocolVersion protocolVersion) { if (parameter instanceof String) { - return TypeCodecs.VARCHAR.encode((String) parameter, protocolVersion); + return TypeCodecs.TEXT.encode((String) parameter, protocolVersion); } else if (parameter instanceof InetAddress) { return TypeCodecs.INET.encode((InetAddress) parameter, protocolVersion); } else if (parameter instanceof List && ((List) parameter).get(0) instanceof String) { @SuppressWarnings("unchecked") List l = (List) parameter; - return TypeCodecs.LIST_OF_VARCHAR.encode(l, protocolVersion); + return AdminResult.Row.LIST_OF_TEXT.encode(l, protocolVersion); } else { throw new IllegalArgumentException( "Unsupported variable type for admin query: " + parameter.getClass()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java index 0f7a9bc0e22..58b2ee933a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java @@ -16,11 +16,12 @@ package com.datastax.oss.driver.internal.core.adminrequest; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodec; -import com.datastax.oss.driver.internal.core.adminrequest.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.Rows; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableMap; import java.net.InetAddress; @@ -79,6 +80,18 @@ public CompletionStage nextPage() { } public static class Row { + private static final TypeCodec> MAP_OF_TEXT_TO_TEXT = + TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.TEXT); + private static final TypeCodec> MAP_OF_TEXT_TO_BLOB = + TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.BLOB); + private static final TypeCodec> MAP_OF_UUID_TO_BLOB = + TypeCodecs.mapOf(TypeCodecs.UUID, TypeCodecs.BLOB); + + @VisibleForTesting + static final TypeCodec> LIST_OF_TEXT = TypeCodecs.listOf(TypeCodecs.TEXT); + + private static final TypeCodec> SET_OF_TEXT = TypeCodecs.setOf(TypeCodecs.TEXT); + private final Map columnSpecs; private final List data; private final ProtocolVersion protocolVersion; @@ -96,7 +109,7 @@ public Boolean getBoolean(String columnName) { return get(columnName, TypeCodecs.BOOLEAN); } - public Integer getInt(String columnName) { + public Integer getInteger(String columnName) { return get(columnName, TypeCodecs.INT); } @@ -104,40 +117,40 @@ public Double getDouble(String columnName) { return get(columnName, TypeCodecs.DOUBLE); } - public String getVarchar(String columnName) { - return get(columnName, TypeCodecs.VARCHAR); + public String getString(String columnName) { + return get(columnName, TypeCodecs.TEXT); } public UUID getUuid(String columnName) { return get(columnName, TypeCodecs.UUID); } - public ByteBuffer getBlob(String columnName) { + public ByteBuffer getByteBuffer(String columnName) { return get(columnName, TypeCodecs.BLOB); } - public InetAddress getInet(String columnName) { + public InetAddress getInetAddress(String columnName) { return get(columnName, TypeCodecs.INET); } - public List getListOfVarchar(String columnName) { - return get(columnName, TypeCodecs.LIST_OF_VARCHAR); + public List getListOfString(String columnName) { + return get(columnName, LIST_OF_TEXT); } - public Set getSetOfVarchar(String columnName) { - return get(columnName, TypeCodecs.SET_OF_VARCHAR); + public Set getSetOfString(String columnName) { + return get(columnName, SET_OF_TEXT); } - public Map getMapOfVarcharToVarchar(String columnName) { - return get(columnName, TypeCodecs.MAP_OF_VARCHAR_TO_VARCHAR); + public Map getMapOfStringToString(String columnName) { + return get(columnName, MAP_OF_TEXT_TO_TEXT); } - public Map getMapOfVarcharToBlob(String columnName) { - return get(columnName, TypeCodecs.MAP_OF_VARCHAR_TO_BLOB); + public Map getMapOfStringToByteBuffer(String columnName) { + return get(columnName, MAP_OF_TEXT_TO_BLOB); } - public Map getMapOfUuidToBlob(String columnName) { - return get(columnName, TypeCodecs.MAP_OF_UUID_TO_BLOB); + public Map getMapOfUuidToByteBuffer(String columnName) { + return get(columnName, MAP_OF_UUID_TO_BLOB); } private T get(String columnName, TypeCodec codec) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java deleted file mode 100644 index 69d1a54c6ff..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BooleanCodec.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; - -public class BooleanCodec implements TypeCodec { - private static final ByteBuffer TRUE = ByteBuffer.wrap(new byte[] {1}); - private static final ByteBuffer FALSE = ByteBuffer.wrap(new byte[] {0}); - - @Override - public ByteBuffer encode(Boolean value, ProtocolVersion protocolVersion) { - if (value == null) { - return null; - } else { - return value ? TRUE.duplicate() : FALSE.duplicate(); - } - } - - @Override - public Boolean decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - if (bytes == null || bytes.remaining() == 0) { - return null; - } else { - return (bytes.get(bytes.position()) == 0) ? Boolean.FALSE : Boolean.TRUE; - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java deleted file mode 100644 index 544ac9c4710..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/IntCodec.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; - -public class IntCodec implements TypeCodec { - - @Override - public ByteBuffer encode(Integer value, ProtocolVersion protocolVersion) { - if (value == null) { - return null; - } else { - ByteBuffer bytes = ByteBuffer.allocate(4); - bytes.putInt(0, value); - return bytes; - } - } - - @Override - public Integer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - if (bytes == null || bytes.remaining() == 0) { - return null; - } else if (bytes.remaining() != 4) { - throw new IllegalArgumentException( - "Invalid 32-bits integer value, expecting 4 bytes but got " + bytes.remaining()); - } else { - return bytes.getInt(bytes.position()); - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java deleted file mode 100644 index 95f540ccc87..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/ListCodec.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class ListCodec implements TypeCodec> { - - private final TypeCodec elementCodec; - - public ListCodec(TypeCodec elementCodec) { - this.elementCodec = elementCodec; - } - - @Override - public ByteBuffer encode(List value, ProtocolVersion protocolVersion) { - // An int indicating the number of elements in the list, followed by the elements. Each element - // is a byte array representing the serialized value, preceded by an int indicating its size. - if (value == null) { - return null; - } else { - int i = 0; - ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; - int toAllocate = 4; // initialize with number of elements - for (T element : value) { - if (element == null) { - throw new NullPointerException("Collection elements cannot be null"); - } - ByteBuffer encodedElement; - try { - encodedElement = elementCodec.encode(element, protocolVersion); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Invalid type for element: " + element.getClass()); - } - encodedElements[i++] = encodedElement; - toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size - } - ByteBuffer result = ByteBuffer.allocate(toAllocate); - result.putInt(value.size()); - for (ByteBuffer encodedElement : encodedElements) { - result.putInt(encodedElement.remaining()); - result.put(encodedElement); - } - return result; - } - } - - @Override - public List decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - List result = new ArrayList<>(); - if (bytes != null && bytes.remaining() != 0) { - int size = bytes.getInt(); - for (int i = 0; i < size; i++) { - int elementSize = bytes.getInt(); - ByteBuffer encodedElement = bytes.slice(); - encodedElement.limit(elementSize); - result.add(elementCodec.decode(encodedElement, protocolVersion)); - } - } - return result; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java deleted file mode 100644 index 3d7a015a7f3..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/MapCodec.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class MapCodec implements TypeCodec> { - - private final TypeCodec keyCodec; - private final TypeCodec valueCodec; - - public MapCodec(TypeCodec keyCodec, TypeCodec valueCodec) { - this.keyCodec = keyCodec; - this.valueCodec = valueCodec; - } - - @Override - public ByteBuffer encode(Map value, ProtocolVersion protocolVersion) { - // An int indicating the number of key/value pairs in the map, followed by the pairs. Each pair - // is a byte array representing the serialized key, preceded by an int indicating its size, - // followed by the value in the same format. - if (value == null) { - return null; - } else { - int i = 0; - ByteBuffer[] encodedElements = new ByteBuffer[value.size() * 2]; - int toAllocate = 4; // initialize with number of elements - for (Map.Entry entry : value.entrySet()) { - if (entry.getValue() == null) { - throw new NullPointerException("Collection elements cannot be null"); - } - ByteBuffer encodedKey; - try { - encodedKey = keyCodec.encode(entry.getKey(), protocolVersion); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Invalid type for key: " + entry.getKey().getClass()); - } - encodedElements[i++] = encodedKey; - toAllocate += 4 + encodedKey.remaining(); // the key preceded by its size - ByteBuffer encodedValue; - try { - encodedValue = valueCodec.encode(entry.getValue(), protocolVersion); - } catch (ClassCastException e) { - throw new IllegalArgumentException( - "Invalid type for value: " + entry.getValue().getClass()); - } - encodedElements[i++] = encodedValue; - toAllocate += 4 + encodedValue.remaining(); // the value preceded by its size - } - ByteBuffer result = ByteBuffer.allocate(toAllocate); - result.putInt(value.size()); - for (ByteBuffer encodedElement : encodedElements) { - result.putInt(encodedElement.remaining()); - result.put(encodedElement); - } - return result; - } - } - - @Override - public Map decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - Map result = new LinkedHashMap<>(); - if (bytes != null && bytes.remaining() != 0) { - int size = bytes.getInt(); - for (int i = 0; i < size; i++) { - int keySize = bytes.getInt(); - ByteBuffer encodedKey = bytes.slice(); - encodedKey.limit(keySize); - K key = keyCodec.decode(encodedKey, protocolVersion); - int valueSize = bytes.getInt(); - ByteBuffer encodedValue = bytes.slice(); - encodedValue.limit(valueSize); - V value = valueCodec.decode(encodedValue, protocolVersion); - result.put(key, value); - } - } - return result; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java deleted file mode 100644 index fc3c960c9d9..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/SetCodec.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; -import java.util.LinkedHashSet; -import java.util.Set; - -public class SetCodec implements TypeCodec> { - - private final TypeCodec elementCodec; - - public SetCodec(TypeCodec elementCodec) { - this.elementCodec = elementCodec; - } - - @Override - public ByteBuffer encode(Set value, ProtocolVersion protocolVersion) { - // An int indicating the number of elements in the set, followed by the elements. Each element - // is a byte array representing the serialized value, preceded by an int indicating its size. - if (value == null) { - return null; - } else { - int i = 0; - ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; - int toAllocate = 4; // initialize with number of elements - for (T element : value) { - if (element == null) { - throw new NullPointerException("Collection elements cannot be null"); - } - ByteBuffer encodedElement; - try { - encodedElement = elementCodec.encode(element, protocolVersion); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Invalid type for element: " + element.getClass()); - } - encodedElements[i++] = encodedElement; - toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size - } - ByteBuffer result = ByteBuffer.allocate(toAllocate); - result.putInt(value.size()); - for (ByteBuffer encodedElement : encodedElements) { - result.putInt(encodedElement.remaining()); - result.put(encodedElement); - } - return result; - } - } - - @Override - public Set decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - Set result = new LinkedHashSet<>(); - if (bytes != null && bytes.remaining() != 0) { - int size = bytes.getInt(); - for (int i = 0; i < size; i++) { - int elementSize = bytes.getInt(); - ByteBuffer encodedElement = bytes.slice(); - encodedElement.limit(elementSize); - result.add(elementCodec.decode(encodedElement, protocolVersion)); - } - } - return result; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java deleted file mode 100644 index 8b42f23163d..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodecs.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -/** The codecs we need for admin queries. */ -public class TypeCodecs { - public static final TypeCodec BOOLEAN = new BooleanCodec(); - public static final TypeCodec INT = new IntCodec(); - public static final TypeCodec DOUBLE = new DoubleCodec(); - public static final TypeCodec VARCHAR = new VarcharCodec(); - public static final TypeCodec UUID = new UuidCodec(); - public static final TypeCodec BLOB = new BlobCodec(); - public static final TypeCodec INET = new InetCodec(); - public static final TypeCodec> LIST_OF_VARCHAR = new ListCodec<>(VARCHAR); - public static final TypeCodec> SET_OF_VARCHAR = new SetCodec<>(VARCHAR); - public static final TypeCodec> MAP_OF_VARCHAR_TO_VARCHAR = - new MapCodec<>(VARCHAR, VARCHAR); - public static final TypeCodec> MAP_OF_VARCHAR_TO_BLOB = - new MapCodec<>(VARCHAR, BLOB); - public static final TypeCodec> MAP_OF_UUID_TO_BLOB = - new MapCodec<>(UUID, BLOB); -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java deleted file mode 100644 index 5e4b1b59640..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/VarcharCodec.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; - -import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.protocol.internal.util.Bytes; -import com.google.common.base.Charsets; -import java.nio.ByteBuffer; - -public class VarcharCodec implements TypeCodec { - - @Override - public ByteBuffer encode(String value, ProtocolVersion protocolVersion) { - return (value == null) ? null : ByteBuffer.wrap(value.getBytes(Charsets.UTF_8)); - } - - @Override - public String decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - if (bytes == null) { - return null; - } else if (bytes.remaining() == 0) { - return ""; - } else { - return new String(Bytes.getArray(bytes), Charsets.UTF_8); - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 307d4597587..6e5695d975b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -132,7 +132,7 @@ private CompletionStage query(DriverChannel channel, String querySt } private NodeInfo buildNodeInfo(AdminResult.Row row) { - InetAddress broadcastRpcAddress = row.getInet("rpc_address"); + InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); InetSocketAddress connectAddress = addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port)); return buildNodeInfo(row, connectAddress); @@ -141,17 +141,17 @@ private NodeInfo buildNodeInfo(AdminResult.Row row) { private NodeInfo buildNodeInfo(AdminResult.Row row, InetSocketAddress connectAddress) { DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder().withConnectAddress(connectAddress); - InetAddress broadcastAddress = row.getInet("broadcast_address"); // in system.local + InetAddress broadcastAddress = row.getInetAddress("broadcast_address"); // in system.local if (broadcastAddress == null) { - broadcastAddress = row.getInet("peer"); // in system.peers + broadcastAddress = row.getInetAddress("peer"); // in system.peers } builder.withBroadcastAddress(broadcastAddress); - builder.withListenAddress(row.getInet("listen_address")); - builder.withDatacenter(row.getVarchar("data_center")); - builder.withRack(row.getVarchar("rack")); - builder.withCassandraVersion(row.getVarchar("release_version")); - builder.withTokens(row.getSetOfVarchar("tokens")); + builder.withListenAddress(row.getInetAddress("listen_address")); + builder.withDatacenter(row.getString("data_center")); + builder.withRack(row.getString("rack")); + builder.withCassandraVersion(row.getString("release_version")); + builder.withTokens(row.getSetOfString("tokens")); return builder.build(); } @@ -169,7 +169,7 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con // The peers table is keyed by broadcast_address, but we only have the translated // broadcast_rpc_address, so we have to traverse the whole table and check the rows one by one. for (AdminResult.Row row : result) { - InetAddress broadcastRpcAddress = row.getInet("rpc_address"); + InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); if (broadcastRpcAddress != null && addressTranslator .translate(new InetSocketAddress(broadcastRpcAddress, port)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 1589e6bf407..15159e7e6e8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -153,17 +153,17 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() }); // The rpc_address in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInet("rpc_address"); + Mockito.verify(peer3).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getVarchar(anyString()); + Mockito.verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInet("rpc_address"); + Mockito.verify(peer2).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2, never()).getVarchar(anyString()); + Mockito.verify(peer2, never()).getString(anyString()); - Mockito.verify(peer1).getInet("rpc_address"); + Mockito.verify(peer1).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); - Mockito.verify(peer1).getVarchar("data_center"); + Mockito.verify(peer1).getString("data_center"); } @Test @@ -188,17 +188,17 @@ public void should_get_new_node_from_peers() { }); // The rpc_address in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInet("rpc_address"); + Mockito.verify(peer3).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getVarchar(anyString()); + Mockito.verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInet("rpc_address"); + Mockito.verify(peer2).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2, never()).getVarchar(anyString()); + Mockito.verify(peer2, never()).getString(anyString()); - Mockito.verify(peer1).getInet("rpc_address"); + Mockito.verify(peer1).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); - Mockito.verify(peer1).getVarchar("data_center"); + Mockito.verify(peer1).getString("data_center"); } @Test @@ -268,14 +268,16 @@ private StubbedQuery(String queryString, AdminResult result) { private AdminResult.Row mockLocalRow(int i) { try { AdminResult.Row row = Mockito.mock(AdminResult.Row.class); - Mockito.when(row.getInet("broadcast_address")) + Mockito.when(row.getInetAddress("broadcast_address")) .thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getVarchar("data_center")).thenReturn("dc" + i); - Mockito.when(row.getInet("listen_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getVarchar("rack")).thenReturn("rack" + i); - Mockito.when(row.getVarchar("release_version")).thenReturn("release_version" + i); - Mockito.when(row.getInet("rpc_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getSetOfVarchar("tokens")).thenReturn(ImmutableSet.of("token" + i)); + Mockito.when(row.getString("data_center")).thenReturn("dc" + i); + Mockito.when(row.getInetAddress("listen_address")) + .thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getString("rack")).thenReturn("rack" + i); + Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); + Mockito.when(row.getInetAddress("rpc_address")) + .thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); return row; } catch (UnknownHostException e) { fail("unexpected", e); @@ -286,12 +288,13 @@ private AdminResult.Row mockLocalRow(int i) { private AdminResult.Row mockPeersRow(int i) { try { AdminResult.Row row = Mockito.mock(AdminResult.Row.class); - Mockito.when(row.getInet("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getVarchar("data_center")).thenReturn("dc" + i); - Mockito.when(row.getVarchar("rack")).thenReturn("rack" + i); - Mockito.when(row.getVarchar("release_version")).thenReturn("release_version" + i); - Mockito.when(row.getInet("rpc_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getSetOfVarchar("tokens")).thenReturn(ImmutableSet.of("token" + i)); + Mockito.when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getString("data_center")).thenReturn("dc" + i); + Mockito.when(row.getString("rack")).thenReturn("rack" + i); + Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); + Mockito.when(row.getInetAddress("rpc_address")) + .thenReturn(InetAddress.getByName("127.0.0." + i)); + Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); return row; } catch (UnknownHostException e) { fail("unexpected", e); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java b/types/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java similarity index 100% rename from core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java rename to types/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 21a6b62fb81..71a55cb0730 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -17,6 +17,9 @@ import com.datastax.oss.driver.internal.core.util.Strings; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; /** * The identifier of CQL element (keyspace, table, column, etc). @@ -51,7 +54,9 @@ * *

There is no internal caching; if you reuse the same identifiers often, */ -public class CqlIdentifier { +public class CqlIdentifier implements Serializable { + + private static final long serialVersionUID = 1; // IMPLEMENTATION NOTES: // This is used internally, and for all API methods where the overhead of requiring the client to @@ -78,6 +83,7 @@ public static CqlIdentifier fromInternal(String internal) { return new CqlIdentifier(internal); } + /** @serial */ private final String internal; private CqlIdentifier(String internal) { @@ -130,4 +136,9 @@ public boolean equals(Object other) { public int hashCode() { return internal.hashCode(); } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Preconditions.checkNotNull(internal, "internal must not be null"); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/types/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java similarity index 67% rename from core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java rename to types/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java index c0b8a8452c2..3a47b464f96 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java +++ b/types/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java @@ -15,14 +15,21 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.detach.Detachable; + /** * A version of the native protocol used by the driver to communicate with the server. * - *

The only reason to have a separate abstraction above {@link CoreProtocolVersion} is to - * accommodate for custom protocol extensions. If you're connecting to a standard Apache Cassandra - * cluster, all {@code ProtocolVersion}s are {@code CoreProtocolVersion} instances. + *

The only reason to model this as an interface (as opposed to an enum type) is to accommodate + * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all + * {@code ProtocolVersion}s are {@code CoreProtocolVersion} instances. */ public interface ProtocolVersion { + /** The default version used for {@link Detachable detached} objects. */ + // Implementation note: we can't use the ProtocolVersionRegistry here, this has to be a + // compile-time constant. + ProtocolVersion DEFAULT = CoreProtocolVersion.V4; + /** * A numeric code that uniquely identifies the version (this is the code used in network frames). */ diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java new file mode 100644 index 00000000000..f290e908ddb --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.type.DataType; + +/** + * A data structure where the values are accessible via a CQL identifier. + * + *

In the driver, these data structures are always accessible by index as well. + */ +public interface AccessibleById extends AccessibleByIndex { + + /** + * Returns the first index where a given identifier appears (depending on the implementation, + * identifiers may appear multiple times). + */ + int firstIndexOf(CqlIdentifier id); + + /** + * Returns the CQL type of the value for the first occurrence of {@code id}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + DataType getType(CqlIdentifier id); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java similarity index 56% rename from core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java rename to types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java index 6a96547a01f..f16e665f3fe 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/TypeCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; +package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; +import com.datastax.oss.driver.api.type.DataType; -/** - * Converts query values to/from the protocol format. - * - *

TODO this will probably be wrapped or superseded by the codecs of the public query API. - */ -public interface TypeCodec { - ByteBuffer encode(T value, ProtocolVersion protocolVersion); +/** A data structure where the values are accessible via an integer index. */ +public interface AccessibleByIndex extends Data { + + /** Returns the number of values. */ + int size(); - T decode(ByteBuffer bytes, ProtocolVersion protocolVersion); + /** + * Returns the CQL type of the {@code i}th value. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + DataType getType(int i); } diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java new file mode 100644 index 00000000000..c500083bc31 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.type.DataType; + +/** + * A data structure where the values are accessible via a name string. + * + *

This is an optimized version of {@link AccessibleById}, in case the overhead of having to + * create a {@link CqlIdentifier} for each value is too much overhead. + * + *

By default, case is ignored when matching names. If multiple names only differ by their case, + * then the first one is chosen. You can force an exact match by double-quoting the name. + * + *

For example, if the data structure contains three values named {@code Foo}, {@code foo} and + * {@code fOO}, then: + * + *

    + *
  • {@code getString("foo")} retrieves the first value (ignore case, first occurrence). + *
  • {@code getString("\"foo\"")} retrieves the second value (exact case). + *
  • {@code getString("\"fOO\"")} retrieves the third value (exact case). + *
  • {@code getString("\"FOO\"")} fails (exact case, no match). + *
+ * + *

In the driver, these data structures are always accessible by index as well. + */ +public interface AccessibleByName extends AccessibleByIndex { + + /** + * Returns the first index where a given identifier appears (depending on the implementation, + * identifiers may appear multiple times). + */ + int firstIndexOf(String name); + + /** + * Returns the CQL type of the value for the first occurrence of {@code name}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * GettableByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + DataType getType(String name); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java new file mode 100644 index 00000000000..464b824f271 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A duration, as defined in CQL. + * + *

It stores months, days, and seconds separately due to the fact that the number of days in a + * month varies, and a day can have 23 or 25 hours if a daylight saving is involved. + */ +public final class CqlDuration { + + @VisibleForTesting static final long NANOS_PER_MICRO = 1000L; + @VisibleForTesting static final long NANOS_PER_MILLI = 1000 * NANOS_PER_MICRO; + @VisibleForTesting static final long NANOS_PER_SECOND = 1000 * NANOS_PER_MILLI; + @VisibleForTesting static final long NANOS_PER_MINUTE = 60 * NANOS_PER_SECOND; + @VisibleForTesting static final long NANOS_PER_HOUR = 60 * NANOS_PER_MINUTE; + @VisibleForTesting static final int DAYS_PER_WEEK = 7; + @VisibleForTesting static final int MONTHS_PER_YEAR = 12; + + /** The Regexp used to parse the duration provided as String. */ + private static final Pattern STANDARD_PATTERN = + Pattern.compile( + "\\G(\\d+)(y|Y|mo|MO|mO|Mo|w|W|d|D|h|H|s|S|ms|MS|mS|Ms|us|US|uS|Us|µs|µS|ns|NS|nS|Ns|m|M)"); + + /** + * The Regexp used to parse the duration when provided in the ISO 8601 format with designators. + */ + private static final Pattern ISO8601_PATTERN = + Pattern.compile("P((\\d+)Y)?((\\d+)M)?((\\d+)D)?(T((\\d+)H)?((\\d+)M)?((\\d+)S)?)?"); + + /** + * The Regexp used to parse the duration when provided in the ISO 8601 format with designators. + */ + private static final Pattern ISO8601_WEEK_PATTERN = Pattern.compile("P(\\d+)W"); + + /** The Regexp used to parse the duration when provided in the ISO 8601 alternative format. */ + private static final Pattern ISO8601_ALTERNATIVE_PATTERN = + Pattern.compile("P(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})"); + + private final int months; + private final int days; + private final long nanoseconds; + + private CqlDuration(int months, int days, long nanoseconds) { + // Makes sure that all the values are negative if one of them is + if ((months < 0 || days < 0 || nanoseconds < 0) + && ((months > 0 || days > 0 || nanoseconds > 0))) { + throw new IllegalArgumentException( + String.format( + "All values must be either negative or positive, got %d months, %d days, %d nanoseconds", + months, days, nanoseconds)); + } + this.months = months; + this.days = days; + this.nanoseconds = nanoseconds; + } + + /** + * Creates a duration with the given number of months, days and nanoseconds. + * + *

A duration can be negative. In this case, all the non zero values must be negative. + * + * @param months the number of months + * @param days the number of days + * @param nanoseconds the number of nanoseconds + * @throws IllegalArgumentException if the values are not all negative or all positive + */ + public static CqlDuration newInstance(int months, int days, long nanoseconds) { + return new CqlDuration(months, days, nanoseconds); + } + + /** + * Converts a String into a duration. + * + *

The accepted formats are: + * + *

    + *
  • multiple digits followed by a time unit like: 12h30m where the time unit can be: + *
      + *
    • {@code y}: years + *
    • {@code m}: months + *
    • {@code w}: weeks + *
    • {@code d}: days + *
    • {@code h}: hours + *
    • {@code m}: minutes + *
    • {@code s}: seconds + *
    • {@code ms}: milliseconds + *
    • {@code us} or {@code µs}: microseconds + *
    • {@code ns}: nanoseconds + *
    + * + *
  • ISO 8601 format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W + *
  • ISO 8601 alternative format: P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] + *
+ * + * @param input the String to convert + */ + public static CqlDuration from(String input) { + boolean isNegative = input.startsWith("-"); + String source = isNegative ? input.substring(1) : input; + + if (source.startsWith("P")) { + if (source.endsWith("W")) return parseIso8601WeekFormat(isNegative, source); + + if (source.contains("-")) return parseIso8601AlternativeFormat(isNegative, source); + + return parseIso8601Format(isNegative, source); + } + return parseStandardFormat(isNegative, source); + } + + private static CqlDuration parseIso8601Format(boolean isNegative, String source) { + Matcher matcher = ISO8601_PATTERN.matcher(source); + if (!matcher.matches()) + throw new IllegalArgumentException( + String.format("Unable to convert '%s' to a duration", source)); + + Builder builder = new Builder(isNegative); + if (matcher.group(1) != null) builder.addYears(groupAsLong(matcher, 2)); + + if (matcher.group(3) != null) builder.addMonths(groupAsLong(matcher, 4)); + + if (matcher.group(5) != null) builder.addDays(groupAsLong(matcher, 6)); + + // Checks if the String contains time information + if (matcher.group(7) != null) { + if (matcher.group(8) != null) builder.addHours(groupAsLong(matcher, 9)); + + if (matcher.group(10) != null) builder.addMinutes(groupAsLong(matcher, 11)); + + if (matcher.group(12) != null) builder.addSeconds(groupAsLong(matcher, 13)); + } + return builder.build(); + } + + private static CqlDuration parseIso8601AlternativeFormat(boolean isNegative, String source) { + Matcher matcher = ISO8601_ALTERNATIVE_PATTERN.matcher(source); + if (!matcher.matches()) + throw new IllegalArgumentException( + String.format("Unable to convert '%s' to a duration", source)); + + return new Builder(isNegative) + .addYears(groupAsLong(matcher, 1)) + .addMonths(groupAsLong(matcher, 2)) + .addDays(groupAsLong(matcher, 3)) + .addHours(groupAsLong(matcher, 4)) + .addMinutes(groupAsLong(matcher, 5)) + .addSeconds(groupAsLong(matcher, 6)) + .build(); + } + + private static CqlDuration parseIso8601WeekFormat(boolean isNegative, String source) { + Matcher matcher = ISO8601_WEEK_PATTERN.matcher(source); + if (!matcher.matches()) + throw new IllegalArgumentException( + String.format("Unable to convert '%s' to a duration", source)); + + return new Builder(isNegative).addWeeks(groupAsLong(matcher, 1)).build(); + } + + private static CqlDuration parseStandardFormat(boolean isNegative, String source) { + Matcher matcher = STANDARD_PATTERN.matcher(source); + if (!matcher.find()) + throw new IllegalArgumentException( + String.format("Unable to convert '%s' to a duration", source)); + + Builder builder = new Builder(isNegative); + boolean done; + + do { + long number = groupAsLong(matcher, 1); + String symbol = matcher.group(2); + add(builder, number, symbol); + done = matcher.end() == source.length(); + } while (matcher.find()); + + if (!done) + throw new IllegalArgumentException( + String.format("Unable to convert '%s' to a duration", source)); + + return builder.build(); + } + + private static long groupAsLong(Matcher matcher, int group) { + return Long.parseLong(matcher.group(group)); + } + + private static Builder add(Builder builder, long number, String symbol) { + String s = symbol.toLowerCase(); + if (s.equals("y")) { + return builder.addYears(number); + } else if (s.equals("mo")) { + return builder.addMonths(number); + } else if (s.equals("w")) { + return builder.addWeeks(number); + } else if (s.equals("d")) { + return builder.addDays(number); + } else if (s.equals("h")) { + return builder.addHours(number); + } else if (s.equals("m")) { + return builder.addMinutes(number); + } else if (s.equals("s")) { + return builder.addSeconds(number); + } else if (s.equals("ms")) { + return builder.addMillis(number); + } else if (s.equals("us") || s.equals("µs")) { + return builder.addMicros(number); + } else if (s.equals("ns")) { + return builder.addNanos(number); + } + throw new IllegalArgumentException(String.format("Unknown duration symbol '%s'", symbol)); + } + + /** + * Appends the result of the division to the specified builder if the dividend is not zero. + * + * @param builder the builder to append to + * @param dividend the dividend + * @param divisor the divisor + * @param unit the time unit to append after the result of the division + * @return the remainder of the division + */ + private static long append(StringBuilder builder, long dividend, long divisor, String unit) { + if (dividend == 0 || dividend < divisor) return dividend; + + builder.append(dividend / divisor).append(unit); + return dividend % divisor; + } + + /** + * Returns the number of months in this duration. + * + * @return the number of months in this duration. + */ + public int getMonths() { + return months; + } + + /** + * Returns the number of days in this duration. + * + * @return the number of days in this duration. + */ + public int getDays() { + return days; + } + + /** + * Returns the number of nanoseconds in this duration. + * + * @return the number of months in this duration. + */ + public long getNanoseconds() { + return nanoseconds; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof CqlDuration) { + CqlDuration that = (CqlDuration) other; + return this.days == that.days + && this.months == that.months + && this.nanoseconds == that.nanoseconds; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(days, months, nanoseconds); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + if (months < 0 || days < 0 || nanoseconds < 0) builder.append('-'); + + long remainder = append(builder, Math.abs(months), MONTHS_PER_YEAR, "y"); + append(builder, remainder, 1, "mo"); + + append(builder, Math.abs(days), 1, "d"); + + if (nanoseconds != 0) { + remainder = append(builder, Math.abs(nanoseconds), NANOS_PER_HOUR, "h"); + remainder = append(builder, remainder, NANOS_PER_MINUTE, "m"); + remainder = append(builder, remainder, NANOS_PER_SECOND, "s"); + remainder = append(builder, remainder, NANOS_PER_MILLI, "ms"); + remainder = append(builder, remainder, NANOS_PER_MICRO, "us"); + append(builder, remainder, 1, "ns"); + } + return builder.toString(); + } + + private static class Builder { + private final boolean isNegative; + private int months; + private int days; + private long nanoseconds; + + /** We need to make sure that the values for each units are provided in order. */ + private int currentUnitIndex; + + public Builder(boolean isNegative) { + this.isNegative = isNegative; + } + + /** + * Adds the specified amount of years. + * + * @param numberOfYears the number of years to add. + * @return this {@code Builder} + */ + public Builder addYears(long numberOfYears) { + validateOrder(1); + validateMonths(numberOfYears, MONTHS_PER_YEAR); + months += numberOfYears * MONTHS_PER_YEAR; + return this; + } + + /** + * Adds the specified amount of months. + * + * @param numberOfMonths the number of months to add. + * @return this {@code Builder} + */ + public Builder addMonths(long numberOfMonths) { + validateOrder(2); + validateMonths(numberOfMonths, 1); + months += numberOfMonths; + return this; + } + + /** + * Adds the specified amount of weeks. + * + * @param numberOfWeeks the number of weeks to add. + * @return this {@code Builder} + */ + public Builder addWeeks(long numberOfWeeks) { + validateOrder(3); + validateDays(numberOfWeeks, DAYS_PER_WEEK); + days += numberOfWeeks * DAYS_PER_WEEK; + return this; + } + + /** + * Adds the specified amount of days. + * + * @param numberOfDays the number of days to add. + * @return this {@code Builder} + */ + public Builder addDays(long numberOfDays) { + validateOrder(4); + validateDays(numberOfDays, 1); + days += numberOfDays; + return this; + } + + /** + * Adds the specified amount of hours. + * + * @param numberOfHours the number of hours to add. + * @return this {@code Builder} + */ + public Builder addHours(long numberOfHours) { + validateOrder(5); + validateNanos(numberOfHours, NANOS_PER_HOUR); + nanoseconds += numberOfHours * NANOS_PER_HOUR; + return this; + } + + /** + * Adds the specified amount of minutes. + * + * @param numberOfMinutes the number of minutes to add. + * @return this {@code Builder} + */ + public Builder addMinutes(long numberOfMinutes) { + validateOrder(6); + validateNanos(numberOfMinutes, NANOS_PER_MINUTE); + nanoseconds += numberOfMinutes * NANOS_PER_MINUTE; + return this; + } + + /** + * Adds the specified amount of seconds. + * + * @param numberOfSeconds the number of seconds to add. + * @return this {@code Builder} + */ + public Builder addSeconds(long numberOfSeconds) { + validateOrder(7); + validateNanos(numberOfSeconds, NANOS_PER_SECOND); + nanoseconds += numberOfSeconds * NANOS_PER_SECOND; + return this; + } + + /** + * Adds the specified amount of milliseconds. + * + * @param numberOfMillis the number of milliseconds to add. + * @return this {@code Builder} + */ + public Builder addMillis(long numberOfMillis) { + validateOrder(8); + validateNanos(numberOfMillis, NANOS_PER_MILLI); + nanoseconds += numberOfMillis * NANOS_PER_MILLI; + return this; + } + + /** + * Adds the specified amount of microseconds. + * + * @param numberOfMicros the number of microseconds to add. + * @return this {@code Builder} + */ + public Builder addMicros(long numberOfMicros) { + validateOrder(9); + validateNanos(numberOfMicros, NANOS_PER_MICRO); + nanoseconds += numberOfMicros * NANOS_PER_MICRO; + return this; + } + + /** + * Adds the specified amount of nanoseconds. + * + * @param numberOfNanos the number of nanoseconds to add. + * @return this {@code Builder} + */ + public Builder addNanos(long numberOfNanos) { + validateOrder(10); + validateNanos(numberOfNanos, 1); + nanoseconds += numberOfNanos; + return this; + } + + /** + * Validates that the total number of months can be stored. + * + * @param units the number of units that need to be added + * @param monthsPerUnit the number of days per unit + */ + private void validateMonths(long units, int monthsPerUnit) { + validate(units, (Integer.MAX_VALUE - months) / monthsPerUnit, "months"); + } + + /** + * Validates that the total number of days can be stored. + * + * @param units the number of units that need to be added + * @param daysPerUnit the number of days per unit + */ + private void validateDays(long units, int daysPerUnit) { + validate(units, (Integer.MAX_VALUE - days) / daysPerUnit, "days"); + } + + /** + * Validates that the total number of nanoseconds can be stored. + * + * @param units the number of units that need to be added + * @param nanosPerUnit the number of nanoseconds per unit + */ + private void validateNanos(long units, long nanosPerUnit) { + validate(units, (Long.MAX_VALUE - nanoseconds) / nanosPerUnit, "nanoseconds"); + } + + /** + * Validates that the specified amount is less than the limit. + * + * @param units the number of units to check + * @param limit the limit on the number of units + * @param unitName the unit name + */ + private void validate(long units, long limit, String unitName) { + checkArgument( + units <= limit, + "Invalid duration. The total number of %s must be less or equal to %s", + unitName, + Integer.MAX_VALUE); + } + + /** + * Validates that the duration values are added in the proper order. + * + * @param unitIndex the unit index (e.g. years=1, months=2, ...) + */ + private void validateOrder(int unitIndex) { + if (unitIndex == currentUnitIndex) + throw new IllegalArgumentException( + String.format( + "Invalid duration. The %s are specified multiple times", getUnitName(unitIndex))); + + if (unitIndex <= currentUnitIndex) + throw new IllegalArgumentException( + String.format( + "Invalid duration. The %s should be after %s", + getUnitName(currentUnitIndex), getUnitName(unitIndex))); + + currentUnitIndex = unitIndex; + } + + /** + * Returns the name of the unit corresponding to the specified index. + * + * @param unitIndex the unit index + * @return the name of the unit corresponding to the specified index. + */ + private String getUnitName(int unitIndex) { + switch (unitIndex) { + case 1: + return "years"; + case 2: + return "months"; + case 3: + return "weeks"; + case 4: + return "days"; + case 5: + return "hours"; + case 6: + return "minutes"; + case 7: + return "seconds"; + case 8: + return "milliseconds"; + case 9: + return "microseconds"; + case 10: + return "nanoseconds"; + default: + throw new AssertionError("unknown unit index: " + unitIndex); + } + } + + public CqlDuration build() { + return isNegative + ? new CqlDuration(-months, -days, -nanoseconds) + : new CqlDuration(months, days, nanoseconds); + } + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java new file mode 100644 index 00000000000..dd760f5ac79 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.detach.Detachable; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; + +/** A data structure containing CQL values. */ +public interface Data { + + /** + * Returns the registry of all the codecs currently available to convert values for this instance. + * + *

If you obtained this object from the driver, this will be set automatically. If you created + * it manually, or just deserialized it, it is set to {@link CodecRegistry#DEFAULT}. You can + * reattach this object to an existing driver instance to use its codec registry. + * + * @see Detachable + */ + CodecRegistry codecRegistry(); + + /** + * Returns the protocol version that is currently used to convert values for this instance. + * + *

If you obtained this object from the driver, this will be set automatically. If you created + * it manually, or just deserialized it, it is set to {@link CoreProtocolVersion#DEFAULT}. You can + * reattach this object to an existing driver instance to use its protocol version. + * + * @see Detachable + */ + ProtocolVersion protocolVersion(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java new file mode 100644 index 00000000000..f23665b3305 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to retrieve its values via a CQL identifier. */ +public interface GettableById extends GettableByIndex, AccessibleById { + + /** + * Returns the raw binary representation of the value for the first occurrence of {@code id}. + * + *

This is primarily for internal use; you'll likely want to use one of the typed getters + * instead, to get a higher-level Java representation. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @return the raw value, or {@code null} if the CQL value is {@code NULL}. For performance + * reasons, this is the actual instance used internally. If you read data from the buffer, + * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative + * methods. If you change the buffer's index or its contents in any way, any other getter + * invocation for this value will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default ByteBuffer getBytesUnsafe(CqlIdentifier id) { + return getBytesUnsafe(firstIndexOf(id)); + } + + /** + * Indicates whether the value for the first occurrence of {@code id} is a CQL {@code NULL}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean isNull(CqlIdentifier id) { + return isNull(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id}, using the given codec for the + * conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T get(CqlIdentifier id, TypeCodec codec) { + return get(firstIndexOf(id), codec); + } + + /** + * Returns the value for the first occurrence of {@code id}, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #get(int, Class)} instead, which may perform slightly better. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(CqlIdentifier id, GenericType targetType) { + return get(firstIndexOf(id), targetType); + } + + /** + * Returns the value for the first occurrence of {@code id}, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #get(int, GenericType)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(CqlIdentifier id, Class targetClass) { + return get(firstIndexOf(id), targetClass); + } + + /** + * Returns the value for the first occurrence of {@code id}, converting it to the most appropriate + * Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

Use this method to dynamically inspect elements when types aren't known in advance, for + * instance if you're writing a generic row logger. If you know the target Java type, it is + * generally preferable to use typed variants, such as the ones for built-in types ({@link + * #getBoolean(int)}, {@link #getInt(int)}, etc.), or {@link #get(int, Class)} and {@link + * #get(int, GenericType)} for custom types. + * + *

The definition of "most appropriate" is unspecified, and left to the appreciation of the + * {@link #codecRegistry()} implementation. By default, the driver uses the mapping described in + * the other {@code getXxx()} methods (for example {@link #getString(int) String for text, varchar + * and ascii}, etc). + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default Object getObject(CqlIdentifier id) { + return getObject(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code false}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Boolean.class)} + * instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean getBoolean(CqlIdentifier id) { + return getBoolean(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Byte.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default byte getByte(CqlIdentifier id) { + return getByte(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Double.class)} + * instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default double getDouble(CqlIdentifier id) { + return getDouble(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Float.class)} + * instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default float getFloat(CqlIdentifier id) { + return getFloat(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Integer.class)} + * instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default int getInt(CqlIdentifier id) { + return getInt(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Long.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default long getLong(CqlIdentifier id) { + return getLong(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(CqlIdentifier)} before calling this method, or use {@code get(id, Short.class)} + * instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default short getShort(CqlIdentifier id) { + return getShort(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Instant getInstant(CqlIdentifier id) { + return getInstant(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java local date. + * + *

By default, this works with CQL type {@code date}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalDate getLocalDate(CqlIdentifier id) { + return getLocalDate(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java local time. + * + *

By default, this works with CQL type {@code time}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalTime getLocalTime(CqlIdentifier id) { + return getLocalTime(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default ByteBuffer getByteBuffer(CqlIdentifier id) { + return getByteBuffer(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default String getString(CqlIdentifier id) { + return getString(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigInteger getBigInteger(CqlIdentifier id) { + return getBigInteger(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigDecimal getBigDecimal(CqlIdentifier id) { + return getBigDecimal(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UUID getUuid(CqlIdentifier id) { + return getUuid(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default InetAddress getInetAddress(CqlIdentifier id) { + return getInetAddress(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a duration. + * + *

By default, this works with CQL type {@code duration}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default CqlDuration getCqlDuration(CqlIdentifier id) { + return getCqlDuration(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default List getList(CqlIdentifier id, Class elementsClass) { + return getList(firstIndexOf(id), elementsClass); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Set getSet(CqlIdentifier id, Class elementsClass) { + return getSet(firstIndexOf(id), elementsClass); + } + + /** + * Returns the value for the first occurrence of {@code id} as a Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Map getMap(CqlIdentifier id, Class keyClass, Class valueClass) { + return getMap(firstIndexOf(id), keyClass, valueClass); + } + + /** + * Returns the value for the first occurrence of {@code id} as a user defined type value. + * + *

By default, this works with CQL user-defined types. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UdtValue getUdtValue(CqlIdentifier id) { + return getUdtValue(firstIndexOf(id)); + } + + /** + * Returns the value for the first occurrence of {@code id} as a tuple value. + * + *

By default, this works with CQL tuples. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default TupleValue getTupleValue(CqlIdentifier id) { + return getTupleValue(firstIndexOf(id)); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java new file mode 100644 index 00000000000..aa9fb71ee53 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to retrieve its values via an integer index. */ +public interface GettableByIndex extends AccessibleByIndex { + + /** + * Returns the raw binary representation of the {@code i}th value. + * + *

This is primarily for internal use; you'll likely want to use one of the typed getters + * instead, to get a higher-level Java representation. + * + * @return the raw value, or {@code null} if the CQL value is {@code NULL}. For performance + * reasons, this is the actual instance used internally. If you read data from the buffer, + * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative + * methods. If you change the buffer's index or its contents in any way, any other getter + * invocation for this value will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + ByteBuffer getBytesUnsafe(int i); + + /** + * Indicates whether the {@code i}th value is a CQL {@code NULL}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean isNull(int i) { + return getBytesUnsafe(i) == null; + } + + /** + * Returns the {@code i}th value, using the given codec for the conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T get(int i, TypeCodec codec) { + return codec.decode(getBytesUnsafe(i), protocolVersion()); + } + + /** + * Returns the {@code i}th value, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #get(int, Class)} instead, which may perform slightly better. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(int i, GenericType targetType) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); + return get(i, codec); + } + + /** + * Returns the {@code i}th value, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #get(int, GenericType)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(int i, Class targetClass) { + // This is duplicated from the GenericType variant, because we want to give the codec registry + // a chance to process the unwrapped class directly, if it can do so in a more efficient way. + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); + return get(i, codec); + } + + /** + * Returns the {@code i}th value, converting it to the most appropriate Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

Use this method to dynamically inspect elements when types aren't known in advance, for + * instance if you're writing a generic row logger. If you know the target Java type, it is + * generally preferable to use typed variants, such as the ones for built-in types ({@link + * #getBoolean(int)}, {@link #getInt(int)}, etc.), or {@link #get(int, Class)} and {@link + * #get(int, GenericType)} for custom types. + * + *

The definition of "most appropriate" is unspecified, and left to the appreciation of the + * {@link #codecRegistry()} implementation. By default, the driver uses the mapping described in + * the other {@code getXxx()} methods (for example {@link #getString(int) String for text, varchar + * and ascii}, etc). + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default Object getObject(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType); + return codec.decode(getBytesUnsafe(i), protocolVersion()); + } + + /** + * Returns the {@code i}th value as a Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code false}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Boolean.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean getBoolean(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Boolean.class); + return (codec instanceof PrimitiveBooleanCodec) + ? ((PrimitiveBooleanCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Byte.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default byte getByte(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Byte.class); + return (codec instanceof PrimitiveByteCodec) + ? ((PrimitiveByteCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Double.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default double getDouble(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Double.class); + return (codec instanceof PrimitiveDoubleCodec) + ? ((PrimitiveDoubleCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Float.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default float getFloat(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Float.class); + return (codec instanceof PrimitiveFloatCodec) + ? ((PrimitiveFloatCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Integer.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default int getInt(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Integer.class); + return (codec instanceof PrimitiveIntCodec) + ? ((PrimitiveIntCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Long.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default long getLong(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Long.class); + return (codec instanceof PrimitiveLongCodec) + ? ((PrimitiveLongCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(int)} before calling this method, or use {@code get(i, Short.class)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default short getShort(int i) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Short.class); + return (codec instanceof PrimitiveShortCodec) + ? ((PrimitiveShortCodec) codec).decodePrimitive(getBytesUnsafe(i), protocolVersion()) + : get(i, codec); + } + + /** + * Returns the {@code i}th value as a Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Instant getInstant(int i) { + return get(i, Instant.class); + } + + /** + * Returns the {@code i}th value as a Java local date. + * + *

By default, this works with CQL type {@code date}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalDate getLocalDate(int i) { + return get(i, LocalDate.class); + } + + /** + * Returns the {@code i}th value as a Java local time. + * + *

By default, this works with CQL type {@code time}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalTime getLocalTime(int i) { + return get(i, LocalTime.class); + } + + /** + * Returns the {@code i}th value as a Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default ByteBuffer getByteBuffer(int i) { + return get(i, ByteBuffer.class); + } + + /** + * Returns the {@code i}th value as a Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default String getString(int i) { + return get(i, String.class); + } + + /** + * Returns the {@code i}th value as a Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigInteger getBigInteger(int i) { + return get(i, BigInteger.class); + } + + /** + * Returns the {@code i}th value as a Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigDecimal getBigDecimal(int i) { + return get(i, BigDecimal.class); + } + + /** + * Returns the {@code i}th value as a Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UUID getUuid(int i) { + return get(i, UUID.class); + } + + /** + * Returns the {@code i}th value as a Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default InetAddress getInetAddress(int i) { + return get(i, InetAddress.class); + } + + /** + * Returns the {@code i}th value as a duration. + * + *

By default, this works with CQL type {@code duration}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default CqlDuration getCqlDuration(int i) { + return get(i, CqlDuration.class); + } + + /** + * Returns the {@code i}th value as a Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #get(int, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default List getList(int i, Class elementsClass) { + return get(i, GenericType.listOf(elementsClass)); + } + + /** + * Returns the {@code i}th value as a Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #get(int, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Set getSet(int i, Class elementsClass) { + return get(i, GenericType.setOf(elementsClass)); + } + + /** + * Returns the {@code i}th value as a Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #get(int, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Map getMap(int i, Class keyClass, Class valueClass) { + return get(i, GenericType.mapOf(keyClass, valueClass)); + } + + /** + * Returns the {@code i}th value as a user defined type value. + * + *

By default, this works with CQL user-defined types. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UdtValue getUdtValue(int i) { + return get(i, UdtValue.class); + } + + /** + * Returns the {@code i}th value as a tuple value. + * + *

By default, this works with CQL tuples. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default TupleValue getTupleValue(int i) { + return get(i, TupleValue.class); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java new file mode 100644 index 00000000000..adcc8111102 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to retrieve its values via a name. */ +public interface GettableByName extends GettableByIndex, AccessibleByName { + + /** + * Returns the raw binary representation of the value for the first occurrence of {@code name}. + * + *

This is primarily for internal use; you'll likely want to use one of the typed getters + * instead, to get a higher-level Java representation. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @return the raw value, or {@code null} if the CQL value is {@code NULL}. For performance + * reasons, this is the actual instance used internally. If you read data from the buffer, + * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative + * methods. If you change the buffer's index or its contents in any way, any other getter + * invocation for this value will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default ByteBuffer getBytesUnsafe(String name) { + return getBytesUnsafe(firstIndexOf(name)); + } + + /** + * Indicates whether the value for the first occurrence of {@code name} is a CQL {@code NULL}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean isNull(String name) { + return isNull(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name}, using the given codec for the + * conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T get(String name, TypeCodec codec) { + return get(firstIndexOf(name), codec); + } + + /** + * Returns the value for the first occurrence of {@code name}, converting it to the given Java + * type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #get(int, Class)} instead, which may perform slightly better. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(String name, GenericType targetType) { + return get(firstIndexOf(name), targetType); + } + + /** + * Returns the value for the first occurrence of {@code name}, converting it to the given Java + * type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #get(int, GenericType)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T get(String name, Class targetClass) { + return get(firstIndexOf(name), targetClass); + } + + /** + * Returns the value for the first occurrence of {@code name}, converting it to the most + * appropriate Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

Use this method to dynamically inspect elements when types aren't known in advance, for + * instance if you're writing a generic row logger. If you know the target Java type, it is + * generally preferable to use typed variants, such as the ones for built-in types ({@link + * #getBoolean(int)}, {@link #getInt(int)}, etc.), or {@link #get(int, Class)} and {@link + * #get(int, GenericType)} for custom types. + * + *

The definition of "most appropriate" is unspecified, and left to the appreciation of the + * {@link #codecRegistry()} implementation. By default, the driver uses the mapping described in + * the other {@code getXxx()} methods (for example {@link #getString(int) String for text, varchar + * and ascii}, etc). + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default Object getObject(String name) { + return getObject(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code false}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Boolean.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean getBoolean(String name) { + return getBoolean(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Byte.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default byte getByte(String name) { + return getByte(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Double.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default double getDouble(String name) { + return getDouble(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0.0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Float.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default float getFloat(String name) { + return getFloat(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Integer.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default int getInt(String name) { + return getInt(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Long.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default long getLong(String name) { + return getLong(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

Note that, due to its signature, this method cannot return {@code null}. If the CQL value is + * {@code NULL}, it will return {@code 0}. If this doesn't work for you, either call {@link + * #isNull(String)} before calling this method, or use {@code get(name, Short.class)} instead. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default short getShort(String name) { + return getShort(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Instant getInstant(String name) { + return getInstant(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java local date. + * + *

By default, this works with CQL type {@code date}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalDate getLocalDate(String name) { + return getLocalDate(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java local time. + * + *

By default, this works with CQL type {@code time}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default LocalTime getLocalTime(String name) { + return getLocalTime(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default ByteBuffer getByteBuffer(String name) { + return getByteBuffer(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default String getString(String name) { + return getString(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigInteger getBigInteger(String name) { + return getBigInteger(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default BigDecimal getBigDecimal(String name) { + return getBigDecimal(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UUID getUuid(String name) { + return getUuid(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default InetAddress getInetAddress(String name) { + return getInetAddress(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a duration. + * + *

By default, this works with CQL type {@code duration}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default CqlDuration getCqlDuration(String name) { + return getCqlDuration(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default List getList(String name, Class elementsClass) { + return getList(firstIndexOf(name), elementsClass); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Set getSet(String name, Class elementsClass) { + return getSet(firstIndexOf(name), elementsClass); + } + + /** + * Returns the value for the first occurrence of {@code name} as a Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #get(int, GenericType)}. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default Map getMap(String name, Class keyClass, Class valueClass) { + return getMap(firstIndexOf(name), keyClass, valueClass); + } + + /** + * Returns the value for the first occurrence of {@code name} as a user defined type value. + * + *

By default, this works with CQL user-defined types. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default UdtValue getUdtValue(String name) { + return getUdtValue(firstIndexOf(name)); + } + + /** + * Returns the value for the first occurrence of {@code name} as a tuple value. + * + *

By default, this works with CQL tuples. + * + *

If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default TupleValue getTupleValue(String name) { + return getTupleValue(firstIndexOf(name)); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java new file mode 100644 index 00000000000..6cd02691f20 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to set its values via a CQL identifier. */ +public interface SettableById> + extends SettableByIndex, AccessibleById { + + /** + * Sets the raw binary representation of the value for the first occurrence of {@code id}. + * + *

This is primarily for internal use; you'll likely want to use one of the typed setters + * instead, to pass a higher-level Java representation. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @param v the raw value, or {@code null} to set the CQL value {@code NULL}. For performance + * reasons, this is the actual instance used internally. If pass in a buffer that you're going + * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() + * duplicate} it beforehand. If you change the buffer's index or its contents in any way, + * further usage of this data will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBytesUnsafe(CqlIdentifier id, ByteBuffer v) { + return setBytesUnsafe(firstIndexOf(id), v); + } + + @Override + default DataType getType(CqlIdentifier id) { + return getType(firstIndexOf(id)); + } + + /** + * Sets the value for the first occurrence of {@code id} to CQL {@code NULL}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToNull(CqlIdentifier id) { + return setToNull(firstIndexOf(id)); + } + + /** + * Sets the value for the first occurrence of {@code id}, using the given codec for the + * conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T set(CqlIdentifier id, V v, TypeCodec codec) { + return set(firstIndexOf(id), v, codec); + } + + /** + * Sets the value for the first occurrence of {@code id}, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #set(int, V, Class)} instead, which may perform slightly better. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(CqlIdentifier id, V v, GenericType targetType) { + return set(firstIndexOf(id), v, targetType); + } + + /** + * Returns the value for the first occurrence of {@code id}, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #set(int, Object, GenericType)} instead. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(CqlIdentifier id, V v, Class targetClass) { + return set(firstIndexOf(id), v, targetClass); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBoolean(CqlIdentifier id, boolean v) { + return setBoolean(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByte(CqlIdentifier id, byte v) { + return setByte(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Double.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setDouble(CqlIdentifier id, double v) { + return setDouble(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Float.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setFloat(CqlIdentifier id, float v) { + return setFloat(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Integer.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInt(CqlIdentifier id, int v) { + return setInt(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Long.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLong(CqlIdentifier id, long v) { + return setLong(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Short.class)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setShort(CqlIdentifier id, short v) { + return setShort(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInstant(CqlIdentifier id, Instant v) { + return setInstant(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java local date. + * + *

By default, this works with CQL type {@code date}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalDate(CqlIdentifier id, LocalDate v) { + return setLocalDate(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java local time. + * + *

By default, this works with CQL type {@code time}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalTime(CqlIdentifier id, LocalTime v) { + return setLocalTime(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByteBuffer(CqlIdentifier id, ByteBuffer v) { + return setByteBuffer(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setString(CqlIdentifier id, String v) { + return setString(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigInteger(CqlIdentifier id, BigInteger v) { + return setBigInteger(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigDecimal(CqlIdentifier id, BigDecimal v) { + return setBigDecimal(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUuid(CqlIdentifier id, UUID v) { + return setUuid(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInetAddress(CqlIdentifier id, InetAddress v) { + return setInetAddress(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided duration. + * + *

By default, this works with CQL type {@code duration}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setCqlDuration(CqlIdentifier id, CqlDuration v) { + return setCqlDuration(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #set(int, Object, GenericType)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setList(CqlIdentifier id, List v, Class elementsClass) { + return setList(firstIndexOf(id), v, elementsClass); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #set(int, Object, GenericType)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setSet(CqlIdentifier id, Set v, Class elementsClass) { + return setSet(firstIndexOf(id), v, elementsClass); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #set(int, Object, GenericType)}. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setMap(CqlIdentifier id, Map v, Class keyClass, Class valueClass) { + return setMap(firstIndexOf(id), v, keyClass, valueClass); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided user defined type value. + * + *

By default, this works with CQL user-defined types. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUdtValue(CqlIdentifier id, UdtValue v) { + return setUdtValue(firstIndexOf(id), v); + } + + /** + * Sets the value for the first occurrence of {@code id} to the provided tuple value. + * + *

By default, this works with CQL tuples. + * + *

If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setTupleValue(CqlIdentifier id, TupleValue v) { + return setTupleValue(firstIndexOf(id), v); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java new file mode 100644 index 00000000000..ce76ef02327 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec; +import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to set its values via an integer index. */ +public interface SettableByIndex> extends AccessibleByIndex { + + /** + * Sets the raw binary representation of the {@code i}th value. + * + *

This is primarily for internal use; you'll likely want to use one of the typed setters + * instead, to pass a higher-level Java representation. + * + * @param v the raw value, or {@code null} to set the CQL value {@code NULL}. For performance + * reasons, this is the actual instance used internally. If pass in a buffer that you're going + * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() + * duplicate} it beforehand. If you change the buffer's index or its contents in any way, + * further usage of this data will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + T setBytesUnsafe(int i, ByteBuffer v); + + /** + * Sets the {@code i}th value to CQL {@code NULL}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToNull(int i) { + return setBytesUnsafe(i, null); + } + + /** + * Sets the {@code i}th value, using the given codec for the conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T set(int i, V v, TypeCodec codec) { + return setBytesUnsafe(i, codec.encode(v, protocolVersion())); + } + + /** + * Sets the {@code i}th value, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #set(int, V, Class)} instead, which may perform slightly better. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(int i, V v, GenericType targetType) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); + return set(i, v, codec); + } + + /** + * Returns the {@code i}th value, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #set(int, Object, GenericType)} instead. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(int i, V v, Class targetClass) { + // This is duplicated from the GenericType variant, because we want to give the codec registry + // a chance to process the unwrapped class directly, if it can do so in a more efficient way. + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); + return set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBoolean(int i, boolean v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Boolean.class); + return (codec instanceof PrimitiveBooleanCodec) + ? setBytesUnsafe(i, ((PrimitiveBooleanCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByte(int i, byte v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Byte.class); + return (codec instanceof PrimitiveByteCodec) + ? setBytesUnsafe(i, ((PrimitiveByteCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Double.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setDouble(int i, double v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Double.class); + return (codec instanceof PrimitiveDoubleCodec) + ? setBytesUnsafe(i, ((PrimitiveDoubleCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Float.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setFloat(int i, float v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Float.class); + return (codec instanceof PrimitiveFloatCodec) + ? setBytesUnsafe(i, ((PrimitiveFloatCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Integer.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInt(int i, int v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Integer.class); + return (codec instanceof PrimitiveIntCodec) + ? setBytesUnsafe(i, ((PrimitiveIntCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Long.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLong(int i, long v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Long.class); + return (codec instanceof PrimitiveLongCodec) + ? setBytesUnsafe(i, ((PrimitiveLongCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Short.class)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setShort(int i, short v) { + DataType cqlType = getType(i); + TypeCodec codec = codecRegistry().codecFor(cqlType, Short.class); + return (codec instanceof PrimitiveShortCodec) + ? setBytesUnsafe(i, ((PrimitiveShortCodec) codec).encodePrimitive(v, protocolVersion())) + : set(i, v, codec); + } + + /** + * Sets the {@code i}th value to the provided Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInstant(int i, Instant v) { + return set(i, v, Instant.class); + } + + /** + * Sets the {@code i}th value to the provided Java local date. + * + *

By default, this works with CQL type {@code date}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalDate(int i, LocalDate v) { + return set(i, v, LocalDate.class); + } + + /** + * Sets the {@code i}th value to the provided Java local time. + * + *

By default, this works with CQL type {@code time}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalTime(int i, LocalTime v) { + return set(i, v, LocalTime.class); + } + + /** + * Sets the {@code i}th value to the provided Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByteBuffer(int i, ByteBuffer v) { + return set(i, v, ByteBuffer.class); + } + + /** + * Sets the {@code i}th value to the provided Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setString(int i, String v) { + return set(i, v, String.class); + } + + /** + * Sets the {@code i}th value to the provided Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigInteger(int i, BigInteger v) { + return set(i, v, BigInteger.class); + } + + /** + * Sets the {@code i}th value to the provided Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigDecimal(int i, BigDecimal v) { + return set(i, v, BigDecimal.class); + } + + /** + * Sets the {@code i}th value to the provided Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUuid(int i, UUID v) { + return set(i, v, UUID.class); + } + + /** + * Sets the {@code i}th value to the provided Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInetAddress(int i, InetAddress v) { + return set(i, v, InetAddress.class); + } + + /** + * Sets the {@code i}th value to the provided duration. + * + *

By default, this works with CQL type {@code duration}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setCqlDuration(int i, CqlDuration v) { + return set(i, v, CqlDuration.class); + } + + /** + * Sets the {@code i}th value to the provided Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #set(int, Object, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setList(int i, List v, Class elementsClass) { + return set(i, v, GenericType.listOf(elementsClass)); + } + + /** + * Sets the {@code i}th value to the provided Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #set(int, Object, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setSet(int i, Set v, Class elementsClass) { + return set(i, v, GenericType.setOf(elementsClass)); + } + + /** + * Sets the {@code i}th value to the provided Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #set(int, Object, GenericType)}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setMap(int i, Map v, Class keyClass, Class valueClass) { + return set(i, v, GenericType.mapOf(keyClass, valueClass)); + } + + /** + * Sets the {@code i}th value to the provided user defined type value. + * + *

By default, this works with CQL user-defined types. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUdtValue(int i, UdtValue v) { + return set(i, v, UdtValue.class); + } + + /** + * Sets the {@code i}th value to the provided tuple value. + * + *

By default, this works with CQL tuples. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setTupleValue(int i, TupleValue v) { + return set(i, v, TupleValue.class); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java new file mode 100644 index 00000000000..ac52a70557f --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** A data structure that provides methods to set its values via a name. */ +public interface SettableByName> + extends SettableByIndex, AccessibleByName { + + /** + * Sets the raw binary representation of the value for the first occurrence of {@code name}. + * + *

This is primarily for internal use; you'll likely want to use one of the typed setters + * instead, to pass a higher-level Java representation. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @param v the raw value, or {@code null} to set the CQL value {@code NULL}. For performance + * reasons, this is the actual instance used internally. If pass in a buffer that you're going + * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() + * duplicate} it beforehand. If you change the buffer's index or its contents in any way, + * further usage of this data will have unpredictable results. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBytesUnsafe(String name, ByteBuffer v) { + return setBytesUnsafe(firstIndexOf(name), v); + } + + @Override + default DataType getType(String name) { + return getType(firstIndexOf(name)); + } + + /** + * Sets the value for the first occurrence of {@code name} to CQL {@code NULL}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToNull(String name) { + return setToNull(firstIndexOf(name)); + } + + /** + * Sets the value for the first occurrence of {@code name}, using the given codec for the + * conversion. + * + *

This method completely bypasses the {@link #codecRegistry()}, and forces the driver to use + * the given codec instead. This can be useful if the codec would collide with a previously + * registered one, or if you want to use the codec just once without registering it. + * + *

It is the caller's responsibility to ensure that the given codec is appropriate for the + * conversion. Failing to do so will result in errors at runtime. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T set(String name, V v, TypeCodec codec) { + return set(firstIndexOf(name), v, codec); + } + + /** + * Sets the value for the first occurrence of {@code name}, converting it to the given Java type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

This variant is for generic Java types. If the target type is not generic, use {@link + * #set(int, V, Class)} instead, which may perform slightly better. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(String name, V v, GenericType targetType) { + return set(firstIndexOf(name), v, targetType); + } + + /** + * Returns the value for the first occurrence of {@code name}, converting it to the given Java + * type. + * + *

The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. + * + *

If the target type is generic, use {@link #set(int, Object, GenericType)} instead. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws CodecNotFoundException if no codec can perform the conversion. + */ + default T set(String name, V v, Class targetClass) { + return set(firstIndexOf(name), v, targetClass); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive boolean. + * + *

By default, this works with CQL type {@code boolean}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBoolean(String name, boolean v) { + return setBoolean(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive byte. + * + *

By default, this works with CQL type {@code tinyint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Boolean.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByte(String name, byte v) { + return setByte(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive double. + * + *

By default, this works with CQL type {@code double}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Double.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setDouble(String name, double v) { + return setDouble(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive float. + * + *

By default, this works with CQL type {@code float}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Float.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setFloat(String name, float v) { + return setFloat(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive integer. + * + *

By default, this works with CQL type {@code int}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Integer.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInt(String name, int v) { + return setInt(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive long. + * + *

By default, this works with CQL types {@code bigint} and {@code counter}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Long.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLong(String name, long v) { + return setLong(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java primitive short. + * + *

By default, this works with CQL type {@code smallint}. + * + *

To set the value to CQL {@code NULL}, use {@link #setToNull(int)}, or {@code set(i, v, + * Short.class)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setShort(String name, short v) { + return setShort(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java instant. + * + *

By default, this works with CQL type {@code timestamp}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInstant(String name, Instant v) { + return setInstant(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java local date. + * + *

By default, this works with CQL type {@code date}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalDate(String name, LocalDate v) { + return setLocalDate(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java local time. + * + *

By default, this works with CQL type {@code time}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setLocalTime(String name, LocalTime v) { + return setLocalTime(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java byte buffer. + * + *

By default, this works with CQL type {@code blob}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setByteBuffer(String name, ByteBuffer v) { + return setByteBuffer(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java string. + * + *

By default, this works with CQL types {@code text}, {@code varchar} and {@code ascii}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setString(String name, String v) { + return setString(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java big integer. + * + *

By default, this works with CQL type {@code varint}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigInteger(String name, BigInteger v) { + return setBigInteger(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java big decimal. + * + *

By default, this works with CQL type {@code decimal}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setBigDecimal(String name, BigDecimal v) { + return setBigDecimal(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java UUID. + * + *

By default, this works with CQL types {@code uuid} and {@code timeuuid}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUuid(String name, UUID v) { + return setUuid(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java IP address. + * + *

By default, this works with CQL type {@code inet}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setInetAddress(String name, InetAddress v) { + return setInetAddress(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided duration. + * + *

By default, this works with CQL type {@code duration}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setCqlDuration(String name, CqlDuration v) { + return setCqlDuration(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java list. + * + *

By default, this works with CQL type {@code list}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex list types, use {@link #set(int, Object, GenericType)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setList(String name, List v, Class elementsClass) { + return setList(firstIndexOf(name), v, elementsClass); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java set. + * + *

By default, this works with CQL type {@code set}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex set types, use {@link #set(int, Object, GenericType)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setSet(String name, Set v, Class elementsClass) { + return setSet(firstIndexOf(name), v, elementsClass); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided Java map. + * + *

By default, this works with CQL type {@code map}. + * + *

This method is provided for convenience when the element type is a non-generic type. For + * more complex map types, use {@link #set(int, Object, GenericType)}. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setMap(String name, Map v, Class keyClass, Class valueClass) { + return setMap(firstIndexOf(name), v, keyClass, valueClass); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided user defined type + * value. + * + *

By default, this works with CQL user-defined types. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setUdtValue(String name, UdtValue v) { + return setUdtValue(firstIndexOf(name), v); + } + + /** + * Sets the value for the first occurrence of {@code name} to the provided tuple value. + * + *

By default, this works with CQL tuples. + * + *

This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setTupleValue(String name, TupleValue v) { + return setTupleValue(firstIndexOf(name), v); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java new file mode 100644 index 00000000000..4c8af45b351 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.detach.Detachable; +import com.datastax.oss.driver.api.type.TupleType; +import java.io.Serializable; + +/** + * Driver-side representation of a CQL {@code tuple} value. + * + *

It is an ordered set of anonymous, typed fields. + * + *

A tuple value is attached if and only if its type is attached (see {@link Detachable}). + */ +public interface TupleValue extends GettableByIndex, SettableByIndex, Serializable { + TupleType getType(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java new file mode 100644 index 00000000000..c94701bcc6e --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.detach.Detachable; +import com.datastax.oss.driver.api.type.UserDefinedType; +import java.io.Serializable; + +/** + * Driver-side representation of an instance of a CQL user defined type. + * + *

It is an ordered set of named, typed fields. + * + *

A tuple value is attached if and only if its type is attached (see {@link Detachable}). + */ +public interface UdtValue + extends GettableById, + GettableByName, + SettableById, + SettableByName, + Serializable { + + UserDefinedType getType(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java b/types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java similarity index 51% rename from core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java rename to types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java index 91b73a9426e..336b5cd3304 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/DoubleCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java @@ -13,29 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; +package com.datastax.oss.driver.api.core.detach; import com.datastax.oss.driver.api.core.ProtocolVersion; -import java.nio.ByteBuffer; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -public class DoubleCodec implements TypeCodec { - @Override - public ByteBuffer encode(Double value, ProtocolVersion protocolVersion) { - if (value == null) { - return null; - } else { - ByteBuffer bytes = ByteBuffer.allocate(8); - bytes.putDouble(0, value); - return bytes; - } - } +/** @see Detachable */ +public interface AttachmentPoint { + AttachmentPoint NONE = + new AttachmentPoint() { + @Override + public ProtocolVersion protocolVersion() { + return ProtocolVersion.DEFAULT; + } - @Override - public Double decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { - if (bytes == null || bytes.remaining() == 0) { - return null; - } else { - return bytes.getDouble(bytes.position()); - } - } + @Override + public CodecRegistry codecRegistry() { + return CodecRegistry.DEFAULT; + } + }; + + ProtocolVersion protocolVersion(); + + CodecRegistry codecRegistry(); } diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java b/types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java new file mode 100644 index 00000000000..2541b71194e --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.detach; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.Data; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; + +/** + * Defines the contract of an object that can be detached and reattached to a driver instance. + * + *

The driver's {@link Data data structure} types (such as rows, tuples and UDT values) store + * their data as byte buffers, and only decode it on demand, when the end user accesses a particular + * column or field. + * + *

Decoding requires a {@link ProtocolVersion} (because the encoded format might change across + * versions), and a {@link CodecRegistry} (because the user might ask us to decode to a custom + * type). + * + *

    + *
  • When a data container was obtained from a driver instance (for example, reading a row from + * a result set, or reading a value from a UDT column), it is attached: its protocol + * version and registry are those of the driver. + *
  • When it is created manually by the user (for example, creating an instance from a manually + * created {@link TupleType}), it is detached: it uses {@link + * ProtocolVersion#DEFAULT} and {@link CodecRegistry#DEFAULT}. + *
+ * + * The only way an attached object can become detached is if it is serialized and deserialized + * (referring to Java serialization). + * + *

A detached object can be reattached to a driver instance. This is done automatically if you + * pass the object to one of the driver methods, for example if you use a manually created tuple as + * a query parameter. + */ +public interface Detachable { + boolean isDetached(); + + void attach(AttachmentPoint attachmentPoint); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java b/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java index 291a4a84e5d..481ef548ea9 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java @@ -15,10 +15,16 @@ */ package com.datastax.oss.driver.api.type; -public interface CustomType { +import com.datastax.oss.protocol.internal.ProtocolConstants; + +public interface CustomType extends DataType { /** * The fully qualified name of the subtype of {@code org.apache.cassandra.db.marshal.AbstractType} * that represents this type server-side. */ String getClassName(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.CUSTOM; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java index 52f677fb304..e1ad8358463 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java @@ -15,11 +15,15 @@ */ package com.datastax.oss.driver.api.type; +import com.datastax.oss.driver.api.core.detach.Detachable; import java.io.Serializable; /** - * The type of a CQL column or function argument. + * The type of a CQL column, field or function argument. * * @see DataTypes */ -public interface DataType extends Serializable {} +public interface DataType extends Detachable, Serializable { + /** The code of the data type in the native protocol specification. */ + int getProtocolCode(); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java b/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java index 14c3c0cd8ac..196dded5aa4 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.type; +import com.datastax.oss.driver.api.core.detach.Detachable; import com.datastax.oss.driver.internal.type.DefaultCustomType; import com.datastax.oss.driver.internal.type.DefaultListType; import com.datastax.oss.driver.internal.type.DefaultMapType; @@ -25,7 +26,7 @@ import com.google.common.collect.ImmutableList; import java.util.Arrays; -/** Utility class to get or build {@link DataType} instances. */ +/** Constants and factory methods to obtain data type instances. */ public class DataTypes { public static final DataType ASCII = new PrimitiveType(ProtocolConstants.DataType.ASCII); @@ -49,8 +50,13 @@ public class DataTypes { public static final DataType TINYINT = new PrimitiveType(ProtocolConstants.DataType.TINYINT); public static final DataType DURATION = new PrimitiveType(ProtocolConstants.DataType.DURATION); - public static CustomType custom(String className) { - return new DefaultCustomType(className); + public static DataType custom(String className) { + // In protocol v4, duration is implemented as a custom type + if ("org.apache.cassandra.db.marshal.DurationType".equals(className)) { + return DURATION; + } else { + return new DefaultCustomType(className); + } } public static ListType listOf(DataType elementType) { @@ -67,6 +73,11 @@ public static MapType mapOf(DataType keyType, DataType valueType) { return new DefaultMapType(keyType, valueType, false); } + /** + * Builds a new, detached tuple type. + * + * @see Detachable + */ public static TupleType tupleOf(DataType... componentTypes) { return new DefaultTupleType(ImmutableList.copyOf(Arrays.asList(componentTypes))); } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java b/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java index 57f84cc3002..e6fcb3e3620 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java @@ -15,8 +15,14 @@ */ package com.datastax.oss.driver.api.type; -public interface ListType { - public DataType getElementType(); +import com.datastax.oss.protocol.internal.ProtocolConstants; - public boolean isFrozen(); +public interface ListType extends DataType { + DataType getElementType(); + + boolean isFrozen(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.LIST; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java b/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java index 0678114d78b..d2786dcc9bf 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java @@ -15,10 +15,16 @@ */ package com.datastax.oss.driver.api.type; -public interface MapType { - public DataType getKeyType(); +import com.datastax.oss.protocol.internal.ProtocolConstants; - public DataType getValueType(); +public interface MapType extends DataType { + DataType getKeyType(); - public boolean isFrozen(); + DataType getValueType(); + + boolean isFrozen(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.MAP; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java b/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java index 8bd8a12efff..1e9482757fb 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java @@ -15,8 +15,14 @@ */ package com.datastax.oss.driver.api.type; -public interface SetType { - public DataType getElementType(); +import com.datastax.oss.protocol.internal.ProtocolConstants; - public boolean isFrozen(); +public interface SetType extends DataType { + DataType getElementType(); + + boolean isFrozen(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.SET; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java b/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java index 4f1f7ac1a48..c84aff98262 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java @@ -15,8 +15,19 @@ */ package com.datastax.oss.driver.api.type; +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.protocol.internal.ProtocolConstants; import java.util.List; -public interface TupleType { +public interface TupleType extends DataType { List getComponentTypes(); + + TupleValue newValue(); + + AttachmentPoint getAttachmentPoint(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.TUPLE; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java index 278238859c4..fa92804fbd9 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java @@ -16,12 +16,37 @@ package com.datastax.oss.driver.api.type; import com.datastax.oss.driver.api.core.CqlIdentifier; -import java.util.Map; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import java.util.List; -public interface UserDefinedType { +public interface UserDefinedType extends DataType { CqlIdentifier getKeyspace(); CqlIdentifier getName(); - Map getFieldTypes(); + List getFieldNames(); + + int firstIndexOf(CqlIdentifier id); + + int firstIndexOf(String name); + + default boolean contains(CqlIdentifier id) { + return firstIndexOf(id) >= 0; + } + + default boolean contains(String name) { + return firstIndexOf(name) >= 0; + } + + List getFieldTypes(); + + UdtValue newValue(); + + AttachmentPoint getAttachmentPoint(); + + default int getProtocolCode() { + return ProtocolConstants.DataType.UDT; + } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java new file mode 100644 index 00000000000..ab86b7c936a --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.type.reflect.GenericType; + +/** Thrown when a suitable {@link TypeCodec} cannot be found by the {@link CodecRegistry}. */ +public class CodecNotFoundException extends RuntimeException { + + private final DataType cqlType; + + private final GenericType javaType; + + public CodecNotFoundException(DataType cqlType, GenericType javaType) { + this( + String.format("Codec not found for requested operation: [%s <-> %s]", cqlType, javaType), + null, + cqlType, + javaType); + } + + public CodecNotFoundException(Throwable cause, DataType cqlType, GenericType javaType) { + this( + String.format( + "Error while looking up codec for requested operation: [%s <-> %s]", cqlType, javaType), + cause, + cqlType, + javaType); + } + + private CodecNotFoundException( + String msg, Throwable cause, DataType cqlType, GenericType javaType) { + super(msg, cause); + this.cqlType = cqlType; + this.javaType = javaType; + } + + public DataType getCqlType() { + return cqlType; + } + + public GenericType getJavaType() { + return javaType; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java new file mode 100644 index 00000000000..cb80cdb04d0 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized boolean codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's boolean getters will use + * it to avoid boxing. + */ +public interface PrimitiveBooleanCodec extends TypeCodec { + + ByteBuffer encodePrimitive(boolean value, ProtocolVersion protocolVersion); + + boolean decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Boolean value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Boolean decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java similarity index 51% rename from core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java rename to types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java index 6d4052688a7..627c4c4614c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/UuidCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java @@ -13,30 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core.adminrequest.codec; +package com.datastax.oss.driver.api.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; -import java.util.UUID; -public class UuidCodec implements TypeCodec { +/** + * A specialized byte codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's byte getters will use it + * to avoid boxing. + */ +public interface PrimitiveByteCodec extends TypeCodec { + + ByteBuffer encodePrimitive(byte value, ProtocolVersion protocolVersion); + + byte decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); @Override - public ByteBuffer encode(UUID value, ProtocolVersion protocolVersion) { - if (value == null) { - return null; - } else { - ByteBuffer bytes = ByteBuffer.allocate(16); - bytes.putLong(0, value.getMostSignificantBits()); - bytes.putLong(8, value.getLeastSignificantBits()); - return bytes; - } + default ByteBuffer encode(Byte value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); } @Override - public UUID decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + default Byte decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { return (bytes == null || bytes.remaining() == 0) ? null - : new UUID(bytes.getLong(bytes.position()), bytes.getLong(bytes.position() + 8)); + : decodePrimitive(bytes, protocolVersion); } } diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java new file mode 100644 index 00000000000..b901f55936c --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized double codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's double getters will use + * it to avoid boxing. + */ +public interface PrimitiveDoubleCodec extends TypeCodec { + + ByteBuffer encodePrimitive(double value, ProtocolVersion protocolVersion); + + double decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Double value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Double decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java new file mode 100644 index 00000000000..af5daec4d13 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized float codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's float getters will use it + * to avoid boxing. + */ +public interface PrimitiveFloatCodec extends TypeCodec { + + ByteBuffer encodePrimitive(float value, ProtocolVersion protocolVersion); + + float decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Float value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Float decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java new file mode 100644 index 00000000000..1fd3a20a542 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized integer codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's integer getters will use + * it to avoid boxing. + */ +public interface PrimitiveIntCodec extends TypeCodec { + + ByteBuffer encodePrimitive(int value, ProtocolVersion protocolVersion); + + int decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Integer value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Integer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java new file mode 100644 index 00000000000..b098bd7ff3e --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized long codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's long getters will use it + * to avoid boxing. + */ +public interface PrimitiveLongCodec extends TypeCodec { + + ByteBuffer encodePrimitive(long value, ProtocolVersion protocolVersion); + + long decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Long value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Long decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java new file mode 100644 index 00000000000..abeb4289f3a --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.nio.ByteBuffer; + +/** + * A specialized short codec that knows how to deal with primitive types. + * + *

If the codec registry returns an instance of this type, the driver's short getters will use it + * to avoid boxing. + */ +public interface PrimitiveShortCodec extends TypeCodec { + + ByteBuffer encodePrimitive(short value, ProtocolVersion protocolVersion); + + short decodePrimitive(ByteBuffer value, ProtocolVersion protocolVersion); + + @Override + default ByteBuffer encode(Short value, ProtocolVersion protocolVersion) { + return (value == null) ? null : encodePrimitive(value, protocolVersion); + } + + @Override + default Short decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return (bytes == null || bytes.remaining() == 0) + ? null + : decodePrimitive(bytes, protocolVersion); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java new file mode 100644 index 00000000000..5cb450dee12 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.google.common.base.Preconditions; +import com.google.common.reflect.TypeToken; +import java.nio.ByteBuffer; + +/** Manages the two-way conversion between a CQL type and a Java type. */ +public interface TypeCodec { + + GenericType getJavaType(); + + DataType getCqlType(); + + /** + * Whether this codec is capable of encoding the given Java type. + * + *

The default implementation is invariant with respect to the passed argument + * (through the usage of {@link GenericType#equals(Object)}) and it's strongly recommended not + * to modify this behavior. This means that a codec will only ever accept the exact + * Java type that it has been created for. + * + *

If the argument represents a Java primitive type, its wrapper type is considered instead. + */ + default boolean canEncode(GenericType javaType) { + Preconditions.checkNotNull(javaType); + return getJavaType().__getToken().equals(javaType.__getToken().wrap()); + } + + /** + * Whether this codec is capable of encoding the given Java type. + * + *

The default implementation wraps the class in a generic type and calls {@link + * #canEncode(GenericType)}, therefore it is invariant as well. + */ + default boolean canEncode(Class javaType) { + Preconditions.checkNotNull(javaType); + return canEncode(GenericType.of(javaType)); + } + + /** + * Whether this codec is capable of encoding the given Java object. + * + *

The object's Java type is inferred from its runtime (raw) type, contrary to {@link + * #canEncode(GenericType)} which is capable of handling generic types. + * + *

The default implementation is covariant with respect to the passed argument and + * it's strongly recommended not to modify this behavior. This means that, by default, a + * codec will accept any subtype of the Java type that it has been created for. + * + *

It can only handle non-parameterized types; codecs handling parameterized types, such as + * collection types, must override this method and perform some sort of "manual" inspection of the + * actual type parameters. + * + *

Similarly, codecs that only accept a partial subset of all possible values must override + * this method and manually inspect the object to check if it complies or not with the codec's + * limitations. + */ + default boolean canEncode(Object object) { + Preconditions.checkNotNull(object); + return getJavaType().__getToken().isSupertypeOf(TypeToken.of(object.getClass())); + } + + /** Whether this codec is capable of decoding the given CQL type. */ + default boolean canDecode(DataType cqlType) { + Preconditions.checkNotNull(cqlType); + return this.getCqlType().equals(cqlType); + } + + ByteBuffer encode(T value, ProtocolVersion protocolVersion); + + T decode(ByteBuffer bytes, ProtocolVersion protocolVersion); + + String format(T value); + + T parse(String value); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java new file mode 100644 index 00000000000..b3541d5a88e --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec; + +import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.type.CustomType; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.internal.type.codec.BigIntCodec; +import com.datastax.oss.driver.internal.type.codec.BlobCodec; +import com.datastax.oss.driver.internal.type.codec.BooleanCodec; +import com.datastax.oss.driver.internal.type.codec.CounterCodec; +import com.datastax.oss.driver.internal.type.codec.CqlDurationCodec; +import com.datastax.oss.driver.internal.type.codec.CustomCodec; +import com.datastax.oss.driver.internal.type.codec.DateCodec; +import com.datastax.oss.driver.internal.type.codec.DecimalCodec; +import com.datastax.oss.driver.internal.type.codec.DoubleCodec; +import com.datastax.oss.driver.internal.type.codec.FloatCodec; +import com.datastax.oss.driver.internal.type.codec.InetCodec; +import com.datastax.oss.driver.internal.type.codec.IntCodec; +import com.datastax.oss.driver.internal.type.codec.ListCodec; +import com.datastax.oss.driver.internal.type.codec.MapCodec; +import com.datastax.oss.driver.internal.type.codec.SetCodec; +import com.datastax.oss.driver.internal.type.codec.SmallIntCodec; +import com.datastax.oss.driver.internal.type.codec.StringCodec; +import com.datastax.oss.driver.internal.type.codec.TimeCodec; +import com.datastax.oss.driver.internal.type.codec.TimeUuidCodec; +import com.datastax.oss.driver.internal.type.codec.TimestampCodec; +import com.datastax.oss.driver.internal.type.codec.TinyIntCodec; +import com.datastax.oss.driver.internal.type.codec.TupleCodec; +import com.datastax.oss.driver.internal.type.codec.UdtCodec; +import com.datastax.oss.driver.internal.type.codec.UuidCodec; +import com.datastax.oss.driver.internal.type.codec.VarIntCodec; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** Constants and factory methods to obtain type codec instances. */ +public class TypeCodecs { + + public static final PrimitiveBooleanCodec BOOLEAN = new BooleanCodec(); + public static final PrimitiveByteCodec TINYINT = new TinyIntCodec(); + public static final PrimitiveDoubleCodec DOUBLE = new DoubleCodec(); + public static final PrimitiveLongCodec COUNTER = new CounterCodec(); + public static final PrimitiveFloatCodec FLOAT = new FloatCodec(); + public static final PrimitiveIntCodec INT = new IntCodec(); + public static final PrimitiveLongCodec BIGINT = new BigIntCodec(); + public static final PrimitiveShortCodec SMALLINT = new SmallIntCodec(); + public static final TypeCodec TIMESTAMP = new TimestampCodec(); + public static final TypeCodec DATE = new DateCodec(); + public static final TypeCodec TIME = new TimeCodec(); + public static final TypeCodec BLOB = new BlobCodec(); + public static final TypeCodec TEXT = new StringCodec(DataTypes.TEXT, Charsets.UTF_8); + public static final TypeCodec ASCII = new StringCodec(DataTypes.ASCII, Charsets.US_ASCII); + public static final TypeCodec VARINT = new VarIntCodec(); + public static final TypeCodec DECIMAL = new DecimalCodec(); + public static final TypeCodec UUID = new UuidCodec(); + public static final TypeCodec TIMEUUID = new TimeUuidCodec(); + public static final TypeCodec INET = new InetCodec(); + public static final TypeCodec DURATION = new CqlDurationCodec(); + + public static TypeCodec custom(DataType cqlType) { + Preconditions.checkArgument(cqlType instanceof CustomType, "cqlType must be a custom type"); + return new CustomCodec((CustomType) cqlType); + } + + public static TypeCodec> listOf(TypeCodec elementCodec) { + return new ListCodec<>(DataTypes.listOf(elementCodec.getCqlType()), elementCodec); + } + + public static TypeCodec> setOf(TypeCodec elementCodec) { + return new SetCodec<>(DataTypes.setOf(elementCodec.getCqlType()), elementCodec); + } + + public static TypeCodec> mapOf(TypeCodec keyCodec, TypeCodec valueCodec) { + return new MapCodec<>( + DataTypes.mapOf(keyCodec.getCqlType(), valueCodec.getCqlType()), keyCodec, valueCodec); + } + + public static TypeCodec tupleOf(TupleType cqlType) { + return new TupleCodec(cqlType); + } + + public static TypeCodec udtOf(UserDefinedType cqlType) { + return new UdtCodec(cqlType); + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java new file mode 100644 index 00000000000..d85fb84fc00 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.type.codec.registry; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; + +public interface CodecRegistry { + /** + * An immutable instance, that only handles built-in driver types (that is, primitive types, and + * collections, tuples, and user defined types thereof). + */ + CodecRegistry DEFAULT = new DefaultCodecRegistry(); + + /** + * Returns a codec to handle the conversion between the given types. + * + * @throws CodecNotFoundException if there is no such codec. + */ + TypeCodec codecFor(DataType cqlType, GenericType javaType); + + /** + * Returns a codec to handle the conversion between the given types. + * + *

The default implementation of this method simply wraps {@code javaType} and calls {@link + * #codecFor(DataType, GenericType)}. Implementors can override it if they can avoid the overhead + * of wrapping. + * + * @throws CodecNotFoundException if there is no such codec. + */ + default TypeCodec codecFor(DataType cqlType, Class javaType) { + return codecFor(cqlType, GenericType.of(javaType)); + } + + /** + * Returns a codec to convert the given CQL type to the Java type deemed most appropriate to + * represent it. + * + *

The definition of "most appropriate" is unspecified, and left to the appreciation of the + * registry implementor. + */ + TypeCodec codecFor(DataType cqlType); + + /** + * Returns a codec to convert the given Java object to the CQL type deemed most appropriate to + * represent it. + * + *

The definition of "most appropriate" is unspecified, and left to the appreciation of the + * registry implementor. + */ + TypeCodec codecFor(T value); +} diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java b/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java index e2cbb83b43f..32aa43741fb 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java @@ -15,7 +15,22 @@ */ package com.datastax.oss.driver.api.type.reflect; +import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; /** * Runtime representation of a generic Java type. @@ -23,7 +38,8 @@ *

This is used by type codecs to indicate which Java types they accept, and by generic getters * and setters in the driver's query API. * - *

To create an instance, use one of the static factory methods, or create an anonymous class: + *

To get an instance, use one of the constants or static factory methods, or create an anonymous + * class: * *

{@code
  * GenericType> fooBarType = new GenericType>(){};
@@ -31,13 +47,75 @@
  *
  * You are encouraged to store and reuse these objects.
  */
-public abstract class GenericType {
+public class GenericType {
+
+  public static final GenericType BOOLEAN = of(Boolean.class);
+  public static final GenericType BYTE = of(Byte.class);
+  public static final GenericType DOUBLE = of(Double.class);
+  public static final GenericType FLOAT = of(Float.class);
+  public static final GenericType INTEGER = of(Integer.class);
+  public static final GenericType LONG = of(Long.class);
+  public static final GenericType SHORT = of(Short.class);
+  public static final GenericType INSTANT = of(Instant.class);
+  public static final GenericType LOCAL_DATE = of(LocalDate.class);
+  public static final GenericType LOCAL_TIME = of(LocalTime.class);
+  public static final GenericType BYTE_BUFFER = of(ByteBuffer.class);
+  public static final GenericType STRING = of(String.class);
+  public static final GenericType BIG_INTEGER = of(BigInteger.class);
+  public static final GenericType BIG_DECIMAL = of(BigDecimal.class);
+  public static final GenericType UUID = of(UUID.class);
+  public static final GenericType INET_ADDRESS = of(InetAddress.class);
+  public static final GenericType CQL_DURATION = of(CqlDuration.class);
+  public static final GenericType TUPLE_VALUE = of(TupleValue.class);
+  public static final GenericType UDT_VALUE = of(UdtValue.class);
 
-  /** Creates a new instance representing a raw Java class. */
   public static  GenericType of(Class type) {
     return new SimpleGenericType<>(type);
   }
 
+  public static GenericType of(java.lang.reflect.Type type) {
+    return new GenericType<>(TypeToken.of(type));
+  }
+
+  public static  GenericType> listOf(Class elementType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, TypeToken.of(elementType));
+    return new GenericType<>(token);
+  }
+
+  public static  GenericType> listOf(GenericType elementType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, elementType.token);
+    return new GenericType<>(token);
+  }
+
+  public static  GenericType> setOf(Class elementType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, TypeToken.of(elementType));
+    return new GenericType<>(token);
+  }
+
+  public static  GenericType> setOf(GenericType elementType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, elementType.token);
+    return new GenericType<>(token);
+  }
+
+  public static  GenericType> mapOf(Class keyType, Class valueType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, TypeToken.of(keyType))
+            .where(new TypeParameter() {}, TypeToken.of(valueType));
+    return new GenericType<>(token);
+  }
+
+  public static  GenericType> mapOf(
+      GenericType keyType, GenericType valueType) {
+    TypeToken> token =
+        new TypeToken>() {}.where(new TypeParameter() {}, keyType.token)
+            .where(new TypeParameter() {}, valueType.token);
+    return new GenericType<>(token);
+  }
+
   // This wraps -- and delegates most of the work to -- a Guava type token. The reason we don't
   // expose that type directly is because we shade Guava.
   private final TypeToken token;
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java
new file mode 100644
index 00000000000..919e734423a
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.core.data;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.data.TupleValue;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.TupleType;
+import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import com.google.common.base.Preconditions;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+public class DefaultTupleValue implements TupleValue {
+
+  private static final long serialVersionUID = 1;
+
+  private final TupleType type;
+  private final ByteBuffer[] values;
+
+  public DefaultTupleValue(TupleType type) {
+    this(type, new ByteBuffer[type.getComponentTypes().size()]);
+  }
+
+  private DefaultTupleValue(TupleType type, ByteBuffer[] values) {
+    Preconditions.checkNotNull(type);
+    this.type = type;
+    this.values = values;
+  }
+
+  @Override
+  public TupleType getType() {
+    return type;
+  }
+
+  @Override
+  public int size() {
+    return values.length;
+  }
+
+  @Override
+  public ByteBuffer getBytesUnsafe(int i) {
+    return values[i];
+  }
+
+  @Override
+  public TupleValue setBytesUnsafe(int i, ByteBuffer v) {
+    values[i] = v;
+    return this;
+  }
+
+  @Override
+  public DataType getType(int i) {
+    return type.getComponentTypes().get(i);
+  }
+
+  @Override
+  public CodecRegistry codecRegistry() {
+    return type.getAttachmentPoint().codecRegistry();
+  }
+
+  @Override
+  public ProtocolVersion protocolVersion() {
+    return type.getAttachmentPoint().protocolVersion();
+  }
+
+  /**
+   * @serialData The type of the tuple, followed by an array of byte arrays representing the values
+   *     (null values are represented by {@code null}).
+   */
+  private Object writeReplace() {
+    return new SerializationProxy(this);
+  }
+
+  private void readObject(ObjectInputStream stream) throws InvalidObjectException {
+    // Should never be called since we serialized a proxy
+    throw new InvalidObjectException("Proxy required");
+  }
+
+  private static class SerializationProxy implements Serializable {
+
+    private static final long serialVersionUID = 1;
+
+    private final TupleType type;
+    private final byte[][] values;
+
+    SerializationProxy(DefaultTupleValue tuple) {
+      this.type = tuple.type;
+      this.values = new byte[tuple.values.length][];
+      for (int i = 0; i < tuple.values.length; i++) {
+        ByteBuffer buffer = tuple.values[i];
+        this.values[i] = (buffer == null) ? null : Bytes.getArray(buffer);
+      }
+    }
+
+    private Object readResolve() {
+      ByteBuffer[] buffers = new ByteBuffer[this.values.length];
+      for (int i = 0; i < this.values.length; i++) {
+        byte[] value = this.values[i];
+        buffers[i] = (value == null) ? null : ByteBuffer.wrap(value);
+      }
+      return new DefaultTupleValue(this.type, buffers);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java
new file mode 100644
index 00000000000..4bbca108a6c
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.core.data;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.data.UdtValue;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.UserDefinedType;
+import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import com.google.common.base.Preconditions;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+public class DefaultUdtValue implements UdtValue {
+
+  private static final long serialVersionUID = 1;
+
+  private final UserDefinedType type;
+  private final ByteBuffer[] values;
+
+  public DefaultUdtValue(UserDefinedType type) {
+    this(type, new ByteBuffer[type.getFieldTypes().size()]);
+  }
+
+  private DefaultUdtValue(UserDefinedType type, ByteBuffer[] values) {
+    Preconditions.checkNotNull(type);
+    this.type = type;
+    this.values = values;
+  }
+
+  @Override
+  public UserDefinedType getType() {
+    return type;
+  }
+
+  @Override
+  public int size() {
+    return values.length;
+  }
+
+  @Override
+  public int firstIndexOf(CqlIdentifier id) {
+    return type.firstIndexOf(id);
+  }
+
+  @Override
+  public int firstIndexOf(String name) {
+    return type.firstIndexOf(name);
+  }
+
+  @Override
+  public DataType getType(int i) {
+    return type.getFieldTypes().get(i);
+  }
+
+  @Override
+  public ByteBuffer getBytesUnsafe(int i) {
+    return values[i];
+  }
+
+  @Override
+  public UdtValue setBytesUnsafe(int i, ByteBuffer v) {
+    values[i] = v;
+    return this;
+  }
+
+  @Override
+  public CodecRegistry codecRegistry() {
+    return type.getAttachmentPoint().codecRegistry();
+  }
+
+  @Override
+  public ProtocolVersion protocolVersion() {
+    return type.getAttachmentPoint().protocolVersion();
+  }
+  /**
+   * @serialData The type of the tuple, followed by an array of byte arrays representing the values
+   *     (null values are represented by {@code null}).
+   */
+  private Object writeReplace() {
+    return new SerializationProxy(this);
+  }
+
+  private void readObject(ObjectInputStream stream) throws InvalidObjectException {
+    // Should never be called since we serialized a proxy
+    throw new InvalidObjectException("Proxy required");
+  }
+
+  private static class SerializationProxy implements Serializable {
+
+    private static final long serialVersionUID = 1;
+
+    private final UserDefinedType type;
+    private final byte[][] values;
+
+    SerializationProxy(DefaultUdtValue udt) {
+      this.type = udt.type;
+      this.values = new byte[udt.values.length][];
+      for (int i = 0; i < udt.values.length; i++) {
+        ByteBuffer buffer = udt.values[i];
+        this.values[i] = (buffer == null) ? null : Bytes.getArray(buffer);
+      }
+    }
+
+    private Object readResolve() {
+      ByteBuffer[] buffers = new ByteBuffer[this.values.length];
+      for (int i = 0; i < this.values.length; i++) {
+        byte[] value = this.values[i];
+        buffers[i] = (value == null) ? null : ByteBuffer.wrap(value);
+      }
+      return new DefaultUdtValue(this.type, buffers);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java b/types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java
new file mode 100644
index 00000000000..b90fff71ddf
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.core.data;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.data.AccessibleByName;
+import com.datastax.oss.driver.api.core.data.GettableById;
+import com.datastax.oss.driver.api.core.data.GettableByName;
+import com.datastax.oss.driver.internal.core.util.Strings;
+import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Indexes an ordered list of identifiers.
+ *
+ * @see GettableByName
+ * @see GettableById
+ */
+public class IdentifierIndex {
+
+  private final Map byId;
+  private final Map byCaseSensitiveName;
+  private final Map byCaseInsensitiveName;
+
+  public IdentifierIndex(List ids) {
+    this.byId = Maps.newHashMapWithExpectedSize(ids.size());
+    this.byCaseSensitiveName = Maps.newHashMapWithExpectedSize(ids.size());
+    this.byCaseInsensitiveName = Maps.newHashMapWithExpectedSize(ids.size());
+
+    int i = 0;
+    for (CqlIdentifier id : ids) {
+      byId.putIfAbsent(id, i);
+      byCaseSensitiveName.putIfAbsent(id.asInternal(), i);
+      byCaseInsensitiveName.putIfAbsent(id.asInternal().toLowerCase(), i);
+      i += 1;
+    }
+  }
+
+  /**
+   * Returns the first occurrence of a given name, given the matching rules described in {@link
+   * AccessibleByName}, or -1 if it's not in the list.
+   */
+  public int firstIndexOf(String name) {
+    Integer index =
+        (Strings.isDoubleQuoted(name))
+            ? byCaseSensitiveName.get(Strings.unDoubleQuote(name))
+            : byCaseInsensitiveName.get(name.toLowerCase());
+    return (index == null) ? -1 : index;
+  }
+
+  /** Returns the first occurrence of a given identifier, or -1 if it's not in the list. */
+  public int firstIndexOf(CqlIdentifier id) {
+    Integer index = byId.get(id);
+    return (index == null) ? -1 : index;
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java
index e62fda88575..5df28f3a7e7 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java
@@ -235,6 +235,24 @@ private static boolean isReservedCqlKeyword(String id) {
     return id != null && RESERVED_KEYWORDS.contains(id.toLowerCase());
   }
 
+  /**
+   * Check whether the given string corresponds to a valid CQL long literal. Long literals are
+   * composed solely by digits, but can have an optional leading minus sign.
+   *
+   * @param str The string to inspect.
+   * @return {@code true} if the given string corresponds to a valid CQL integer literal, {@code
+   *     false} otherwise.
+   */
+  public static boolean isLongLiteral(String str) {
+    if (str == null || str.isEmpty()) return false;
+    char[] chars = str.toCharArray();
+    for (int i = 0; i < chars.length; i++) {
+      char c = chars[i];
+      if ((c < '0' && (i != 0 || c != '-')) || c > '9') return false;
+    }
+    return true;
+  }
+
   private Strings() {}
 
   private static final Set RESERVED_KEYWORDS =
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java
index 081b681b24c..7e47ae8bbfc 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java
@@ -15,6 +15,7 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.CustomType;
 import com.google.common.base.Preconditions;
 import java.io.IOException;
@@ -37,6 +38,16 @@ public String getClassName() {
     return className;
   }
 
+  @Override
+  public boolean isDetached() {
+    return false;
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    // nothing to do
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java
index b6aacde5508..8fa6ad81440 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java
@@ -15,6 +15,7 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.ListType;
 import com.google.common.base.Preconditions;
@@ -46,6 +47,16 @@ public boolean isFrozen() {
     return frozen;
   }
 
+  @Override
+  public boolean isDetached() {
+    return elementType.isDetached();
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    elementType.attach(attachmentPoint);
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java
index caec0a5638f..ecbc2c672e0 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java
@@ -15,6 +15,7 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.MapType;
 import com.google.common.base.Preconditions;
@@ -56,6 +57,17 @@ public boolean isFrozen() {
     return frozen;
   }
 
+  @Override
+  public boolean isDetached() {
+    return keyType.isDetached() && valueType.isDetached();
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    keyType.attach(attachmentPoint);
+    valueType.attach(attachmentPoint);
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java
index 360b80f01f0..55e8df6bba2 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java
@@ -15,6 +15,7 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.SetType;
 import com.google.common.base.Preconditions;
@@ -46,6 +47,16 @@ public boolean isFrozen() {
     return frozen;
   }
 
+  @Override
+  public boolean isDetached() {
+    return elementType.isDetached();
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    elementType.attach(attachmentPoint);
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java
index 7e03ec4e7b5..7ad587cbe0a 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java
@@ -15,8 +15,11 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.data.TupleValue;
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.TupleType;
+import com.datastax.oss.driver.internal.core.data.DefaultTupleValue;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -31,9 +34,16 @@ public class DefaultTupleType implements TupleType {
   /** @serial */
   private final ImmutableList componentTypes;
 
-  public DefaultTupleType(List componentTypes) {
+  private transient volatile AttachmentPoint attachmentPoint;
+
+  public DefaultTupleType(List componentTypes, AttachmentPoint attachmentPoint) {
     Preconditions.checkNotNull(componentTypes);
     this.componentTypes = ImmutableList.copyOf(componentTypes);
+    this.attachmentPoint = attachmentPoint;
+  }
+
+  public DefaultTupleType(List componentTypes) {
+    this(componentTypes, AttachmentPoint.NONE);
   }
 
   @Override
@@ -41,6 +51,29 @@ public List getComponentTypes() {
     return componentTypes;
   }
 
+  @Override
+  public TupleValue newValue() {
+    return new DefaultTupleValue(this);
+  }
+
+  @Override
+  public boolean isDetached() {
+    return attachmentPoint != AttachmentPoint.NONE;
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    this.attachmentPoint = attachmentPoint;
+    for (DataType componentType : componentTypes) {
+      componentType.attach(attachmentPoint);
+    }
+  }
+
+  @Override
+  public AttachmentPoint getAttachmentPoint() {
+    return attachmentPoint;
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
@@ -68,5 +101,6 @@ public String toString() {
   private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
     in.defaultReadObject();
     Preconditions.checkNotNull(componentTypes);
+    this.attachmentPoint = AttachmentPoint.NONE;
   }
 }
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java
index 211eddc2983..dedb9f20d9f 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java
@@ -16,13 +16,17 @@
 package com.datastax.oss.driver.internal.type;
 
 import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.data.UdtValue;
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.UserDefinedType;
+import com.datastax.oss.driver.internal.core.data.DefaultUdtValue;
+import com.datastax.oss.driver.internal.core.data.IdentifierIndex;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.io.ObjectInputStream;
-import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 
 public class DefaultUserDefinedType implements UserDefinedType {
@@ -34,16 +38,40 @@ public class DefaultUserDefinedType implements UserDefinedType {
   /** @serial */
   private final CqlIdentifier name;
   /** @serial */
-  private final ImmutableMap fieldTypes;
+  private final List fieldNames;
+  /** @serial */
+  private final List fieldTypes;
+
+  private transient IdentifierIndex index;
+  private transient volatile AttachmentPoint attachmentPoint;
 
   public DefaultUserDefinedType(
-      CqlIdentifier keyspace, CqlIdentifier name, Map fieldTypes) {
+      CqlIdentifier keyspace,
+      CqlIdentifier name,
+      List fieldNames,
+      List fieldTypes,
+      AttachmentPoint attachmentPoint) {
     Preconditions.checkNotNull(keyspace);
     Preconditions.checkNotNull(name);
-    Preconditions.checkNotNull(fieldTypes);
+    Preconditions.checkArgument(
+        fieldNames != null && fieldNames.size() > 0, "Field names list can't be null or empty");
+    Preconditions.checkArgument(
+        fieldTypes != null && fieldTypes.size() == fieldNames.size(),
+        "There should be the same number of field names and types");
     this.keyspace = keyspace;
     this.name = name;
-    this.fieldTypes = ImmutableMap.copyOf(fieldTypes);
+    this.fieldNames = ImmutableList.copyOf(fieldNames);
+    this.fieldTypes = ImmutableList.copyOf(fieldTypes);
+    this.index = new IdentifierIndex(this.fieldNames);
+    this.attachmentPoint = attachmentPoint;
+  }
+
+  public DefaultUserDefinedType(
+      CqlIdentifier keyspace,
+      CqlIdentifier name,
+      List fieldNames,
+      List fieldTypes) {
+    this(keyspace, name, fieldNames, fieldTypes, AttachmentPoint.NONE);
   }
 
   @Override
@@ -57,10 +85,48 @@ public CqlIdentifier getName() {
   }
 
   @Override
-  public Map getFieldTypes() {
+  public List getFieldNames() {
+    return fieldNames;
+  }
+
+  @Override
+  public int firstIndexOf(CqlIdentifier id) {
+    return index.firstIndexOf(id);
+  }
+
+  @Override
+  public int firstIndexOf(String name) {
+    return index.firstIndexOf(name);
+  }
+
+  @Override
+  public List getFieldTypes() {
     return fieldTypes;
   }
 
+  @Override
+  public UdtValue newValue() {
+    return new DefaultUdtValue(this);
+  }
+
+  @Override
+  public boolean isDetached() {
+    return attachmentPoint == null;
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    this.attachmentPoint = attachmentPoint;
+    for (DataType fieldType : fieldTypes) {
+      fieldType.attach(attachmentPoint);
+    }
+  }
+
+  @Override
+  public AttachmentPoint getAttachmentPoint() {
+    return attachmentPoint;
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
@@ -69,6 +135,7 @@ public boolean equals(Object other) {
       UserDefinedType that = (UserDefinedType) other;
       return this.keyspace.equals(that.getKeyspace())
           && this.name.equals(that.getName())
+          && this.fieldNames.equals(that.getFieldNames())
           && this.fieldTypes.equals(that.getFieldTypes());
     } else {
       return false;
@@ -77,7 +144,7 @@ public boolean equals(Object other) {
 
   @Override
   public int hashCode() {
-    return Objects.hash(keyspace, name, fieldTypes);
+    return Objects.hash(keyspace, name, fieldNames, fieldTypes);
   }
 
   @Override
@@ -89,6 +156,11 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
     in.defaultReadObject();
     Preconditions.checkNotNull(keyspace);
     Preconditions.checkNotNull(name);
-    Preconditions.checkNotNull(fieldTypes);
+    Preconditions.checkArgument(
+        fieldNames != null && fieldNames.size() > 0, "Field names list can't be null or empty");
+    Preconditions.checkArgument(
+        fieldTypes != null && fieldTypes.size() == fieldNames.size(),
+        "There should be the same number of field names and types");
+    this.index = new IdentifierIndex(this.fieldNames);
   }
 }
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java
index f44b10bc584..48a1517f531 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java
@@ -15,17 +15,35 @@
  */
 package com.datastax.oss.driver.internal.type;
 
+import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.protocol.internal.ProtocolConstants;
 
 public class PrimitiveType implements DataType {
 
-  public final int protocolCode;
+  private final int protocolCode;
+
+  private transient volatile boolean attached;
 
   public PrimitiveType(int protocolCode) {
     this.protocolCode = protocolCode;
   }
 
+  @Override
+  public int getProtocolCode() {
+    return protocolCode;
+  }
+
+  @Override
+  public boolean isDetached() {
+    return false;
+  }
+
+  @Override
+  public void attach(AttachmentPoint attachmentPoint) {
+    // nothing to do
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == this) {
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java
index c692ee0bb47..5c5725a1261 100644
--- a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java
@@ -18,7 +18,7 @@
 import com.datastax.oss.driver.api.core.CqlIdentifier;
 import com.datastax.oss.driver.api.type.DataType;
 import com.datastax.oss.driver.api.type.UserDefinedType;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableList;
 
 /**
  * Helper class to build {@link UserDefinedType} instances.
@@ -32,24 +32,28 @@ public class UserDefinedTypeBuilder {
 
   private final CqlIdentifier keyspaceName;
   private final CqlIdentifier typeName;
-  private final ImmutableMap.Builder fieldTypesBuilder;
+  private final ImmutableList.Builder fieldNames;
+  private final ImmutableList.Builder fieldTypes;
 
   public UserDefinedTypeBuilder(CqlIdentifier keyspaceName, CqlIdentifier typeName) {
     this.keyspaceName = keyspaceName;
     this.typeName = typeName;
-    this.fieldTypesBuilder = ImmutableMap.builder();
+    this.fieldNames = ImmutableList.builder();
+    this.fieldTypes = ImmutableList.builder();
   }
 
   /**
    * Adds a new field. The fields in the resulting type will be in the order of the calls to this
    * method.
    */
-  public UserDefinedTypeBuilder withField(CqlIdentifier name, DataType dataType) {
-    fieldTypesBuilder.put(name, dataType);
+  public UserDefinedTypeBuilder withField(CqlIdentifier name, DataType type) {
+    fieldNames.add(name);
+    fieldTypes.add(type);
     return this;
   }
 
   public UserDefinedType build() {
-    return new DefaultUserDefinedType(keyspaceName, typeName, fieldTypesBuilder.build());
+    return new DefaultUserDefinedType(
+        keyspaceName, typeName, fieldNames.build(), fieldTypes.build());
   }
 }
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java
new file mode 100644
index 00000000000..e9d1079806a
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class BigIntCodec implements PrimitiveLongCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.LONG;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.BIGINT;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(long value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(8);
+    bytes.putLong(0, value);
+    return bytes;
+  }
+
+  @Override
+  public long decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 8) {
+      throw new IllegalArgumentException(
+          "Invalid 64-bits long value, expecting 8 bytes but got " + bytes.remaining());
+    } else {
+      return bytes.getLong(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Long value) {
+    return (value == null) ? "NULL" : Long.toString(value);
+  }
+
+  @Override
+  public Long parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Long.parseLong(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 64-bits long value from \"%s\"", value));
+    }
+  }
+}
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java
similarity index 56%
rename from core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java
rename to types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java
index 0e95123d101..7bd26b72c41 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/BlobCodec.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java
@@ -13,12 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.datastax.oss.driver.internal.core.adminrequest.codec;
+package com.datastax.oss.driver.internal.type.codec;
 
 import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.protocol.internal.util.Bytes;
 import java.nio.ByteBuffer;
 
 public class BlobCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BYTE_BUFFER;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.BLOB;
+  }
+
   @Override
   public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) {
     return (value == null) ? null : value.duplicate();
@@ -28,4 +43,16 @@ public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) {
   public ByteBuffer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
     return (bytes == null) ? null : bytes.duplicate();
   }
+
+  @Override
+  public String format(ByteBuffer value) {
+    return (value == null) ? "NULL" : Bytes.toHexString(value);
+  }
+
+  @Override
+  public ByteBuffer parse(String value) {
+    return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+        ? null
+        : Bytes.fromHexString(value);
+  }
 }
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java
new file mode 100644
index 00000000000..6dc48a16942
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class BooleanCodec implements PrimitiveBooleanCodec {
+
+  private static final ByteBuffer TRUE = ByteBuffer.wrap(new byte[] {1});
+  private static final ByteBuffer FALSE = ByteBuffer.wrap(new byte[] {0});
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BOOLEAN;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.BOOLEAN;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(boolean value, ProtocolVersion protocolVersion) {
+    return value ? TRUE.duplicate() : FALSE.duplicate();
+  }
+
+  @Override
+  public boolean decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return false;
+    } else if (bytes.remaining() != 1) {
+      throw new IllegalArgumentException(
+          "Invalid boolean value, expecting 1 byte but got " + bytes.remaining());
+    } else {
+      return bytes.get(bytes.position()) != 0;
+    }
+  }
+
+  @Override
+  public String format(Boolean value) {
+    if (value == null) {
+      return "NULL";
+    } else {
+      return value ? "true" : "false";
+    }
+  }
+
+  @Override
+  public Boolean parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    } else if (value.equalsIgnoreCase(Boolean.FALSE.toString())) {
+      return false;
+    } else if (value.equalsIgnoreCase(Boolean.TRUE.toString())) {
+      return true;
+    } else {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse boolean value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java
new file mode 100644
index 00000000000..671eaa81c99
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+
+public class CounterCodec extends BigIntCodec {
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.COUNTER;
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java
new file mode 100644
index 00000000000..156ce1c912f
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.data.CqlDuration;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.type.util.VIntCoding;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class CqlDurationCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.CQL_DURATION;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.DURATION;
+  }
+
+  @Override
+  public ByteBuffer encode(CqlDuration value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    long months = value.getMonths();
+    long days = value.getDays();
+    long nanoseconds = value.getNanoseconds();
+    int size =
+        VIntCoding.computeVIntSize(months)
+            + VIntCoding.computeVIntSize(days)
+            + VIntCoding.computeVIntSize(nanoseconds);
+    ByteArrayDataOutput out = ByteStreams.newDataOutput(size);
+    try {
+      VIntCoding.writeVInt(months, out);
+      VIntCoding.writeVInt(days, out);
+      VIntCoding.writeVInt(nanoseconds, out);
+    } catch (IOException e) {
+      // cannot happen
+      throw new AssertionError();
+    }
+    return ByteBuffer.wrap(out.toByteArray());
+  }
+
+  @Override
+  public CqlDuration decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    } else {
+      DataInput in = ByteStreams.newDataInput(Bytes.getArray(bytes));
+      try {
+        int months = (int) VIntCoding.readVInt(in);
+        int days = (int) VIntCoding.readVInt(in);
+        long nanoseconds = VIntCoding.readVInt(in);
+        return CqlDuration.newInstance(months, days, nanoseconds);
+      } catch (IOException e) {
+        // cannot happen
+        throw new AssertionError();
+      }
+    }
+  }
+
+  @Override
+  public String format(CqlDuration value) {
+    return (value == null) ? "NULL" : value.toString();
+  }
+
+  @Override
+  public CqlDuration parse(String value) {
+    return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+        ? null
+        : CqlDuration.from(value);
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java
new file mode 100644
index 00000000000..3a1ebb0c6a7
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.CustomType;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import java.nio.ByteBuffer;
+
+public class CustomCodec implements TypeCodec {
+
+  private final CustomType cqlType;
+
+  public CustomCodec(CustomType cqlType) {
+    this.cqlType = cqlType;
+  }
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BYTE_BUFFER;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) {
+    return (value == null) ? null : value.duplicate();
+  }
+
+  @Override
+  public ByteBuffer decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    return (bytes == null) ? null : bytes.duplicate();
+  }
+
+  @Override
+  public String format(ByteBuffer value) {
+    return (value == null) ? "NULL" : Bytes.toHexString(value);
+  }
+
+  @Override
+  public ByteBuffer parse(String value) {
+    return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+        ? null
+        : Bytes.fromHexString(value);
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java
new file mode 100644
index 00000000000..ba4457b1bca
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.TypeCodecs;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.core.util.Strings;
+import java.nio.ByteBuffer;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+import static java.lang.Long.parseLong;
+
+public class DateCodec implements TypeCodec {
+
+  private static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.LOCAL_DATE;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.DATE;
+  }
+
+  @Override
+  public ByteBuffer encode(LocalDate value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    long days = ChronoUnit.DAYS.between(EPOCH, value);
+    int unsigned = signedToUnsigned((int) days);
+    return TypeCodecs.INT.encodePrimitive(unsigned, protocolVersion);
+  }
+
+  @Override
+  public LocalDate decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    }
+    int unsigned = TypeCodecs.INT.decodePrimitive(bytes, protocolVersion);
+    int signed = unsignedToSigned(unsigned);
+    return EPOCH.plusDays(signed);
+  }
+
+  @Override
+  public String format(LocalDate value) {
+    return (value == null) ? "NULL" : Strings.quote(DateTimeFormatter.ISO_LOCAL_DATE.format(value));
+  }
+
+  @Override
+  public LocalDate parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    // single quotes are optional for long literals, mandatory for date patterns
+    // strip enclosing single quotes, if any
+    if (Strings.isQuoted(value)) {
+      value = Strings.unquote(value);
+    }
+
+    if (Strings.isLongLiteral(value)) {
+      long raw;
+      try {
+        raw = parseLong(value);
+      } catch (NumberFormatException e) {
+        throw new IllegalArgumentException(
+            String.format("Cannot parse date value from \"%s\"", value));
+      }
+      int days;
+      try {
+        days = cqlDateToDaysSinceEpoch(raw);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format("Cannot parse date value from \"%s\"", value));
+      }
+      return EPOCH.plusDays(days);
+    }
+
+    try {
+      return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE);
+    } catch (RuntimeException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse date value from \"%s\"", value));
+    }
+  }
+
+  private static int signedToUnsigned(int signed) {
+    return signed - Integer.MIN_VALUE;
+  }
+
+  private static int unsignedToSigned(int unsigned) {
+    return unsigned + Integer.MIN_VALUE; // this relies on overflow for "negative" values
+  }
+
+  /**
+   * Converts a raw CQL long representing a numeric DATE literal to the number of days since the
+   * Epoch. In CQL, numeric DATE literals are longs (unsigned integers actually) between 0 and 2^32
+   * - 1, with the epoch in the middle; this method re-centers the epoch at 0.
+   */
+  private static int cqlDateToDaysSinceEpoch(long raw) {
+    if (raw < 0 || raw > MAX_CQL_LONG_VALUE)
+      throw new IllegalArgumentException(
+          String.format(
+              "Numeric literals for DATE must be between 0 and %d (got %d)",
+              MAX_CQL_LONG_VALUE, raw));
+    return (int) (raw - EPOCH_AS_CQL_LONG);
+  }
+
+  private static final long MAX_CQL_LONG_VALUE = ((1L << 32) - 1);
+  private static final long EPOCH_AS_CQL_LONG = (1L << 31);
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java
new file mode 100644
index 00000000000..aab150a790a
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+public class DecimalCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BIG_DECIMAL;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.DECIMAL;
+  }
+
+  @Override
+  public ByteBuffer encode(BigDecimal value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    BigInteger bi = value.unscaledValue();
+    int scale = value.scale();
+    byte[] bibytes = bi.toByteArray();
+
+    ByteBuffer bytes = ByteBuffer.allocate(4 + bibytes.length);
+    bytes.putInt(scale);
+    bytes.put(bibytes);
+    bytes.rewind();
+    return bytes;
+  }
+
+  @Override
+  public BigDecimal decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    } else if (bytes.remaining() < 4) {
+      throw new IllegalArgumentException(
+          "Invalid decimal value, expecting at least 4 bytes but got " + bytes.remaining());
+    }
+
+    bytes = bytes.duplicate();
+    int scale = bytes.getInt();
+    byte[] bibytes = new byte[bytes.remaining()];
+    bytes.get(bibytes);
+
+    BigInteger bi = new BigInteger(bibytes);
+    return new BigDecimal(bi, scale);
+  }
+
+  @Override
+  public String format(BigDecimal value) {
+    return (value == null) ? "NULL" : value.toString();
+  }
+
+  @Override
+  public BigDecimal parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : new BigDecimal(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse decimal value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java
new file mode 100644
index 00000000000..1555e351cf0
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class DoubleCodec implements PrimitiveDoubleCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.DOUBLE;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.DOUBLE;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(double value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(8);
+    bytes.putDouble(0, value);
+    return bytes;
+  }
+
+  @Override
+  public double decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 8) {
+      throw new IllegalArgumentException(
+          "Invalid 64-bits double value, expecting 8 bytes but got " + bytes.remaining());
+    } else {
+      return bytes.getDouble(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Double value) {
+    return (value == null) ? "NULL" : Double.toString(value);
+  }
+
+  @Override
+  public Double parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Double.parseDouble(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 64-bits double value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java
new file mode 100644
index 00000000000..57276955fa4
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class FloatCodec implements PrimitiveFloatCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.FLOAT;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.FLOAT;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(float value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(4);
+    bytes.putFloat(0, value);
+    return bytes;
+  }
+
+  @Override
+  public float decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 4) {
+      throw new IllegalArgumentException(
+          "Invalid 32-bits float value, expecting 4 bytes but got " + bytes.remaining());
+    } else {
+      return bytes.getFloat(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Float value) {
+    return (value == null) ? "NULL" : Float.toString(value);
+  }
+
+  @Override
+  public Float parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Float.parseFloat(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 32-bits float value from \"%s\"", value));
+    }
+  }
+}
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java
similarity index 51%
rename from core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java
rename to types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java
index 219ad6b14c2..dd953996e84 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/codec/InetCodec.java
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java
@@ -13,15 +13,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.datastax.oss.driver.internal.core.adminrequest.codec;
+package com.datastax.oss.driver.internal.type.codec;
 
 import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.core.util.Strings;
 import com.datastax.oss.protocol.internal.util.Bytes;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 
 public class InetCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.INET_ADDRESS;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.INET;
+  }
 
   @Override
   public ByteBuffer encode(InetAddress value, ProtocolVersion protocolVersion) {
@@ -30,7 +44,9 @@ public ByteBuffer encode(InetAddress value, ProtocolVersion protocolVersion) {
 
   @Override
   public InetAddress decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
-    if (bytes == null || bytes.remaining() == 0) return null;
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    }
     try {
       return InetAddress.getByAddress(Bytes.getArray(bytes));
     } catch (UnknownHostException e) {
@@ -38,4 +54,28 @@ public InetAddress decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
           "Invalid bytes for inet value, got " + bytes.remaining() + " bytes");
     }
   }
+
+  @Override
+  public String format(InetAddress value) {
+    return (value == null) ? "NULL" : ("'" + value.getHostAddress() + "'");
+  }
+
+  @Override
+  public InetAddress parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    value = value.trim();
+    if (!Strings.isQuoted(value)) {
+      throw new IllegalArgumentException(
+          String.format("inet values must be enclosed in single quotes (\"%s\")", value));
+    }
+    try {
+      return InetAddress.getByName(value.substring(1, value.length() - 1));
+    } catch (Exception e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse inet value from \"%s\"", value));
+    }
+  }
 }
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java
new file mode 100644
index 00000000000..50790614c33
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class IntCodec implements PrimitiveIntCodec {
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.INTEGER;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.INT;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(int value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(4);
+    bytes.putInt(0, value);
+    return bytes;
+  }
+
+  @Override
+  public int decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 4) {
+      throw new IllegalArgumentException(
+          "Invalid 32-bits integer value, expecting 4 bytes but got " + bytes.remaining());
+    } else {
+      return bytes.getInt(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Integer value) {
+    return (value == null) ? "NULL" : Integer.toString(value);
+  }
+
+  @Override
+  public Integer parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 32-bits int value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java
new file mode 100644
index 00000000000..a193f8a7067
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.ListType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.google.common.base.Preconditions;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListCodec implements TypeCodec> {
+
+  private final DataType cqlType;
+  private final GenericType> javaType;
+  private final TypeCodec elementCodec;
+
+  public ListCodec(DataType cqlType, TypeCodec elementCodec) {
+    this.cqlType = cqlType;
+    this.javaType = GenericType.listOf(elementCodec.getJavaType());
+    this.elementCodec = elementCodec;
+    Preconditions.checkArgument(cqlType instanceof ListType);
+  }
+
+  @Override
+  public GenericType> getJavaType() {
+    return javaType;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    if (List.class.isAssignableFrom(value.getClass())) {
+      // runtime type ok, now check element type
+      List list = (List) value;
+      return list.isEmpty() || elementCodec.canEncode(list.get(0));
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public ByteBuffer encode(List value, ProtocolVersion protocolVersion) {
+    // An int indicating the number of elements in the list, followed by the elements. Each element
+    // is a byte array representing the serialized value, preceded by an int indicating its size.
+    if (value == null) {
+      return null;
+    } else {
+      int i = 0;
+      ByteBuffer[] encodedElements = new ByteBuffer[value.size()];
+      int toAllocate = 4; // initialize with number of elements
+      for (T element : value) {
+        if (element == null) {
+          throw new NullPointerException("Collection elements cannot be null");
+        }
+        ByteBuffer encodedElement;
+        try {
+          encodedElement = elementCodec.encode(element, protocolVersion);
+        } catch (ClassCastException e) {
+          throw new IllegalArgumentException("Invalid type for element: " + element.getClass());
+        }
+        encodedElements[i++] = encodedElement;
+        toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size
+      }
+      ByteBuffer result = ByteBuffer.allocate(toAllocate);
+      result.putInt(value.size());
+      for (ByteBuffer encodedElement : encodedElements) {
+        result.putInt(encodedElement.remaining());
+        result.put(encodedElement);
+      }
+      result.flip();
+      return result;
+    }
+  }
+
+  @Override
+  public List decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return new ArrayList<>(0);
+    } else {
+      ByteBuffer input = bytes.duplicate();
+      int size = input.getInt();
+      List result = new ArrayList<>(size);
+      for (int i = 0; i < size; i++) {
+        int elementSize = input.getInt();
+        ByteBuffer encodedElement = input.slice();
+        encodedElement.limit(elementSize);
+        input.position(input.position() + elementSize);
+        result.add(elementCodec.decode(encodedElement, protocolVersion));
+      }
+      return result;
+    }
+  }
+
+  @Override
+  public String format(List value) {
+    if (value == null) {
+      return "NULL";
+    }
+    StringBuilder sb = new StringBuilder("[");
+    boolean first = true;
+    for (T t : value) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      sb.append(elementCodec.format(t));
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  public List parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) return null;
+
+    int idx = ParseUtils.skipSpaces(value, 0);
+    if (value.charAt(idx++) != '[')
+      throw new IllegalArgumentException(
+          String.format(
+              "Cannot parse list value from \"%s\", at character %d expecting '[' but got '%c'",
+              value, idx, value.charAt(idx)));
+
+    idx = ParseUtils.skipSpaces(value, idx);
+
+    if (value.charAt(idx) == ']') {
+      return new ArrayList<>(0);
+    }
+
+    List list = new ArrayList<>();
+    while (idx < value.length()) {
+      int n;
+      try {
+        n = ParseUtils.skipCQLValue(value, idx);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse list value from \"%s\", invalid CQL value at character %d",
+                value, idx),
+            e);
+      }
+
+      list.add(elementCodec.parse(value.substring(idx, n)));
+      idx = n;
+
+      idx = ParseUtils.skipSpaces(value, idx);
+      if (value.charAt(idx) == ']') return list;
+      if (value.charAt(idx++) != ',')
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse list value from \"%s\", at character %d expecting ',' but got '%c'",
+                value, idx, value.charAt(idx)));
+
+      idx = ParseUtils.skipSpaces(value, idx);
+    }
+    throw new IllegalArgumentException(
+        String.format("Malformed list value \"%s\", missing closing ']'", value));
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java
new file mode 100644
index 00000000000..f254aae0eee
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.google.common.collect.Maps;
+import java.nio.ByteBuffer;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MapCodec implements TypeCodec> {
+
+  private final DataType cqlType;
+  private final GenericType> javaType;
+  private final TypeCodec keyCodec;
+  private final TypeCodec valueCodec;
+
+  public MapCodec(DataType cqlType, TypeCodec keyCodec, TypeCodec valueCodec) {
+    this.cqlType = cqlType;
+    this.keyCodec = keyCodec;
+    this.valueCodec = valueCodec;
+    this.javaType = GenericType.mapOf(keyCodec.getJavaType(), valueCodec.getJavaType());
+  }
+
+  @Override
+  public GenericType> getJavaType() {
+    return javaType;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    if (value instanceof Map) {
+      // runtime type ok, now check key and value types
+      Map map = (Map) value;
+      if (map.isEmpty()) {
+        return true;
+      }
+      Map.Entry entry = map.entrySet().iterator().next();
+      return keyCodec.canEncode(entry.getKey()) && valueCodec.canEncode(entry.getValue());
+    }
+    return false;
+  }
+
+  @Override
+  public ByteBuffer encode(Map value, ProtocolVersion protocolVersion) {
+    // An int indicating the number of key/value pairs in the map, followed by the pairs. Each pair
+    // is a byte array representing the serialized key, preceded by an int indicating its size,
+    // followed by the value in the same format.
+    if (value == null) {
+      return null;
+    } else {
+      int i = 0;
+      ByteBuffer[] encodedElements = new ByteBuffer[value.size() * 2];
+      int toAllocate = 4; // initialize with number of elements
+      for (Map.Entry entry : value.entrySet()) {
+        if (entry.getValue() == null) {
+          throw new NullPointerException("Collection elements cannot be null");
+        }
+        ByteBuffer encodedKey;
+        try {
+          encodedKey = keyCodec.encode(entry.getKey(), protocolVersion);
+        } catch (ClassCastException e) {
+          throw new IllegalArgumentException("Invalid type for key: " + entry.getKey().getClass());
+        }
+        encodedElements[i++] = encodedKey;
+        toAllocate += 4 + encodedKey.remaining(); // the key preceded by its size
+        ByteBuffer encodedValue;
+        try {
+          encodedValue = valueCodec.encode(entry.getValue(), protocolVersion);
+        } catch (ClassCastException e) {
+          throw new IllegalArgumentException(
+              "Invalid type for value: " + entry.getValue().getClass());
+        }
+        encodedElements[i++] = encodedValue;
+        toAllocate += 4 + encodedValue.remaining(); // the value preceded by its size
+      }
+      ByteBuffer result = ByteBuffer.allocate(toAllocate);
+      result.putInt(value.size());
+      for (ByteBuffer encodedElement : encodedElements) {
+        result.putInt(encodedElement.remaining());
+        result.put(encodedElement);
+      }
+      result.flip();
+      return result;
+    }
+  }
+
+  @Override
+  public Map decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return new LinkedHashMap<>(0);
+    } else {
+      ByteBuffer input = bytes.duplicate();
+      int size = input.getInt();
+      Map result = Maps.newLinkedHashMapWithExpectedSize(size);
+      for (int i = 0; i < size; i++) {
+        int keySize = input.getInt();
+        ByteBuffer encodedKey = input.slice();
+        encodedKey.limit(keySize);
+        input.position(input.position() + keySize);
+        K key = keyCodec.decode(encodedKey, protocolVersion);
+
+        int valueSize = input.getInt();
+        ByteBuffer encodedValue = input.slice();
+        encodedValue.limit(valueSize);
+        input.position(input.position() + valueSize);
+        V value = valueCodec.decode(encodedValue, protocolVersion);
+
+        result.put(key, value);
+      }
+      return result;
+    }
+  }
+
+  @Override
+  public String format(Map value) {
+    if (value == null) {
+      return "NULL";
+    }
+    StringBuilder sb = new StringBuilder();
+    sb.append("{");
+    boolean first = true;
+    for (Map.Entry e : value.entrySet()) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      sb.append(keyCodec.format(e.getKey()));
+      sb.append(":");
+      sb.append(valueCodec.format(e.getValue()));
+    }
+    sb.append("}");
+    return sb.toString();
+  }
+
+  @Override
+  public Map parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    int idx = ParseUtils.skipSpaces(value, 0);
+    if (value.charAt(idx++) != '{') {
+      throw new IllegalArgumentException(
+          String.format(
+              "cannot parse map value from \"%s\", at character %d expecting '{' but got '%c'",
+              value, idx, value.charAt(idx)));
+    }
+
+    idx = ParseUtils.skipSpaces(value, idx);
+
+    if (value.charAt(idx) == '}') {
+      return new LinkedHashMap<>(0);
+    }
+
+    Map map = new LinkedHashMap<>();
+    while (idx < value.length()) {
+      int n;
+      try {
+        n = ParseUtils.skipCQLValue(value, idx);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse map value from \"%s\", invalid CQL value at character %d",
+                value, idx),
+            e);
+      }
+
+      K k = keyCodec.parse(value.substring(idx, n));
+      idx = n;
+
+      idx = ParseUtils.skipSpaces(value, idx);
+      if (value.charAt(idx++) != ':') {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse map value from \"%s\", at character %d expecting ':' but got '%c'",
+                value, idx, value.charAt(idx)));
+      }
+      idx = ParseUtils.skipSpaces(value, idx);
+
+      try {
+        n = ParseUtils.skipCQLValue(value, idx);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse map value from \"%s\", invalid CQL value at character %d",
+                value, idx),
+            e);
+      }
+
+      V v = valueCodec.parse(value.substring(idx, n));
+      idx = n;
+
+      map.put(k, v);
+
+      idx = ParseUtils.skipSpaces(value, idx);
+      if (value.charAt(idx) == '}') {
+        return map;
+      }
+      if (value.charAt(idx++) != ',') {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse map value from \"%s\", at character %d expecting ',' but got '%c'",
+                value, idx, value.charAt(idx)));
+      }
+
+      idx = ParseUtils.skipSpaces(value, idx);
+    }
+    throw new IllegalArgumentException(
+        String.format("Malformed map value \"%s\", missing closing '}'", value));
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java
new file mode 100644
index 00000000000..ed0a70b64d1
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+public class ParseUtils {
+
+  /**
+   * Returns the index of the first character in toParse from idx that is not a "space".
+   *
+   * @param toParse the string to skip space on.
+   * @param idx the index to start skipping space from.
+   * @return the index of the first character in toParse from idx that is not a "space.
+   */
+  public static int skipSpaces(String toParse, int idx) {
+    while (isBlank(toParse.charAt(idx)) && idx < toParse.length()) ++idx;
+    return idx;
+  }
+
+  /**
+   * Assuming that idx points to the beginning of a CQL value in toParse, returns the index of the
+   * first character after this value.
+   *
+   * @param toParse the string to skip a value form.
+   * @param idx the index to start parsing a value from.
+   * @return the index ending the CQL value starting at {@code idx}.
+   * @throws IllegalArgumentException if idx doesn't point to the start of a valid CQL value.
+   */
+  public static int skipCQLValue(String toParse, int idx) {
+    if (idx >= toParse.length()) throw new IllegalArgumentException();
+
+    if (isBlank(toParse.charAt(idx))) throw new IllegalArgumentException();
+
+    int cbrackets = 0;
+    int sbrackets = 0;
+    int parens = 0;
+    boolean inString = false;
+
+    do {
+      char c = toParse.charAt(idx);
+      if (inString) {
+        if (c == '\'') {
+          if (idx + 1 < toParse.length() && toParse.charAt(idx + 1) == '\'') {
+            ++idx; // this is an escaped quote, skip it
+          } else {
+            inString = false;
+            if (cbrackets == 0 && sbrackets == 0 && parens == 0) return idx + 1;
+          }
+        }
+        // Skip any other character
+      } else if (c == '\'') {
+        inString = true;
+      } else if (c == '{') {
+        ++cbrackets;
+      } else if (c == '[') {
+        ++sbrackets;
+      } else if (c == '(') {
+        ++parens;
+      } else if (c == '}') {
+        if (cbrackets == 0) return idx;
+
+        --cbrackets;
+        if (cbrackets == 0 && sbrackets == 0 && parens == 0) return idx + 1;
+      } else if (c == ']') {
+        if (sbrackets == 0) return idx;
+
+        --sbrackets;
+        if (cbrackets == 0 && sbrackets == 0 && parens == 0) return idx + 1;
+      } else if (c == ')') {
+        if (parens == 0) return idx;
+
+        --parens;
+        if (cbrackets == 0 && sbrackets == 0 && parens == 0) return idx + 1;
+      } else if (isBlank(c) || !isCqlIdentifierChar(c)) {
+        if (cbrackets == 0 && sbrackets == 0 && parens == 0) return idx;
+      }
+    } while (++idx < toParse.length());
+
+    if (inString || cbrackets != 0 || sbrackets != 0 || parens != 0)
+      throw new IllegalArgumentException();
+    return idx;
+  }
+
+  /**
+   * Assuming that idx points to the beginning of a CQL identifier in toParse, returns the index of
+   * the first character after this identifier.
+   *
+   * @param toParse the string to skip an identifier from.
+   * @param idx the index to start parsing an identifier from.
+   * @return the index ending the CQL identifier starting at {@code idx}.
+   * @throws IllegalArgumentException if idx doesn't point to the start of a valid CQL identifier.
+   */
+  public static int skipCQLId(String toParse, int idx) {
+    if (idx >= toParse.length()) throw new IllegalArgumentException();
+
+    char c = toParse.charAt(idx);
+    if (isCqlIdentifierChar(c)) {
+      while (idx < toParse.length() && isCqlIdentifierChar(toParse.charAt(idx))) idx++;
+      return idx;
+    }
+
+    if (c != '"') throw new IllegalArgumentException();
+
+    while (++idx < toParse.length()) {
+      c = toParse.charAt(idx);
+      if (c != '"') continue;
+
+      if (idx + 1 < toParse.length() && toParse.charAt(idx + 1) == '\"')
+        ++idx; // this is an escaped double quote, skip it
+      else return idx + 1;
+    }
+    throw new IllegalArgumentException();
+  }
+
+  private static boolean isBlank(int c) {
+    return c == ' ' || c == '\t' || c == '\n';
+  }
+
+  private static boolean isCqlIdentifierChar(int c) {
+    return (c >= '0' && c <= '9')
+        || (c >= 'a' && c <= 'z')
+        || (c >= 'A' && c <= 'Z')
+        || c == '-'
+        || c == '+'
+        || c == '.'
+        || c == '_'
+        || c == '&';
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java
new file mode 100644
index 00000000000..c34ab5683ed
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.SetType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import java.nio.ByteBuffer;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SetCodec implements TypeCodec> {
+
+  private final DataType cqlType;
+  private final GenericType> javaType;
+  private final TypeCodec elementCodec;
+
+  public SetCodec(DataType cqlType, TypeCodec elementCodec) {
+    this.cqlType = cqlType;
+    this.javaType = GenericType.setOf(elementCodec.getJavaType());
+    this.elementCodec = elementCodec;
+    Preconditions.checkArgument(cqlType instanceof SetType);
+  }
+
+  @Override
+  public GenericType> getJavaType() {
+    return javaType;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    if (Set.class.isAssignableFrom(value.getClass())) {
+      // runtime type ok, now check element type
+      Set set = (Set) value;
+      return set.isEmpty() || elementCodec.canEncode(set.iterator().next());
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public ByteBuffer encode(Set value, ProtocolVersion protocolVersion) {
+    // An int indicating the number of elements in the set, followed by the elements. Each element
+    // is a byte array representing the serialized value, preceded by an int indicating its size.
+    if (value == null) {
+      return null;
+    } else {
+      int i = 0;
+      ByteBuffer[] encodedElements = new ByteBuffer[value.size()];
+      int toAllocate = 4; // initialize with number of elements
+      for (T element : value) {
+        if (element == null) {
+          throw new NullPointerException("Collection elements cannot be null");
+        }
+        ByteBuffer encodedElement;
+        try {
+          encodedElement = elementCodec.encode(element, protocolVersion);
+        } catch (ClassCastException e) {
+          throw new IllegalArgumentException("Invalid type for element: " + element.getClass());
+        }
+        encodedElements[i++] = encodedElement;
+        toAllocate += 4 + encodedElement.remaining(); // the element preceded by its size
+      }
+      ByteBuffer result = ByteBuffer.allocate(toAllocate);
+      result.putInt(value.size());
+      for (ByteBuffer encodedElement : encodedElements) {
+        result.putInt(encodedElement.remaining());
+        result.put(encodedElement);
+      }
+      result.flip();
+      return result;
+    }
+  }
+
+  @Override
+  public Set decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return new LinkedHashSet<>(0);
+    } else {
+      ByteBuffer input = bytes.duplicate();
+      int size = input.getInt();
+      Set result = Sets.newLinkedHashSetWithExpectedSize(size);
+      for (int i = 0; i < size; i++) {
+        int elementSize = input.getInt();
+        ByteBuffer encodedElement = input.slice();
+        encodedElement.limit(elementSize);
+        input.position(input.position() + elementSize);
+        result.add(elementCodec.decode(encodedElement, protocolVersion));
+      }
+      return result;
+    }
+  }
+
+  @Override
+  public String format(Set value) {
+    if (value == null) {
+      return "NULL";
+    }
+    StringBuilder sb = new StringBuilder("{");
+    boolean first = true;
+    for (T t : value) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      sb.append(elementCodec.format(t));
+    }
+    sb.append("}");
+    return sb.toString();
+  }
+
+  @Override
+  public Set parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) return null;
+
+    int idx = ParseUtils.skipSpaces(value, 0);
+    if (value.charAt(idx++) != '{')
+      throw new IllegalArgumentException(
+          String.format(
+              "Cannot parse set value from \"%s\", at character %d expecting '{' but got '%c'",
+              value, idx, value.charAt(idx)));
+
+    idx = ParseUtils.skipSpaces(value, idx);
+
+    if (value.charAt(idx) == '}') {
+      return new LinkedHashSet<>(0);
+    }
+
+    Set set = new LinkedHashSet<>();
+    while (idx < value.length()) {
+      int n;
+      try {
+        n = ParseUtils.skipCQLValue(value, idx);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse set value from \"%s\", invalid CQL value at character %d",
+                value, idx),
+            e);
+      }
+
+      set.add(elementCodec.parse(value.substring(idx, n)));
+      idx = n;
+
+      idx = ParseUtils.skipSpaces(value, idx);
+      if (value.charAt(idx) == '}') return set;
+      if (value.charAt(idx++) != ',')
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse set value from \"%s\", at character %d expecting ',' but got '%c'",
+                value, idx, value.charAt(idx)));
+
+      idx = ParseUtils.skipSpaces(value, idx);
+    }
+    throw new IllegalArgumentException(
+        String.format("Malformed set value \"%s\", missing closing '}'", value));
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java
new file mode 100644
index 00000000000..fe504ab64ea
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class SmallIntCodec implements PrimitiveShortCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.SHORT;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.SMALLINT;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(short value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(2);
+    bytes.putShort(0, value);
+    return bytes;
+  }
+
+  @Override
+  public short decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 2) {
+      throw new IllegalArgumentException(
+          "Invalid 16-bits integer value, expecting 2 bytes but got " + bytes.remaining());
+    } else {
+      return bytes.getShort(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Short value) {
+    return (value == null) ? "NULL" : Short.toString(value);
+  }
+
+  @Override
+  public Short parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Short.parseShort(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 16-bits int value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java
new file mode 100644
index 00000000000..d8662484f57
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.core.util.Strings;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+public class StringCodec implements TypeCodec {
+
+  private final DataType cqlType;
+  private final Charset charset;
+
+  public StringCodec(DataType cqlType, Charset charset) {
+    this.cqlType = cqlType;
+    this.charset = charset;
+  }
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.STRING;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public ByteBuffer encode(String value, ProtocolVersion protocolVersion) {
+    return (value == null) ? null : ByteBuffer.wrap(value.getBytes(charset));
+  }
+
+  @Override
+  public String decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null) {
+      return null;
+    } else if (bytes.remaining() == 0) {
+      return "";
+    } else {
+      return new String(Bytes.getArray(bytes), charset);
+    }
+  }
+
+  @Override
+  public String format(String value) {
+    return (value == null) ? "NULL" : Strings.quote(value);
+  }
+
+  @Override
+  public String parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    } else if (!Strings.isQuoted(value)) {
+      throw new IllegalArgumentException(
+          "text or varchar values must be enclosed by single quotes");
+    } else {
+      return Strings.unquote(value);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java
new file mode 100644
index 00000000000..cb7ebb1af51
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.TypeCodecs;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.core.util.Strings;
+import java.nio.ByteBuffer;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+public class TimeCodec implements TypeCodec {
+
+  private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.LOCAL_TIME;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.TIME;
+  }
+
+  @Override
+  public ByteBuffer encode(LocalTime value, ProtocolVersion protocolVersion) {
+    return (value == null)
+        ? null
+        : TypeCodecs.BIGINT.encodePrimitive(value.toNanoOfDay(), protocolVersion);
+  }
+
+  @Override
+  public LocalTime decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    } else {
+      long nanosOfDay = TypeCodecs.BIGINT.decodePrimitive(bytes, protocolVersion);
+      return LocalTime.ofNanoOfDay(nanosOfDay);
+    }
+  }
+
+  @Override
+  public String format(LocalTime value) {
+    return (value == null) ? "NULL" : Strings.quote(FORMATTER.format(value));
+  }
+
+  @Override
+  public LocalTime parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    // enclosing single quotes required, even for long literals
+    if (!Strings.isQuoted(value)) {
+      throw new IllegalArgumentException("time values must be enclosed by single quotes");
+    }
+    value = value.substring(1, value.length() - 1);
+
+    if (Strings.isLongLiteral(value)) {
+      try {
+        return LocalTime.ofNanoOfDay(Long.parseLong(value));
+      } catch (NumberFormatException e) {
+        throw new IllegalArgumentException(
+            String.format("Cannot parse time value from \"%s\"", value), e);
+      }
+    }
+
+    try {
+      return LocalTime.parse(value);
+    } catch (RuntimeException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse time value from \"%s\"", value), e);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java
new file mode 100644
index 00000000000..b4712c4c2bd
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+public class TimeUuidCodec extends UuidCodec {
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.TIMEUUID;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    return super.canEncode(value) && ((UUID) value).version() == 1;
+  }
+
+  @Override
+  public ByteBuffer encode(UUID value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    } else if (value.version() != 1) {
+      throw new IllegalArgumentException(
+          String.format("%s is not a Type 1 (time-based) UUID", value));
+    } else {
+      return super.encode(value, protocolVersion);
+    }
+  }
+
+  @Override
+  public String format(UUID value) {
+    if (value == null) {
+      return "NULL";
+    } else if (value.version() != 1) {
+      throw new IllegalArgumentException(
+          String.format("%s is not a Type 1 (time-based) UUID", value));
+    } else {
+      return super.format(value);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java
new file mode 100644
index 00000000000..282bdc2f80e
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.TypeCodecs;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.driver.internal.core.util.Strings;
+import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+
+import static java.lang.Long.parseLong;
+
+public class TimestampCodec implements TypeCodec {
+
+  /** A {@link DateTimeFormatter} that parses (most) of the ISO formats accepted in CQL. */
+  private static final DateTimeFormatter PARSER =
+      new java.time.format.DateTimeFormatterBuilder()
+          .parseCaseSensitive()
+          .parseStrict()
+          .append(DateTimeFormatter.ISO_LOCAL_DATE)
+          .optionalStart()
+          .appendLiteral('T')
+          .appendValue(ChronoField.HOUR_OF_DAY, 2)
+          .appendLiteral(':')
+          .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+          .optionalEnd()
+          .optionalStart()
+          .appendLiteral(':')
+          .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+          .optionalEnd()
+          .optionalStart()
+          .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+          .optionalEnd()
+          .optionalStart()
+          .appendZoneId()
+          .optionalEnd()
+          .toFormatter()
+          .withZone(ZoneOffset.UTC);
+
+  private static final DateTimeFormatter FORMATTER =
+      DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxxx").withZone(ZoneOffset.UTC);
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.INSTANT;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.TIMESTAMP;
+  }
+
+  @Override
+  public ByteBuffer encode(Instant value, ProtocolVersion protocolVersion) {
+    return (value == null)
+        ? null
+        : TypeCodecs.BIGINT.encodePrimitive(value.toEpochMilli(), protocolVersion);
+  }
+
+  @Override
+  public Instant decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    return (bytes == null || bytes.remaining() == 0)
+        ? null
+        : Instant.ofEpochMilli(TypeCodecs.BIGINT.decodePrimitive(bytes, protocolVersion));
+  }
+
+  @Override
+  public String format(Instant value) {
+    return (value == null) ? "NULL" : Strings.quote(FORMATTER.format(value));
+  }
+
+  @Override
+  public Instant parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+    // strip enclosing single quotes, if any
+    if (Strings.isQuoted(value)) {
+      value = Strings.unquote(value);
+    }
+    if (Strings.isLongLiteral(value)) {
+      try {
+        return Instant.ofEpochMilli(parseLong(value));
+      } catch (NumberFormatException e) {
+        throw new IllegalArgumentException(
+            String.format("Cannot parse timestamp value from \"%s\"", value));
+      }
+    }
+    try {
+      return Instant.from(PARSER.parse(value));
+    } catch (DateTimeParseException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse timestamp value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java
new file mode 100644
index 00000000000..2c085c5b3d5
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+
+public class TinyIntCodec implements PrimitiveByteCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BYTE;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.TINYINT;
+  }
+
+  @Override
+  public ByteBuffer encodePrimitive(byte value, ProtocolVersion protocolVersion) {
+    ByteBuffer bytes = ByteBuffer.allocate(1);
+    bytes.put(0, value);
+    return bytes;
+  }
+
+  @Override
+  public byte decodePrimitive(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return 0;
+    } else if (bytes.remaining() != 1) {
+      throw new IllegalArgumentException(
+          "Invalid 8-bits integer value, expecting 1 byte but got " + bytes.remaining());
+    } else {
+      return bytes.get(bytes.position());
+    }
+  }
+
+  @Override
+  public String format(Byte value) {
+    return (value == null) ? "NULL" : Byte.toString(value);
+  }
+
+  @Override
+  public Byte parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : Byte.parseByte(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse 8-bits int value from \"%s\"", value));
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java
new file mode 100644
index 00000000000..3c717aba07d
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.data.TupleValue;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.TupleType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+public class TupleCodec implements TypeCodec {
+
+  private final TupleType cqlType;
+
+  public TupleCodec(TupleType cqlType) {
+    this.cqlType = cqlType;
+  }
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.TUPLE_VALUE;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    return (value instanceof TupleValue) && ((TupleValue) value).getType().equals(cqlType);
+  }
+
+  @Override
+  public ByteBuffer encode(TupleValue value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    if (!value.getType().equals(cqlType)) {
+      throw new IllegalArgumentException(
+          String.format("Invalid tuple type, expected %s but got %s", cqlType, value.getType()));
+    }
+    // Encoding: each field as a [bytes] value ([bytes] = int length + contents, null is
+    // represented by -1)
+    int toAllocate = 0;
+    for (int i = 0; i < value.size(); i++) {
+      ByteBuffer field = value.getBytesUnsafe(i);
+      toAllocate += 4 + (field == null ? 0 : field.remaining());
+    }
+    ByteBuffer result = ByteBuffer.allocate(toAllocate);
+    for (int i = 0; i < value.size(); i++) {
+      ByteBuffer field = value.getBytesUnsafe(i);
+      if (field == null) {
+        result.putInt(-1);
+      } else {
+        result.putInt(field.remaining());
+        result.put(field.duplicate());
+      }
+    }
+    return (ByteBuffer) result.flip();
+  }
+
+  @Override
+  public TupleValue decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null) {
+      return null;
+    }
+    // empty byte buffers will result in empty values
+    try {
+      ByteBuffer input = bytes.duplicate();
+      TupleValue value = cqlType.newValue();
+      int i = 0;
+      while (input.hasRemaining()) {
+        if (i > cqlType.getComponentTypes().size()) {
+          throw new IllegalArgumentException(
+              String.format(
+                  "Too many fields in encoded tuple, expected %d",
+                  cqlType.getComponentTypes().size()));
+        }
+        int elementSize = input.getInt();
+        ByteBuffer element;
+        if (elementSize == -1) {
+          element = null;
+        } else {
+          element = input.slice();
+          element.limit(elementSize);
+          input.position(input.position() + elementSize);
+        }
+        value.setBytesUnsafe(i, element);
+        i += 1;
+      }
+      return value;
+    } catch (BufferUnderflowException e) {
+      throw new IllegalArgumentException("Not enough bytes to deserialize a tuple", e);
+    }
+  }
+
+  @Override
+  public String format(TupleValue value) {
+    if (value == null) {
+      return "NULL";
+    }
+    if (!value.getType().equals(cqlType)) {
+      throw new IllegalArgumentException(
+          String.format("Invalid tuple type, expected %s but got %s", cqlType, value.getType()));
+    }
+    CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry();
+
+    StringBuilder sb = new StringBuilder("(");
+    boolean first = true;
+    for (int i = 0; i < value.size(); i++) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      DataType elementType = cqlType.getComponentTypes().get(i);
+      TypeCodec codec = registry.codecFor(elementType);
+      sb.append(codec.format(value.get(i, codec)));
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  @Override
+  public TupleValue parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    TupleValue tuple = cqlType.newValue();
+
+    int position = ParseUtils.skipSpaces(value, 0);
+    if (value.charAt(position++) != '(') {
+      throw new IllegalArgumentException(
+          String.format(
+              "Cannot parse tuple value from \"%s\", at character %d expecting '(' but got '%c'",
+              value, position, value.charAt(position)));
+    }
+
+    position = ParseUtils.skipSpaces(value, position);
+
+    if (value.charAt(position) == ')') {
+      return tuple;
+    }
+
+    CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry();
+
+    int i = 0;
+    while (position < value.length()) {
+      int n;
+      try {
+        n = ParseUtils.skipCQLValue(value, position);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse tuple value from \"%s\", invalid CQL value at character %d",
+                value, position),
+            e);
+      }
+
+      String fieldValue = value.substring(position, n);
+      DataType elementType = cqlType.getComponentTypes().get(i);
+      TypeCodec codec = registry.codecFor(elementType);
+      tuple.set(i, codec.parse(fieldValue), codec);
+
+      position = n;
+      i += 1;
+
+      position = ParseUtils.skipSpaces(value, position);
+      if (value.charAt(position) == ')') return tuple;
+      if (value.charAt(position) != ',')
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse tuple value from \"%s\", at character %d expecting ',' but got '%c'",
+                value, position, value.charAt(position)));
+      ++position; // skip ','
+
+      position = ParseUtils.skipSpaces(value, position);
+    }
+    throw new IllegalArgumentException(
+        String.format("Malformed tuple value \"%s\", missing closing ')'", value));
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java
new file mode 100644
index 00000000000..18cb48021f5
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.data.UdtValue;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.UserDefinedType;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+public class UdtCodec implements TypeCodec {
+
+  private final UserDefinedType cqlType;
+
+  public UdtCodec(UserDefinedType cqlType) {
+    this.cqlType = cqlType;
+  }
+
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.UDT_VALUE;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return cqlType;
+  }
+
+  @Override
+  public boolean canEncode(Object value) {
+    return UdtValue.class.isAssignableFrom(value.getClass())
+        && ((UdtValue) value).getType().equals(cqlType);
+  }
+
+  @Override
+  public ByteBuffer encode(UdtValue value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    if (!value.getType().equals(cqlType)) {
+      throw new IllegalArgumentException(
+          String.format(
+              "Invalid user defined type, expected %s but got %s", cqlType, value.getType()));
+    }
+    // Encoding: each field as a [bytes] value ([bytes] = int length + contents, null is
+    // represented by -1)
+    int toAllocate = 0;
+    int size = cqlType.getFieldTypes().size();
+    for (int i = 0; i < size; i++) {
+      ByteBuffer field = value.getBytesUnsafe(i);
+      toAllocate += 4 + (field == null ? 0 : field.remaining());
+    }
+    ByteBuffer result = ByteBuffer.allocate(toAllocate);
+    for (int i = 0; i < value.size(); i++) {
+      ByteBuffer field = value.getBytesUnsafe(i);
+      if (field == null) {
+        result.putInt(-1);
+      } else {
+        result.putInt(field.remaining());
+        result.put(field.duplicate());
+      }
+    }
+    return (ByteBuffer) result.flip();
+  }
+
+  @Override
+  public UdtValue decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null) {
+      return null;
+    }
+    // empty byte buffers will result in empty values
+    try {
+      ByteBuffer input = bytes.duplicate();
+      UdtValue value = cqlType.newValue();
+      int i = 0;
+      while (input.hasRemaining()) {
+        if (i > cqlType.getFieldTypes().size()) {
+          throw new IllegalArgumentException(
+              String.format(
+                  "Too many fields in encoded UDT value, expected %d",
+                  cqlType.getFieldTypes().size()));
+        }
+        int elementSize = input.getInt();
+        ByteBuffer element;
+        if (elementSize == -1) {
+          element = null;
+        } else {
+          element = input.slice();
+          element.limit(elementSize);
+          input.position(input.position() + elementSize);
+        }
+        value.setBytesUnsafe(i, element);
+        i += 1;
+      }
+      return value;
+    } catch (BufferUnderflowException e) {
+      throw new IllegalArgumentException("Not enough bytes to deserialize a UDT value", e);
+    }
+  }
+
+  @Override
+  public String format(UdtValue value) {
+    if (value == null) {
+      return "NULL";
+    }
+
+    CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry();
+
+    StringBuilder sb = new StringBuilder("{");
+    int size = cqlType.getFieldTypes().size();
+    boolean first = true;
+    for (int i = 0; i < size; i++) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(",");
+      }
+      CqlIdentifier elementName = cqlType.getFieldNames().get(i);
+      sb.append(elementName.asPrettyCql());
+      sb.append(":");
+      DataType elementType = cqlType.getFieldTypes().get(i);
+      TypeCodec codec = registry.codecFor(elementType);
+      sb.append(codec.format(value.get(i, codec)));
+    }
+    sb.append("}");
+    return sb.toString();
+  }
+
+  @Override
+  public UdtValue parse(String value) {
+    if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) {
+      return null;
+    }
+
+    UdtValue udt = cqlType.newValue();
+
+    int position = ParseUtils.skipSpaces(value, 0);
+    if (value.charAt(position++) != '{') {
+      throw new IllegalArgumentException(
+          String.format(
+              "Cannot parse UDT value from \"%s\", at character %d expecting '{' but got '%c'",
+              value, position, value.charAt(position)));
+    }
+
+    position = ParseUtils.skipSpaces(value, position);
+
+    if (value.charAt(position) == '}') {
+      return udt;
+    }
+
+    CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry();
+
+    while (position < value.length()) {
+      int n;
+      try {
+        n = ParseUtils.skipCQLId(value, position);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse UDT value from \"%s\", cannot parse a CQL identifier at character %d",
+                value, position),
+            e);
+      }
+      CqlIdentifier id = CqlIdentifier.fromInternal(value.substring(position, n));
+      position = n;
+
+      if (!cqlType.contains(id))
+        throw new IllegalArgumentException(
+            String.format("Unknown field %s in value \"%s\"", id, value));
+
+      position = ParseUtils.skipSpaces(value, position);
+      if (value.charAt(position++) != ':')
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse UDT value from \"%s\", at character %d expecting ':' but got '%c'",
+                value, position, value.charAt(position)));
+      position = ParseUtils.skipSpaces(value, position);
+
+      try {
+        n = ParseUtils.skipCQLValue(value, position);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse UDT value from \"%s\", invalid CQL value at character %d",
+                value, position),
+            e);
+      }
+
+      String fieldValue = value.substring(position, n);
+      // This works because ids occur at most once in UDTs
+      DataType fieldType = cqlType.getFieldTypes().get(cqlType.firstIndexOf(id));
+      TypeCodec codec = registry.codecFor(fieldType);
+      udt.set(id, codec.parse(fieldValue), codec);
+      position = n;
+
+      position = ParseUtils.skipSpaces(value, position);
+      if (value.charAt(position) == '}') {
+        return udt;
+      }
+      if (value.charAt(position) != ',') {
+        throw new IllegalArgumentException(
+            String.format(
+                "Cannot parse UDT value from \"%s\", at character %d expecting ',' but got '%c'",
+                value, position, value.charAt(position)));
+      }
+      ++position; // skip ','
+
+      position = ParseUtils.skipSpaces(value, position);
+    }
+    throw new IllegalArgumentException(
+        String.format("Malformed UDT value \"%s\", missing closing '}'", value));
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java
new file mode 100644
index 00000000000..bd85b5f5b49
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+public class UuidCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.UUID;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.UUID;
+  }
+
+  @Override
+  public ByteBuffer encode(UUID value, ProtocolVersion protocolVersion) {
+    if (value == null) {
+      return null;
+    }
+    ByteBuffer bytes = ByteBuffer.allocate(16);
+    bytes.putLong(0, value.getMostSignificantBits());
+    bytes.putLong(8, value.getLeastSignificantBits());
+    return bytes;
+  }
+
+  @Override
+  public UUID decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    if (bytes == null || bytes.remaining() == 0) {
+      return null;
+    } else if (bytes.remaining() != 16) {
+      throw new IllegalArgumentException(
+          "Unexpected number of bytes for a UUID, expected 16, got " + bytes.remaining());
+    } else {
+      return new UUID(bytes.getLong(bytes.position()), bytes.getLong(bytes.position() + 8));
+    }
+  }
+
+  @Override
+  public String format(UUID value) {
+    return (value == null) ? "NULL" : value.toString();
+  }
+
+  @Override
+  public UUID parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : UUID.fromString(value);
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse UUID value from \"%s\"", value), e);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java
new file mode 100644
index 00000000000..eb61839d3ec
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.DataTypes;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.protocol.internal.util.Bytes;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+public class VarIntCodec implements TypeCodec {
+  @Override
+  public GenericType getJavaType() {
+    return GenericType.BIG_INTEGER;
+  }
+
+  @Override
+  public DataType getCqlType() {
+    return DataTypes.VARINT;
+  }
+
+  @Override
+  public ByteBuffer encode(BigInteger value, ProtocolVersion protocolVersion) {
+    return (value == null) ? null : ByteBuffer.wrap(value.toByteArray());
+  }
+
+  @Override
+  public BigInteger decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
+    return (bytes == null) || bytes.remaining() == 0 ? null : new BigInteger(Bytes.getArray(bytes));
+  }
+
+  @Override
+  public String format(BigInteger value) {
+    return (value == null) ? "NULL" : value.toString();
+  }
+
+  @Override
+  public BigInteger parse(String value) {
+    try {
+      return (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
+          ? null
+          : new BigInteger(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+          String.format("Cannot parse varint value from \"%s\"", value), e);
+    }
+  }
+}
diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java
new file mode 100644
index 00000000000..3debdb444a9
--- /dev/null
+++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2017-2017 DataStax Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.type.codec.registry;
+
+import com.datastax.oss.driver.api.core.data.TupleValue;
+import com.datastax.oss.driver.api.core.data.UdtValue;
+import com.datastax.oss.driver.api.type.CustomType;
+import com.datastax.oss.driver.api.type.DataType;
+import com.datastax.oss.driver.api.type.ListType;
+import com.datastax.oss.driver.api.type.MapType;
+import com.datastax.oss.driver.api.type.SetType;
+import com.datastax.oss.driver.api.type.TupleType;
+import com.datastax.oss.driver.api.type.UserDefinedType;
+import com.datastax.oss.driver.api.type.codec.CodecNotFoundException;
+import com.datastax.oss.driver.api.type.codec.TypeCodec;
+import com.datastax.oss.driver.api.type.codec.TypeCodecs;
+import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry;
+import com.datastax.oss.driver.api.type.reflect.GenericType;
+import com.datastax.oss.protocol.internal.util.IntMap;
+import com.google.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A codec registry that handles built-in type mappings, can be extended with a list of
+ * user-provided codecs, generates more complex codecs from those basic codecs, and caches generated
+ * codecs for reuse.
+ *
+ * 

The primitive mappings always take precedence over any user codec. The list of user codecs can + * not be modified after construction. + * + *

This class is abstract in order to be agnostic from the cache implementation. Subclasses must + * implement {@link #getCachedCodec(DataType, GenericType)}. + */ +public abstract class CachingCodecRegistry implements CodecRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(CachingCodecRegistry.class); + + // Implementation notes: + // - built-in primitive codecs are served directly, without hitting the cache + // - same for user codecs (we assume the cardinality will always be low, so a sequential array + // traversal is cheap). + + private final TypeCodec[] userCodecs; + + protected CachingCodecRegistry(TypeCodec... userCodecs) { + this.userCodecs = userCodecs; + } + + /** + * Gets a complex codec from the cache. + * + *

If the codec does not exist in the cache, this method must generate it with {@link + * #createCodec(DataType, GenericType)} (and most likely put it in the cache too for future + * calls). + */ + protected abstract TypeCodec getCachedCodec(DataType cqlType, GenericType javaType); + + @Override + public TypeCodec codecFor(DataType cqlType, GenericType javaType) { + LOG.trace("Looking up codec for {} <-> {}", cqlType, javaType); + TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); + if (primitiveCodec != null && primitiveCodec.canEncode(javaType)) { + LOG.trace("Found matching primitive codec {}", primitiveCodec); + return safeCast(primitiveCodec); + } + for (TypeCodec userCodec : userCodecs) { + if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { + LOG.trace("Found matching user codec {}", userCodec); + return safeCast(userCodec); + } + } + return safeCast(getCachedCodec(cqlType, javaType)); + } + + @Override + public TypeCodec codecFor(DataType cqlType, Class javaType) { + LOG.trace("Looking up codec for {} <-> {}", cqlType, javaType); + TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); + if (primitiveCodec != null && primitiveCodec.getJavaType().__getToken().getType() == javaType) { + LOG.trace("Found matching primitive codec {}", primitiveCodec); + return safeCast(primitiveCodec); + } + for (TypeCodec userCodec : userCodecs) { + if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { + LOG.trace("Found matching user codec {}", userCodec); + return safeCast(userCodec); + } + } + return safeCast(getCachedCodec(cqlType, GenericType.of(javaType))); + } + + @Override + public TypeCodec codecFor(DataType cqlType) { + LOG.trace("Looking up codec for CQL type {}", cqlType); + TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); + if (primitiveCodec != null) { + LOG.trace("Found matching primitive codec {}", primitiveCodec); + return safeCast(primitiveCodec); + } + for (TypeCodec userCodec : userCodecs) { + if (userCodec.canDecode(cqlType)) { + LOG.trace("Found matching user codec {}", userCodec); + return safeCast(userCodec); + } + } + return safeCast(getCachedCodec(cqlType, null)); + } + + @Override + public TypeCodec codecFor(T value) { + Preconditions.checkNotNull(value); + LOG.trace("Looking up codec for object {}", value); + + // Quick check for two trivial cases + if (value instanceof TupleValue) { + return safeCast(codecFor(((TupleValue) value).getType(), TupleValue.class)); + } else if (value instanceof UdtValue) { + return safeCast(codecFor(((UdtValue) value).getType(), UdtValue.class)); + } + + for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { + if (primitiveCodec.canEncode(value)) { + LOG.trace("Found matching primitive codec {}", primitiveCodec); + return safeCast(primitiveCodec); + } + } + for (TypeCodec userCodec : userCodecs) { + if (userCodec.canEncode(value)) { + LOG.trace("Found matching user codec {}", userCodec); + return safeCast(userCodec); + } + } + GenericType javaType = inspectType(value); + LOG.trace("Continuing based on inferred type {}", javaType); + return safeCast(getCachedCodec(null, javaType)); + } + + // Not exposed publicly, this is only used for the recursion from createCodec(GenericType) + private TypeCodec codecFor(GenericType javaType) { + LOG.trace("Looking up codec for Java type {}", javaType); + for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { + if (primitiveCodec.canEncode(javaType)) { + LOG.trace("Found matching primitive codec {}", primitiveCodec); + return safeCast(primitiveCodec); + } + } + for (TypeCodec userCodec : userCodecs) { + if (userCodec.canEncode(javaType)) { + LOG.trace("Found matching user codec {}", userCodec); + return safeCast(userCodec); + } + } + return safeCast(getCachedCodec(null, javaType)); + } + + private GenericType inspectType(Object value) { + if (value instanceof List) { + List list = (List) value; + if (list.isEmpty()) { + // The empty list is always encoded the same way, so any element type will do + return GenericType.listOf(Boolean.class); + } else { + GenericType elementType = inspectType(list.get(0)); + return GenericType.listOf(elementType); + } + } else if (value instanceof Set) { + Set set = (Set) value; + if (set.isEmpty()) { + return GenericType.setOf(Boolean.class); + } else { + GenericType elementType = inspectType(set.iterator().next()); + return GenericType.setOf(elementType); + } + } else if (value instanceof Map) { + Map map = (Map) value; + if (map.isEmpty()) { + return GenericType.mapOf(Boolean.class, Boolean.class); + } else { + Map.Entry entry = map.entrySet().iterator().next(); + GenericType keyType = inspectType(entry.getKey()); + GenericType valueType = inspectType(entry.getValue()); + return GenericType.mapOf(keyType, valueType); + } + } else { + // There's not much more we can do + return GenericType.of(value.getClass()); + } + } + + // Try to create a codec when we haven't found it in the cache + protected TypeCodec createCodec(DataType cqlType, GenericType javaType) { + LOG.trace("Cache miss, creating codec"); + // Either type can be null, but not both. + if (javaType == null) { + assert cqlType != null; + return createCodec(cqlType); + } else if (cqlType == null) { + return createCodec(javaType); + } + TypeToken token = javaType.__getToken(); + if (cqlType instanceof ListType && List.class.isAssignableFrom(token.getRawType())) { + DataType elementCqlType = ((ListType) cqlType).getElementType(); + TypeCodec elementCodec; + if (token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType elementJavaType = GenericType.of(typeArguments[0]); + elementCodec = safeCast(codecFor(elementCqlType, elementJavaType)); + } else { + elementCodec = codecFor(elementCqlType); + } + return TypeCodecs.listOf(elementCodec); + } else if (cqlType instanceof SetType && Set.class.isAssignableFrom(token.getRawType())) { + DataType elementCqlType = ((SetType) cqlType).getElementType(); + TypeCodec elementCodec; + if (token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType elementJavaType = GenericType.of(typeArguments[0]); + elementCodec = safeCast(codecFor(elementCqlType, elementJavaType)); + } else { + elementCodec = codecFor(elementCqlType); + } + return TypeCodecs.setOf(elementCodec); + } else if (cqlType instanceof MapType && Map.class.isAssignableFrom(token.getRawType())) { + DataType keyCqlType = ((MapType) cqlType).getKeyType(); + DataType valueCqlType = ((MapType) cqlType).getValueType(); + TypeCodec keyCodec; + TypeCodec valueCodec; + if (token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType keyJavaType = GenericType.of(typeArguments[0]); + GenericType valueJavaType = GenericType.of(typeArguments[1]); + keyCodec = safeCast(codecFor(keyCqlType, keyJavaType)); + valueCodec = safeCast(codecFor(valueCqlType, valueJavaType)); + } else { + keyCodec = codecFor(keyCqlType); + valueCodec = codecFor(valueCqlType); + } + return TypeCodecs.mapOf(keyCodec, valueCodec); + } else if (cqlType instanceof TupleType + && TupleValue.class.isAssignableFrom(token.getRawType())) { + return TypeCodecs.tupleOf((TupleType) cqlType); + } else if (cqlType instanceof UserDefinedType + && UdtValue.class.isAssignableFrom(token.getRawType())) { + return TypeCodecs.udtOf((UserDefinedType) cqlType); + } else if (cqlType instanceof CustomType + && ByteBuffer.class.isAssignableFrom(token.getRawType())) { + return TypeCodecs.custom(cqlType); + } + throw new CodecNotFoundException(cqlType, javaType); + } + + // Try to create a codec when we haven't found it in the cache. + // Variant where the CQL type is unknown. + private TypeCodec createCodec(GenericType javaType) { + TypeToken token = javaType.__getToken(); + if (List.class.isAssignableFrom(token.getRawType()) + && token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType elementType = GenericType.of(typeArguments[0]); + TypeCodec elementCodec = codecFor(elementType); + return TypeCodecs.listOf(elementCodec); + } else if (Set.class.isAssignableFrom(token.getRawType()) + && token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType elementType = GenericType.of(typeArguments[0]); + TypeCodec elementCodec = codecFor(elementType); + return TypeCodecs.setOf(elementCodec); + } else if (Map.class.isAssignableFrom(token.getRawType()) + && token.getType() instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); + GenericType keyType = GenericType.of(typeArguments[0]); + GenericType valueType = GenericType.of(typeArguments[1]); + TypeCodec keyCodec = codecFor(keyType); + TypeCodec valueCodec = codecFor(valueType); + return TypeCodecs.mapOf(keyCodec, valueCodec); + } + throw new CodecNotFoundException(null, javaType); + } + + // Try to create a codec when we haven't found it in the cache. + // Variant where the Java type is unknown. + private TypeCodec createCodec(DataType cqlType) { + if (cqlType instanceof ListType) { + DataType elementType = ((ListType) cqlType).getElementType(); + TypeCodec elementCodec = codecFor(elementType); + return TypeCodecs.listOf(elementCodec); + } else if (cqlType instanceof SetType) { + DataType elementType = ((SetType) cqlType).getElementType(); + TypeCodec elementCodec = codecFor(elementType); + return TypeCodecs.setOf(elementCodec); + } else if (cqlType instanceof MapType) { + DataType keyType = ((MapType) cqlType).getKeyType(); + DataType valueType = ((MapType) cqlType).getValueType(); + TypeCodec keyCodec = codecFor(keyType); + TypeCodec valueCodec = codecFor(valueType); + return TypeCodecs.mapOf(keyCodec, valueCodec); + } else if (cqlType instanceof TupleType) { + return TypeCodecs.tupleOf((TupleType) cqlType); + } else if (cqlType instanceof UserDefinedType) { + return TypeCodecs.udtOf((UserDefinedType) cqlType); + } else if (cqlType instanceof CustomType) { + return TypeCodecs.custom(cqlType); + } + throw new CodecNotFoundException(cqlType, null); + } + + // roughly sorted by popularity + private static final TypeCodec[] PRIMITIVE_CODECS = + new TypeCodec[] { + // Must be declared before AsciiCodec so it gets chosen when CQL type not available + TypeCodecs.TEXT, + // Must be declared before TimeUUIDCodec so it gets chosen when CQL type not available + TypeCodecs.UUID, + TypeCodecs.TIMEUUID, + TypeCodecs.TIMESTAMP, + TypeCodecs.INT, + TypeCodecs.BIGINT, + TypeCodecs.BLOB, + TypeCodecs.DOUBLE, + TypeCodecs.FLOAT, + TypeCodecs.DECIMAL, + TypeCodecs.VARINT, + TypeCodecs.INET, + TypeCodecs.BOOLEAN, + TypeCodecs.SMALLINT, + TypeCodecs.TINYINT, + TypeCodecs.DATE, + TypeCodecs.TIME, + TypeCodecs.DURATION, + TypeCodecs.COUNTER, + TypeCodecs.ASCII + }; + + private static final IntMap PRIMITIVE_CODECS_BY_CODE = + sortByProtocolCode(PRIMITIVE_CODECS); + + private static IntMap sortByProtocolCode(TypeCodec[] codecs) { + IntMap.Builder builder = IntMap.builder(); + for (TypeCodec codec : codecs) { + builder.put(codec.getCqlType().getProtocolCode(), codec); + } + return builder.build(); + } + + // We call this after validating the types, so we know the cast will never fail. + private static TypeCodec safeCast(TypeCodec codec) { + @SuppressWarnings("unchecked") + TypeCodec result = (TypeCodec) codec; + return result; + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java new file mode 100644 index 00000000000..e39cd447661 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec.registry; + +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.RemovalListener; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The default codec registry implementation. + * + *

It is a caching registry based on Guava cache (note that the driver shades Guava). + */ +public class DefaultCodecRegistry extends CachingCodecRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultCodecRegistry.class); + + private final LoadingCache> cache; + + /** + * Creates a new instance, with some amount of control over the cache behavior. + * + *

Giving full access to the Guava cache API would be too much work, since it is shaded and we + * have to wrap everything. If you need something that's not available here, it's easy enough to + * write your own CachingCodecRegistry implementation. It's doubtful that stuff like cache + * eviction is that useful anyway. + */ + public DefaultCodecRegistry( + int initialCacheCapacity, + BiFunction, Integer> cacheWeigher, + int maximumCacheWeight, + BiConsumer> cacheRemovalListener, + TypeCodec... userCodecs) { + + super(userCodecs); + CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); + if (initialCacheCapacity > 0) { + cacheBuilder.initialCapacity(initialCacheCapacity); + } + if (cacheWeigher != null) { + cacheBuilder.weigher(cacheWeigher::apply).maximumWeight(maximumCacheWeight); + } + if (cacheRemovalListener != null) { + //noinspection ResultOfMethodCallIgnored + cacheBuilder.removalListener( + (RemovalListener>) + notification -> + cacheRemovalListener.accept(notification.getKey(), notification.getValue())); + } + this.cache = + cacheBuilder.build( + new CacheLoader>() { + @Override + public TypeCodec load(CacheKey key) throws Exception { + return createCodec(key.cqlType, key.javaType); + } + }); + } + + public DefaultCodecRegistry(TypeCodec... userCodecs) { + this(0, null, 0, null, userCodecs); + } + + @Override + protected TypeCodec getCachedCodec(DataType cqlType, GenericType javaType) { + LOG.trace("Checking cache"); + return cache.getUnchecked(new CacheKey(cqlType, javaType)); + } + + public static final class CacheKey { + + public final DataType cqlType; + public final GenericType javaType; + + public CacheKey(DataType cqlType, GenericType javaType) { + this.javaType = javaType; + this.cqlType = cqlType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof CacheKey) { + CacheKey that = (CacheKey) other; + return Objects.equals(this.cqlType, that.cqlType) + && Objects.equals(this.javaType, that.javaType); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(cqlType, javaType); + } + } +} diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java b/types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java new file mode 100644 index 00000000000..9905c13907a --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package com.datastax.oss.driver.internal.type.util; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * Variable length encoding inspired from Google varints. + * + *

Cassandra vints are encoded with the most significant group first. The most significant byte + * will contains the information about how many extra bytes need to be read as well as the most + * significant bits of the integer. The number of extra bytes to read is encoded as 1 bit on the + * left side. For example, if we need to read 3 more bytes the first byte will start with 1110. If + * the encoded integer is 8 bytes long the vint will be encoded on 9 bytes and the first byte will + * be: 11111111 + * + *

Signed integers are (like protocol buffer varints) encoded using the ZigZag encoding so that + * numbers with a small absolute value have a small vint encoded value too. + * + *

Note that there is also a type called {@code varint} in the CQL protocol specification. This + * is completely unrelated. + */ +public class VIntCoding { + + private static long readUnsignedVInt(DataInput input) throws IOException { + int firstByte = input.readByte(); + + //Bail out early if this is one byte, necessary or it fails later + if (firstByte >= 0) { + return firstByte; + } + + int size = numberOfExtraBytesToRead(firstByte); + long retval = firstByte & firstByteValueMask(size); + for (int ii = 0; ii < size; ii++) { + byte b = input.readByte(); + retval <<= 8; + retval |= b & 0xff; + } + + return retval; + } + + public static long readVInt(DataInput input) throws IOException { + return decodeZigZag64(readUnsignedVInt(input)); + } + + // & this with the first byte to give the value part for a given extraBytesToRead encoded in the byte + private static int firstByteValueMask(int extraBytesToRead) { + // by including the known 0bit in the mask, we can use this for encodeExtraBytesToRead + return 0xff >> extraBytesToRead; + } + + private static int encodeExtraBytesToRead(int extraBytesToRead) { + // because we have an extra bit in the value mask, we just need to invert it + return ~firstByteValueMask(extraBytesToRead); + } + + private static int numberOfExtraBytesToRead(int firstByte) { + // we count number of set upper bits; so if we simply invert all of the bits, we're golden + // this is aided by the fact that we only work with negative numbers, so when upcast to an int all + // of the new upper bits are also set, so by inverting we set all of them to zero + return Integer.numberOfLeadingZeros(~firstByte) - 24; + } + + private static final ThreadLocal encodingBuffer = + ThreadLocal.withInitial(() -> new byte[9]); + + private static void writeUnsignedVInt(long value, DataOutput output) throws IOException { + int size = VIntCoding.computeUnsignedVIntSize(value); + if (size == 1) { + output.write((int) value); + return; + } + + output.write(VIntCoding.encodeVInt(value, size), 0, size); + } + + private static byte[] encodeVInt(long value, int size) { + byte encodingSpace[] = encodingBuffer.get(); + int extraBytes = size - 1; + + for (int i = extraBytes; i >= 0; --i) { + encodingSpace[i] = (byte) value; + value >>= 8; + } + encodingSpace[0] |= encodeExtraBytesToRead(extraBytes); + return encodingSpace; + } + + public static void writeVInt(long value, DataOutput output) throws IOException { + writeUnsignedVInt(encodeZigZag64(value), output); + } + + /** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be + * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits + * to be varint encoded, thus always taking 10 bytes on the wire.) + * + * @param n an unsigned 64-bit integer, stored in a signed int because Java has no explicit + * unsigned support. + * @return a signed 64-bit integer. + */ + private static long decodeZigZag64(final long n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be + * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits + * to be varint encoded, thus always taking 10 bytes on the wire.) + * + * @param n a signed 64-bit integer. + * @return an unsigned 64-bit integer, stored in a signed int because Java has no explicit + * unsigned support. + */ + private static long encodeZigZag64(final long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); + } + + /** Compute the number of bytes that would be needed to encode a varint. */ + public static int computeVIntSize(final long param) { + return computeUnsignedVIntSize(encodeZigZag64(param)); + } + + /** Compute the number of bytes that would be needed to encode an unsigned varint. */ + private static int computeUnsignedVIntSize(final long value) { + int magnitude = + Long.numberOfLeadingZeros( + value | 1); // | with 1 to ensure magnitude <= 63, so (63 - 1) / 7 <= 8 + return (639 - magnitude * 9) >> 6; + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java new file mode 100644 index 00000000000..312db90a0d6 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class CqlDurationTest { + + @Test + public void testFromStringWithStandardPattern() { + assertThat(CqlDuration.from("1y2mo")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); + assertThat(CqlDuration.from("-1y2mo")).isEqualTo(CqlDuration.newInstance(-14, 0, 0)); + assertThat(CqlDuration.from("1Y2MO")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); + assertThat(CqlDuration.from("2w")).isEqualTo(CqlDuration.newInstance(0, 14, 0)); + assertThat(CqlDuration.from("2d10h")) + .isEqualTo(CqlDuration.newInstance(0, 2, 10 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("2d")).isEqualTo(CqlDuration.newInstance(0, 2, 0)); + assertThat(CqlDuration.from("30h")) + .isEqualTo(CqlDuration.newInstance(0, 0, 30 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("30h20m")) + .isEqualTo( + CqlDuration.newInstance( + 0, 0, 30 * CqlDuration.NANOS_PER_HOUR + 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("20m")) + .isEqualTo(CqlDuration.newInstance(0, 0, 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("56s")) + .isEqualTo(CqlDuration.newInstance(0, 0, 56 * CqlDuration.NANOS_PER_SECOND)); + assertThat(CqlDuration.from("567ms")) + .isEqualTo(CqlDuration.newInstance(0, 0, 567 * CqlDuration.NANOS_PER_MILLI)); + assertThat(CqlDuration.from("1950us")) + .isEqualTo(CqlDuration.newInstance(0, 0, 1950 * CqlDuration.NANOS_PER_MICRO)); + assertThat(CqlDuration.from("1950µs")) + .isEqualTo(CqlDuration.newInstance(0, 0, 1950 * CqlDuration.NANOS_PER_MICRO)); + assertThat(CqlDuration.from("1950000ns")).isEqualTo(CqlDuration.newInstance(0, 0, 1950000)); + assertThat(CqlDuration.from("1950000NS")).isEqualTo(CqlDuration.newInstance(0, 0, 1950000)); + assertThat(CqlDuration.from("-1950000ns")).isEqualTo(CqlDuration.newInstance(0, 0, -1950000)); + assertThat(CqlDuration.from("1y3mo2h10m")) + .isEqualTo(CqlDuration.newInstance(15, 0, 130 * CqlDuration.NANOS_PER_MINUTE)); + } + + @Test + public void testFromStringWithIso8601Pattern() { + assertThat(CqlDuration.from("P1Y2D")).isEqualTo(CqlDuration.newInstance(12, 2, 0)); + assertThat(CqlDuration.from("P1Y2M")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); + assertThat(CqlDuration.from("P2W")).isEqualTo(CqlDuration.newInstance(0, 14, 0)); + assertThat(CqlDuration.from("P1YT2H")) + .isEqualTo(CqlDuration.newInstance(12, 0, 2 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("-P1Y2M")).isEqualTo(CqlDuration.newInstance(-14, 0, 0)); + assertThat(CqlDuration.from("P2D")).isEqualTo(CqlDuration.newInstance(0, 2, 0)); + assertThat(CqlDuration.from("PT30H")) + .isEqualTo(CqlDuration.newInstance(0, 0, 30 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("PT30H20M")) + .isEqualTo( + CqlDuration.newInstance( + 0, 0, 30 * CqlDuration.NANOS_PER_HOUR + 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("PT20M")) + .isEqualTo(CqlDuration.newInstance(0, 0, 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("PT56S")) + .isEqualTo(CqlDuration.newInstance(0, 0, 56 * CqlDuration.NANOS_PER_SECOND)); + assertThat(CqlDuration.from("P1Y3MT2H10M")) + .isEqualTo(CqlDuration.newInstance(15, 0, 130 * CqlDuration.NANOS_PER_MINUTE)); + } + + @Test + public void testFromStringWithIso8601AlternativePattern() { + assertThat(CqlDuration.from("P0001-00-02T00:00:00")) + .isEqualTo(CqlDuration.newInstance(12, 2, 0)); + assertThat(CqlDuration.from("P0001-02-00T00:00:00")) + .isEqualTo(CqlDuration.newInstance(14, 0, 0)); + assertThat(CqlDuration.from("P0001-00-00T02:00:00")) + .isEqualTo(CqlDuration.newInstance(12, 0, 2 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("-P0001-02-00T00:00:00")) + .isEqualTo(CqlDuration.newInstance(-14, 0, 0)); + assertThat(CqlDuration.from("P0000-00-02T00:00:00")) + .isEqualTo(CqlDuration.newInstance(0, 2, 0)); + assertThat(CqlDuration.from("P0000-00-00T30:00:00")) + .isEqualTo(CqlDuration.newInstance(0, 0, 30 * CqlDuration.NANOS_PER_HOUR)); + assertThat(CqlDuration.from("P0000-00-00T30:20:00")) + .isEqualTo( + CqlDuration.newInstance( + 0, 0, 30 * CqlDuration.NANOS_PER_HOUR + 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("P0000-00-00T00:20:00")) + .isEqualTo(CqlDuration.newInstance(0, 0, 20 * CqlDuration.NANOS_PER_MINUTE)); + assertThat(CqlDuration.from("P0000-00-00T00:00:56")) + .isEqualTo(CqlDuration.newInstance(0, 0, 56 * CqlDuration.NANOS_PER_SECOND)); + assertThat(CqlDuration.from("P0001-03-00T02:10:00")) + .isEqualTo(CqlDuration.newInstance(15, 0, 130 * CqlDuration.NANOS_PER_MINUTE)); + } + + @Test + public void testInvalidDurations() { + assertInvalidDuration( + Long.MAX_VALUE + "d", + "Invalid duration. The total number of days must be less or equal to 2147483647"); + assertInvalidDuration("2µ", "Unable to convert '2µ' to a duration"); + assertInvalidDuration("-2µ", "Unable to convert '2µ' to a duration"); + assertInvalidDuration("12.5s", "Unable to convert '12.5s' to a duration"); + assertInvalidDuration("2m12.5s", "Unable to convert '2m12.5s' to a duration"); + assertInvalidDuration("2m-12s", "Unable to convert '2m-12s' to a duration"); + assertInvalidDuration("12s3s", "Invalid duration. The seconds are specified multiple times"); + assertInvalidDuration("12s3m", "Invalid duration. The seconds should be after minutes"); + assertInvalidDuration("1Y3M4D", "Invalid duration. The minutes should be after days"); + assertInvalidDuration("P2Y3W", "Unable to convert 'P2Y3W' to a duration"); + assertInvalidDuration("P0002-00-20", "Unable to convert 'P0002-00-20' to a duration"); + } + + private void assertInvalidDuration(String duration, String expectedErrorMessage) { + try { + CqlDuration.from(duration); + fail("Expected RuntimeException"); + } catch (RuntimeException e) { + assertThat(e.getMessage()).isEqualTo(expectedErrorMessage); + } + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java b/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java index a8333ad3e13..9d325cc7d5f 100644 --- a/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java +++ b/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java @@ -17,6 +17,7 @@ import com.google.common.reflect.TypeToken; import java.util.List; +import java.util.Map; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -35,4 +36,17 @@ public void should_capture_generic_type() { TypeToken> stringListToken = new TypeToken>() {}; assertThat(stringListType.__getToken()).isEqualTo(stringListToken); } + + @Test + public void should_wrap_classes_in_collection() { + GenericType> mapType = GenericType.mapOf(String.class, Integer.class); + assertThat(mapType.__getToken()).isEqualTo(new TypeToken>() {}); + } + + @Test + public void should_wrap_types_in_collection() { + GenericType>> mapType = + GenericType.mapOf(GenericType.of(String.class), GenericType.listOf(Integer.class)); + assertThat(mapType.__getToken()).isEqualTo(new TypeToken>>() {}); + } } diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java new file mode 100644 index 00000000000..f9e38ac4270 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.GettableById; +import com.datastax.oss.driver.api.core.data.GettableByName; +import com.datastax.oss.driver.api.core.data.SettableById; +import com.datastax.oss.driver.api.core.data.SettableByName; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +public abstract class AccessibleByIdTestBase< + T extends GettableById & SettableById & GettableByName & SettableByName> + extends AccessibleByIndexTestBase { + + private static final CqlIdentifier FIELD0_ID = CqlIdentifier.fromInternal("field0"); + private static final String FIELD0_NAME = "field0"; + + @Test + public void should_set_primitive_value_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setInt(FIELD0_ID, 1); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_object_value_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + + // When + t.setString(FIELD0_ID, "a"); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x61")); + } + + @Test + public void should_set_bytes_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_to_null_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + t.setToNull(FIELD0_ID); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isNull(); + } + + @Test + public void should_set_with_explicit_class_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_ID, "1", String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_type_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_ID, "1", GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_codec_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_ID, "1", intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_get_primitive_value_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + int i = t.getInt(FIELD0_ID); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(i).isEqualTo(1); + } + + @Test + public void should_get_object_value_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x61")); + + // When + String s = t.getString(FIELD0_ID); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("a"); + } + + @Test + public void should_get_bytes_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + ByteBuffer bytes = t.getBytesUnsafe(FIELD0_ID); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_test_if_null_by_id() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, null); + + // When + boolean isNull = t.isNull(FIELD0_ID); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(isNull).isTrue(); + } + + @Test + public void should_get_with_explicit_class_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_ID, String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_type_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_ID, GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_codec_by_id() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_ID, intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_set_primitive_value_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setInt(FIELD0_NAME, 1); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_object_value_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + + // When + t.setString(FIELD0_NAME, "a"); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x61")); + } + + @Test + public void should_set_bytes_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_to_null_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + t.setToNull(FIELD0_NAME); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isNull(); + } + + @Test + public void should_set_with_explicit_class_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_NAME, "1", String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_type_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_NAME, "1", GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_codec_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(FIELD0_NAME, "1", intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_get_primitive_value_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + int i = t.getInt(FIELD0_NAME); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(i).isEqualTo(1); + } + + @Test + public void should_get_object_value_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x61")); + + // When + String s = t.getString(FIELD0_NAME); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("a"); + } + + @Test + public void should_get_bytes_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + ByteBuffer bytes = t.getBytesUnsafe(FIELD0_NAME); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_test_if_null_by_name() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, null); + + // When + boolean isNull = t.isNull(FIELD0_NAME); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(isNull).isTrue(); + } + + @Test + public void should_get_with_explicit_class_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_NAME, String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_type_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_NAME, GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_codec_by_name() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(FIELD0_NAME, intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java new file mode 100644 index 00000000000..ebf25a07575 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.data; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.GettableByIndex; +import com.datastax.oss.driver.api.core.data.SettableByIndex; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.util.List; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +public abstract class AccessibleByIndexTestBase> { + + protected abstract T newInstance(List dataTypes, AttachmentPoint attachmentPoint); + + @Mock protected AttachmentPoint attachmentPoint; + @Mock protected CodecRegistry codecRegistry; + protected PrimitiveIntCodec intCodec; + protected TypeCodec doubleCodec; + protected TypeCodec textCodec; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + + intCodec = Mockito.spy(TypeCodecs.INT); + doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); + textCodec = Mockito.spy(TypeCodecs.TEXT); + + Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) + .thenAnswer(i -> doubleCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + } + + @Test + public void should_set_primitive_value_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setInt(0, 1); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_object_value_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + + // When + t.setString(0, "a"); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x61")); + } + + @Test + public void should_set_bytes_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_to_null_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + t.setToNull(0); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(t.getBytesUnsafe(0)).isNull(); + } + + @Test + public void should_set_with_explicit_class_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(0, "1", String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_type_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(0, "1", GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_set_with_explicit_codec_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.set(0, "1", intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_get_primitive_value_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + int i = t.getInt(0); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(i).isEqualTo(1); + } + + @Test + public void should_get_object_value_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x61")); + + // When + String s = t.getString(0); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("a"); + } + + @Test + public void should_get_bytes_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + ByteBuffer bytes = t.getBytesUnsafe(0); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); + } + + @Test + public void should_test_if_null_by_index() { + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, null); + + // When + boolean isNull = t.isNull(0); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + assertThat(isNull).isTrue(); + } + + @Test + public void should_get_with_explicit_class_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(0, String.class); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_type_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + .thenAnswer(i -> intToStringCodec); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(0, GenericType.STRING); + + // Then + Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } + + @Test + public void should_get_with_explicit_codec_by_index() { + // Given + CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + // When + String s = t.get(0, intToStringCodec); + + // Then + Mockito.verifyZeroInteractions(codecRegistry); + Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(s).isEqualTo("1"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java new file mode 100644 index 00000000000..e01ca85158e --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.data; + +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.internal.type.DefaultTupleType; +import java.util.List; + +public class DefaultTupleValueTest extends AccessibleByIndexTestBase { + + @Override + protected TupleValue newInstance(List dataTypes, AttachmentPoint attachmentPoint) { + DefaultTupleType type = new DefaultTupleType(dataTypes, attachmentPoint); + return type.newValue(); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java new file mode 100644 index 00000000000..1ce3da3cb81 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.internal.type.UserDefinedTypeBuilder; +import java.util.List; + +public class DefaultUdtValueTest extends AccessibleByIdTestBase { + + @Override + protected UdtValue newInstance(List dataTypes, AttachmentPoint attachmentPoint) { + UserDefinedTypeBuilder builder = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")); + for (int i = 0; i < dataTypes.size(); i++) { + builder.withField(CqlIdentifier.fromInternal("field" + i), dataTypes.get(i)); + } + UserDefinedType userDefinedType = builder.build(); + userDefinedType.attach(attachmentPoint); + return userDefinedType.newValue(); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java new file mode 100644 index 00000000000..d325f1290c1 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.data; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.google.common.collect.ImmutableList; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IdentifierIndexTest { + private static final CqlIdentifier Foo = CqlIdentifier.fromInternal("Foo"); + private static final CqlIdentifier foo = CqlIdentifier.fromInternal("foo"); + private static final CqlIdentifier fOO = CqlIdentifier.fromInternal("fOO"); + private IdentifierIndex index = new IdentifierIndex(ImmutableList.of(Foo, foo, fOO)); + + @Test + public void should_find_first_index_of_existing_identifier() { + assertThat(index.firstIndexOf(Foo)).isEqualTo(0); + assertThat(index.firstIndexOf(foo)).isEqualTo(1); + assertThat(index.firstIndexOf(fOO)).isEqualTo(2); + } + + @Test + public void should_not_find_index_of_nonexistent_identifier() { + assertThat(index.firstIndexOf(CqlIdentifier.fromInternal("FOO"))).isEqualTo(-1); + } + + @Test + public void should_find_first_index_of_case_insensitive_name() { + assertThat(index.firstIndexOf("foo")).isEqualTo(0); + } + + @Test + public void should_not_find_first_index_of_nonexistent_case_insensitive_name() { + assertThat(index.firstIndexOf("bar")).isEqualTo(-1); + } + + @Test + public void should_find_first_index_of_case_sensitive_name() { + assertThat(index.firstIndexOf("\"Foo\"")).isEqualTo(0); + assertThat(index.firstIndexOf("\"foo\"")).isEqualTo(1); + assertThat(index.firstIndexOf("\"fOO\"")).isEqualTo(2); + } + + @Test + public void should_not_find_index_of_nonexistent_case_sensitive_name() { + assertThat(index.firstIndexOf("\"FOO\"")).isEqualTo(-1); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java new file mode 100644 index 00000000000..f654a90b16e --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BigIntCodecTest extends CodecTestBase { + + public BigIntCodecTest() { + this.codec = TypeCodecs.BIGINT; + } + + @Test + public void should_encode() { + assertThat(encode(1L)).isEqualTo("0x0000000000000001"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000000000001")).isEqualTo(1L); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000000000000000" + "0000"); + } + + @Test + public void should_format() { + assertThat(format(1L)).isEqualTo("1"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("1")).isEqualTo(1L); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a number"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_if_out_of_range() { + parse(Long.toString(Long.MAX_VALUE) + "0"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java new file mode 100644 index 00000000000..1f5ec1ea27f --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.nio.ByteBuffer; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BlobCodecTest extends CodecTestBase { + private static final ByteBuffer BUFFER = Bytes.fromHexString("0xcafebabe"); + + public BlobCodecTest() { + this.codec = TypeCodecs.BLOB; + } + + @Test + public void should_encode() { + assertThat(encode(BUFFER)).isEqualTo("0xcafebabe"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_not_share_position_between_input_and_encoded() { + int inputPosition = BUFFER.position(); + ByteBuffer encoded = codec.encode(BUFFER, ProtocolVersion.DEFAULT); + // Read from the encoded buffer to change its position + encoded.get(); + // The input buffer should not be affected + assertThat(BUFFER.position()).isEqualTo(inputPosition); + } + + @Test + public void should_decode() { + assertThat(decode("0xcafebabe")).isEqualTo(BUFFER); + assertThat(decode("0x").capacity()).isEqualTo(0); + assertThat(decode(null)).isNull(); + } + + @Test + public void should_not_share_position_between_decoded_and_input() { + int inputPosition = BUFFER.position(); + ByteBuffer decoded = codec.decode(BUFFER, ProtocolVersion.DEFAULT); + // Read from the decoded buffer to change its position + decoded.get(); + // The input buffer should not be affected + assertThat(BUFFER.position()).isEqualTo(inputPosition); + } + + @Test + public void should_format() { + assertThat(format(BUFFER)).isEqualTo("0xcafebabe"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0xcafebabe")).isEqualTo(BUFFER); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a blob"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java new file mode 100644 index 00000000000..51e74c540ba --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BooleanCodecTest extends CodecTestBase { + + public BooleanCodecTest() { + this.codec = TypeCodecs.BOOLEAN; + } + + @Test + public void should_encode() { + assertThat(encode(false)).isEqualTo("0x00"); + assertThat(encode(true)).isEqualTo("0x01"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x00")).isFalse(); + assertThat(decode("0x01")).isTrue(); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format(true)).isEqualTo("true"); + assertThat(format(false)).isEqualTo("false"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("true")).isEqualTo(true); + assertThat(parse("false")).isEqualTo(false); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("maybe"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java new file mode 100644 index 00000000000..4482274e2de --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.nio.ByteBuffer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CodecTestBase { + protected TypeCodec codec; + + protected String encode(T t, ProtocolVersion protocolVersion) { + assertThat(codec).isNotNull().as("Must set codec before calling this method"); + ByteBuffer bytes = codec.encode(t, protocolVersion); + return (bytes == null) ? null : Bytes.toHexString(bytes); + } + + protected String encode(T t) { + return encode(t, ProtocolVersion.DEFAULT); + } + + protected T decode(String hexString, ProtocolVersion protocolVersion) { + assertThat(codec).isNotNull().as("Must set codec before calling this method"); + ByteBuffer bytes = (hexString == null) ? null : Bytes.fromHexString(hexString); + // Decode twice, to assert that decode leaves the input buffer in its original state + codec.decode(bytes, protocolVersion); + return codec.decode(bytes, protocolVersion); + } + + protected T decode(String hexString) { + return decode(hexString, ProtocolVersion.DEFAULT); + } + + protected String format(T t) { + assertThat(codec).isNotNull().as("Must set codec before calling this method"); + return codec.format(t); + } + + protected T parse(String s) { + assertThat(codec).isNotNull().as("Must set codec before calling this method"); + return codec.parse(s); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java new file mode 100644 index 00000000000..0705c8aee4b --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CounterCodecTest extends CodecTestBase { + + public CounterCodecTest() { + this.codec = TypeCodecs.COUNTER; + } + + @Test + public void should_encode() { + assertThat(encode(1L)).isEqualTo("0x0000000000000001"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000000000001")).isEqualTo(1L); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000000000000000" + "0000"); + } + + @Test + public void should_format() { + assertThat(format(1L)).isEqualTo("1"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("1")).isEqualTo(1L); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a number"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_if_out_of_range() { + parse(Long.toString(Long.MAX_VALUE) + "0"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java new file mode 100644 index 00000000000..87644271446 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CqlDurationCodecTest extends CodecTestBase { + + private static final CqlDuration DURATION = CqlDuration.newInstance(1, 2, 3); + + public CqlDurationCodecTest() { + this.codec = TypeCodecs.DURATION; + } + + @Test + public void should_encode() { + assertThat(encode(DURATION)) + .isEqualTo( + "0x" + + "02" // 1 (encoded as 2 because of zig-zag encoding) + + "04" // 2 (same) + + "06" // 3 (same) + ); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x020406")).isEqualTo(DURATION); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format(DURATION)).isEqualTo("1mo2d3ns"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("1mo2d3ns")).isEqualTo(DURATION); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a duration"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java new file mode 100644 index 00000000000..4beb828b085 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import java.nio.ByteBuffer; + +/** + * A sample user codec implementation that we use in our tests. + * + *

It maps a CQL string to a Java string containing its textual representation. + */ +public class CqlIntToStringCodec implements TypeCodec { + + @Override + public GenericType getJavaType() { + return GenericType.STRING; + } + + @Override + public DataType getCqlType() { + return DataTypes.INT; + } + + @Override + public ByteBuffer encode(String value, ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } else { + return TypeCodecs.INT.encode(Integer.parseInt(value), protocolVersion); + } + } + + @Override + public String decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return TypeCodecs.INT.decode(bytes, protocolVersion).toString(); + } + + @Override + public String format(String value) { + throw new UnsupportedOperationException("Not implemented for this test"); + } + + @Override + public String parse(String value) { + throw new UnsupportedOperationException("Not implemented for this test"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java new file mode 100644 index 00000000000..fcaf063a1f2 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.nio.ByteBuffer; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomCodecTest extends CodecTestBase { + private static final ByteBuffer BUFFER = Bytes.fromHexString("0xcafebabe"); + + public CustomCodecTest() { + this.codec = TypeCodecs.custom(DataTypes.custom("com.test.MyClass")); + } + + @Test + public void should_encode() { + assertThat(encode(BUFFER)).isEqualTo("0xcafebabe"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_not_share_position_between_input_and_encoded() { + int inputPosition = BUFFER.position(); + ByteBuffer encoded = codec.encode(BUFFER, ProtocolVersion.DEFAULT); + // Read from the encoded buffer to change its position + encoded.get(); + // The input buffer should not be affected + assertThat(BUFFER.position()).isEqualTo(inputPosition); + } + + @Test + public void should_decode() { + assertThat(decode("0xcafebabe")).isEqualTo(BUFFER); + assertThat(decode("0x").capacity()).isEqualTo(0); + assertThat(decode(null)).isNull(); + } + + @Test + public void should_not_share_position_between_decoded_and_input() { + int inputPosition = BUFFER.position(); + ByteBuffer decoded = codec.decode(BUFFER, ProtocolVersion.DEFAULT); + // Read from the decoded buffer to change its position + decoded.get(); + // The input buffer should not be affected + assertThat(BUFFER.position()).isEqualTo(inputPosition); + } + + @Test + public void should_format() { + assertThat(format(BUFFER)).isEqualTo("0xcafebabe"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0xcafebabe")).isEqualTo(BUFFER); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a blob"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java new file mode 100644 index 00000000000..e561168b653 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.time.LocalDate; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DateCodecTest extends CodecTestBase { + + private static final LocalDate EPOCH = LocalDate.ofEpochDay(0); + private static final LocalDate MIN = LocalDate.parse("-5877641-06-23"); + private static final LocalDate MAX = LocalDate.parse("+5881580-07-11"); + + public DateCodecTest() { + this.codec = TypeCodecs.DATE; + } + + @Test + public void should_encode() { + // Dates are encoded as a number of days since the epoch, stored on 8 bytes with 0 in the + // middle. + assertThat(encode(MIN)).isEqualTo("0x00000000"); + // The "middle" is the one that has only the most significant bit set (because it has the same + // number of values before and after it, determined by all possible combinations of the + // remaining bits) + assertThat(encode(EPOCH)).isEqualTo("0x80000000"); + assertThat(encode(MAX)).isEqualTo("0xffffffff"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x00000000")).isEqualTo(MIN); + assertThat(decode("0x80000000")).isEqualTo(EPOCH); + assertThat(decode("0xffffffff")).isEqualTo(MAX); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x00000000" + "0000"); + } + + @Test + public void should_format() { + // No need to test various values because the codec delegates directly to the JDK's formatter, + // which we assume does its job correctly. + assertThat(format(EPOCH)).isEqualTo("'1970-01-01'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + // Raw number + assertThat(parse("0")).isEqualTo(MIN); + assertThat(parse("2147483648")).isEqualTo(EPOCH); + + // Date format + assertThat(parse("'-5877641-06-23'")).isEqualTo(MIN); + assertThat(parse("'1970-01-01'")).isEqualTo(EPOCH); + assertThat(parse("'2014-01-01'")).isEqualTo(LocalDate.parse("2014-01-01")); + + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a date"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java new file mode 100644 index 00000000000..bd33b8a3fd7 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.math.BigDecimal; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DecimalCodecTest extends CodecTestBase { + + public DecimalCodecTest() { + this.codec = TypeCodecs.DECIMAL; + } + + @Test + public void should_encode() { + assertThat(encode(BigDecimal.ONE)) + .isEqualTo( + "0x" + + "00000000" // scale + + "01" // unscaled value + ); + assertThat(encode(BigDecimal.valueOf(128, 4))) + .isEqualTo( + "0x" + + "00000004" // scale + + "0080" // unscaled value + ); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000001")).isEqualTo(BigDecimal.ONE); + assertThat(decode("0x000000040080")).isEqualTo(BigDecimal.valueOf(128, 4)); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format(BigDecimal.ONE)).isEqualTo("1"); + assertThat(format(BigDecimal.valueOf(128, 4))).isEqualTo("0.0128"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("1")).isEqualTo(BigDecimal.ONE); + assertThat(parse("0.0128")).isEqualTo(BigDecimal.valueOf(128, 4)); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a decimal"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java new file mode 100644 index 00000000000..609f10378d4 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DoubleCodecTest extends CodecTestBase { + + public DoubleCodecTest() { + this.codec = TypeCodecs.DOUBLE; + } + + @Test + public void should_encode() { + // Our codec relies on the JDK's ByteBuffer API. We're not testing the JDK, so no need to try + // a thousand different values. + assertThat(encode(0.0)).isEqualTo("0x0000000000000000"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000000000000")).isEqualTo(0.0); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format(0.0)).isEqualTo("0.0"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0.0")).isEqualTo(0.0); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a double"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java new file mode 100644 index 00000000000..77b62b91d29 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FloatCodecTest extends CodecTestBase { + + public FloatCodecTest() { + this.codec = TypeCodecs.FLOAT; + } + + @Test + public void should_encode() { + // Our codec relies on the JDK's ByteBuffer API. We're not testing the JDK, so no need to try + // a thousand different values. + assertThat(encode(0.0f)).isEqualTo("0x00000000"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x00000000")).isEqualTo(0.0f); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format(0.0f)).isEqualTo("0.0"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0.0")).isEqualTo(0.0f); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a float"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java new file mode 100644 index 00000000000..19aaf37bfe7 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.google.common.base.Strings; +import java.net.InetAddress; +import java.net.UnknownHostException; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class InetCodecTest extends CodecTestBase { + + private static final InetAddress V4_ADDRESS; + private static final InetAddress V6_ADDRESS; + + static { + try { + V4_ADDRESS = InetAddress.getByName("127.0.0.1"); + V6_ADDRESS = InetAddress.getByName("::1"); + } catch (UnknownHostException e) { + fail("unexpected error", e); + throw new AssertionError(); // never reached + } + } + + public InetCodecTest() { + this.codec = TypeCodecs.INET; + } + + @Test + public void should_encode() { + assertThat(encode(V4_ADDRESS)).isEqualTo("0x7f000001"); + assertThat(encode(V6_ADDRESS)).isEqualTo("0x00000000000000000000000000000001"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x7f000001")).isEqualTo(V4_ADDRESS); + assertThat(decode("0x00000000000000000000000000000001")).isEqualTo(V6_ADDRESS); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_incorrect_byte_count() { + decode("0x" + Strings.repeat("00", 7)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x" + Strings.repeat("00", 17)); + } + + @Test + public void should_format() { + assertThat(format(V4_ADDRESS)).isEqualTo("'127.0.0.1'"); + assertThat(format(V6_ADDRESS)).isEqualTo("'0:0:0:0:0:0:0:1'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("'127.0.0.1'")).isEqualTo(V4_ADDRESS); + assertThat(parse("'0:0:0:0:0:0:0:1'")).isEqualTo(V6_ADDRESS); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not an address"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java new file mode 100644 index 00000000000..c64c28ac95b --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IntCodecTest extends CodecTestBase { + + public IntCodecTest() { + this.codec = TypeCodecs.INT; + } + + @Test + public void should_encode() { + // Our codec relies on the JDK's ByteBuffer API. We're not testing the JDK, so no need to try + // a thousand different values. + assertThat(encode(0)).isEqualTo("0x00000000"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x00000000")).isEqualTo(0); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000000000000000"); + } + + @Test + public void should_format() { + assertThat(format(0)).isEqualTo("0"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0")).isEqualTo(0); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not an int"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java new file mode 100644 index 00000000000..bddaed59797 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ListCodecTest extends CodecTestBase> { + + @Mock private TypeCodec elementCodec; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); + Mockito.when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); + codec = TypeCodecs.listOf(elementCodec); + } + + @Test + public void should_encode_null() { + assertThat(encode(null)).isNull(); + } + + @Test + public void should_encode_empty_list() { + assertThat(encode(new ArrayList<>())).isEqualTo("0x00000000"); + } + + @Test + public void should_encode_non_empty_list() { + Mockito.when(elementCodec.encode(1, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x01")); + Mockito.when(elementCodec.encode(2, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x0002")); + Mockito.when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x000003")); + + assertThat(encode(ImmutableList.of(1, 2, 3))) + .isEqualTo( + "0x" + + "00000003" // number of elements + + "0000000101" // size + contents of element 1 + + "000000020002" // size + contents of element 2 + + "00000003000003" // size + contents of element 3 + ); + } + + @Test + public void should_decode_null_as_empty_list() { + assertThat(decode(null)).isEmpty(); + } + + @Test + public void should_decode_empty_list() { + assertThat(decode("0x00000000")).isEmpty(); + } + + @Test + public void should_decode_non_empty_list() { + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) + .thenReturn(1); + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) + .thenReturn(2); + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) + .thenReturn(3); + + assertThat(decode("0x" + "00000003" + "0000000101" + "000000020002" + "00000003000003")) + .containsExactly(1, 2, 3); + } + + @Test + public void should_format_null_list() { + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_format_empty_list() { + assertThat(format(new ArrayList<>())).isEqualTo("[]"); + } + + @Test + public void should_format_non_empty_list() { + Mockito.when(elementCodec.format(1)).thenReturn("a"); + Mockito.when(elementCodec.format(2)).thenReturn("b"); + Mockito.when(elementCodec.format(3)).thenReturn("c"); + + assertThat(format(ImmutableList.of(1, 2, 3))).isEqualTo("[a,b,c]"); + } + + @Test + public void should_parse_null_or_empty_string() { + assertThat(parse(null)).isNull(); + assertThat(parse("")).isNull(); + } + + @Test + public void should_parse_empty_list() { + assertThat(parse("[]")).isEmpty(); + } + + @Test + public void should_parse_non_empty_list() { + Mockito.when(elementCodec.parse("a")).thenReturn(1); + Mockito.when(elementCodec.parse("b")).thenReturn(2); + Mockito.when(elementCodec.parse("c")).thenReturn(3); + + assertThat(parse("[a,b,c]")).containsExactly(1, 2, 3); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_malformed_list() { + parse("not a list"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java new file mode 100644 index 00000000000..fa4d090bda3 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableMap; +import java.util.LinkedHashMap; +import java.util.Map; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MapCodecTest extends CodecTestBase> { + + @Mock private TypeCodec keyCodec; + @Mock private TypeCodec valueCodec; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(keyCodec.getCqlType()).thenReturn(DataTypes.TEXT); + Mockito.when(keyCodec.getJavaType()).thenReturn(GenericType.STRING); + + Mockito.when(valueCodec.getCqlType()).thenReturn(DataTypes.INT); + Mockito.when(valueCodec.getJavaType()).thenReturn(GenericType.INTEGER); + codec = TypeCodecs.mapOf(keyCodec, valueCodec); + } + + @Test + public void should_encode_null() { + assertThat(encode(null)).isNull(); + } + + @Test + public void should_encode_empty_map() { + assertThat(encode(new LinkedHashMap<>())).isEqualTo("0x00000000"); + } + + @Test + public void should_encode_non_empty_map() { + Mockito.when(keyCodec.encode("a", ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x10")); + Mockito.when(keyCodec.encode("b", ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x2000")); + Mockito.when(keyCodec.encode("c", ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x300000")); + + Mockito.when(valueCodec.encode(1, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x01")); + Mockito.when(valueCodec.encode(2, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x0002")); + Mockito.when(valueCodec.encode(3, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x000003")); + + assertThat(encode(ImmutableMap.of("a", 1, "b", 2, "c", 3))) + .isEqualTo( + "0x" + + "00000003" // number of key-value pairs + + "0000000110" // size + contents of key 1 + + "0000000101" // size + contents of value 1 + + "000000022000" // size + contents of key 2 + + "000000020002" // size + contents of value 2 + + "00000003300000" // size + contents of key 3 + + "00000003000003" // size + contents of value 3 + ); + } + + @Test + public void should_decode_null_as_empty_map() { + assertThat(decode(null)).isEmpty(); + } + + @Test + public void should_decode_empty_map() { + assertThat(decode("0x00000000")).isEmpty(); + } + + @Test + public void should_decode_non_empty_map() { + Mockito.when(keyCodec.decode(Bytes.fromHexString("0x10"), ProtocolVersion.DEFAULT)) + .thenReturn("a"); + Mockito.when(keyCodec.decode(Bytes.fromHexString("0x2000"), ProtocolVersion.DEFAULT)) + .thenReturn("b"); + Mockito.when(keyCodec.decode(Bytes.fromHexString("0x300000"), ProtocolVersion.DEFAULT)) + .thenReturn("c"); + + Mockito.when(valueCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) + .thenReturn(1); + Mockito.when(valueCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) + .thenReturn(2); + Mockito.when(valueCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) + .thenReturn(3); + + assertThat( + decode( + "0x" + + "00000003" + + "0000000110" + + "0000000101" + + "000000022000" + + "000000020002" + + "00000003300000" + + "00000003000003")) + .containsOnlyKeys("a", "b", "c") + .containsEntry("a", 1) + .containsEntry("b", 2) + .containsEntry("c", 3); + } + + @Test + public void should_format_null_map() { + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_format_empty_map() { + assertThat(format(new LinkedHashMap<>())).isEqualTo("{}"); + } + + @Test + public void should_format_non_empty_map() { + Mockito.when(keyCodec.format("a")).thenReturn("foo"); + Mockito.when(keyCodec.format("b")).thenReturn("bar"); + Mockito.when(keyCodec.format("c")).thenReturn("baz"); + + Mockito.when(valueCodec.format(1)).thenReturn("qux"); + Mockito.when(valueCodec.format(2)).thenReturn("quux"); + Mockito.when(valueCodec.format(3)).thenReturn("quuz"); + + assertThat(format(ImmutableMap.of("a", 1, "b", 2, "c", 3))) + .isEqualTo("{foo:qux,bar:quux,baz:quuz}"); + } + + @Test + public void should_parse_null_or_empty_string() { + assertThat(parse(null)).isNull(); + assertThat(parse("")).isNull(); + } + + @Test + public void should_parse_empty_map() { + assertThat(parse("{}")).isEmpty(); + } + + @Test + public void should_parse_non_empty_map() { + Mockito.when(keyCodec.parse("foo")).thenReturn("a"); + Mockito.when(keyCodec.parse("bar")).thenReturn("b"); + Mockito.when(keyCodec.parse("baz")).thenReturn("c"); + + Mockito.when(valueCodec.parse("qux")).thenReturn(1); + Mockito.when(valueCodec.parse("quux")).thenReturn(2); + Mockito.when(valueCodec.parse("quuz")).thenReturn(3); + + assertThat(parse("{foo:qux,bar:quux,baz:quuz}")) + .containsOnlyKeys("a", "b", "c") + .containsEntry("a", 1) + .containsEntry("b", 2) + .containsEntry("c", 3); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_malformed_map() { + parse("not a map"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java new file mode 100644 index 00000000000..09575d117be --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SetCodecTest extends CodecTestBase> { + + @Mock private TypeCodec elementCodec; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); + Mockito.when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); + codec = TypeCodecs.setOf(elementCodec); + } + + @Test + public void should_encode_null() { + assertThat(encode(null)).isNull(); + } + + @Test + public void should_encode_empty_set() { + assertThat(encode(new LinkedHashSet<>())).isEqualTo("0x00000000"); + } + + @Test + public void should_encode_non_empty_set() { + Mockito.when(elementCodec.encode(1, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x01")); + Mockito.when(elementCodec.encode(2, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x0002")); + Mockito.when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) + .thenReturn(Bytes.fromHexString("0x000003")); + + assertThat(encode(ImmutableSet.of(1, 2, 3))) + .isEqualTo( + "0x" + + "00000003" // number of elements + + "0000000101" // size + contents of element 1 + + "000000020002" // size + contents of element 2 + + "00000003000003" // size + contents of element 3 + ); + } + + @Test + public void should_decode_null_as_empty_set() { + assertThat(decode(null)).isEmpty(); + } + + @Test + public void should_decode_empty_set() { + assertThat(decode("0x00000000")).isEmpty(); + } + + @Test + public void should_decode_non_empty_set() { + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) + .thenReturn(1); + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) + .thenReturn(2); + Mockito.when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) + .thenReturn(3); + + assertThat(decode("0x" + "00000003" + "0000000101" + "000000020002" + "00000003000003")) + .containsExactly(1, 2, 3); + } + + @Test + public void should_format_null_set() { + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_format_empty_set() { + assertThat(format(new LinkedHashSet<>())).isEqualTo("{}"); + } + + @Test + public void should_format_non_empty_set() { + Mockito.when(elementCodec.format(1)).thenReturn("a"); + Mockito.when(elementCodec.format(2)).thenReturn("b"); + Mockito.when(elementCodec.format(3)).thenReturn("c"); + + assertThat(format(ImmutableSet.of(1, 2, 3))).isEqualTo("{a,b,c}"); + } + + @Test + public void should_parse_null_or_empty_string() { + assertThat(parse(null)).isNull(); + assertThat(parse("")).isNull(); + } + + @Test + public void should_parse_empty_set() { + assertThat(parse("{}")).isEmpty(); + } + + @Test + public void should_parse_non_empty_set() { + Mockito.when(elementCodec.parse("a")).thenReturn(1); + Mockito.when(elementCodec.parse("b")).thenReturn(2); + Mockito.when(elementCodec.parse("c")).thenReturn(3); + + assertThat(parse("{a,b,c}")).containsExactly(1, 2, 3); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_malformed_set() { + parse("not a set"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java new file mode 100644 index 00000000000..a9e5f492a53 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SmallIntCodecTest extends CodecTestBase { + + public SmallIntCodecTest() { + this.codec = TypeCodecs.SMALLINT; + } + + @Test + public void should_encode() { + // Our codec relies on the JDK's ByteBuffer API. We're not testing the JDK, so no need to try + // a thousand different values. + assertThat(encode((short) 0)).isEqualTo("0x0000"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000")).isEqualTo((short) 0); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x00"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x000000"); + } + + @Test + public void should_format() { + assertThat(format((short) 0)).isEqualTo("0"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0")).isEqualTo((short) 0); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a smallint"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java new file mode 100644 index 00000000000..9e7459dee1a --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StringCodecTest extends CodecTestBase { + + public StringCodecTest() { + // We don't test ASCII, since it only differs by the encoding used + this.codec = TypeCodecs.TEXT; + } + + @Test + public void should_encode() { + assertThat(encode("hello")).isEqualTo("0x68656c6c6f"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x68656c6c6f")).isEqualTo("hello"); + assertThat(decode("0x")).isEmpty(); + assertThat(decode(null)).isNull(); + } + + @Test + public void should_format() { + assertThat(format("hello")).isEqualTo("'hello'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("'hello'")).isEqualTo("hello"); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a string"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java new file mode 100644 index 00000000000..0f0e1e43f35 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.time.LocalTime; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimeCodecTest extends CodecTestBase { + + public TimeCodecTest() { + this.codec = TypeCodecs.TIME; + } + + @Test + public void should_encode() { + assertThat(encode(LocalTime.MIDNIGHT)).isEqualTo("0x0000000000000000"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000000000000")).isEqualTo(LocalTime.MIDNIGHT); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000000000000000" + "0000"); + } + + @Test + public void should_format() { + // No need to test various values because the codec delegates directly to the JDK's formatter, + // which we assume does its job correctly. + assertThat(format(LocalTime.MIDNIGHT)).isEqualTo("'00:00:00.000'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + // Raw number + assertThat(parse("'0'")).isEqualTo(LocalTime.MIDNIGHT); + + // String format + assertThat(parse("'00:00'")).isEqualTo(LocalTime.MIDNIGHT); + + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a time"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java new file mode 100644 index 00000000000..8f1678d16a0 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.util.UUID; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimeUuidCodecTest extends CodecTestBase { + + public static final UUID TIME_BASED = new UUID(6342305776366260711L, -5736720392086604862L); + public static final UUID NOT_TIME_BASED = new UUID(2, 1); + + public TimeUuidCodecTest() { + this.codec = TypeCodecs.TIMEUUID; + + assertThat(TIME_BASED.version()).isEqualTo(1); + assertThat(NOT_TIME_BASED.version()).isNotEqualTo(1); + } + + @Test + public void should_encode_time_uuid() { + assertThat(encode(TIME_BASED)).isEqualTo("0x58046580293811e7b0631332a5f033c2"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_not_encode_non_time_uuid() { + assertThat(codec.canEncode(NOT_TIME_BASED)).isFalse(); + encode(NOT_TIME_BASED); + } + + @Test + public void should_format_time_uuid() { + assertThat(format(TIME_BASED)).isEqualTo("58046580-2938-11e7-b063-1332a5f033c2"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_not_format_non_time_uuid() { + format(NOT_TIME_BASED); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java new file mode 100644 index 00000000000..63a2ab2f21d --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.time.Instant; +import java.time.LocalDate; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimestampCodecTest extends CodecTestBase { + + public TimestampCodecTest() { + this.codec = TypeCodecs.TIMESTAMP; + } + + @Test + public void should_encode() { + assertThat(encode(Instant.EPOCH)).isEqualTo("0x0000000000000000"); + assertThat(encode(Instant.ofEpochMilli(128))).isEqualTo("0x0000000000000080"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x0000000000000000").toEpochMilli()).isEqualTo(0); + assertThat(decode("0x0000000000000080").toEpochMilli()).isEqualTo(128); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000000000000000" + "0000"); + } + + @Test + public void should_format() { + // No need to test various values because the codec delegates directly to the JDK's formatter, + // which we assume does its job correctly. + assertThat(format(Instant.EPOCH)).isEqualTo("'1970-01-01T00:00:00.000+00:00'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + // Raw number + assertThat(parse("'0'")).isEqualTo(Instant.EPOCH); + + // Date format + assertThat(parse("'1970-01-01T00:00Z'")).isEqualTo(Instant.EPOCH); + + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a timestamp"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java new file mode 100644 index 00000000000..bada793de10 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TinyIntCodecTest extends CodecTestBase { + + public TinyIntCodecTest() { + this.codec = TypeCodecs.TINYINT; + } + + @Test + public void should_encode() { + // Our codec relies on the JDK's ByteBuffer API. We're not testing the JDK, so no need to try + // a thousand different values. + assertThat(encode((byte) 0)).isEqualTo("0x00"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x00")).isEqualTo((byte) 0); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x0000"); + } + + @Test + public void should_format() { + assertThat(format((byte) 0)).isEqualTo("0"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("0")).isEqualTo((byte) 0); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a tinyint"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java new file mode 100644 index 00000000000..d49d2a082e4 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.data.DefaultTupleValue; +import com.datastax.oss.driver.internal.type.DefaultTupleType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TupleCodecTest extends CodecTestBase { + + @Mock private AttachmentPoint attachmentPoint; + @Mock private CodecRegistry codecRegistry; + private PrimitiveIntCodec intCodec; + private TypeCodec doubleCodec; + private TypeCodec textCodec; + + private TupleType tupleType; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + + intCodec = Mockito.spy(TypeCodecs.INT); + doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); + textCodec = Mockito.spy(TypeCodecs.TEXT); + + // Called by the getters/setters + Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) + .thenAnswer(i -> doubleCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + + // Called by format/parse + Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); + + tupleType = + new DefaultTupleType( + ImmutableList.of(DataTypes.INT, DataTypes.DOUBLE, DataTypes.TEXT), attachmentPoint); + + codec = TypeCodecs.tupleOf(tupleType); + } + + @Test + public void should_encode_null_tuple() { + assertThat(encode(null)).isNull(); + } + + @Test + public void should_encode_tuple() { + TupleValue tuple = tupleType.newValue(); + tuple.setInt(0, 1); + tuple.setToNull(1); + tuple.setString(2, "a"); + + assertThat(encode(tuple)) + .isEqualTo( + "0x" + + ("00000004" + "00000001") // size and contents of field 0 + + "ffffffff" // null field 1 + + ("00000001" + "61") // size and contents of field 2 + ); + + Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + // null values are handled directly in the tuple codec, without calling the child codec: + Mockito.verifyZeroInteractions(doubleCodec); + Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + } + + @Test + public void should_decode_null_tuple() { + assertThat(decode(null)).isNull(); + } + + @Test + public void should_decode_tuple() { + TupleValue tuple = decode("0x" + ("00000004" + "00000001") + "ffffffff" + ("00000001" + "61")); + + assertThat(tuple.getInt(0)).isEqualTo(1); + assertThat(tuple.isNull(1)).isTrue(); + assertThat(tuple.getString(2)).isEqualTo("a"); + + Mockito.verify(intCodec) + .decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); + Mockito.verifyZeroInteractions(doubleCodec); + Mockito.verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); + } + + @Test + public void should_format_null_tuple() { + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_format_tuple() { + TupleValue tuple = tupleType.newValue(); + tuple.setInt(0, 1); + tuple.setToNull(1); + tuple.setString(2, "a"); + + assertThat(format(tuple)).isEqualTo("(1,NULL,'a')"); + + Mockito.verify(intCodec).format(1); + Mockito.verify(doubleCodec).format(null); + Mockito.verify(textCodec).format("a"); + } + + @Test + public void should_parse_null_tuple() { + assertThat(parse(null)).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("NULL")).isNull(); + } + + @Test + public void should_parse_tuple() { + TupleValue tuple = parse("(1,NULL,'a')"); + + assertThat(tuple.getInt(0)).isEqualTo(1); + assertThat(tuple.isNull(1)); + assertThat(tuple.getString(2)).isEqualTo("a"); + + Mockito.verify(intCodec).parse("1"); + Mockito.verify(doubleCodec).parse("NULL"); + Mockito.verify(textCodec).parse("'a'"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a tuple"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java new file mode 100644 index 00000000000..ccd83444caa --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.type.DefaultUserDefinedType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UdtCodecTest extends CodecTestBase { + + @Mock private AttachmentPoint attachmentPoint; + @Mock private CodecRegistry codecRegistry; + private PrimitiveIntCodec intCodec; + private TypeCodec doubleCodec; + private TypeCodec textCodec; + + private UserDefinedType userType; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + + intCodec = Mockito.spy(TypeCodecs.INT); + doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); + textCodec = Mockito.spy(TypeCodecs.TEXT); + + // Called by the getters/setters + Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) + .thenAnswer(i -> doubleCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + + // Called by format/parse + Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); + + userType = + new DefaultUserDefinedType( + CqlIdentifier.fromInternal("ks"), + CqlIdentifier.fromInternal("type"), + ImmutableList.of( + CqlIdentifier.fromInternal("field1"), + CqlIdentifier.fromInternal("field2"), + CqlIdentifier.fromInternal("field3")), + ImmutableList.of(DataTypes.INT, DataTypes.DOUBLE, DataTypes.TEXT), + attachmentPoint); + + codec = TypeCodecs.udtOf(userType); + } + + @Test + public void should_encode_null_udt() { + assertThat(encode(null)).isNull(); + } + + @Test + public void should_encode_udt() { + UdtValue udt = userType.newValue(); + udt.setInt("field1", 1); + udt.setToNull("field2"); + udt.setString("field3", "a"); + + assertThat(encode(udt)) + .isEqualTo( + "0x" + + ("00000004" + "00000001") // size and contents of field 0 + + "ffffffff" // null field 1 + + ("00000001" + "61") // size and contents of field 2 + ); + + Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + // null values are handled directly in the udt codec, without calling the child codec: + Mockito.verifyZeroInteractions(doubleCodec); + Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + } + + @Test + public void should_decode_null_udt() { + assertThat(decode(null)).isNull(); + } + + @Test + public void should_decode_udt() { + UdtValue udt = decode("0x" + ("00000004" + "00000001") + "ffffffff" + ("00000001" + "61")); + + assertThat(udt.getInt(0)).isEqualTo(1); + assertThat(udt.isNull(1)).isTrue(); + assertThat(udt.getString(2)).isEqualTo("a"); + + Mockito.verify(intCodec) + .decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); + Mockito.verifyZeroInteractions(doubleCodec); + Mockito.verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); + } + + @Test + public void should_format_null_udt() { + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_format_udt() { + UdtValue udt = userType.newValue(); + udt.setInt(0, 1); + udt.setToNull(1); + udt.setString(2, "a"); + + assertThat(format(udt)).isEqualTo("{field1:1,field2:NULL,field3:'a'}"); + + Mockito.verify(intCodec).format(1); + Mockito.verify(doubleCodec).format(null); + Mockito.verify(textCodec).format("a"); + } + + @Test + public void should_parse_null_udt() { + assertThat(parse(null)).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("NULL")).isNull(); + } + + @Test + public void should_parse_udt() { + UdtValue udt = parse("{field1:1,field2:NULL,field3:'a'}"); + + assertThat(udt.getInt(0)).isEqualTo(1); + assertThat(udt.isNull(1)); + assertThat(udt.getString(2)).isEqualTo("a"); + + Mockito.verify(intCodec).parse("1"); + Mockito.verify(doubleCodec).parse("NULL"); + Mockito.verify(textCodec).parse("'a'"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a udt"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java new file mode 100644 index 00000000000..0b0e820ac65 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.util.UUID; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UuidCodecTest extends CodecTestBase { + private final UUID MOCK_UUID = new UUID(2L, 1L); + + public UuidCodecTest() { + this.codec = TypeCodecs.UUID; + } + + @Test + public void should_encode() { + assertThat(encode(MOCK_UUID)).isEqualTo("0x00000000000000020000000000000001"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + UUID decoded = decode("0x00000000000000020000000000000001"); + assertThat(decoded.getMostSignificantBits()).isEqualTo(2L); + assertThat(decoded.getLeastSignificantBits()).isEqualTo(1L); + + assertThat(decode(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + decode("0x0000"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + decode("0x00000000000000020000000000000001" + "0000"); + } + + @Test + public void should_format() { + assertThat(format(MOCK_UUID)).isEqualTo("00000000-0000-0002-0000-000000000001"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("00000000-0000-0002-0000-000000000001")).isEqualTo(MOCK_UUID); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a uuid"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java new file mode 100644 index 00000000000..927ecd87981 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec; + +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import java.math.BigInteger; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class VarintCodecTest extends CodecTestBase { + + public VarintCodecTest() { + this.codec = TypeCodecs.VARINT; + } + + @Test + public void should_encode() { + assertThat(encode(BigInteger.ONE)).isEqualTo("0x01"); + assertThat(encode(BigInteger.valueOf(128))).isEqualTo("0x0080"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + assertThat(decode("0x01")).isEqualTo(BigInteger.ONE); + assertThat(decode("0x0080")).isEqualTo(BigInteger.valueOf(128)); + assertThat(decode("0x")).isNull(); + assertThat(decode(null)).isNull(); + } + + @Test + public void should_format() { + assertThat(format(BigInteger.ONE)).isEqualTo("1"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + public void should_parse() { + assertThat(parse("1")).isEqualTo(BigInteger.ONE); + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void should_fail_to_parse_invalid_input() { + parse("not a varint"); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java new file mode 100644 index 00000000000..9c5bf9ff24c --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type.codec.registry; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.TupleValue; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.ListType; +import com.datastax.oss.driver.api.type.MapType; +import com.datastax.oss.driver.api.type.SetType; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.internal.type.UserDefinedTypeBuilder; +import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Period; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.fail; + +public class CachingCodecRegistryTest { + + @Mock private BiConsumer> onCacheLookup; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void should_find_primitive_codecs_for_types() { + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + checkPrimitiveMappings(registry, TypeCodecs.BOOLEAN); + checkPrimitiveMappings(registry, TypeCodecs.TINYINT); + checkPrimitiveMappings(registry, TypeCodecs.DOUBLE); + checkPrimitiveMappings(registry, TypeCodecs.COUNTER); + checkPrimitiveMappings(registry, TypeCodecs.FLOAT); + checkPrimitiveMappings(registry, TypeCodecs.INT); + checkPrimitiveMappings(registry, TypeCodecs.BIGINT); + checkPrimitiveMappings(registry, TypeCodecs.SMALLINT); + checkPrimitiveMappings(registry, TypeCodecs.TIMESTAMP); + checkPrimitiveMappings(registry, TypeCodecs.DATE); + checkPrimitiveMappings(registry, TypeCodecs.TIME); + checkPrimitiveMappings(registry, TypeCodecs.BLOB); + checkPrimitiveMappings(registry, TypeCodecs.TEXT); + checkPrimitiveMappings(registry, TypeCodecs.ASCII); + checkPrimitiveMappings(registry, TypeCodecs.VARINT); + checkPrimitiveMappings(registry, TypeCodecs.DECIMAL); + checkPrimitiveMappings(registry, TypeCodecs.UUID); + checkPrimitiveMappings(registry, TypeCodecs.TIMEUUID); + checkPrimitiveMappings(registry, TypeCodecs.INET); + checkPrimitiveMappings(registry, TypeCodecs.DURATION); + // Primitive mappings never hit the cache + Mockito.verifyZeroInteractions(onCacheLookup); + } + + private void checkPrimitiveMappings(TestCachingCodecRegistry registry, TypeCodec codec) { + DataType cqlType = codec.getCqlType(); + GenericType javaType = codec.getJavaType(); + + assertThat(registry.codecFor(cqlType, javaType)).isSameAs(codec); + assertThat(registry.codecFor(cqlType)).isSameAs(codec); + + assertThat(javaType.__getToken().getType()).isInstanceOf(Class.class); + Class javaClass = (Class) javaType.__getToken().getType(); + assertThat(registry.codecFor(cqlType, javaClass)).isSameAs(codec); + } + + @Test + public void should_find_primitive_codecs_for_value() throws Exception { + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + assertThat(registry.codecFor(true)).isEqualTo(TypeCodecs.BOOLEAN); + assertThat(registry.codecFor((byte) 0)).isEqualTo(TypeCodecs.TINYINT); + assertThat(registry.codecFor(0.0)).isEqualTo(TypeCodecs.DOUBLE); + assertThat(registry.codecFor(0.0f)).isEqualTo(TypeCodecs.FLOAT); + assertThat(registry.codecFor(0)).isEqualTo(TypeCodecs.INT); + assertThat(registry.codecFor(0L)).isEqualTo(TypeCodecs.BIGINT); + assertThat(registry.codecFor((short) 0)).isEqualTo(TypeCodecs.SMALLINT); + assertThat(registry.codecFor(Instant.EPOCH)).isEqualTo(TypeCodecs.TIMESTAMP); + assertThat(registry.codecFor(LocalDate.MIN)).isEqualTo(TypeCodecs.DATE); + assertThat(registry.codecFor(LocalTime.MIDNIGHT)).isEqualTo(TypeCodecs.TIME); + assertThat(registry.codecFor(ByteBuffer.allocate(0))).isEqualTo(TypeCodecs.BLOB); + assertThat(registry.codecFor("")).isEqualTo(TypeCodecs.TEXT); + assertThat(registry.codecFor(BigInteger.ONE)).isEqualTo(TypeCodecs.VARINT); + assertThat(registry.codecFor(BigDecimal.ONE)).isEqualTo(TypeCodecs.DECIMAL); + assertThat(registry.codecFor(new UUID(2L, 1L))).isEqualTo(TypeCodecs.UUID); + assertThat(registry.codecFor(InetAddress.getByName("127.0.0.1"))).isEqualTo(TypeCodecs.INET); + assertThat(registry.codecFor(CqlDuration.newInstance(1, 2, 3))).isEqualTo(TypeCodecs.DURATION); + Mockito.verifyZeroInteractions(onCacheLookup); + } + + @Test + public void should_find_user_codec_for_built_in_java_type() { + // int and String are built-in types, but int <-> String is not a built-in mapping + CqlIntToStringCodec intToStringCodec1 = new CqlIntToStringCodec(); + // register a second codec to also check that the first one is preferred + CqlIntToStringCodec intToStringCodec2 = new CqlIntToStringCodec(); + TestCachingCodecRegistry registry = + new TestCachingCodecRegistry(onCacheLookup, intToStringCodec1, intToStringCodec2); + + // When the mapping is not ambiguous, the user type should be returned + assertThat(registry.codecFor(DataTypes.INT, GenericType.STRING)).isSameAs(intToStringCodec1); + assertThat(registry.codecFor(DataTypes.INT, String.class)).isSameAs(intToStringCodec1); + + // When there is an ambiguity with a built-in codec, the built-in codec should have priority + assertThat(registry.codecFor(DataTypes.INT)).isSameAs(TypeCodecs.INT); + assertThat(registry.codecFor("")).isSameAs(TypeCodecs.TEXT); + + Mockito.verifyZeroInteractions(onCacheLookup); + } + + @Test + public void should_find_user_codec_for_custom_java_type() { + TextToPeriodCodec textToPeriodCodec1 = new TextToPeriodCodec(); + TextToPeriodCodec textToPeriodCodec2 = new TextToPeriodCodec(); + TestCachingCodecRegistry registry = + new TestCachingCodecRegistry(onCacheLookup, textToPeriodCodec1, textToPeriodCodec2); + + assertThat(registry.codecFor(DataTypes.TEXT, GenericType.of(Period.class))) + .isSameAs(textToPeriodCodec1); + assertThat(registry.codecFor(DataTypes.TEXT, Period.class)).isSameAs(textToPeriodCodec1); + // Now even the search by value is not ambiguous + assertThat(registry.codecFor(Period.ofDays(1))).isSameAs(textToPeriodCodec1); + + // The search by CQL type only still returns the built-in codec + assertThat(registry.codecFor(DataTypes.TEXT)).isSameAs(TypeCodecs.TEXT); + + Mockito.verifyZeroInteractions(onCacheLookup); + } + + @Test + public void should_create_list_codec() { + ListType cqlType = DataTypes.listOf(DataTypes.listOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + List> value = ImmutableList.of(ImmutableList.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(cqlType, javaType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + // Cache lookup for the codec, and recursively for its subcodec + inOrder.verify(onCacheLookup).accept(cqlType, javaType); + inOrder + .verify(onCacheLookup) + .accept(DataTypes.listOf(DataTypes.INT), GenericType.listOf(GenericType.INTEGER)); + + codec = registry.codecFor(cqlType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, null); + inOrder.verify(onCacheLookup).accept(DataTypes.listOf(DataTypes.INT), null); + + codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(null, javaType); + inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void should_create_set_codec() { + SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + Set> value = ImmutableSet.of(ImmutableSet.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(cqlType, javaType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + // Cache lookup for the codec, and recursively for its subcodec + inOrder.verify(onCacheLookup).accept(cqlType, javaType); + inOrder + .verify(onCacheLookup) + .accept(DataTypes.setOf(DataTypes.INT), GenericType.setOf(GenericType.INTEGER)); + + codec = registry.codecFor(cqlType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, null); + inOrder.verify(onCacheLookup).accept(DataTypes.setOf(DataTypes.INT), null); + + codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(null, javaType); + inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void should_create_map_codec() { + MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT)); + GenericType>> javaType = + new GenericType>>() {}; + Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(cqlType, javaType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + // Cache lookup for the codec, and recursively for its subcodec + inOrder.verify(onCacheLookup).accept(cqlType, javaType); + inOrder + .verify(onCacheLookup) + .accept( + DataTypes.mapOf(DataTypes.INT, DataTypes.INT), + GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER)); + + codec = registry.codecFor(cqlType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, null); + inOrder.verify(onCacheLookup).accept(DataTypes.mapOf(DataTypes.INT, DataTypes.INT), null); + + codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(null, javaType); + inOrder + .verify(onCacheLookup) + .accept(null, GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void should_create_tuple_codec() { + TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT)); + TupleValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec codec = registry.codecFor(cqlType, GenericType.TUPLE_VALUE); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.canEncode(TupleValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, GenericType.TUPLE_VALUE); + // field codecs are only looked up when fields are accessed, so no cache hit for list now + + codec = registry.codecFor(cqlType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.canEncode(TupleValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, null); + + codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.canEncode(TupleValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, GenericType.TUPLE_VALUE); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void should_create_udt_codec() { + UserDefinedType cqlType = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.listOf(DataTypes.TEXT)) + .build(); + UdtValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec codec = registry.codecFor(cqlType, GenericType.UDT_VALUE); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.canEncode(UdtValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, GenericType.UDT_VALUE); + // field codecs are only looked up when fields are accessed, so no cache hit for list now + + codec = registry.codecFor(cqlType); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.canEncode(UdtValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, null); + + codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.canEncode(UdtValue.class)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + inOrder.verify(onCacheLookup).accept(cqlType, GenericType.UDT_VALUE); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void should_not_find_codec_if_java_type_unknown() { + try { + CodecRegistry.DEFAULT.codecFor(StringBuilder.class); + fail("Should not have found a codec for ANY <-> StringBuilder"); + } catch (UncheckedExecutionException e) { + // expected + } + try { + CodecRegistry.DEFAULT.codecFor(DataTypes.TEXT, StringBuilder.class); + fail("Should not have found a codec for varchar <-> StringBuilder"); + } catch (UncheckedExecutionException e) { + // expected + } + try { + CodecRegistry.DEFAULT.codecFor(new StringBuilder()); + fail("Should not have found a codec for ANY <-> StringBuilder"); + } catch (UncheckedExecutionException e) { + // expected + } + } + + // Our intent is not to test Guava cache, so we don't need an actual cache here. + // The only thing we want to check in our tests is if getCachedCodec was called. + public static class TestCachingCodecRegistry extends CachingCodecRegistry { + private final BiConsumer> onCacheLookup; + + public TestCachingCodecRegistry( + BiConsumer> onCacheLookup, TypeCodec... userCodecs) { + super(userCodecs); + this.onCacheLookup = onCacheLookup; + } + + @Override + protected TypeCodec getCachedCodec(DataType cqlType, GenericType javaType) { + onCacheLookup.accept(cqlType, javaType); + return createCodec(cqlType, javaType); + } + } + + public static class TextToPeriodCodec implements TypeCodec { + @Override + public GenericType getJavaType() { + return GenericType.of(Period.class); + } + + @Override + public DataType getCqlType() { + return DataTypes.TEXT; + } + + @Override + public ByteBuffer encode(Period value, ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("not implemented for this test"); + } + + @Override + public Period decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("not implemented for this test"); + } + + @Override + public String format(Period value) { + throw new UnsupportedOperationException("not implemented for this test"); + } + + @Override + public Period parse(String value) { + throw new UnsupportedOperationException("not implemented for this test"); + } + } +} From c05e014dcf8569de79c78cb70119f8e4dd806de2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Apr 2017 13:56:17 -0700 Subject: [PATCH 033/742] Optimize codec registry hot path - test UDT and tuple after primitives in codecFor(Object) - use instanceof in canEncode(Object) where possible - use Class#isAssignableFrom or `==` in canEncode(Class) where possible --- .../oss/driver/api/type/codec/TypeCodec.java | 20 +++++++++++++------ .../internal/type/codec/BigIntCodec.java | 10 ++++++++++ .../driver/internal/type/codec/BlobCodec.java | 10 ++++++++++ .../internal/type/codec/BooleanCodec.java | 10 ++++++++++ .../internal/type/codec/CqlDurationCodec.java | 10 ++++++++++ .../internal/type/codec/CustomCodec.java | 10 ++++++++++ .../driver/internal/type/codec/DateCodec.java | 10 ++++++++++ .../internal/type/codec/DecimalCodec.java | 10 ++++++++++ .../internal/type/codec/DoubleCodec.java | 10 ++++++++++ .../internal/type/codec/FloatCodec.java | 10 ++++++++++ .../driver/internal/type/codec/InetCodec.java | 10 ++++++++++ .../driver/internal/type/codec/IntCodec.java | 10 ++++++++++ .../internal/type/codec/SmallIntCodec.java | 10 ++++++++++ .../internal/type/codec/StringCodec.java | 10 ++++++++++ .../driver/internal/type/codec/TimeCodec.java | 10 ++++++++++ .../internal/type/codec/TimeUuidCodec.java | 8 +++++++- .../internal/type/codec/TimestampCodec.java | 10 ++++++++++ .../internal/type/codec/TinyIntCodec.java | 10 ++++++++++ .../internal/type/codec/TupleCodec.java | 5 +++++ .../driver/internal/type/codec/UdtCodec.java | 8 ++++++-- .../driver/internal/type/codec/UuidCodec.java | 10 ++++++++++ .../internal/type/codec/VarIntCodec.java | 10 ++++++++++ .../codec/registry/CachingCodecRegistry.java | 14 ++++++------- 23 files changed, 219 insertions(+), 16 deletions(-) diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java index 5cb450dee12..0e2dccd2252 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java @@ -49,10 +49,15 @@ default boolean canEncode(GenericType javaType) { * *

The default implementation wraps the class in a generic type and calls {@link * #canEncode(GenericType)}, therefore it is invariant as well. + * + *

Implementors are encouraged to override this method if there is a more efficient way. In + * particular, if the codec targets a non-generic class, the check can be done with {@code + * Class#isAssignableFrom}. Or even better, with {@code ==} if that class also happens to be + * final. */ - default boolean canEncode(Class javaType) { - Preconditions.checkNotNull(javaType); - return canEncode(GenericType.of(javaType)); + default boolean canEncode(Class javaClass) { + Preconditions.checkNotNull(javaClass); + return canEncode(GenericType.of(javaClass)); } /** @@ -72,10 +77,13 @@ default boolean canEncode(Class javaType) { *

Similarly, codecs that only accept a partial subset of all possible values must override * this method and manually inspect the object to check if it complies or not with the codec's * limitations. + * + *

Finally, if the codec targets a non-generic Java class, it might be possible to implement + * this method with a simple {@code instanceof} check. */ - default boolean canEncode(Object object) { - Preconditions.checkNotNull(object); - return getJavaType().__getToken().isSupertypeOf(TypeToken.of(object.getClass())); + default boolean canEncode(Object value) { + Preconditions.checkNotNull(value); + return getJavaType().__getToken().isSupertypeOf(TypeToken.of(value.getClass())); } /** Whether this codec is capable of decoding the given CQL type. */ diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java index e9d1079806a..19df4f389e1 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java @@ -33,6 +33,16 @@ public DataType getCqlType() { return DataTypes.BIGINT; } + @Override + public boolean canEncode(Object value) { + return value instanceof Long; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Long.class; + } + @Override public ByteBuffer encodePrimitive(long value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(8); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java index 7bd26b72c41..7756f1c6fc9 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java @@ -34,6 +34,16 @@ public DataType getCqlType() { return DataTypes.BLOB; } + @Override + public boolean canEncode(Object value) { + return value instanceof ByteBuffer; + } + + @Override + public boolean canEncode(Class javaClass) { + return ByteBuffer.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) { return (value == null) ? null : value.duplicate(); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java index 6dc48a16942..92c6f6c9369 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java @@ -37,6 +37,16 @@ public DataType getCqlType() { return DataTypes.BOOLEAN; } + @Override + public boolean canEncode(Object value) { + return value instanceof Boolean; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Boolean.class; + } + @Override public ByteBuffer encodePrimitive(boolean value, ProtocolVersion protocolVersion) { return value ? TRUE.duplicate() : FALSE.duplicate(); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java index 156ce1c912f..bbf13b801b4 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java @@ -40,6 +40,16 @@ public DataType getCqlType() { return DataTypes.DURATION; } + @Override + public boolean canEncode(Object value) { + return value instanceof CqlDuration; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == CqlDuration.class; + } + @Override public ByteBuffer encode(CqlDuration value, ProtocolVersion protocolVersion) { if (value == null) { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java index 3a1ebb0c6a7..75d79454396 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java @@ -41,6 +41,16 @@ public DataType getCqlType() { return cqlType; } + @Override + public boolean canEncode(Object value) { + return value instanceof ByteBuffer; + } + + @Override + public boolean canEncode(Class javaClass) { + return ByteBuffer.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(ByteBuffer value, ProtocolVersion protocolVersion) { return (value == null) ? null : value.duplicate(); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java index ba4457b1bca..da77b7a0f87 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java @@ -43,6 +43,16 @@ public DataType getCqlType() { return DataTypes.DATE; } + @Override + public boolean canEncode(Object value) { + return value instanceof LocalDate; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == LocalDate.class; + } + @Override public ByteBuffer encode(LocalDate value, ProtocolVersion protocolVersion) { if (value == null) { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java index aab150a790a..c728fe87537 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java @@ -35,6 +35,16 @@ public DataType getCqlType() { return DataTypes.DECIMAL; } + @Override + public boolean canEncode(Object value) { + return value instanceof BigDecimal; + } + + @Override + public boolean canEncode(Class javaClass) { + return BigDecimal.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(BigDecimal value, ProtocolVersion protocolVersion) { if (value == null) { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java index 1555e351cf0..f0a251673f7 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java @@ -33,6 +33,16 @@ public DataType getCqlType() { return DataTypes.DOUBLE; } + @Override + public boolean canEncode(Object value) { + return value instanceof Double; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Double.class; + } + @Override public ByteBuffer encodePrimitive(double value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(8); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java index 57276955fa4..bd817fcedf0 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java @@ -33,6 +33,16 @@ public DataType getCqlType() { return DataTypes.FLOAT; } + @Override + public boolean canEncode(Object value) { + return value instanceof Float; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Float.class; + } + @Override public ByteBuffer encodePrimitive(float value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(4); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java index dd953996e84..d2cfd6bf636 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java @@ -37,6 +37,16 @@ public DataType getCqlType() { return DataTypes.INET; } + @Override + public boolean canEncode(Object value) { + return value instanceof InetAddress; + } + + @Override + public boolean canEncode(Class javaClass) { + return InetAddress.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(InetAddress value, ProtocolVersion protocolVersion) { return (value == null) ? null : ByteBuffer.wrap(value.getAddress()); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java index 50790614c33..8c7cd44b47a 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java @@ -34,6 +34,16 @@ public DataType getCqlType() { return DataTypes.INT; } + @Override + public boolean canEncode(Object value) { + return value instanceof Integer; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Integer.class; + } + @Override public ByteBuffer encodePrimitive(int value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(4); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java index fe504ab64ea..5709c7d268f 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java @@ -33,6 +33,16 @@ public DataType getCqlType() { return DataTypes.SMALLINT; } + @Override + public boolean canEncode(Object value) { + return value instanceof Short; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Short.class; + } + @Override public ByteBuffer encodePrimitive(short value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(2); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java index d8662484f57..4944f8991f2 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java @@ -44,6 +44,16 @@ public DataType getCqlType() { return cqlType; } + @Override + public boolean canEncode(Object value) { + return value instanceof String; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == String.class; + } + @Override public ByteBuffer encode(String value, ProtocolVersion protocolVersion) { return (value == null) ? null : ByteBuffer.wrap(value.getBytes(charset)); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java index cb7ebb1af51..0a8fae74165 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java @@ -40,6 +40,16 @@ public DataType getCqlType() { return DataTypes.TIME; } + @Override + public boolean canEncode(Object value) { + return value instanceof LocalTime; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == LocalTime.class; + } + @Override public ByteBuffer encode(LocalTime value, ProtocolVersion protocolVersion) { return (value == null) diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java index b4712c4c2bd..128a4b64e7b 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.reflect.GenericType; import java.nio.ByteBuffer; import java.util.UUID; @@ -29,7 +30,12 @@ public DataType getCqlType() { @Override public boolean canEncode(Object value) { - return super.canEncode(value) && ((UUID) value).version() == 1; + return value instanceof UUID && ((UUID) value).version() == 1; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == UUID.class; } @Override diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java index 282bdc2f80e..ca6d2b71275 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java @@ -71,6 +71,16 @@ public DataType getCqlType() { return DataTypes.TIMESTAMP; } + @Override + public boolean canEncode(Object value) { + return value instanceof Instant; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Instant.class; + } + @Override public ByteBuffer encode(Instant value, ProtocolVersion protocolVersion) { return (value == null) diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java index 2c085c5b3d5..27d5431c217 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java @@ -33,6 +33,16 @@ public DataType getCqlType() { return DataTypes.TINYINT; } + @Override + public boolean canEncode(Object value) { + return value instanceof Byte; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == Byte.class; + } + @Override public ByteBuffer encodePrimitive(byte value, ProtocolVersion protocolVersion) { ByteBuffer bytes = ByteBuffer.allocate(1); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java index 3c717aba07d..0f2cb6682db 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java @@ -48,6 +48,11 @@ public boolean canEncode(Object value) { return (value instanceof TupleValue) && ((TupleValue) value).getType().equals(cqlType); } + @Override + public boolean canEncode(Class javaClass) { + return TupleValue.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(TupleValue value, ProtocolVersion protocolVersion) { if (value == null) { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java index 18cb48021f5..6c380fce859 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java @@ -46,8 +46,12 @@ public DataType getCqlType() { @Override public boolean canEncode(Object value) { - return UdtValue.class.isAssignableFrom(value.getClass()) - && ((UdtValue) value).getType().equals(cqlType); + return value instanceof UdtValue && ((UdtValue) value).getType().equals(cqlType); + } + + @Override + public boolean canEncode(Class javaClass) { + return UdtValue.class.isAssignableFrom(javaClass); } @Override diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java index bd85b5f5b49..85d7833c06e 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java @@ -34,6 +34,16 @@ public DataType getCqlType() { return DataTypes.UUID; } + @Override + public boolean canEncode(Object value) { + return value instanceof UUID; + } + + @Override + public boolean canEncode(Class javaClass) { + return javaClass == UUID.class; + } + @Override public ByteBuffer encode(UUID value, ProtocolVersion protocolVersion) { if (value == null) { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java index eb61839d3ec..fa770bb9f8a 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java @@ -35,6 +35,16 @@ public DataType getCqlType() { return DataTypes.VARINT; } + @Override + public boolean canEncode(Object value) { + return value instanceof BigInteger; + } + + @Override + public boolean canEncode(Class javaClass) { + return BigInteger.class.isAssignableFrom(javaClass); + } + @Override public ByteBuffer encode(BigInteger value, ProtocolVersion protocolVersion) { return (value == null) ? null : ByteBuffer.wrap(value.toByteArray()); diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java index 3debdb444a9..d5c44c2281d 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java @@ -132,13 +132,6 @@ public TypeCodec codecFor(T value) { Preconditions.checkNotNull(value); LOG.trace("Looking up codec for object {}", value); - // Quick check for two trivial cases - if (value instanceof TupleValue) { - return safeCast(codecFor(((TupleValue) value).getType(), TupleValue.class)); - } else if (value instanceof UdtValue) { - return safeCast(codecFor(((UdtValue) value).getType(), UdtValue.class)); - } - for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { if (primitiveCodec.canEncode(value)) { LOG.trace("Found matching primitive codec {}", primitiveCodec); @@ -151,6 +144,13 @@ public TypeCodec codecFor(T value) { return safeCast(userCodec); } } + + if (value instanceof TupleValue) { + return safeCast(codecFor(((TupleValue) value).getType(), TupleValue.class)); + } else if (value instanceof UdtValue) { + return safeCast(codecFor(((UdtValue) value).getType(), UdtValue.class)); + } + GenericType javaType = inspectType(value); LOG.trace("Continuing based on inferred type {}", javaType); return safeCast(getCachedCodec(null, javaType)); From 8c6479a85b46149554738b93561d86fa8194040a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Apr 2017 15:23:07 -0700 Subject: [PATCH 034/742] Initialize contributing guidelines --- CONTRIBUTING.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..ee48d1d55aa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,115 @@ +# Contributing guidelines + +## Code formatting + +We follow the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). See +https://github.com/google/google-java-format for IDE plugins. The rules are not configurable. + +The build will fail if the code is not formatted. To format all files from the command line, run: + +``` +mvn fmt:format -Dformat.validateOnly=false +``` + +Some aspects are not covered by the formatter: +* imports: please configure your IDE to follow the guide (no wildcard imports, normal imports + in ASCII sort order come first, followed by a blank line, followed by static imports in ASCII + sort order). +* braces must be used with `if`, `else`, `for`, `do` and `while` statements, even when the body is + empty or contains only a single statement. +* implementation comments: wrap them to respect the column limit of 100 characters. +* XML files: indent with two spaces and wrap to respect the column limit of 100 characters. + + +## Coding style -- production code + +Do not use static imports. They make things harder to understand when you look at the code +someplace where you don't have IDE support, like Github's code view. + +Avoid abbreviations in class and variable names. A good rule of thumb is that you should only use +them if you would also do so verbally, for example "id" and "config" are probably reasonable. +Single-letter variables are permissible if the variable scope is only a few lines, or for commonly +understood cases (like `i` for a loop index). + +Keep source files short. Short files are easy to understand and test. The average should probably +be around 200-300 lines. + +### Javadoc + +All types in "API" packages must be documented. For "internal" packages, documentation is optional, +but in no way discouraged: it's generally a good idea to have a class-level comment that explains +where the component fits in the architecture, and anything else that you feel is important. + +You don't need to document every parameter or return type, or even every method. Don't document +something if it is completely obvious, we don't want to end up with this: + +```java +/** + * Returns the name. + * + * @return the name + */ +String getName(); +``` + +On the other hand, there is often something useful to say about a method, so most should have at +least a one-line comment. Use common sense. + +Driver users coding in their IDE should find the right documentation at the right time. Try to +think of how they will come into contact with the class. For example, if a type is constructed with +a builder, each builder method should probably explain what the default is when you don't call it. + +Avoid using too many links, they can make comments harder to read, especially in the IDE. Link to a +type the first time it's mentioned, then use a text description ("this registry"...) or an `@code` +block. Don't link to a class in its own documentation. Don't link to types that appear right below +in the documented item's signature. + +```java +/** +* @return this {@link Builder} <-- completely unnecessary +*/ +Builder withLimit(int limit) { +``` + + +## Coding style -- test code + +Static imports are permitted in a couple of places: +* AssertJ's `assertThat` / `fail`. +* Some Mockito methods, provided that you're already using a non-statically imported method at the + beginning of the line. For example: + ```java + // any and eq are statically imported, it's pretty clear that they at least relate to Mockito + Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + ``` + +Test methods names use lower snake case, generally start with `should`, and clearly indicate the +purpose of the test, for example: `should_fail_if_key_already_exists`. If you have trouble coming +up with a simple name, it might be a sign that your test does too much, and should be split. + +We use AssertJ (`assertThat`) for assertions. Don't use TestNG's assertions (`assertEquals`, +`assertNull`, etc). + +Don't try to generify at all cost: a bit of duplication is acceptable, if that helps keep the tests +simple to understand (a newcomer should be able to understand how to fix a failing test without +having to read too much code). + +## License headers + +The build will fail if some license headers are missing. To update all files from the command line, +run: + +``` +mvn license:format +``` + +## Pre-commit hook (highly recommended) + +Ensure `pre-commit.sh` is executable, then run: + +``` +ln -s ../../pre-commit.sh .git/hooks/pre-commit +``` + +This will only allow commits if the tests pass. It is also a good reminder to keep the test suite +short. From 658419da1725e4632a159d25a33d93f9a2f41a36 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 08:57:00 -0700 Subject: [PATCH 035/742] Add cluster shutdown Introduce AsyncAutoCloseable and have all driver components implement it. Note: user-provided components like LBP and AddressTranslator are not closed yet. --- .../driver/api/core/AsyncAutoCloseable.java | 65 ++++ .../datastax/oss/driver/api/core/Cluster.java | 4 +- .../driver/internal/core/DefaultCluster.java | 124 +++++- .../core/context/DefaultNettyOptions.java | 2 +- .../internal/core/context/NettyOptions.java | 2 +- .../core/control/ControlConnection.java | 18 +- .../core/metadata/DefaultTopologyMonitor.java | 33 +- .../core/metadata/MetadataManager.java | 48 ++- .../core/metadata/NodeStateManager.java | 361 ++++++++++-------- .../core/metadata/TopologyMonitor.java | 3 +- .../internal/core/pool/ChannelPool.java | 29 +- .../core/util/concurrent/Debouncer.java | 16 + .../core/control/ControlConnectionTest.java | 10 +- .../metadata/DefaultTopologyMonitorTest.java | 13 + .../core/metadata/MetadataManagerTest.java | 10 +- .../core/metadata/NodeStateManagerTest.java | 13 + .../internal/core/pool/ChannelPoolTest.java | 4 +- .../core/util/concurrent/DebouncerTest.java | 32 ++ 18 files changed, 579 insertions(+), 208 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java new file mode 100644 index 00000000000..b104c27e371 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.concurrent.CompletionStage; + +/** + * An object that can be closed in an asynchronous, non-blocking manner. + * + *

For convenience, this extends the JDK's {@code AutoCloseable} in order to be usable in + * try-with-resource blocks (in that case, the blocking {@link #close()} will be used). + */ +public interface AsyncAutoCloseable extends AutoCloseable { + + /** + * Returns a stage that will complete when {@link #close()} or {@link #forceCloseAsync()} is + * called, and the shutdown sequence completes. + */ + CompletionStage closeFuture(); + + /** + * Initiates an orderly shutdown: no new requests are accepted, but all pending requests are + * allowed to complete normally. + * + * @return a stage that will complete when the shutdown sequence is complete. Multiple calls to + * this method or {@link #forceCloseAsync()} always return the same instance. + */ + CompletionStage closeAsync(); + + /** + * Initiates a forced shutdown of this instance: no new requests are accepted, and all pending + * requests will complete with an exception. + * + * @return a stage that will complete when the shutdown sequence is complete. Multiple calls to + * this method or {@link #close()} always return the same instance. + */ + CompletionStage forceCloseAsync(); + + /** + * {@inheritDoc} + * + *

This method is implemented by calling {@link #closeAsync()} and blocking on the result. This + * should not be called on a driver thread. + */ + @Override + default void close() throws Exception { + BlockingOperation.checkNotDriverThread(); + CompletableFutures.getUninterruptibly(closeAsync().toCompletableFuture()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 737a6a4b202..1ab6904aa54 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -18,9 +18,10 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import java.util.concurrent.CompletionStage; /** An instance of the driver, that connects to a Cassandra cluster. */ -public interface Cluster { +public interface Cluster extends AsyncAutoCloseable { /** Returns a builder to create a new instance of the default implementation. */ static ClusterBuilder builder() { return new ClusterBuilder(); @@ -44,5 +45,6 @@ static ClusterBuilder builder() { */ Metadata getMetadata(); + /** Returns a context that provides access to all the policies used by this driver instance. */ DriverContext getContext(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 0f9eb9da933..5ad704cec6f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -15,17 +15,24 @@ */ package com.datastax.oss.driver.internal.core; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.google.common.collect.ImmutableList; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,15 +73,38 @@ public DriverContext getContext() { return context; } + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage forceCloseAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + return singleThreaded.closeFuture; + } + private class SingleThreaded { private final InternalDriverContext context; private final Set initialContactPoints; + private final NodeStateManager nodeStateManager; private final CompletableFuture initFuture = new CompletableFuture<>(); private boolean initWasCalled; + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; + private boolean forceCloseWasCalled; + private List> childrenCloseFutures; private SingleThreaded(InternalDriverContext context, Set contactPoints) { this.context = context; + this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; } @@ -85,8 +115,6 @@ private void init() { } initWasCalled = true; - new NodeStateManager(context); - // If any contact points were provided, store them in the metadata right away (the // control connection will need them if it has to initialize) MetadataManager metadataManager = context.metadataManager(); @@ -124,5 +152,97 @@ private void init() { return null; }); } + + private void close() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + + LOG.debug("Closing {}", this); + childrenCloseFutures = new ArrayList<>(); + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + LOG.debug("Closing {}", closeable); + childrenCloseFutures.add(closeable.closeAsync()); + } + CompletableFutures.whenAllDone(childrenCloseFutures) + .whenCompleteAsync(this::onChildrenClosed, adminExecutor); + } + + private void forceClose() { + assert adminExecutor.inEventLoop(); + if (forceCloseWasCalled) { + return; + } + forceCloseWasCalled = true; + + LOG.debug("Force-closing {} (was {}closed before)", this, (closeWasCalled ? "" : "not ")); + + if (closeWasCalled) { + // childrenCloseFutures is already created, and onChildrenClosed has already been called + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + LOG.debug("Force-closing {}", closeable); + closeable.forceCloseAsync(); + } + } else { + closeWasCalled = true; + childrenCloseFutures = new ArrayList<>(); + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + LOG.debug("Force-closing {}", closeable); + childrenCloseFutures.add(closeable.forceCloseAsync()); + } + CompletableFutures.whenAllDone(childrenCloseFutures) + .whenCompleteAsync(this::onChildrenClosed, adminExecutor); + } + } + + private void onChildrenClosed(@SuppressWarnings("unused") Void ignored, Throwable error) { + assert adminExecutor.inEventLoop(); + if (error != null) { + LOG.warn("Unexpected error while closing", error); + } + try { + for (CompletionStage future : childrenCloseFutures) { + warnIfFailed(future); + } + context + .nettyOptions() + .onClose() + .addListener( + f -> { + if (!f.isSuccess()) { + closeFuture.completeExceptionally(f.cause()); + } else { + closeFuture.complete(null); + } + }); + } catch (Throwable t) { + // Being paranoid here, but we don't want to risk swallowing an exception and leaving close + // hanging + LOG.warn("Unexpected error while closing", t); + } + } + + private void warnIfFailed(CompletionStage stage) { + CompletableFuture future = stage.toCompletableFuture(); + assert future.isDone(); + if (future.isCompletedExceptionally()) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + // InterruptedException can't happen actually, but including it to make compiler happy + LOG.warn("Unexpected error while closing", e.getCause()); + } + } + } + + private List internalComponentsToClose() { + return ImmutableList.of( + nodeStateManager, + metadataManager, + context.topologyMonitor(), + context.controlConnection()); + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index c3bf98c5f92..86060848c4a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -76,7 +76,7 @@ public void afterChannelInitialized(Channel channel) { } @Override - public Future onShutdown() { + public Future onClose() { return ioEventLoopGroup.shutdownGracefully(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 5186ae2244d..532741c20bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -79,5 +79,5 @@ public interface NettyOptions { * that you have allocated elsewhere in this component, for example shut down custom event loop * groups. */ - Future onShutdown(); + Future onClose(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 6ade97ee58e..728c8621dc9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.control; import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -57,7 +58,7 @@ *

If a custom {@link TopologyMonitor} is used, the control connection is used only for schema * refreshes; if schema metadata is also disabled, the control connection never initializes. */ -public class ControlConnection implements EventCallback { +public class ControlConnection implements EventCallback, AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(ControlConnection.class); private final InternalDriverContext context; @@ -103,8 +104,19 @@ public void reconnectNow() { RunOrSchedule.on(adminExecutor, singleThreaded::reconnectNow); } - /** Note: control queries are never critical, so there is no graceful close. */ - public CompletionStage forceClose() { + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage closeAsync() { + // Control queries are never critical, so there is no graceful close. + return forceCloseAsync(); + } + + @Override + public CompletionStage forceCloseAsync() { RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); return singleThreaded.closeFuture; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 6e5695d975b..edf16bc94b1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetAddress; @@ -54,23 +55,31 @@ public class DefaultTopologyMonitor implements TopologyMonitor { private final ControlConnection controlConnection; private final AddressTranslator addressTranslator; private final Duration timeout; + private final CompletableFuture closeFuture; @VisibleForTesting volatile int port = -1; public DefaultTopologyMonitor(InternalDriverContext context) { this.controlConnection = context.controlConnection(); - addressTranslator = context.addressTranslator(); + this.addressTranslator = context.addressTranslator(); DriverConfigProfile config = context.config().defaultProfile(); this.timeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); + this.closeFuture = new CompletableFuture<>(); } @Override public CompletionStage init() { + if (closeFuture.isDone()) { + return CompletableFutures.failedFuture(new IllegalStateException("closed")); + } return controlConnection.init(true); } @Override public CompletionStage> refreshNode(Node node) { + if (closeFuture.isDone()) { + return CompletableFutures.failedFuture(new IllegalStateException("closed")); + } LOG.debug("Refreshing info for {}", node); DriverChannel channel = controlConnection.channel(); if (node.getConnectAddress().equals(channel.address())) { @@ -93,6 +102,9 @@ public CompletionStage> refreshNode(Node node) { @Override public CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress) { + if (closeFuture.isDone()) { + return CompletableFutures.failedFuture(new IllegalStateException("closed")); + } LOG.debug("Fetching info for new node {}", connectAddress); DriverChannel channel = controlConnection.channel(); return query(channel, "SELECT * FROM system.peers") @@ -101,6 +113,9 @@ public CompletionStage> getNewNodeInfo(InetSocketAddress conn @Override public CompletionStage> refreshNodeList() { + if (closeFuture.isDone()) { + return CompletableFutures.failedFuture(new IllegalStateException("closed")); + } LOG.debug("Refreshing node list"); DriverChannel channel = controlConnection.channel(); savePort(channel); @@ -120,6 +135,22 @@ public CompletionStage> refreshNodeList() { }); } + @Override + public CompletionStage closeFuture() { + return closeFuture; + } + + @Override + public CompletionStage closeAsync() { + closeFuture.complete(null); + return closeFuture; + } + + @Override + public CompletionStage forceCloseAsync() { + return closeAsync(); + } + @VisibleForTesting protected CompletionStage query( DriverChannel channel, String queryString, Map parameters) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 061163bc75e..ed34d13c736 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -31,7 +32,7 @@ import org.slf4j.LoggerFactory; /** Holds the immutable instance of the {@link Metadata}, and handles requests to update it. */ -public class MetadataManager { +public class MetadataManager implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class); private final InternalDriverContext context; @@ -42,7 +43,7 @@ public class MetadataManager { public MetadataManager(InternalDriverContext context) { this.context = context; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(context); + this.singleThreaded = new SingleThreaded(); this.metadata = DefaultMetadata.EMPTY; } @@ -115,14 +116,27 @@ public void refreshSchema( // TODO refresh schema metadata } - // TODO user-controlled refresh, shutdown? + // TODO user-controlled refresh? - private class SingleThreaded { - private final InternalDriverContext context; + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } - private SingleThreaded(InternalDriverContext context) { - this.context = context; - } + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage forceCloseAsync() { + return this.closeAsync(); + } + + private class SingleThreaded { + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; private void initNodes( Set addresses, CompletableFuture initNodesFuture) { @@ -162,15 +176,25 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { private void removeNode(InetSocketAddress address) { refresh(new RemoveNodeRefresh(metadata, address)); } + + private void close() { + if (closeWasCalled) { + return; + } + closeWasCalled = true; + closeFuture.complete(null); + } } @VisibleForTesting Void refresh(MetadataRefresh refresh) { assert adminExecutor.inEventLoop(); - refresh.compute(); - metadata = refresh.newMetadata; - for (Object event : refresh.events) { - context.eventBus().fire(event); + if (!singleThreaded.closeWasCalled) { + refresh.compute(); + metadata = refresh.newMetadata; + for (Object event : refresh.events) { + context.eventBus().fire(event); + } } return null; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 6fcd86cd6ca..840eda1a9d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -29,6 +30,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,184 +41,228 @@ * *

See {@link NodeState} and {@link TopologyEvent} for a description of the state change rules. */ -public class NodeStateManager { +public class NodeStateManager implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(NodeStateManager.class); private final EventExecutor adminExecutor; - private final MetadataManager metadataManager; - private final EventBus eventBus; - private final Debouncer> topologyEventDebouncer; + private final SingleThreaded singleThreaded; public NodeStateManager(InternalDriverContext context) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.metadataManager = context.metadataManager(); - - DriverConfigProfile config = context.config().defaultProfile(); - this.topologyEventDebouncer = - new Debouncer<>( - adminExecutor, - this::coalesceTopologyEvents, - this::flushTopologyEvents, - config.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW), - config.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); - - this.eventBus = context.eventBus(); - this.eventBus.register( - ChannelEvent.class, RunOrSchedule.on(adminExecutor, this::onChannelEvent)); - this.eventBus.register( - TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); - // Note: this component exists for the whole life of the driver instance, so don't worry about - // unregistering the listeners. + this.singleThreaded = new SingleThreaded(context); } - private void onChannelEvent(ChannelEvent event) { - assert adminExecutor.inEventLoop(); - LOG.debug("Processing {}", event); - @SuppressWarnings("SuspiciousMethodCalls") - DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); - assert node != null; - switch (event.type) { - case OPENED: - node.openConnections += 1; - if (node.state == NodeState.DOWN || node.state == NodeState.UNKNOWN) { - setState(node, NodeState.UP, "a new connection was opened to it"); - } - break; - case CLOSED: - node.openConnections -= 1; - break; - case RECONNECTION_STARTED: - node.reconnections += 1; - if (node.openConnections == 0) { - setState(node, NodeState.DOWN, "it has no connections and started reconnecting"); - } - break; - case RECONNECTION_STOPPED: - node.reconnections -= 1; - break; - } + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; } - private void onDebouncedTopologyEvent(TopologyEvent event) { - assert adminExecutor.inEventLoop(); - LOG.debug("Processing {}", event); - DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); - switch (event.type) { - case SUGGEST_UP: - if (node == null) { - LOG.debug("Received UP event for unknown node {}, adding it", event.address); - metadataManager.addNode(event.address); - } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("Not setting {} UP because it is FORCED_DOWN", node); - } else { - setState(node, NodeState.UP, "an UP topology event was received"); - } - break; - case SUGGEST_DOWN: - if (node == null) { - LOG.debug("Received DOWN event for unknown node {}, ignoring it", event.address); - } else if (node.openConnections > 0) { - LOG.debug("Not setting {} DOWN because it still has active connections", node); - } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("Not setting {} DOWN because it is FORCED_DOWN", node); - } else { - setState(node, NodeState.DOWN, "a DOWN topology event was received"); - } - break; - case FORCE_UP: - if (node == null) { - LOG.debug("Received FORCE_UP event for unknown node {}, adding it", event.address); - metadataManager.addNode(event.address); - } else { - setState(node, NodeState.UP, "a FORCE_UP topology event was received"); - } - break; - case FORCE_DOWN: - if (node == null) { - LOG.debug("Received FORCE_DOWN event for unknown node {}, ignoring it", event.address); - } else { - setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); - } - break; - case SUGGEST_ADDED: - if (node != null) { - LOG.debug( - "Received ADDED event for {} but it is already in our metadata, ignoring", node); - } else { - metadataManager.addNode(event.address); - } - break; - case SUGGEST_REMOVED: - if (node == null) { - LOG.debug( - "Received REMOVED event for {} but it is not in our metadata, ignoring", - event.address); - } else { - metadataManager.removeNode(event.address); - } - break; - } + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; } - // Called by the event bus, needs debouncing - private void onTopologyEvent(TopologyEvent event) { - assert adminExecutor.inEventLoop(); - topologyEventDebouncer.receive(event); + @Override + public CompletionStage forceCloseAsync() { + return closeAsync(); } - // Called to process debounced events before flushing - private Collection coalesceTopologyEvents(List events) { - assert adminExecutor.inEventLoop(); - Collection result; - if (events.size() == 1) { - result = events; - } else { - // Keep the last FORCE* event for each node, or if there is none the last normal event - Map last = Maps.newHashMapWithExpectedSize(events.size()); - for (TopologyEvent event : events) { - if (event.isForceEvent() - || !last.containsKey(event.address) - || !last.get(event.address).isForceEvent()) { - last.put(event.address, event); - } + private class SingleThreaded { + + private final MetadataManager metadataManager; + private final EventBus eventBus; + private final Debouncer> topologyEventDebouncer; + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; + + private SingleThreaded(InternalDriverContext context) { + this.metadataManager = context.metadataManager(); + + DriverConfigProfile config = context.config().defaultProfile(); + this.topologyEventDebouncer = + new Debouncer<>( + adminExecutor, + this::coalesceTopologyEvents, + this::flushTopologyEvents, + config.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW), + config.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); + + this.eventBus = context.eventBus(); + this.eventBus.register( + ChannelEvent.class, RunOrSchedule.on(adminExecutor, this::onChannelEvent)); + this.eventBus.register( + TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); + // Note: this component exists for the whole life of the driver instance, so don't worry about + // unregistering the listeners. + + } + + private void onChannelEvent(ChannelEvent event) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + LOG.debug("Processing {}", event); + @SuppressWarnings("SuspiciousMethodCalls") + DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + assert node != null; + switch (event.type) { + case OPENED: + node.openConnections += 1; + if (node.state == NodeState.DOWN || node.state == NodeState.UNKNOWN) { + setState(node, NodeState.UP, "a new connection was opened to it"); + } + break; + case CLOSED: + node.openConnections -= 1; + break; + case RECONNECTION_STARTED: + node.reconnections += 1; + if (node.openConnections == 0) { + setState(node, NodeState.DOWN, "it has no connections and started reconnecting"); + } + break; + case RECONNECTION_STOPPED: + node.reconnections -= 1; + break; } - result = last.values(); } - LOG.debug("Coalesced topology events: {} => {}", events, result); - return result; - } - // Called when the debouncer flushes - private void flushTopologyEvents(Collection events) { - assert adminExecutor.inEventLoop(); - for (TopologyEvent event : events) { - onDebouncedTopologyEvent(event); + private void onDebouncedTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + LOG.debug("Processing {}", event); + DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + switch (event.type) { + case SUGGEST_UP: + if (node == null) { + LOG.debug("Received UP event for unknown node {}, adding it", event.address); + metadataManager.addNode(event.address); + } else if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("Not setting {} UP because it is FORCED_DOWN", node); + } else { + setState(node, NodeState.UP, "an UP topology event was received"); + } + break; + case SUGGEST_DOWN: + if (node == null) { + LOG.debug("Received DOWN event for unknown node {}, ignoring it", event.address); + } else if (node.openConnections > 0) { + LOG.debug("Not setting {} DOWN because it still has active connections", node); + } else if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("Not setting {} DOWN because it is FORCED_DOWN", node); + } else { + setState(node, NodeState.DOWN, "a DOWN topology event was received"); + } + break; + case FORCE_UP: + if (node == null) { + LOG.debug("Received FORCE_UP event for unknown node {}, adding it", event.address); + metadataManager.addNode(event.address); + } else { + setState(node, NodeState.UP, "a FORCE_UP topology event was received"); + } + break; + case FORCE_DOWN: + if (node == null) { + LOG.debug("Received FORCE_DOWN event for unknown node {}, ignoring it", event.address); + } else { + setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); + } + break; + case SUGGEST_ADDED: + if (node != null) { + LOG.debug( + "Received ADDED event for {} but it is already in our metadata, ignoring", node); + } else { + metadataManager.addNode(event.address); + } + break; + case SUGGEST_REMOVED: + if (node == null) { + LOG.debug( + "Received REMOVED event for {} but it is not in our metadata, ignoring", + event.address); + } else { + metadataManager.removeNode(event.address); + } + break; + } + } + + // Called by the event bus, needs debouncing + private void onTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + topologyEventDebouncer.receive(event); } - } - private void setState(DefaultNode node, NodeState newState, String reason) { - NodeState oldState = node.state; - if (oldState != newState) { - LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); - node.state = newState; - if (newState != NodeState.UP) { - // Fire the event immediately - eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + // Called to process debounced events before flushing + private Collection coalesceTopologyEvents(List events) { + assert adminExecutor.inEventLoop(); + Collection result; + if (events.size() == 1) { + result = events; } else { - // Refresh the node first (but still fire event if that fails) - metadataManager - .refreshNode(node) - .whenComplete( - (success, error) -> { - try { - if (error != null) { - LOG.debug("Error while refreshing info for " + node, error); + // Keep the last FORCE* event for each node, or if there is none the last normal event + Map last = Maps.newHashMapWithExpectedSize(events.size()); + for (TopologyEvent event : events) { + if (event.isForceEvent() + || !last.containsKey(event.address) + || !last.get(event.address).isForceEvent()) { + last.put(event.address, event); + } + } + result = last.values(); + } + LOG.debug("Coalesced topology events: {} => {}", events, result); + return result; + } + + // Called when the debouncer flushes + private void flushTopologyEvents(Collection events) { + assert adminExecutor.inEventLoop(); + for (TopologyEvent event : events) { + onDebouncedTopologyEvent(event); + } + } + + private void close() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + topologyEventDebouncer.stop(); + closeFuture.complete(null); + } + + private void setState(DefaultNode node, NodeState newState, String reason) { + NodeState oldState = node.state; + if (oldState != newState) { + LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); + node.state = newState; + if (newState != NodeState.UP) { + // Fire the event immediately + eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + } else { + // Refresh the node first (but still fire event if that fails) + metadataManager + .refreshNode(node) + .whenComplete( + (success, error) -> { + try { + if (error != null) { + LOG.debug("Error while refreshing info for " + node, error); + } + eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); + } catch (Throwable t) { + LOG.warn("Unexpected exception", t); } - eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); - } catch (Throwable t) { - LOG.warn("Unexpected exception", t); - } - }); + }); + } } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index b8a80cb478b..e999734a249 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.metadata.Node; @@ -37,7 +38,7 @@ * refreshes are done with queries to system tables. If you prefer to rely on an external monitoring * tool, this can be completely overridden. */ -public interface TopologyMonitor { +public interface TopologyMonitor extends AsyncAutoCloseable { /** * Triggers the initialization of the monitor. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index d359a0333af..5d504a82e43 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.pool; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -53,7 +54,7 @@ *

If one or more channels go down, a reconnection process starts in order to replace them; it * runs until the channel count is back to its intended target. */ -public class ChannelPool { +public class ChannelPool implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(ChannelPool.class); /** @@ -120,26 +121,20 @@ public CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { return RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspaceName)); } - /** - * Closes the pool gracefully: subsequent calls to {@link #next()} will fail, but the pool's - * channels will be closed gracefully (allowing pending requests to complete). - */ - public CompletionStage close() { - RunOrSchedule.on(adminExecutor, singleThreaded::close); + @Override + public CompletionStage closeFuture() { return singleThreaded.closeFuture; } - /** - * Closes the pool forcefully: subsequent calls to {@link #next()} will fail, and the pool's - * channels will be closed forcefully (aborting pending requests). - */ - public CompletionStage forceClose() { - RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); return singleThreaded.closeFuture; } - /** Does not close the pool, but returns a completion stage that will complete when it does. */ - public CompletionStage closeFuture() { + @Override + public CompletionStage forceCloseAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); return singleThreaded.closeFuture; } @@ -156,7 +151,7 @@ private class SingleThreaded { private int wantedCount; private CompletableFuture connectFuture = new CompletableFuture<>(); private boolean isConnecting; - private CompletableFuture closeFuture = new CompletableFuture<>(); + private CompletableFuture closeFuture = new CompletableFuture<>(); private boolean isClosing; private CompletableFuture setKeyspaceFuture; @@ -366,7 +361,7 @@ private void close() { eventBus.fire(ChannelEvent.channelClosed(address)); return channel.close(); }, - () -> closeFuture.complete(ChannelPool.this), + () -> closeFuture.complete(null), (channel, error) -> LOG.warn(ChannelPool.this + " error closing channel " + channel, error)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java index 6c8ae599a7d..091bdd17c9f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java @@ -50,6 +50,7 @@ public class Debouncer { private List currentBatch = new ArrayList<>(); private ScheduledFuture nextFlush; + private boolean stopped; /** * Creates a new instance. @@ -77,6 +78,9 @@ public Debouncer( /** This must be called on eventExecutor too. */ public void receive(T element) { assert adminExecutor.inEventLoop(); + if (stopped) { + return; + } if (window.isZero() || maxEvents == 1) { LOG.debug( "Received {}, flushing immediately (window = {}, maxEvents = {})", @@ -123,4 +127,16 @@ private void cancelNextFlush() { } } } + + /** + * Stop debouncing: the next flush is cancelled, and all pending and future events will be + * ignored. + */ + public void stop() { + assert adminExecutor.inEventLoop(); + if (!stopped) { + stopped = true; + cancelNextFlush(); + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 48ce8ce602f..f855a99f945 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -33,7 +33,7 @@ public class ControlConnectionTest extends ControlConnectionTestBase { @Test public void should_close_successfully_if_it_was_never_init() { // When - CompletionStage closeFuture = controlConnection.forceClose(); + CompletionStage closeFuture = controlConnection.forceCloseAsync(); // Then assertThat(closeFuture).isSuccess(); @@ -261,7 +261,7 @@ public void should_not_force_reconnection_if_closed() { factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CompletionStage closeFuture = controlConnection.forceClose(); + CompletionStage closeFuture = controlConnection.forceCloseAsync(); assertThat(closeFuture).isSuccess(); // When @@ -287,7 +287,7 @@ public void should_close_channel_when_closing() { assertThat(initFuture).isSuccess(); // When - CompletionStage closeFuture = controlConnection.forceClose(); + CompletionStage closeFuture = controlConnection.forceCloseAsync(); waitForPendingAdminTasks(); // Then @@ -331,7 +331,7 @@ public void should_close_channel_if_closed_during_reconnection() { // When // the control connection gets closed before channel2 initialization is complete - controlConnection.forceClose(); + controlConnection.forceCloseAsync(); waitForPendingAdminTasks(); channel2Future.complete(channel2); waitForPendingAdminTasks(); @@ -377,7 +377,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { // When // the control connection gets closed before channel1 initialization fails - controlConnection.forceClose(); + controlConnection.forceCloseAsync(); channel1Future.completeExceptionally(new Exception("mock failure")); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 15159e7e6e8..e1c4dc9443c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -224,6 +224,19 @@ public void should_refresh_node_list_from_local_and_peers() { }); } + @Test + public void should_stop_executing_queries_once_closed() throws Exception { + // Given + topologyMonitor.close(); + + // When + CompletionStage> futureInfos = topologyMonitor.refreshNodeList(); + + // Then + assertThat(futureInfos) + .isFailed(error -> assertThat(error).isInstanceOf(IllegalStateException.class)); + } + /** Mocks the query execution logic. */ private static class TestTopologyMonitor extends DefaultTopologyMonitor { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 041a7918b4a..1e11b66caee 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -69,6 +69,11 @@ public void setup() { metadataManager = new TestMetadataManager(context); } + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + @Test public void should_add_contact_points() { // When @@ -200,11 +205,6 @@ public void should_remove_node() { assertThat(refresh.toRemove).isEqualTo(ADDRESS1); } - @AfterMethod - public void teardown() { - adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); - } - private class TestMetadataManager extends MetadataManager { private List refreshes = new CopyOnWriteArrayList<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index f885fbc5eb6..3ed41f5774e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -493,6 +493,19 @@ public void should_keep_node_up_if_reconnection_starts_with_some_connections() { Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); } + @Test + public void should_ignore_events_when_closed() throws Exception { + NodeStateManager manager = new NodeStateManager(context); + assertThat(node1.reconnections).isEqualTo(0); + + manager.close(); + + eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + waitForPendingAdminTasks(); + + assertThat(node1.reconnections).isEqualTo(0); + } + // Wait for all the tasks on the pool's admin executor to complete. private void waitForPendingAdminTasks() { // This works because the event loop group is single-threaded diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index f99efa513fe..03e6dca692d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -589,7 +589,7 @@ public void should_close_all_channels_when_closed() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(ADDRESS, 1); - CompletionStage closeFuture = pool.close(); + CompletionStage closeFuture = pool.closeAsync(); waitForPendingAdminTasks(); // The two original channels were closed normally @@ -651,7 +651,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(ADDRESS, 1); - CompletionStage closeFuture = pool.forceClose(); + CompletionStage closeFuture = pool.forceCloseAsync(); waitForPendingAdminTasks(); // The two original channels were force-closed diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java index 338a741cd93..73582c8e4aa 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java @@ -162,4 +162,36 @@ public void should_force_flush_after_max_events() { Mockito.verify(scheduledFuture, times(9)).cancel(true); assertThat(results).containsExactly("0,1,2,3,4,5,6,7,8,9"); } + + @Test + public void should_cancel_next_flush_when_stopped() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); + + debouncer.receive(1); + Mockito.verify(adminExecutor) + .schedule( + Mockito.any(Runnable.class), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + + debouncer.stop(); + Mockito.verify(scheduledFuture).cancel(true); + } + + @Test + public void should_ignore_new_events_when_flushed() { + Debouncer debouncer = + new Debouncer<>( + adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); + debouncer.stop(); + + debouncer.receive(1); + Mockito.verify(adminExecutor, never()) + .schedule( + Mockito.any(Runnable.class), + Mockito.eq(DEFAULT_WINDOW.toNanos()), + Mockito.eq(TimeUnit.NANOSECONDS)); + } } From fe287e4b4be6d3df4be7df77b64f098397f6bb83 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 09:04:01 -0700 Subject: [PATCH 036/742] Remove `git stash` from pre-commit hook --- CONTRIBUTING.md | 7 ++++++- pre-commit.sh | 12 ++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee48d1d55aa..c83e861d09c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,4 +112,9 @@ ln -s ../../pre-commit.sh .git/hooks/pre-commit ``` This will only allow commits if the tests pass. It is also a good reminder to keep the test suite -short. +short. + +Note: the tests run on the current state of the working directory. I tried to add a `git stash` in +the script to only test what's actually being committed, but I couldn't get it to run reliably +(it's still in there but commented). Keep this in mind when you commit, and don't forget to re-add +the changes if the first attempt failed and you fixed the tests. diff --git a/pre-commit.sh b/pre-commit.sh index e1dc2bbc590..c87ea5bf9ca 100755 --- a/pre-commit.sh +++ b/pre-commit.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash -STASH_NAME="pre-commit-$(date +%s)" -git stash save --keep-index $STASH_NAME +# STASH_NAME="pre-commit-$(date +%s)" +# git stash save --keep-index $STASH_NAME mvn clean test RESULT=$? -STASHES=$(git stash list) -if [[ $STASHES == *$STASH_NAME* ]]; then - git stash pop -fi +# STASHES=$(git stash list) +# if [[ $STASHES == *$STASH_NAME* ]]; then +# git stash pop +# fi [ $RESULT -ne 0 ] && exit 1 exit 0 From 5902846a1d8c6440cfe465b4c9e69ae55e3e6cad Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 10:39:40 -0700 Subject: [PATCH 037/742] Add PoC for Session public API --- .../driver/api/core/cql/AsyncResultSet.java | 18 +++++++ .../oss/driver/api/core/cql/CqlSession.java | 49 +++++++++++++++++++ .../driver/api/core/cql/PrepareRequest.java | 31 ++++++++++++ .../api/core/cql/PreparedStatement.java | 18 +++++++ .../oss/driver/api/core/cql/ResultSet.java | 18 +++++++ .../oss/driver/api/core/cql/Statement.java | 20 ++++++++ .../oss/driver/api/core/session/Request.java | 28 +++++++++++ .../oss/driver/api/core/session/Session.java | 31 ++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java new file mode 100644 index 00000000000..e92256b6ee5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +public interface AsyncResultSet {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java new file mode 100644 index 00000000000..a4974889de5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.Request; +import java.util.concurrent.CompletionStage; + +public interface CqlSession extends Session { + + // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion + default ResultSet execute(Statement statement) { + return execute((Request) statement); + } + + // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion + default AsyncResultSet executeAsync(Statement statement) { + return executeAsync((Request) statement); + } + + default PreparedStatement prepare(String query) { + return execute(PrepareRequest.from(query)); + } + + default PreparedStatement prepare(Statement query) { + return execute(PrepareRequest.from(query)); + } + + default CompletionStage prepareAsync(String query) { + return executeAsync(PrepareRequest.from(query)); + } + + default CompletionStage prepareAsync(Statement query) { + return executeAsync(PrepareRequest.from(query)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java new file mode 100644 index 00000000000..10b092e14f6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.session.Request; +import java.util.concurrent.CompletionStage; + +public interface PrepareRequest + extends Request> { + + static PrepareRequest from(String query) { + throw new UnsupportedOperationException("TODO"); + } + + static PrepareRequest from(Statement statement) { + throw new UnsupportedOperationException("TODO"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java new file mode 100644 index 00000000000..ae86aa22286 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +public interface PreparedStatement {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java new file mode 100644 index 00000000000..9fcf7488fa5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +public interface ResultSet {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java new file mode 100644 index 00000000000..ad174891877 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.session.Request; + +public interface Statement extends Request {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java new file mode 100644 index 00000000000..d894d129bba --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +/** + * A request executed by a {@link Session}. + * + *

This is a high-level abstraction, agnostic to the actual language (e.g. CQL). A request can be + * anything that can be converted to a protocol message, provided that you register a request + * processor with the driver to do that conversion. + * + * @param the type of response when this request is executed synchronously. + * @param the type of response when this request is executed asynchronously. + */ +public interface Request {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java new file mode 100644 index 00000000000..d58708e5497 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.cql.CqlSession; + +/** + * A nexus to send requests to a Cassandra cluster. + * + *

This is a high-level abstraction that can handle any kind of request (provided that you have + * registered a custom request processor with the driver). For regular CQL queries, see {@link + * CqlSession}. + */ +public interface Session { + SyncResultT execute(Request request); + + AsyncResultT executeAsync(Request request); +} From f3b666ff038710455dcb3c8a3ab578723dc63a14 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 14:13:48 -0700 Subject: [PATCH 038/742] Detect when setKeyspace fails at pool init --- .../api/core/InvalidKeyspaceException.java | 23 ++++++++++++++ .../core/channel/ProtocolInitHandler.java | 4 +++ .../internal/core/pool/ChannelPool.java | 24 +++++++++++--- .../core/channel/ProtocolInitHandlerTest.java | 31 +++++++++++++++++++ .../internal/core/pool/ChannelPoolTest.java | 19 ++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java new file mode 100644 index 00000000000..6e184fc52e9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +/** Thrown when a session gets created with an invalid keyspace. */ +public class InvalidKeyspaceException extends DriverException { + public InvalidKeyspaceException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 2dfdb1685c4..5942683e4e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.auth.AuthenticationException; @@ -246,6 +247,9 @@ void onResponse(Message response) { fail( UnsupportedProtocolVersionException.forSingleAttempt( channel.remoteAddress(), initialProtocolVersion)); + } else if (step == Step.SET_KEYSPACE + && error.code == ProtocolConstants.ErrorCode.INVALID) { + fail(new InvalidKeyspaceException(error.message)); } else { failOnUnexpected(error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 5d504a82e43..645a87e10e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; @@ -79,6 +80,7 @@ public static CompletionStage init( private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; + private volatile boolean invalidKeyspace; private ChannelPool( InetSocketAddress address, @@ -86,7 +88,7 @@ private ChannelPool( int channelCount, InternalDriverContext context) { - adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(address, keyspaceName, channelCount, context); } @@ -95,6 +97,14 @@ private CompletionStage connect() { return singleThreaded.connectFuture; } + /** + * Whether all channels failed due to an invalid keyspace. This is only used at initialization. We + * don't make the decision to close the pool here yet, that's done at the session level. + */ + public boolean isInvalidKeyspace() { + return invalidKeyspace; + } + /** * @return the channel that has the most available stream ids. This is called on the direct * request path, and we want to avoid complex check-then-act semantics; therefore this might @@ -217,6 +227,7 @@ private CompletionStage addMissingChannels() { private boolean onAllConnected(@SuppressWarnings("unused") Void v) { assert adminExecutor.inEventLoop(); ClusterNameMismatchException clusterNameMismatch = null; + int invalidKeyspaceErrors = 0; for (CompletionStage pendingChannel : pendingChannels) { CompletableFuture future = pendingChannel.toCompletableFuture(); assert future.isDone(); @@ -243,18 +254,23 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } catch (InterruptedException e) { // can't happen, the future is done } catch (ExecutionException e) { - LOG.debug(ChannelPool.this + " error while opening new channel", e.getCause()); + Throwable cause = e.getCause(); + LOG.debug(ChannelPool.this + " error while opening new channel", cause); // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) // TODO auth exception => WARN and keep reconnecting // TODO protocol error => WARN and force down - if (e.getCause() instanceof ClusterNameMismatchException) { + if (cause instanceof ClusterNameMismatchException) { // This will likely be thrown by all channels, but finish the loop cleanly - clusterNameMismatch = (ClusterNameMismatchException) e.getCause(); + clusterNameMismatch = (ClusterNameMismatchException) cause; + } else if (cause instanceof InvalidKeyspaceException) { + invalidKeyspaceErrors += 1; } } } + // If all channels failed, assume the keyspace is wrong + invalidKeyspace = (invalidKeyspaceErrors == pendingChannels.size()); pendingChannels.clear(); if (clusterNameMismatch != null) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 53bcc498e0d..477d5ae25a5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -367,4 +368,34 @@ public void should_initialize_with_keyspace_and_events() { assertThat(connectFuture).isSuccess(); } + + @Test + public void should_fail_to_initialize_if_keyspace_is_invalid() { + DriverChannelOptions driverChannelOptions = + DriverChannelOptions.builder().withKeyspace(CqlIdentifier.fromCql("ks")).build(); + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + writeInboundFrame(readOutboundFrame(), new Ready()); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + assertThat(((Query) requestFrame.message).query).isEqualTo("USE \"ks\""); + writeInboundFrame( + requestFrame, new Error(ProtocolConstants.ErrorCode.INVALID, "invalid keyspace")); + + assertThat(connectFuture) + .isFailed( + error -> + assertThat(error) + .isInstanceOf(InvalidKeyspaceException.class) + .hasMessage("invalid keyspace")); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 03e6dca692d..17c2d5486ae 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.pool; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -136,6 +137,24 @@ public void should_initialize_when_all_channels_fail() throws Exception { factoryHelper.verifyNoMoreCalls(); } + @Test + public void should_indicate_when_keyspace_failed_on_all_channels() { + int poolSize = 3; + + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .build(); + + CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); + } + @Test public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { int poolSize = 3; From 973db7db0fe7faf247ccb4585d4aedcbc44cbb78 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 14:53:27 -0700 Subject: [PATCH 039/742] Improve future utilities --- .../driver/internal/core/DefaultCluster.java | 51 +++++++------------ .../internal/core/pool/ChannelPool.java | 37 +++++++------- .../util/concurrent/CompletableFutures.java | 37 +++++++++++++- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 5ad704cec6f..e9a5d00de66 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -166,8 +165,7 @@ private void close() { LOG.debug("Closing {}", closeable); childrenCloseFutures.add(closeable.closeAsync()); } - CompletableFutures.whenAllDone(childrenCloseFutures) - .whenCompleteAsync(this::onChildrenClosed, adminExecutor); + CompletableFutures.whenAllDone(childrenCloseFutures, this::onChildrenClosed, adminExecutor); } private void forceClose() { @@ -192,48 +190,33 @@ private void forceClose() { LOG.debug("Force-closing {}", closeable); childrenCloseFutures.add(closeable.forceCloseAsync()); } - CompletableFutures.whenAllDone(childrenCloseFutures) - .whenCompleteAsync(this::onChildrenClosed, adminExecutor); + CompletableFutures.whenAllDone(childrenCloseFutures, this::onChildrenClosed, adminExecutor); } } - private void onChildrenClosed(@SuppressWarnings("unused") Void ignored, Throwable error) { + private void onChildrenClosed() { assert adminExecutor.inEventLoop(); - if (error != null) { - LOG.warn("Unexpected error while closing", error); - } - try { - for (CompletionStage future : childrenCloseFutures) { - warnIfFailed(future); - } - context - .nettyOptions() - .onClose() - .addListener( - f -> { - if (!f.isSuccess()) { - closeFuture.completeExceptionally(f.cause()); - } else { - closeFuture.complete(null); - } - }); - } catch (Throwable t) { - // Being paranoid here, but we don't want to risk swallowing an exception and leaving close - // hanging - LOG.warn("Unexpected error while closing", t); + for (CompletionStage future : childrenCloseFutures) { + warnIfFailed(future); } + context + .nettyOptions() + .onClose() + .addListener( + f -> { + if (!f.isSuccess()) { + closeFuture.completeExceptionally(f.cause()); + } else { + closeFuture.complete(null); + } + }); } private void warnIfFailed(CompletionStage stage) { CompletableFuture future = stage.toCompletableFuture(); assert future.isDone(); if (future.isCompletedExceptionally()) { - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - // InterruptedException can't happen actually, but including it to make compiler happy - LOG.warn("Unexpected error while closing", e.getCause()); - } + LOG.warn("Unexpected error while closing", CompletableFutures.getFailed(future)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 645a87e10e9..5b8dedbb7ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -220,7 +220,7 @@ private CompletionStage addMissingChannels() { CompletionStage channelFuture = channelFactory.connect(address, options); pendingChannels.add(channelFuture); } - return CompletableFutures.whenAllDone(pendingChannels) + return CompletableFutures.allDone(pendingChannels) .thenApplyAsync(this::onAllConnected, adminExecutor); } @@ -231,8 +231,22 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { for (CompletionStage pendingChannel : pendingChannels) { CompletableFuture future = pendingChannel.toCompletableFuture(); assert future.isDone(); - try { - DriverChannel channel = future.get(); + if (future.isCompletedExceptionally()) { + Throwable error = CompletableFutures.getFailed(future); + LOG.debug(ChannelPool.this + " error while opening new channel", error); + // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) + + // TODO auth exception => WARN and keep reconnecting + // TODO protocol error => WARN and force down + + if (error instanceof ClusterNameMismatchException) { + // This will likely be thrown by all channels, but finish the loop cleanly + clusterNameMismatch = (ClusterNameMismatchException) error; + } else if (error instanceof InvalidKeyspaceException) { + invalidKeyspaceErrors += 1; + } + } else { + DriverChannel channel = CompletableFutures.getCompleted(future); if (isClosing) { LOG.debug( "{} new channel added ({}) but the pool was closed, closing it", @@ -251,26 +265,11 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { .submit(() -> onChannelClosed(channel)) .addListener(UncaughtExceptions::log)); } - } catch (InterruptedException e) { - // can't happen, the future is done - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - LOG.debug(ChannelPool.this + " error while opening new channel", cause); - // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) - - // TODO auth exception => WARN and keep reconnecting - // TODO protocol error => WARN and force down - - if (cause instanceof ClusterNameMismatchException) { - // This will likely be thrown by all channels, but finish the loop cleanly - clusterNameMismatch = (ClusterNameMismatchException) cause; - } else if (cause instanceof InvalidKeyspaceException) { - invalidKeyspaceErrors += 1; - } } } // If all channels failed, assume the keyspace is wrong invalidKeyspace = (invalidKeyspaceErrors == pendingChannels.size()); + pendingChannels.clear(); if (clusterNameMismatch != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 6d1dbcc1e25..1afee7c3d26 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -16,13 +16,14 @@ package com.datastax.oss.driver.internal.core.util.concurrent; import com.datastax.oss.driver.api.core.DriverException; +import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; public class CompletableFutures { @@ -45,7 +46,7 @@ public static void completeFrom(CompletionStage source, CompletableFuture } /** @return a completion stage that completes when all inputs are done (success or failure). */ - public static CompletionStage whenAllDone(List> inputs) { + public static CompletionStage allDone(List> inputs) { CompletableFuture result = new CompletableFuture<>(); final int todo = inputs.size(); final AtomicInteger done = new AtomicInteger(); @@ -60,6 +61,38 @@ public static CompletionStage whenAllDone(List> inp return result; } + /** Do something when all inputs are done (success or failure). */ + public static void whenAllDone( + List> inputs, Runnable callback, Executor executor) { + allDone(inputs) + .thenAcceptAsync(success -> callback.run(), executor) + .exceptionally(UncaughtExceptions::log); + } + + /** Get the result now, when we know for sure that the future is complete. */ + public static T getCompleted(CompletableFuture future) { + Preconditions.checkArgument(future.isDone() && !future.isCompletedExceptionally()); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + // Neither can happen given the precondition + throw new AssertionError("Unexpected error", e); + } + } + + /** Get the error now, when we know for sure that the future is failed. */ + public static Throwable getFailed(CompletableFuture future) { + Preconditions.checkArgument(future.isCompletedExceptionally()); + try { + future.get(); + throw new AssertionError("future should be failed"); + } catch (InterruptedException e) { + throw new AssertionError("Unexpected error", e); + } catch (ExecutionException e) { + return e.getCause(); + } + } + public static T getUninterruptibly(CompletableFuture future) { boolean interrupted = false; try { From 65184cd42ce3d84edd3d8dde39ed9b29cf7df500 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 15:23:47 -0700 Subject: [PATCH 040/742] Init pool with a node instead of an address As a consequence, channel events can now use a node as well. --- .../internal/core/channel/ChannelEvent.java | 33 +++-- .../core/control/ControlConnection.java | 8 +- .../internal/core/metadata/DefaultNode.java | 17 +++ .../core/metadata/NodeStateManager.java | 3 +- .../internal/core/pool/ChannelPool.java | 48 ++++---- .../core/control/ControlConnectionTest.java | 38 +++--- .../control/ControlConnectionTestBase.java | 9 +- .../core/metadata/NodeStateManagerTest.java | 28 ++--- .../internal/core/pool/ChannelPoolTest.java | 113 +++++++++--------- 9 files changed, 154 insertions(+), 143 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java index 06b04285739..59895bac1d3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.metadata.Node; import java.net.SocketAddress; import java.util.Objects; @@ -27,32 +28,28 @@ public enum Type { RECONNECTION_STOPPED } - public static ChannelEvent channelOpened(SocketAddress address) { - return new ChannelEvent(Type.OPENED, address); + public static ChannelEvent channelOpened(Node node) { + return new ChannelEvent(Type.OPENED, node); } - public static ChannelEvent channelClosed(SocketAddress address) { - return new ChannelEvent(Type.CLOSED, address); + public static ChannelEvent channelClosed(Node node) { + return new ChannelEvent(Type.CLOSED, node); } - public static ChannelEvent reconnectionStarted(SocketAddress address) { - return new ChannelEvent(Type.RECONNECTION_STARTED, address); + public static ChannelEvent reconnectionStarted(Node node) { + return new ChannelEvent(Type.RECONNECTION_STARTED, node); } - public static ChannelEvent reconnectionStopped(SocketAddress address) { - return new ChannelEvent(Type.RECONNECTION_STOPPED, address); + public static ChannelEvent reconnectionStopped(Node node) { + return new ChannelEvent(Type.RECONNECTION_STOPPED, node); } public final Type type; - /** - * We use SocketAddress because some of our tests use the local Netty transport, but in production - * it will always be InetSocketAddress. - */ - public final SocketAddress address; + public final Node node; - public ChannelEvent(Type type, SocketAddress address) { + public ChannelEvent(Type type, Node node) { this.type = type; - this.address = address; + this.node = node; } @Override @@ -61,7 +58,7 @@ public boolean equals(Object other) { return true; } else if (other instanceof ChannelEvent) { ChannelEvent that = (ChannelEvent) other; - return this.type == that.type && Objects.equals(this.address, that.address); + return this.type == that.type && Objects.equals(this.node, that.node); } else { return false; } @@ -69,11 +66,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(type, address); + return Objects.hash(type, node); } @Override public String toString() { - return "ChannelEvent(" + type + ", " + address + ")"; + return "ChannelEvent(" + type + ", " + node + ")"; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 728c8621dc9..07ee114968f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -272,13 +272,13 @@ private void connect( previousChannel.forceClose(); } ControlConnection.this.channel = channel; - context.eventBus().fire(ChannelEvent.channelOpened(node.getConnectAddress())); + context.eventBus().fire(ChannelEvent.channelOpened(node)); channel .closeFuture() .addListener( f -> adminExecutor - .submit(() -> onChannelClosed(channel)) + .submit(() -> onChannelClosed(channel, node)) .addListener(UncaughtExceptions::log)); onSuccess.run(); } @@ -312,10 +312,10 @@ private void onSuccessfulReconnect() { // TODO refresh schema metadata } - private void onChannelClosed(DriverChannel channel) { + private void onChannelClosed(DriverChannel channel, Node node) { assert adminExecutor.inEventLoop(); LOG.debug("Lost channel {}", channel); - context.eventBus().fire(ChannelEvent.channelClosed(channel.address())); + context.eventBus().fire(ChannelEvent.channelClosed(node)); if (!closeWasCalled && !reconnection.isRunning()) { reconnection.start(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 1e64d04ddc1..4d1a46c0b00 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -106,6 +106,23 @@ public NodeDistance getDistance() { return distance; } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Node) { + Node that = (Node) other; + return this.connectAddress.equals(that.getConnectAddress()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return connectAddress.hashCode(); + } + @Override public String toString() { return connectAddress.toString(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 840eda1a9d2..7adb0b911d6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -104,8 +104,7 @@ private void onChannelEvent(ChannelEvent event) { return; } LOG.debug("Processing {}", event); - @SuppressWarnings("SuspiciousMethodCalls") - DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + DefaultNode node = (DefaultNode) event.node; assert node != null; switch (event.type) { case OPENED: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 5b8dedbb7ea..0dd669c1b63 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; @@ -34,13 +35,11 @@ import com.google.common.collect.Sets; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; -import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Function; @@ -67,29 +66,24 @@ public class ChannelPool implements AsyncAutoCloseable { * channels (i.e. {@link #next()} return {@code null}) and is reconnecting. */ public static CompletionStage init( - InetSocketAddress address, - CqlIdentifier keyspaceName, - int channelCount, - InternalDriverContext context) { - ChannelPool pool = new ChannelPool(address, keyspaceName, channelCount, context); + Node node, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { + ChannelPool pool = new ChannelPool(node, keyspaceName, channelCount, context); return pool.connect(); } // This is read concurrently, but only mutated on adminExecutor (by methods in SingleThreaded) @VisibleForTesting final ChannelSet channels = new ChannelSet(); + private final Node node; private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; private volatile boolean invalidKeyspace; private ChannelPool( - InetSocketAddress address, - CqlIdentifier keyspaceName, - int channelCount, - InternalDriverContext context) { - + Node node, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { + this.node = node; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(address, keyspaceName, channelCount, context); + this.singleThreaded = new SingleThreaded(keyspaceName, channelCount, context); } private CompletionStage connect() { @@ -97,6 +91,10 @@ private CompletionStage connect() { return singleThreaded.connectFuture; } + public Node getNode() { + return node; + } + /** * Whether all channels failed due to an invalid keyspace. This is only used at initialization. We * don't make the decision to close the pool here yet, that's done at the session level. @@ -151,7 +149,6 @@ public CompletionStage forceCloseAsync() { /** Holds all administration tasks, that are confined to the admin executor. */ private class SingleThreaded { - private final InetSocketAddress address; private final ChannelFactory channelFactory; private final EventBus eventBus; // The channels that are currently connecting @@ -168,11 +165,7 @@ private class SingleThreaded { private CqlIdentifier keyspaceName; private SingleThreaded( - InetSocketAddress address, - CqlIdentifier keyspaceName, - int wantedCount, - InternalDriverContext context) { - this.address = address; + CqlIdentifier keyspaceName, int wantedCount, InternalDriverContext context) { this.keyspaceName = keyspaceName; this.wantedCount = wantedCount; this.channelFactory = context.channelFactory(); @@ -182,8 +175,8 @@ private SingleThreaded( adminExecutor, context.reconnectionPolicy(), this::addMissingChannels, - () -> eventBus.fire(ChannelEvent.reconnectionStarted(this.address)), - () -> eventBus.fire(ChannelEvent.reconnectionStopped(this.address))); + () -> eventBus.fire(ChannelEvent.reconnectionStarted(node)), + () -> eventBus.fire(ChannelEvent.reconnectionStopped(node))); } private void connect() { @@ -217,7 +210,8 @@ private CompletionStage addMissingChannels() { .reportAvailableIds(wantedCount > 1) .build(); for (int i = 0; i < missing; i++) { - CompletionStage channelFuture = channelFactory.connect(address, options); + CompletionStage channelFuture = + channelFactory.connect(node.getConnectAddress(), options); pendingChannels.add(channelFuture); } return CompletableFutures.allDone(pendingChannels) @@ -256,7 +250,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } else { LOG.debug("{} new channel added {}", ChannelPool.this, channel); channels.add(channel); - eventBus.fire(ChannelEvent.channelOpened(address)); + eventBus.fire(ChannelEvent.channelOpened(node)); channel .closeFuture() .addListener( @@ -274,7 +268,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { if (clusterNameMismatch != null) { LOG.warn(clusterNameMismatch.getMessage()); - eventBus.fire(TopologyEvent.forceDown(address)); + eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); // Don't bother continuing, the pool will get shut down soon anyway return true; } @@ -295,7 +289,7 @@ private void onChannelClosed(DriverChannel channel) { assert adminExecutor.inEventLoop(); LOG.debug("{} lost channel {}", ChannelPool.this, channel); channels.remove(channel); - eventBus.fire(ChannelEvent.channelClosed(address)); + eventBus.fire(ChannelEvent.channelClosed(node)); if (!isClosing && !reconnection.isRunning()) { reconnection.start(); } @@ -334,7 +328,7 @@ private void shrinkIfTooManyChannels() { for (DriverChannel channel : toRemove) { channels.remove(channel); channel.close(); - eventBus.fire(ChannelEvent.channelClosed(address)); + eventBus.fire(ChannelEvent.channelClosed(node)); } } } @@ -373,7 +367,7 @@ private void close() { forAllChannels( channel -> { - eventBus.fire(ChannelEvent.channelClosed(address)); + eventBus.fire(ChannelEvent.channelClosed(node)); return channel.close(); }, () -> closeFuture.complete(null), diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index f855a99f945..18b8ced41e7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -21,12 +21,14 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; public class ControlConnectionTest extends ControlConnectionTestBase { @@ -54,7 +56,7 @@ public void should_init_with_first_contact_point_if_reachable() { // Then assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); factoryHelper.verifyNoMoreCalls(); } @@ -96,7 +98,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { // Then assertThat(initFuture) .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); // each attempt tries all nodes, so there is no reconnection Mockito.verify(reconnectionPolicy, never()).newSchedule(); @@ -146,7 +148,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // When failChannel(channel1, "mock channel failure"); @@ -159,8 +161,8 @@ public void should_reconnect_if_channel_goes_down() throws Exception { factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); Mockito.verify(metadataManager).refreshNodes(); Mockito.verify(loadBalancingPolicyWrapper).init(); @@ -186,12 +188,12 @@ public void should_force_reconnection_if_pending() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled for later failChannel(channel1, "mock channel failure"); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); // When @@ -202,7 +204,7 @@ public void should_force_reconnection_if_pending() { // Then assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); factoryHelper.verifyNoMoreCalls(); } @@ -224,7 +226,7 @@ public void should_force_reconnection_even_if_connected() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // When controlConnection.reconnectNow(); @@ -235,8 +237,8 @@ public void should_force_reconnection_even_if_connected() { waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(channel1).forceClose(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); factoryHelper.verifyNoMoreCalls(); } @@ -293,7 +295,7 @@ public void should_close_channel_when_closing() { // Then assertThat(closeFuture).isSuccess(); Mockito.verify(channel1).forceClose(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); factoryHelper.verifyNoMoreCalls(); } @@ -318,12 +320,12 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled failChannel(channel1, "mock channel failure"); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCall(ADDRESS1); // channel2 starts initializing (but the future is not completed yet) @@ -339,8 +341,8 @@ public void should_close_channel_if_closed_during_reconnection() { // Then Mockito.verify(channel2).forceClose(); // no event because the control connection never "owned" the channel - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS2)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS2)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE2)); factoryHelper.verifyNoMoreCalls(); } @@ -365,12 +367,12 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled failChannel(channel1, "mock channel failure"); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); // channel1 starts initializing (but the future is not completed yet) factoryHelper.waitForCall(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index ddb899bbfa2..f68af5a6f62 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -56,6 +56,8 @@ abstract class ControlConnectionTestBase { protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + protected static final DefaultNode NODE1 = new DefaultNode(ADDRESS1); + protected static final DefaultNode NODE2 = new DefaultNode(ADDRESS2); @Mock protected InternalDriverContext context; @Mock protected ReconnectionPolicy reconnectionPolicy; @@ -97,16 +99,13 @@ public void setup() { // it. Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - DefaultNode node1 = new DefaultNode(ADDRESS1); - DefaultNode node2 = new DefaultNode(ADDRESS2); - Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()) .thenAnswer( i -> { ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); - queryPlan.offer(node1); - queryPlan.offer(node2); + queryPlan.offer(NODE1); + queryPlan.offer(NODE2); return queryPlan; }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 3ed41f5774e..fee0386fd59 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -175,7 +175,7 @@ public void should_ignore_down_event_if_node_is_down_or_forced_down() { public void should_ignore_down_event_if_node_has_active_connections() { new NodeStateManager(context); node1.state = NodeState.UP; - eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelOpened(node1)); waitForPendingAdminTasks(); assertThat(node1.openConnections).isEqualTo(1); @@ -404,12 +404,12 @@ public void should_track_open_connections() { assertThat(node1.openConnections).isEqualTo(0); - eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); - eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelOpened(node1)); + eventBus.fire(ChannelEvent.channelOpened(node1)); waitForPendingAdminTasks(); assertThat(node1.openConnections).isEqualTo(2); - eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelClosed(node1)); waitForPendingAdminTasks(); assertThat(node1.openConnections).isEqualTo(1); } @@ -423,7 +423,7 @@ public void should_mark_node_up_if_down_or_unknown_and_connection_opened() { node1.state = oldState; // When - eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelOpened(node1)); waitForPendingAdminTasks(); // Then @@ -439,7 +439,7 @@ public void should_not_mark_node_up_if_forced_down_and_connection_opened() { node1.state = NodeState.FORCED_DOWN; // When - eventBus.fire(ChannelEvent.channelOpened(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelOpened(node1)); waitForPendingAdminTasks(); // Then @@ -453,12 +453,12 @@ public void should_track_reconnections() { assertThat(node1.reconnections).isEqualTo(0); - eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); - eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); waitForPendingAdminTasks(); assertThat(node1.reconnections).isEqualTo(2); - eventBus.fire(ChannelEvent.reconnectionStopped(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStopped(node1)); waitForPendingAdminTasks(); assertThat(node1.reconnections).isEqualTo(1); } @@ -470,8 +470,8 @@ public void should_mark_node_down_if_reconnection_starts_with_no_connections() { node1.state = NodeState.UP; node1.openConnections = 1; - eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); - eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelClosed(node1)); + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.DOWN); @@ -485,8 +485,8 @@ public void should_keep_node_up_if_reconnection_starts_with_some_connections() { node1.state = NodeState.UP; node1.openConnections = 2; - eventBus.fire(ChannelEvent.channelClosed(node1.getConnectAddress())); - eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.channelClosed(node1)); + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.UP); @@ -500,7 +500,7 @@ public void should_ignore_events_when_closed() throws Exception { manager.close(); - eventBus.fire(ChannelEvent.reconnectionStarted(node1.getConnectAddress())); + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); waitForPendingAdminTasks(); assertThat(node1.reconnections).isEqualTo(0); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 17c2d5486ae..e2562fb89ae 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; @@ -27,6 +28,7 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.ChannelPromise; @@ -56,7 +58,8 @@ import static org.mockito.Mockito.times; public class ChannelPoolTest { - private static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); + public static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); + private static final Node NODE = new DefaultNode(ADDRESS); private @Mock InternalDriverContext context; private @Mock ReconnectionPolicy reconnectionPolicy; @@ -103,14 +106,14 @@ public void should_initialize_when_all_channels_succeed() throws Exception { .success(ADDRESS, channel3) .build(); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture) .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); - Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(ADDRESS)); + Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); factoryHelper.verifyNoMoreCalls(); } @@ -126,13 +129,13 @@ public void should_initialize_when_all_channels_fail() throws Exception { .failure(ADDRESS, "mock channel init failure") .build(); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); factoryHelper.verifyNoMoreCalls(); } @@ -148,7 +151,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) .build(); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -168,13 +171,13 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro .failure(ADDRESS, error) .build(); - ChannelPool.init(ADDRESS, null, poolSize, context); + ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); factoryHelper.verifyNoMoreCalls(); } @@ -199,7 +202,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -207,17 +210,17 @@ public void should_reconnect_when_init_incomplete() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); // A reconnection should have been scheduled Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); channel2Future.complete(channel2); factoryHelper.waitForCalls(ADDRESS, 1); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); assertThat(pool.channels).containsOnly(channel1, channel2); @@ -244,7 +247,7 @@ public void should_reconnect_when_channel_dies() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -252,22 +255,22 @@ public void should_reconnect_when_channel_dies() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); // Simulate fatal error on channel2 ((ChannelPromise) channel2.closeFuture()) .setFailure(new Exception("mock channel init failure")); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); factoryHelper.waitForCall(ADDRESS); channel3Future.complete(channel3); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); assertThat(pool.channels).containsOnly(channel1, channel3); @@ -291,7 +294,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -299,12 +302,12 @@ public void should_shrink_outside_of_reconnection() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(NODE)); pool.resize(2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); assertThat(pool.channels).containsOnly(channel3, channel4); @@ -336,19 +339,19 @@ public void should_shrink_during_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); // A reconnection should have been scheduled to add the missing channels, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); pool.resize(2); @@ -362,9 +365,9 @@ public void should_shrink_during_reconnection() throws Exception { waitForPendingAdminTasks(); // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); assertThat(pool.channels).containsOnly(channel3, channel4); factoryHelper.verifyNoMoreCalls(); @@ -391,11 +394,11 @@ public void should_grow_outside_of_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -406,12 +409,12 @@ public void should_grow_outside_of_reconnection() throws Exception { // The resizing should have triggered a reconnection Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -444,11 +447,11 @@ public void should_grow_during_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -456,7 +459,7 @@ public void should_grow_during_reconnection() throws Exception { // A reconnection should have been scheduled to add the missing channel, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); pool.resize(4); @@ -466,23 +469,23 @@ public void should_grow_during_reconnection() throws Exception { channel2Future.complete(channel2); factoryHelper.waitForCall(ADDRESS); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); assertThat(pool.channels).containsOnly(channel1, channel2); // A second attempt should have been scheduled since we're now still under the target size Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); // Same reconnection is still running, no additional events - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(ADDRESS)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); // Two more channels get opened, bringing us to the target count factoryHelper.waitForCalls(ADDRESS, 2); channel3Future.complete(channel3); channel4Future.complete(channel4); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -502,7 +505,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { .success(ADDRESS, channel2) .build(); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -543,7 +546,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { .pending(ADDRESS, channel2Future) .build(); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -553,7 +556,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { // Check that reconnection has kicked in, but do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(ADDRESS)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); factoryHelper.waitForCalls(ADDRESS, 2); // Switch keyspace, it succeeds immediately since there is no active channel @@ -567,7 +570,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { channel2Future.complete(channel2); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(ADDRESS)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); Mockito.verify(channel1).setKeyspace(newKeyspace); Mockito.verify(channel2).setKeyspace(newKeyspace); @@ -595,11 +598,11 @@ public void should_close_all_channels_when_closed() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -614,7 +617,7 @@ public void should_close_all_channels_when_closed() throws Exception { // The two original channels were closed normally Mockito.verify(channel1).close(); Mockito.verify(channel2).close(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); // Complete the reconnecting channel channel3Future.complete(channel3); @@ -623,8 +626,8 @@ public void should_close_all_channels_when_closed() throws Exception { // It should be force-closed once we find out the pool was closed Mockito.verify(channel3).forceClose(); // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); // Note that we don't wait for reconnected channels to close, so the pool only depends on // channel 1 and 2 @@ -657,14 +660,14 @@ public void should_force_close_all_channels_when_force_closed() throws Exception .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(ADDRESS, null, poolSize, context); + CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); @@ -676,7 +679,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // The two original channels were force-closed Mockito.verify(channel1).close(); Mockito.verify(channel2).close(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); // Complete the reconnecting channel channel3Future.complete(channel3); @@ -685,8 +688,8 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // It should be force-closed once we find out the pool was closed Mockito.verify(channel3).forceClose(); // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(ADDRESS)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(ADDRESS)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); // Note that we don't wait for reconnected channels to close, so the pool only depends on // channel 1 and 2 From afca2b61485749204e6f405341a96d2b380515ae Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 18:17:36 -0700 Subject: [PATCH 041/742] Pass distance instead of size to pool --- .../api/core/config/CoreDriverOption.java | 3 + .../internal/core/pool/ChannelPool.java | 33 ++++-- core/src/main/resources/reference.conf | 13 +++ .../internal/core/pool/ChannelPoolTest.java | 104 +++++++++++------- 4 files changed, 104 insertions(+), 49 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 206b3030061..10cf933dd5d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -41,6 +41,9 @@ public enum CoreDriverOption implements DriverOption { RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection.config.base-delay", true), RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), + POOLING_LOCAL_CONNECTIONS("pooling.local.connections", true), + POOLING_REMOTE_CONNECTIONS("pooling.remote.connections", true), + ADDRESS_TRANSLATOR_CLASS("address-translation.translator-class", true), AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 0dd669c1b63..1380fc9383b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -18,6 +18,9 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -66,8 +69,8 @@ public class ChannelPool implements AsyncAutoCloseable { * channels (i.e. {@link #next()} return {@code null}) and is reconnecting. */ public static CompletionStage init( - Node node, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { - ChannelPool pool = new ChannelPool(node, keyspaceName, channelCount, context); + Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { + ChannelPool pool = new ChannelPool(node, keyspaceName, distance, context); return pool.connect(); } @@ -80,10 +83,10 @@ public static CompletionStage init( private volatile boolean invalidKeyspace; private ChannelPool( - Node node, CqlIdentifier keyspaceName, int channelCount, InternalDriverContext context) { + Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { this.node = node; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(keyspaceName, channelCount, context); + this.singleThreaded = new SingleThreaded(keyspaceName, distance, context); } private CompletionStage connect() { @@ -114,8 +117,8 @@ public DriverChannel next() { return channels.next(); } - public void resize(int newChannelCount) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newChannelCount)); + public void resize(NodeDistance newDistance) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newDistance)); } /** @@ -149,6 +152,7 @@ public CompletionStage forceCloseAsync() { /** Holds all administration tasks, that are confined to the admin executor. */ private class SingleThreaded { + private final DriverConfig config; private final ChannelFactory channelFactory; private final EventBus eventBus; // The channels that are currently connecting @@ -165,9 +169,10 @@ private class SingleThreaded { private CqlIdentifier keyspaceName; private SingleThreaded( - CqlIdentifier keyspaceName, int wantedCount, InternalDriverContext context) { + CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { this.keyspaceName = keyspaceName; - this.wantedCount = wantedCount; + this.config = context.config(); + this.wantedCount = computeSize(distance); this.channelFactory = context.channelFactory(); this.eventBus = context.eventBus(); this.reconnection = @@ -295,8 +300,9 @@ private void onChannelClosed(DriverChannel channel) { } } - private void resize(int newChannelCount) { + private void resize(NodeDistance newDistance) { assert adminExecutor.inEventLoop(); + int newChannelCount = computeSize(newDistance); if (newChannelCount > wantedCount) { LOG.debug("{} growing ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); wantedCount = newChannelCount; @@ -409,5 +415,14 @@ private void forceClose() { channel.forceClose(); } } + + private int computeSize(NodeDistance distance) { + return config + .defaultProfile() + .getInt( + (distance == NodeDistance.LOCAL) + ? CoreDriverOption.POOLING_LOCAL_CONNECTIONS + : CoreDriverOption.POOLING_REMOTE_CONNECTIONS); + } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index ae97c2e12ae..6459ac5097b 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -88,6 +88,19 @@ datastax-java-driver { page-size = 5000 } } + + # The driver maintains a connection pool to each node, according to the distance assigned to it + # by the load balancing policy. If the distance is IGNORED, no connections are maintained. + pooling { + local { + # The number of connections in the pool. + connections = 1 + } + remote { + connections = 1 + } + } + metadata { # Topology events are external signals that inform the driver of the state of Cassandra nodes # (by default, they correspond to gossip events received on the control connection). diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index e2562fb89ae..861fec9b8d0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -17,8 +17,12 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -58,15 +62,17 @@ import static org.mockito.Mockito.times; public class ChannelPoolTest { - public static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); + private static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); private static final Node NODE = new DefaultNode(ADDRESS); - private @Mock InternalDriverContext context; - private @Mock ReconnectionPolicy reconnectionPolicy; - private @Mock ReconnectionSchedule reconnectionSchedule; - private @Mock NettyOptions nettyOptions; - private @Mock EventBus eventBus; - private @Mock ChannelFactory channelFactory; + @Mock private InternalDriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultProfile; + @Mock private ReconnectionPolicy reconnectionPolicy; + @Mock private ReconnectionSchedule reconnectionSchedule; + @Mock private NettyOptions nettyOptions; + @Mock private EventBus eventBus; + @Mock private ChannelFactory channelFactory; private DefaultEventLoopGroup adminEventLoopGroup; @BeforeMethod @@ -77,6 +83,8 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.defaultProfile()).thenReturn(defaultProfile); Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.channelFactory()).thenReturn(channelFactory); @@ -94,7 +102,7 @@ public void teardown() { @Test public void should_initialize_when_all_channels_succeed() throws Exception { - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -106,7 +114,8 @@ public void should_initialize_when_all_channels_succeed() throws Exception { .success(ADDRESS, channel3) .build(); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -120,7 +129,7 @@ public void should_initialize_when_all_channels_succeed() throws Exception { @Test public void should_initialize_when_all_channels_fail() throws Exception { - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -129,7 +138,8 @@ public void should_initialize_when_all_channels_fail() throws Exception { .failure(ADDRESS, "mock channel init failure") .build(); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -142,7 +152,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { @Test public void should_indicate_when_keyspace_failed_on_all_channels() { - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -151,7 +161,8 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) .build(); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -160,7 +171,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { @Test public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); ClusterNameMismatchException error = new ClusterNameMismatchException(ADDRESS, "actual", "expected"); @@ -171,7 +182,7 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro .failure(ADDRESS, error) .build(); - ChannelPool.init(NODE, null, poolSize, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -187,7 +198,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { // Short delay so we don't have to wait in the test Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -202,7 +213,8 @@ public void should_reconnect_when_init_incomplete() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -231,7 +243,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { public void should_reconnect_when_channel_dies() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -247,7 +259,8 @@ public void should_reconnect_when_channel_dies() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -279,7 +292,8 @@ public void should_reconnect_when_channel_dies() throws Exception { @Test public void should_shrink_outside_of_reconnection() throws Exception { - int poolSize = 4; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -294,7 +308,8 @@ public void should_shrink_outside_of_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -304,7 +319,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(NODE)); - pool.resize(2); + pool.resize(NodeDistance.LOCAL); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); @@ -318,7 +333,8 @@ public void should_shrink_outside_of_reconnection() throws Exception { public void should_shrink_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 4; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -339,7 +355,8 @@ public void should_shrink_during_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -353,7 +370,7 @@ public void should_shrink_during_reconnection() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - pool.resize(2); + pool.resize(NodeDistance.LOCAL); waitForPendingAdminTasks(); @@ -377,7 +394,8 @@ public void should_shrink_during_reconnection() throws Exception { public void should_grow_outside_of_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -394,7 +412,8 @@ public void should_grow_outside_of_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -404,7 +423,7 @@ public void should_grow_outside_of_reconnection() throws Exception { ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); - pool.resize(4); + pool.resize(NodeDistance.REMOTE); waitForPendingAdminTasks(); // The resizing should have triggered a reconnection @@ -425,7 +444,8 @@ public void should_grow_outside_of_reconnection() throws Exception { public void should_grow_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -447,7 +467,8 @@ public void should_grow_during_reconnection() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -461,7 +482,7 @@ public void should_grow_during_reconnection() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - pool.resize(4); + pool.resize(NodeDistance.REMOTE); waitForPendingAdminTasks(); @@ -494,18 +515,18 @@ public void should_grow_during_reconnection() throws Exception { @Test public void should_switch_keyspace_on_existing_channels() throws Exception { - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); - CompletableFuture channel2Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) .success(ADDRESS, channel1) .success(ADDRESS, channel2) .build(); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -530,7 +551,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { public void should_switch_keyspace_on_pending_channels() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 2; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); CompletableFuture channel1Future = new CompletableFuture<>(); @@ -546,7 +567,8 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { .pending(ADDRESS, channel2Future) .build(); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -581,7 +603,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { public void should_close_all_channels_when_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -598,7 +620,8 @@ public void should_close_all_channels_when_closed() throws Exception { .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -643,7 +666,7 @@ public void should_close_all_channels_when_closed() throws Exception { public void should_force_close_all_channels_when_force_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - int poolSize = 3; + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -660,7 +683,8 @@ public void should_force_close_all_channels_when_force_closed() throws Exception .build(); InOrder inOrder = Mockito.inOrder(eventBus); - CompletionStage poolFuture = ChannelPool.init(NODE, null, poolSize, context); + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); From 6ee2eb889b20c2a7738552e0deeaf73c96c405ff Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Apr 2017 11:36:34 -0700 Subject: [PATCH 042/742] Start session implementation with pool management --- core/console.scala | 11 + .../datastax/oss/driver/api/core/Cluster.java | 35 + .../oss/driver/api/core/ClusterBuilder.java | 2 +- .../oss/driver/api/core/session/Session.java | 3 +- .../driver/internal/core/DefaultCluster.java | 77 +- .../core/context/DefaultDriverContext.java | 7 + .../core/context/InternalDriverContext.java | 3 + .../internal/core/pool/ChannelPool.java | 9 + .../core/pool/ChannelPoolFactory.java | 30 + .../internal/core/session/DefaultSession.java | 388 ++++++++++ .../util/concurrent/CompletableFutures.java | 4 +- .../internal/core/CompletionStageAssert.java | 6 + .../core/session/DefaultSessionTest.java | 710 ++++++++++++++++++ .../session/MockChannelPoolFactoryHelper.java | 204 +++++ 14 files changed, 1469 insertions(+), 20 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java diff --git a/core/console.scala b/core/console.scala index 277bb1c97bb..8f2c8305997 100644 --- a/core/console.scala +++ b/core/console.scala @@ -11,6 +11,8 @@ * Use Ctrl+C instead. */ import com.datastax.oss.driver.api.core._ +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent +import com.datastax.oss.driver.internal.core.context.InternalDriverContext import java.net.InetSocketAddress import scala.collection.JavaConversions._ @@ -22,3 +24,12 @@ val address5 = new InetSocketAddress("127.0.0.5", 9042) val address6 = new InetSocketAddress("127.0.0.6", 9042) val builder = Cluster.builder().withContactPoints(Set(address1)) + +println("********************************************") +println("* To start a driver instance, run: *") +println("* implicit val cluster = builder.build *") +println("********************************************") + +def fire(event: AnyRef)(implicit cluster: Cluster): Unit = { + cluster.getContext.asInstanceOf[InternalDriverContext].eventBus().fire(event) +} \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 1ab6904aa54..effa046fdc4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -16,8 +16,12 @@ package com.datastax.oss.driver.api.core; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.concurrent.CompletionStage; /** An instance of the driver, that connects to a Cassandra cluster. */ @@ -47,4 +51,35 @@ static ClusterBuilder builder() { /** Returns a context that provides access to all the policies used by this driver instance. */ DriverContext getContext(); + + /** Creates a new session to execute requests against a given keyspace. */ + CompletionStage connectAsync(CqlIdentifier keyspace); + + /** + * Creates a new session not tied to any keyspace. + * + *

This is equivalent to {@code this.connectAsync(null)}. + */ + default CompletionStage connectAsync() { + return connectAsync(null); + } + + /** + * Convenience method to call {@link #connectAsync(CqlIdentifier)} and block for the result. + * + *

This must not be called on a driver thread. + */ + default CqlSession connect(CqlIdentifier keyspace) { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(connectAsync(keyspace)); + } + + /** + * Convenience method to call {@link #connectAsync()} and block for the result. + * + *

This must not be called on a driver thread. + */ + default CqlSession connect() { + return connect(null); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 36f89297176..1611f3dbe5a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -121,7 +121,7 @@ public CompletionStage buildAsync() { /** Convenience method to call {@link #buildAsync()} and block on the result. */ public Cluster build() { BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(buildAsync().toCompletableFuture()); + return CompletableFutures.getUninterruptibly(buildAsync()); } private static T buildIfNull(T value, Supplier builder) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index d58708e5497..831e6b1b71c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.cql.CqlSession; /** @@ -24,7 +25,7 @@ * registered a custom request processor with the driver). For regular CQL queries, see {@link * CqlSession}. */ -public interface Session { +public interface Session extends AsyncAutoCloseable { SyncResultT execute(Request request); AsyncResultT executeAsync(Request request); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index e9a5d00de66..29ca42dcf71 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -17,13 +17,19 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.google.common.collect.ImmutableList; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; @@ -72,6 +78,13 @@ public DriverContext getContext() { return context; } + @Override + public CompletionStage connectAsync(CqlIdentifier keyspace) { + CompletableFuture connectFuture = new CompletableFuture<>(); + RunOrSchedule.on(adminExecutor, () -> singleThreaded.connect(keyspace, connectFuture)); + return connectFuture; + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -99,12 +112,15 @@ private class SingleThreaded { private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; private boolean forceCloseWasCalled; - private List> childrenCloseFutures; + // Note: closed sessions are not removed from the list. If this creates a memory issue, there + // is something really wrong in the client program + private List sessions; private SingleThreaded(InternalDriverContext context, Set contactPoints) { this.context = context; this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; + this.sessions = new ArrayList<>(); } private void init() { @@ -152,6 +168,30 @@ private void init() { }); } + private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + connectFuture.completeExceptionally(new DriverException("Cluster was closed")); + } else { + DefaultSession.init(context, keyspace) + .whenCompleteAsync( + (session, error) -> { + if (error != null) { + connectFuture.completeExceptionally(error); + } else if (closeWasCalled) { + connectFuture.completeExceptionally( + new DriverException("Cluster was closed while session was initializing")); + session.forceCloseAsync(); + } else { + sessions.add(session); + connectFuture.complete(session); + } + }, + adminExecutor) + .exceptionally(UncaughtExceptions::log); + } + } + private void close() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { @@ -160,12 +200,13 @@ private void close() { closeWasCalled = true; LOG.debug("Closing {}", this); - childrenCloseFutures = new ArrayList<>(); + List> childrenCloseStages = new ArrayList<>(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { LOG.debug("Closing {}", closeable); - childrenCloseFutures.add(closeable.closeAsync()); + childrenCloseStages.add(closeable.closeAsync()); } - CompletableFutures.whenAllDone(childrenCloseFutures, this::onChildrenClosed, adminExecutor); + CompletableFutures.whenAllDone( + childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); } private void forceClose() { @@ -178,26 +219,27 @@ private void forceClose() { LOG.debug("Force-closing {} (was {}closed before)", this, (closeWasCalled ? "" : "not ")); if (closeWasCalled) { - // childrenCloseFutures is already created, and onChildrenClosed has already been called + // onChildrenClosed has already been called for (AsyncAutoCloseable closeable : internalComponentsToClose()) { LOG.debug("Force-closing {}", closeable); closeable.forceCloseAsync(); } } else { closeWasCalled = true; - childrenCloseFutures = new ArrayList<>(); + List> childrenCloseStages = new ArrayList<>(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { LOG.debug("Force-closing {}", closeable); - childrenCloseFutures.add(closeable.forceCloseAsync()); + childrenCloseStages.add(closeable.forceCloseAsync()); } - CompletableFutures.whenAllDone(childrenCloseFutures, this::onChildrenClosed, adminExecutor); + CompletableFutures.whenAllDone( + childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); } } - private void onChildrenClosed() { + private void onChildrenClosed(List> childrenCloseStages) { assert adminExecutor.inEventLoop(); - for (CompletionStage future : childrenCloseFutures) { - warnIfFailed(future); + for (CompletionStage stage : childrenCloseStages) { + warnIfFailed(stage); } context .nettyOptions() @@ -221,11 +263,14 @@ private void warnIfFailed(CompletionStage stage) { } private List internalComponentsToClose() { - return ImmutableList.of( - nodeStateManager, - metadataManager, - context.topologyMonitor(), - context.controlConnection()); + return ImmutableList.builder() + .addAll(sessions) + .add( + nodeStateManager, + metadataManager, + context.topologyMonitor(), + context.controlConnection()) + .build(); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index ce9961b2767..05c24b0800d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -103,6 +104,7 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); private final DriverConfig config; + private final ChannelPoolFactory channelPoolFactory = new ChannelPoolFactory(); public DefaultDriverContext(DriverConfig config) { this.config = config; @@ -274,6 +276,11 @@ public ChannelFactory channelFactory() { return channelFactoryRef.get(); } + @Override + public ChannelPoolFactory channelPoolFactory() { + return channelPoolFactory; + } + @Override public TopologyMonitor topologyMonitor() { return topologyMonitorRef.get(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index adb73b465b4..51851d343d4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; @@ -48,6 +49,8 @@ public interface InternalDriverContext extends DriverContext { ChannelFactory channelFactory(); + ChannelPoolFactory channelPoolFactory(); + TopologyMonitor topologyMonitor(); MetadataManager metadataManager(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 1380fc9383b..67c98cd18aa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -132,6 +132,10 @@ public CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { return RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspaceName)); } + public void reconnectNow() { + RunOrSchedule.on(adminExecutor, singleThreaded::reconnectNow); + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -362,6 +366,11 @@ private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { return setKeyspaceFuture; } + private void reconnectNow() { + assert adminExecutor.inEventLoop(); + reconnection.reconnectNow(false); + } + private void close() { assert adminExecutor.inEventLoop(); if (isClosing) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java new file mode 100644 index 00000000000..3ddbba23463 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.concurrent.CompletionStage; + +/** Just a level of indirection to make testing easier. */ +public class ChannelPoolFactory { + public CompletionStage init( + Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { + return ChannelPool.init(node, keyspaceName, distance, context); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java new file mode 100644 index 00000000000..895cc4cc83d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.google.common.annotations.VisibleForTesting; +import io.netty.util.concurrent.EventExecutor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The session implementation. + * + *

It maintains a {@link ChannelPool} to each node that the {@link LoadBalancingPolicy} set to a + * non-ignored distance. It listens for distance events and node state events, in order to adjust + * the pools accordingly. + * + *

It executes requests by: + * + *

    + *
  • picking the appropriate processor to convert the request into a protocol message. + *
  • getting a query plan from the load balancing policy + *
  • trying to send the message on each pool, in the order of the query plan + *
+ */ +public class DefaultSession implements CqlSession { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); + + public static CompletionStage init( + InternalDriverContext context, CqlIdentifier keyspace) { + return new DefaultSession(context, keyspace).init(); + } + + private final EventExecutor adminExecutor; + private final SingleThreaded singleThreaded; + + @VisibleForTesting + final ConcurrentMap pools = + new ConcurrentHashMap<>( + 16, + 0.75f, + // the map will only be updated from adminExecutor + 1); + + private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace) { + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.singleThreaded = new SingleThreaded(context, keyspace); + } + + private CompletionStage init() { + RunOrSchedule.on(adminExecutor, singleThreaded::init); + return singleThreaded.initFuture; + } + + @Override + public SyncResultT execute( + Request request) { + throw new UnsupportedOperationException("TODO"); + } + + @Override + public AsyncResultT executeAsync( + Request request) { + throw new UnsupportedOperationException("TODO"); + } + + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage forceCloseAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + return singleThreaded.closeFuture; + } + + private class SingleThreaded { + + private final InternalDriverContext context; + private final ChannelPoolFactory channelPoolFactory; + private final CompletableFuture initFuture = new CompletableFuture<>(); + private boolean initWasCalled; + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; + private boolean forceCloseWasCalled; + private final Object distanceListenerKey; + private final ReplayingEventFilter distanceEventFilter = + new ReplayingEventFilter<>(this::processDistanceEvent); + private final Object stateListenerKey; + private final ReplayingEventFilter stateEventFilter = + new ReplayingEventFilter<>(this::processStateEvent); + // The pools that we have opened but have not finished initializing yet + private final Map> pending = new HashMap<>(); + // If we receive events while a pool is initializing, the last one is stored here + private final Map pendingDistanceEvents = new HashMap<>(); + private final Map pendingStateEvents = new HashMap<>(); + + private CqlIdentifier keyspace; + + private SingleThreaded(InternalDriverContext context, CqlIdentifier keyspace) { + this.context = context; + this.channelPoolFactory = context.channelPoolFactory(); + this.keyspace = keyspace; + this.distanceListenerKey = + context + .eventBus() + .register( + DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); + this.stateListenerKey = + context + .eventBus() + .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); + } + + private void init() { + assert adminExecutor.inEventLoop(); + if (initWasCalled) { + return; + } + initWasCalled = true; + + LOG.debug("Initializing {}", DefaultSession.this); + + // Make sure we don't miss any event while the pools are initializing + distanceEventFilter.start(); + stateEventFilter.start(); + + Collection nodes = context.metadataManager().getMetadata().getNodes().values(); + List> poolStages = new ArrayList<>(nodes.size()); + for (Node node : nodes) { + NodeDistance distance = node.getDistance(); + if (distance == NodeDistance.IGNORED) { + LOG.debug("Skipping {} because it is IGNORED", node); + } else if (node.getState() == NodeState.FORCED_DOWN) { + LOG.debug("Skipping {} because it is FORCED_DOWN", node); + } else { + poolStages.add(channelPoolFactory.init(node, keyspace, distance, context)); + } + } + CompletableFutures.whenAllDone(poolStages, () -> this.onPoolsInit(poolStages), adminExecutor); + } + + private void onPoolsInit(List> poolStages) { + assert adminExecutor.inEventLoop(); + LOG.debug("{}: all pools have finished initializing", DefaultSession.this); + // We will only propagate an invalid keyspace error if all pools get it + boolean allInvalidKeyspaces = true; + for (CompletionStage poolStage : poolStages) { + // Note: pool init always succeeds + ChannelPool pool = CompletableFutures.getCompleted(poolStage.toCompletableFuture()); + boolean invalidKeyspace = pool.isInvalidKeyspace(); + LOG.debug("Pool to {} -- invalid keyspace = {}", pool.getNode(), invalidKeyspace); + allInvalidKeyspaces &= invalidKeyspace; + pools.put(pool.getNode(), pool); + } + if (allInvalidKeyspaces) { + initFuture.completeExceptionally( + new InvalidKeyspaceException("Invalid keyspace " + keyspace.asPrettyCql())); + forceClose(); + } else { + initFuture.complete(DefaultSession.this); + distanceEventFilter.markReady(); + stateEventFilter.markReady(); + } + } + + private void onDistanceEvent(DistanceEvent event) { + assert adminExecutor.inEventLoop(); + distanceEventFilter.accept(event); + } + + private void onStateEvent(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + stateEventFilter.accept(event); + } + + private void processDistanceEvent(DistanceEvent event) { + assert adminExecutor.inEventLoop(); + // no need to check closeWasCalled, because we stop listening for events one closed + DefaultNode node = event.node; + NodeDistance newDistance = event.distance; + if (pending.containsKey(node)) { + pendingDistanceEvents.put(node, event); + } else if (newDistance == NodeDistance.IGNORED && pools.containsKey(node)) { + ChannelPool pool = pools.remove(node); + if (pool != null) { + LOG.debug("{} became IGNORED, destroying pool", node); + pool.closeAsync() + .exceptionally( + error -> { + LOG.warn("Error closing pool", error); + return null; + }); + } + } else { + NodeState state = node.getState(); + if (state == NodeState.FORCED_DOWN) { + LOG.warn("{} became {} but it is FORCED_DOWN, ignoring", node, newDistance); + return; + } + ChannelPool pool = pools.get(node); + if (pool == null) { + LOG.debug("{} became {} and no pool found, initializing it", node, newDistance); + CompletionStage poolFuture = + channelPoolFactory.init(node, keyspace, newDistance, context); + pending.put(node, poolFuture); + poolFuture + .thenAcceptAsync(this::onPoolAdded, adminExecutor) + .exceptionally(UncaughtExceptions::log); + } else { + LOG.debug("{} became {}, resizing it", node, newDistance); + pool.resize(newDistance); + } + } + } + + private void processStateEvent(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + // no need to check closeWasCalled, because we stop listening for events one closed + DefaultNode node = event.node; + NodeState newState = event.newState; + if (pending.containsKey(node)) { + pendingStateEvents.put(node, event); + } else if (newState == NodeState.FORCED_DOWN) { + ChannelPool pool = pools.remove(node); + if (pool != null) { + LOG.debug("{} became FORCED_DOWN, destroying pool", node); + pool.closeAsync() + .exceptionally( + error -> { + LOG.warn("Error closing pool", error); + return null; + }); + } + } else if (newState == NodeState.UP) { + ChannelPool pool = pools.get(node); + if (pool == null) { + LOG.debug("{} came back UP and no pool found, initializing it"); + CompletionStage poolFuture = + channelPoolFactory.init(node, keyspace, node.getDistance(), context); + pending.put(node, poolFuture); + poolFuture + .thenAcceptAsync(this::onPoolAdded, adminExecutor) + .exceptionally(UncaughtExceptions::log); + } else { + LOG.debug("{} came back UP, triggering pool reconnection", node); + pool.reconnectNow(); + } + } + } + + private void onPoolAdded(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + Node node = pool.getNode(); + if (closeWasCalled) { + LOG.debug("Session was closed while a pool to {} was initializing, closing it", node); + pool.forceCloseAsync(); + } else { + LOG.debug("New pool to {} initialized", node); + pending.remove(node); + pools.put(node, pool); + DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); + NodeStateEvent stateEvent = pendingStateEvents.remove(node); + if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { + LOG.debug("Received {} while the pool was initializing, processing it now", stateEvent); + processStateEvent(stateEvent); + } else if (distanceEvent != null) { + LOG.debug( + "Received {} while the pool was initializing, processing it now", distanceEvent); + processDistanceEvent(distanceEvent); + } + } + } + + private void close() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + + // Stop listening for events + context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); + context.eventBus().unregister(stateListenerKey, NodeStateEvent.class); + + List> closePoolStages = new ArrayList<>(pools.size()); + for (ChannelPool pool : pools.values()) { + closePoolStages.add(pool.closeAsync()); + } + CompletableFutures.whenAllDone( + closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + } + + private void forceClose() { + assert adminExecutor.inEventLoop(); + if (forceCloseWasCalled) { + return; + } + forceCloseWasCalled = true; + + if (closeWasCalled) { + for (ChannelPool pool : pools.values()) { + pool.forceCloseAsync(); + } + } else { + List> closePoolStages = new ArrayList<>(pools.size()); + for (ChannelPool pool : pools.values()) { + closePoolStages.add(pool.forceCloseAsync()); + } + CompletableFutures.whenAllDone( + closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + } + } + + private void onAllPoolsClosed(List> closePoolStages) { + assert adminExecutor.inEventLoop(); + Throwable firstError = null; + for (CompletionStage closePoolStage : closePoolStages) { + CompletableFuture closePoolFuture = closePoolStage.toCompletableFuture(); + assert closePoolFuture.isDone(); + if (closePoolFuture.isCompletedExceptionally()) { + Throwable error = CompletableFutures.getFailed(closePoolFuture); + if (firstError == null) { + firstError = error; + } else { + LOG.error( + "Error closing multiple pools in the same session, logging because only " + + "the first one is included in the session's failed future", + error); + } + } + } + if (firstError != null) { + closeFuture.completeExceptionally(new DriverException("Error closing pool(s)", firstError)); + } else { + closeFuture.complete(null); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 1afee7c3d26..e5c6f9b9a8b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -93,12 +93,12 @@ public static Throwable getFailed(CompletableFuture future) { } } - public static T getUninterruptibly(CompletableFuture future) { + public static T getUninterruptibly(CompletionStage stage) { boolean interrupted = false; try { while (true) { try { - return future.get(); + return stage.toCompletableFuture().get(); } catch (InterruptedException e) { interrupted = true; } catch (ExecutionException e) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java index bb5d85157b1..b0dcf3c5613 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -22,6 +22,7 @@ import java.util.function.Consumer; import org.assertj.core.api.AbstractAssert; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; public class CompletionStageAssert @@ -64,4 +65,9 @@ public CompletionStageAssert isFailed(Consumer failureAssertions) public CompletionStageAssert isFailed() { return isFailed(f -> {}); } + + public CompletionStageAssert isNotDone() { + assertThat(actual.toCompletableFuture().isDone()).isFalse(); + return this; + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java new file mode 100644 index 00000000000..d5867ba8cb6 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.util.concurrent.Future; +import java.net.InetSocketAddress; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.timeout; + +public class DefaultSessionTest { + + private static final CqlIdentifier KEYSPACE = CqlIdentifier.fromInternal("ks"); + + @Mock private InternalDriverContext context; + @Mock private NettyOptions nettyOptions; + @Mock private ChannelPoolFactory channelPoolFactory; + @Mock private MetadataManager metadataManager; + @Mock private Metadata metadata; + + private DefaultNode node1; + private DefaultNode node2; + private DefaultNode node3; + private DefaultEventLoopGroup adminEventLoopGroup; + private EventBus eventBus; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + adminEventLoopGroup = new DefaultEventLoopGroup(1); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + + Mockito.when(context.channelPoolFactory()).thenReturn(channelPoolFactory); + + eventBus = Mockito.spy(new EventBus()); + Mockito.when(context.eventBus()).thenReturn(eventBus); + + node1 = mockLocalNode(1); + node2 = mockLocalNode(2); + node3 = mockLocalNode(3); + ImmutableMap nodes = + ImmutableMap.of( + node1.getConnectAddress(), node1, + node2.getConnectAddress(), node2, + node3.getConnectAddress(), node3); + Mockito.when(metadata.getNodes()).thenReturn(nodes); + Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + } + + @Test + public void should_initialize_pools_with_distances() { + Mockito.when(node3.getDistance()).thenReturn(NodeDistance.REMOTE); + + CompletableFuture pool1Future = new CompletableFuture<>(); + CompletableFuture pool2Future = new CompletableFuture<>(); + CompletableFuture pool3Future = new CompletableFuture<>(); + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .pending(node1, KEYSPACE, NodeDistance.LOCAL, pool1Future) + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .pending(node3, KEYSPACE, NodeDistance.REMOTE, pool3Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.REMOTE); + waitForPendingAdminTasks(); + + assertThat(initFuture).isNotDone(); + + pool1Future.complete(pool1); + pool2Future.complete(pool2); + pool3Future.complete(pool3); + waitForPendingAdminTasks(); + + assertThat(initFuture) + .isSuccess( + session -> + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3)); + } + + @Test + public void should_not_connect_to_ignored_nodes() { + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture) + .isSuccess( + session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + } + + @Test + public void should_not_connect_to_forced_down_nodes() { + Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture) + .isSuccess( + session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + } + + @Test + public void should_adjust_distance_if_changed_while_init() { + CompletableFuture pool1Future = new CompletableFuture<>(); + CompletableFuture pool2Future = new CompletableFuture<>(); + CompletableFuture pool3Future = new CompletableFuture<>(); + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .pending(node1, KEYSPACE, NodeDistance.LOCAL, pool1Future) + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + + assertThat(initFuture).isNotDone(); + + // Distance changes while init still pending + eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); + + pool1Future.complete(pool1); + pool2Future.complete(pool2); + pool3Future.complete(pool3); + waitForPendingAdminTasks(); + + Mockito.verify(pool2).resize(NodeDistance.REMOTE); + + assertThat(initFuture) + .isSuccess( + session -> + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3)); + } + + @Test + public void should_remove_pool_if_ignored_while_init() { + CompletableFuture pool1Future = new CompletableFuture<>(); + CompletableFuture pool2Future = new CompletableFuture<>(); + CompletableFuture pool3Future = new CompletableFuture<>(); + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .pending(node1, KEYSPACE, NodeDistance.LOCAL, pool1Future) + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + + assertThat(initFuture).isNotDone(); + + // Distance changes while init still pending + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); + + pool1Future.complete(pool1); + pool2Future.complete(pool2); + pool3Future.complete(pool3); + waitForPendingAdminTasks(); + + Mockito.verify(pool2).closeAsync(); + + assertThat(initFuture) + .isSuccess( + session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + } + + @Test + public void should_remove_pool_if_forced_down_while_init() { + CompletableFuture pool1Future = new CompletableFuture<>(); + CompletableFuture pool2Future = new CompletableFuture<>(); + CompletableFuture pool3Future = new CompletableFuture<>(); + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .pending(node1, KEYSPACE, NodeDistance.LOCAL, pool1Future) + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + + assertThat(initFuture).isNotDone(); + + // Forced down while init still pending + eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); + + pool1Future.complete(pool1); + pool2Future.complete(pool2); + pool3Future.complete(pool3); + waitForPendingAdminTasks(); + + Mockito.verify(pool2).closeAsync(); + + assertThat(initFuture) + .isSuccess( + session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + } + + @Test + public void should_resize_pool_if_distance_changes() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + + eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); + Mockito.verify(pool2, timeout(100)).resize(NodeDistance.REMOTE); + } + + @Test + public void should_remove_pool_if_node_becomes_ignored() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); + Mockito.verify(pool2, timeout(100)).closeAsync(); + + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + } + + @Test + public void should_recreate_pool_if_node_becomes_not_ignored() { + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // When node2 becomes not ignored + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); + + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + } + + @Test + public void should_remove_pool_if_node_is_forced_down() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + + eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); + Mockito.verify(pool2, timeout(100)).closeAsync(); + + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + } + + @Test + public void should_recreate_pool_if_node_is_forced_back_up() { + Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // init + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // when node2 comes back up + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + } + + @Test + public void should_adjust_distance_if_changed_while_recreating() { + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + CompletableFuture pool2Future = new CompletableFuture<>(); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // When node2 becomes not ignored + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); + + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + + // Distance changes again while pool init is in progress + eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); + + // Now pool init succeeds + pool2Future.complete(pool2); + waitForPendingAdminTasks(); + + // Pool should have been adjusted + Mockito.verify(pool2).resize(NodeDistance.REMOTE); + + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + } + + @Test + public void should_remove_pool_if_ignored_while_recreating() { + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + CompletableFuture pool2Future = new CompletableFuture<>(); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // When node2 becomes not ignored + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); + + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + + // Distance changes to ignored while pool init is in progress + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); + + // Now pool init succeeds + pool2Future.complete(pool2); + waitForPendingAdminTasks(); + + // Pool should have been closed + Mockito.verify(pool2).closeAsync(); + + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + } + + @Test + public void should_remove_pool_if_forced_down_while_recreating() { + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + CompletableFuture pool2Future = new CompletableFuture<>(); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // Initial connection + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // When node2 becomes not ignored + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); + + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + + // Forced down while pool init is in progress + eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); + + // Now pool init succeeds + pool2Future.complete(pool2); + waitForPendingAdminTasks(); + + // Pool should have been closed + Mockito.verify(pool2).closeAsync(); + + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + } + + @Test + public void should_close_all_pools_when_closing() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + + CompletionStage closeFuture = session.closeAsync(); + waitForPendingAdminTasks(); + assertThat(closeFuture).isSuccess(); + + Mockito.verify(pool1).closeAsync(); + Mockito.verify(pool2).closeAsync(); + Mockito.verify(pool3).closeAsync(); + } + + @Test + public void should_force_close_all_pools_when_force_closing() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + + CompletionStage closeFuture = session.forceCloseAsync(); + waitForPendingAdminTasks(); + assertThat(closeFuture).isSuccess(); + + Mockito.verify(pool1).forceCloseAsync(); + Mockito.verify(pool2).forceCloseAsync(); + Mockito.verify(pool3).forceCloseAsync(); + } + + @Test + public void should_close_pool_if_recreated_while_closing() { + Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + CompletableFuture pool2Future = new CompletableFuture<>(); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // init + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // when node2 comes back up + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + + // node2 comes back up, start initializing a pool for it + eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + + // but the session gets closed before pool init completes + CompletionStage closeFuture = session.closeAsync(); + waitForPendingAdminTasks(); + assertThat(closeFuture).isSuccess(); + + // now pool init completes + pool2Future.complete(pool2); + waitForPendingAdminTasks(); + + // Pool should have been closed + Mockito.verify(pool2).forceCloseAsync(); + } + + private ChannelPool mockPool(Node node) { + ChannelPool pool = Mockito.mock(ChannelPool.class); + Mockito.when(pool.getNode()).thenReturn(node); + CompletableFuture closeFuture = new CompletableFuture<>(); + Mockito.when(pool.closeFuture()).thenReturn(closeFuture); + Mockito.when(pool.closeAsync()) + .then( + i -> { + closeFuture.complete(null); + return closeFuture; + }); + Mockito.when(pool.forceCloseAsync()) + .then( + i -> { + closeFuture.complete(null); + return closeFuture; + }); + return pool; + } + + private static DefaultNode mockLocalNode(int i) { + DefaultNode node = Mockito.mock(DefaultNode.class); + Mockito.when(node.getConnectAddress()).thenReturn(new InetSocketAddress("127.0.0." + i, 9042)); + Mockito.when(node.getDistance()).thenReturn(NodeDistance.LOCAL); + Mockito.when(node.toString()).thenReturn("node" + i); + return node; + } + + // Wait for all the tasks on the pool's admin executor to complete. + private void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java new file mode 100644 index 00000000000..f40f0bb9738 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.internal.util.MockUtil; +import org.mockito.stubbing.OngoingStubbing; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; + +public class MockChannelPoolFactoryHelper { + + public static MockChannelPoolFactoryHelper.Builder builder( + ChannelPoolFactory channelPoolFactory) { + return new MockChannelPoolFactoryHelper.Builder(channelPoolFactory); + } + + private final ChannelPoolFactory channelPoolFactory; + private final InOrder inOrder; + // If waitForCalls sees more invocations than expected, the difference is stored here + private final Map previous = new HashMap<>(); + + private MockChannelPoolFactoryHelper(ChannelPoolFactory channelPoolFactory) { + this.channelPoolFactory = channelPoolFactory; + this.inOrder = Mockito.inOrder(channelPoolFactory); + } + + public void waitForCall(Node node, CqlIdentifier keyspace, NodeDistance distance) { + waitForCalls(node, keyspace, distance, 1); + } + + /** + * Waits for a given number of calls to {@code ChannelPoolFactory.init()}. + * + *

Because we test asynchronous, non-blocking code, there might already be more calls than + * expected when this method is called. If so, the extra calls are stored and stored and will be + * taken into account next time. + */ + public void waitForCalls(Node node, CqlIdentifier keyspace, NodeDistance distance, int expected) { + Params params = new Params(node, keyspace, distance); + int fromLastTime = previous.getOrDefault(params, 0); + if (fromLastTime >= expected) { + previous.put(params, fromLastTime - expected); + return; + } + expected -= fromLastTime; + + // Because we test asynchronous, non-blocking code, there might have been already more + // invocations than expected. Use `atLeast` and a captor to find out. + ArgumentCaptor contextCaptor = + ArgumentCaptor.forClass(InternalDriverContext.class); + inOrder + .verify(channelPoolFactory, timeout(100).atLeast(expected)) + .init(eq(node), eq(keyspace), eq(distance), contextCaptor.capture()); + int actual = contextCaptor.getAllValues().size(); + + int extras = actual - expected; + if (extras > 0) { + previous.compute(params, (k, v) -> (v == null) ? extras : v + extras); + } + } + + public static class Builder { + private final ChannelPoolFactory channelPoolFactory; + private final ListMultimap invocations = + MultimapBuilder.hashKeys().arrayListValues().build(); + + private Builder(ChannelPoolFactory channelPoolFactory) { + assertThat(MockUtil.isMock(channelPoolFactory)).isTrue().as("expected a mock"); + Mockito.verifyZeroInteractions(channelPoolFactory); + this.channelPoolFactory = channelPoolFactory; + } + + public Builder success( + Node node, CqlIdentifier keyspaceName, NodeDistance distance, ChannelPool pool) { + invocations.put(new Params(node, keyspaceName, distance), pool); + return this; + } + + public Builder failure( + Node node, CqlIdentifier keyspaceName, NodeDistance distance, String error) { + invocations.put(new Params(node, keyspaceName, distance), new Exception(error)); + return this; + } + + public Builder failure( + Node node, CqlIdentifier keyspaceName, NodeDistance distance, Throwable error) { + invocations.put(new Params(node, keyspaceName, distance), error); + return this; + } + + public Builder pending( + Node node, + CqlIdentifier keyspaceName, + NodeDistance distance, + CompletionStage future) { + invocations.put(new Params(node, keyspaceName, distance), future); + return this; + } + + public MockChannelPoolFactoryHelper build() { + stub(); + return new MockChannelPoolFactoryHelper(channelPoolFactory); + } + + private void stub() { + for (Params params : invocations.keySet()) { + LinkedList> results = new LinkedList<>(); + for (Object object : invocations.get(params)) { + if (object instanceof ChannelPool) { + results.add(CompletableFuture.completedFuture(((ChannelPool) object))); + } else if (object instanceof Throwable) { + results.add(CompletableFutures.failedFuture(((Throwable) object))); + } else if (object instanceof CompletableFuture) { + @SuppressWarnings("unchecked") + CompletionStage future = (CompletionStage) object; + results.add(future); + } else { + fail("unexpected type: " + object.getClass()); + } + } + if (results.size() > 0) { + CompletionStage first = results.poll(); + OngoingStubbing> ongoingStubbing = + Mockito.when( + channelPoolFactory.init( + eq(params.node), + eq(params.keyspace), + eq(params.distance), + any(InternalDriverContext.class))) + .thenReturn(first); + for (CompletionStage result : results) { + ongoingStubbing.thenReturn(result); + } + } + } + } + } + + private static class Params { + private final Node node; + private final CqlIdentifier keyspace; + private final NodeDistance distance; + + private Params(Node node, CqlIdentifier keyspace, NodeDistance distance) { + this.node = node; + this.keyspace = keyspace; + this.distance = distance; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Params) { + Params that = (Params) other; + return Objects.equals(this.node, that.node) + && Objects.equals(this.keyspace, that.keyspace) + && Objects.equals(this.distance, that.distance); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(node, keyspace, distance); + } + } +} From 1d7114dcb734f0e8e9346078174d3961ee88aecd Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 30 Apr 2017 11:12:39 -0700 Subject: [PATCH 043/742] Test types serialization and detachment --- .../driver/internal/type/DefaultMapType.java | 2 +- .../internal/type/DefaultTupleType.java | 2 +- .../internal/type/DefaultUserDefinedType.java | 3 +- .../driver/internal/type/PrimitiveType.java | 3 +- .../driver/internal/SerializationHelper.java | 54 +++++ .../internal/type/DataTypeDetachableTest.java | 185 ++++++++++++++++++ .../type/DataTypeSerializationTest.java | 60 ++++++ 7 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java create mode 100644 types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java index ecbc2c672e0..67645d7c9f2 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java @@ -59,7 +59,7 @@ public boolean isFrozen() { @Override public boolean isDetached() { - return keyType.isDetached() && valueType.isDetached(); + return keyType.isDetached() || valueType.isDetached(); } @Override diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java index 7ad587cbe0a..f8c7ca3b579 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java @@ -58,7 +58,7 @@ public TupleValue newValue() { @Override public boolean isDetached() { - return attachmentPoint != AttachmentPoint.NONE; + return attachmentPoint == AttachmentPoint.NONE; } @Override diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java index dedb9f20d9f..1e1dc8e2434 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java @@ -111,7 +111,7 @@ public UdtValue newValue() { @Override public boolean isDetached() { - return attachmentPoint == null; + return attachmentPoint == AttachmentPoint.NONE; } @Override @@ -161,6 +161,7 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE Preconditions.checkArgument( fieldTypes != null && fieldTypes.size() == fieldNames.size(), "There should be the same number of field names and types"); + this.attachmentPoint = AttachmentPoint.NONE; this.index = new IdentifierIndex(this.fieldNames); } } diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java index 48a1517f531..ee461f58dbe 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java @@ -21,10 +21,9 @@ public class PrimitiveType implements DataType { + /** @serial */ private final int protocolCode; - private transient volatile boolean attached; - public PrimitiveType(int protocolCode) { this.protocolCode = protocolCode; } diff --git a/types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java b/types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java new file mode 100644 index 00000000000..59649725b52 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import static org.assertj.core.api.Assertions.fail; + +public abstract class SerializationHelper { + + public static byte[] serialize(T t) { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bytes); + out.writeObject(t); + return bytes.toByteArray(); + } catch (Exception e) { + fail("Unexpected error", e); + throw new AssertionError(); // never reached + } + } + + public static T deserialize(byte[] bytes) { + try { + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); + @SuppressWarnings("unchecked") + T t = (T) in.readObject(); + return t; + } catch (Exception e) { + fail("Unexpected error", e); + throw new AssertionError(); // never reached + } + } + + public static T serializeAndDeserialize(T t) { + return deserialize(serialize(t)); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java new file mode 100644 index 00000000000..6e59f7870a0 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.ListType; +import com.datastax.oss.driver.api.type.MapType; +import com.datastax.oss.driver.api.type.SetType; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.internal.SerializationHelper; +import com.google.common.collect.ImmutableList; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DataTypeDetachableTest { + + @Mock private AttachmentPoint attachmentPoint; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void simple_types_should_never_be_detached() { + // Because simple types don't need the codec registry, we consider them as always attached by default + for (DataType simpleType : ImmutableList.of(DataTypes.INT, DataTypes.custom("some.class"))) { + assertThat(simpleType.isDetached()).isFalse(); + assertThat(SerializationHelper.serializeAndDeserialize(simpleType).isDetached()).isFalse(); + } + } + + @Test + public void manually_created_tuple_should_be_detached() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT, DataTypes.TEXT); + assertThat(tuple.isDetached()).isTrue(); + } + + @Test + public void attaching_tuple_should_attach_all_of_its_subtypes() { + TupleType tuple1 = DataTypes.tupleOf(DataTypes.INT); + TupleType tuple2 = DataTypes.tupleOf(DataTypes.TEXT, tuple1); + + assertThat(tuple1.isDetached()).isTrue(); + assertThat(tuple2.isDetached()).isTrue(); + + tuple2.attach(attachmentPoint); + + assertThat(tuple1.isDetached()).isFalse(); + } + + @Test + public void manually_created_udt_should_be_detached() { + UserDefinedType udt = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) + .build(); + assertThat(udt.isDetached()).isTrue(); + } + + @Test + public void attaching_udt_should_attach_all_of_its_subtypes() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT); + UserDefinedType udt = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), tuple) + .build(); + + assertThat(tuple.isDetached()).isTrue(); + assertThat(udt.isDetached()).isTrue(); + + udt.attach(attachmentPoint); + + assertThat(tuple.isDetached()).isFalse(); + } + + @Test + public void list_should_be_attached_if_its_element_is() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT); + ListType list = DataTypes.listOf(tuple); + + assertThat(tuple.isDetached()).isTrue(); + assertThat(list.isDetached()).isTrue(); + + tuple.attach(attachmentPoint); + + assertThat(list.isDetached()).isFalse(); + } + + @Test + public void attaching_list_should_attach_its_element() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT); + ListType list = DataTypes.listOf(tuple); + + assertThat(tuple.isDetached()).isTrue(); + assertThat(list.isDetached()).isTrue(); + + list.attach(attachmentPoint); + + assertThat(tuple.isDetached()).isFalse(); + } + + @Test + public void set_should_be_attached_if_its_element_is() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT); + SetType set = DataTypes.setOf(tuple); + + assertThat(tuple.isDetached()).isTrue(); + assertThat(set.isDetached()).isTrue(); + + tuple.attach(attachmentPoint); + + assertThat(set.isDetached()).isFalse(); + } + + @Test + public void attaching_set_should_attach_its_element() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT); + SetType set = DataTypes.setOf(tuple); + + assertThat(tuple.isDetached()).isTrue(); + assertThat(set.isDetached()).isTrue(); + + set.attach(attachmentPoint); + + assertThat(tuple.isDetached()).isFalse(); + } + + @Test + public void map_should_be_attached_if_its_elements_are() { + TupleType tuple1 = DataTypes.tupleOf(DataTypes.INT); + TupleType tuple2 = DataTypes.tupleOf(DataTypes.TEXT); + MapType map = DataTypes.mapOf(tuple1, tuple2); + + assertThat(tuple1.isDetached()).isTrue(); + assertThat(tuple2.isDetached()).isTrue(); + assertThat(map.isDetached()).isTrue(); + + tuple1.attach(attachmentPoint); + assertThat(map.isDetached()).isTrue(); + + tuple2.attach(attachmentPoint); + assertThat(map.isDetached()).isFalse(); + } + + @Test + public void attaching_map_should_attach_all_of_its_subtypes() { + TupleType tuple1 = DataTypes.tupleOf(DataTypes.INT); + TupleType tuple2 = DataTypes.tupleOf(DataTypes.TEXT); + MapType map = DataTypes.mapOf(tuple1, tuple2); + + assertThat(tuple1.isDetached()).isTrue(); + assertThat(tuple2.isDetached()).isTrue(); + + map.attach(attachmentPoint); + + assertThat(tuple1.isDetached()).isFalse(); + assertThat(tuple2.isDetached()).isFalse(); + } +} diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java new file mode 100644 index 00000000000..08dcc585df3 --- /dev/null +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.internal.SerializationHelper; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DataTypeSerializationTest { + + @Test + public void should_serialize_and_deserialize() { + TupleType tuple = DataTypes.tupleOf(DataTypes.INT, DataTypes.TEXT); + UserDefinedType udt = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) + .build(); + + // Because primitive and custom types never use the codec registry, we consider them always attached + should_serialize_and_deserialize(DataTypes.INT, false); + should_serialize_and_deserialize(DataTypes.custom("some.class.name"), false); + + should_serialize_and_deserialize(tuple, true); + should_serialize_and_deserialize(udt, true); + should_serialize_and_deserialize(DataTypes.listOf(DataTypes.INT), false); + should_serialize_and_deserialize(DataTypes.listOf(tuple), true); + should_serialize_and_deserialize(DataTypes.setOf(udt), true); + should_serialize_and_deserialize(DataTypes.mapOf(tuple, udt), true); + } + + private void should_serialize_and_deserialize(DataType in, boolean expectDetached) { + // When + DataType out = SerializationHelper.serializeAndDeserialize(in); + + // Then + assertThat(out).isEqualTo(in); + assertThat(out.isDetached()).isEqualTo(expectDetached); + } +} From 707e41312bab6582db28734f018cdc9cb544db57 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 30 Apr 2017 11:30:24 -0700 Subject: [PATCH 044/742] Test udt and tuple serialization --- .../core/data/DefaultTupleValueTest.java | 23 ++++++++++++++++ .../core/data/DefaultUdtValueTest.java | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index e01ca85158e..b9afd6680a5 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -18,8 +18,15 @@ import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.internal.SerializationHelper; import com.datastax.oss.driver.internal.type.DefaultTupleType; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; import java.util.List; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; public class DefaultTupleValueTest extends AccessibleByIndexTestBase { @@ -28,4 +35,20 @@ protected TupleValue newInstance(List dataTypes, AttachmentPoint attac DefaultTupleType type = new DefaultTupleType(dataTypes, attachmentPoint); return type.newValue(); } + + @Test + public void should_serialize_and_deserialize() { + DefaultTupleType type = + new DefaultTupleType(ImmutableList.of(DataTypes.INT, DataTypes.TEXT), attachmentPoint); + TupleValue in = type.newValue(); + in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); + + TupleValue out = SerializationHelper.serializeAndDeserialize(in); + + assertThat(out.getType()).isEqualTo(in.getType()); + assertThat(out.getType().isDetached()).isTrue(); + assertThat(Bytes.toHexString(out.getBytesUnsafe(0))).isEqualTo("0x00000001"); + assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); + } } diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index 1ce3da3cb81..c5eab0b753d 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -19,9 +19,15 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.internal.SerializationHelper; import com.datastax.oss.driver.internal.type.UserDefinedTypeBuilder; +import com.datastax.oss.protocol.internal.util.Bytes; import java.util.List; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; public class DefaultUdtValueTest extends AccessibleByIdTestBase { @@ -37,4 +43,24 @@ protected UdtValue newInstance(List dataTypes, AttachmentPoint attachm userDefinedType.attach(attachmentPoint); return userDefinedType.newValue(); } + + @Test + public void should_serialize_and_deserialize() { + UserDefinedType type = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) + .build(); + UdtValue in = type.newValue(); + in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); + + UdtValue out = SerializationHelper.serializeAndDeserialize(in); + + assertThat(out.getType()).isEqualTo(in.getType()); + assertThat(out.getType().isDetached()).isTrue(); + assertThat(Bytes.toHexString(out.getBytesUnsafe(0))).isEqualTo("0x00000001"); + assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); + } } From c7c2d71210a42e6d0a035fbb04ba6317a41ae610 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 3 May 2017 17:41:10 -0700 Subject: [PATCH 045/742] Fix concurrency issue in event filter List needs to be concurrent because it is updated by multiple "readers". --- .../internal/core/util/concurrent/ReplayingEventFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java index a7bfad9c67e..dc3c295e983 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -53,7 +54,7 @@ private enum State { public ReplayingEventFilter(Consumer consumer) { this.consumer = consumer; this.state = State.NEW; - this.recordedEvents = new ArrayList<>(); + this.recordedEvents = new CopyOnWriteArrayList<>(); } public void start() { From ea6652615cf6afc25d587a4044c93e8b8091ba92 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 4 May 2017 10:04:17 -0700 Subject: [PATCH 046/742] Use set instead of list in round-robin policy --- .../api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index ae1bb59ce77..2212860cc6f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntUnaryOperator; import org.slf4j.Logger; @@ -38,7 +39,7 @@ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; private final AtomicInteger startIndex = new AtomicInteger(); - private final CopyOnWriteArrayList liveNodes = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { // nothing to do From b10555c5e57b1dc55760834e1dac80fc750acd5e Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 8 May 2017 10:28:59 -0700 Subject: [PATCH 047/742] Test round-robin policy --- .../RoundRobinLoadBalancingPolicy.java | 6 +- .../RoundRobinLoadBalancingPolicyTest.java | 115 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index 2212860cc6f..c2b94b9d6bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -17,10 +17,10 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntUnaryOperator; @@ -48,9 +48,11 @@ public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext c @Override public void init(Set nodes, DistanceReporter distanceReporter) { LOG.debug("Initializing with {}", nodes); - this.liveNodes.addAll(nodes); for (Node node : nodes) { distanceReporter.setDistance(node, NodeDistance.LOCAL); + if (node.getState() == NodeState.UNKNOWN || node.getState() == NodeState.UP) { + this.liveNodes.add(node); + } } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java new file mode 100644 index 00000000000..6429cb05cf8 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.loadbalancing; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.google.common.collect.ImmutableSet; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RoundRobinLoadBalancingPolicyTest { + @Mock private DriverContext context; + @Mock private DistanceReporter distanceReporter; + @Mock private Node node1, node2, node3; + + private RoundRobinLoadBalancingPolicy policy; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(node1.getState()).thenReturn(NodeState.UP); + Mockito.when(node2.getState()).thenReturn(NodeState.UP); + Mockito.when(node3.getState()).thenReturn(NodeState.UP); + + policy = new RoundRobinLoadBalancingPolicy(context); + } + + @Test + public void should_set_all_nodes_to_local_distance() { + policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); + + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + } + + @Test + public void should_return_round_robin_query_plans() { + policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); + + assertThat(policy.newQueryPlan()).containsExactly(node1, node2, node3); + assertThat(policy.newQueryPlan()).containsExactly(node2, node3, node1); + assertThat(policy.newQueryPlan()).containsExactly(node3, node1, node2); + } + + @Test + public void should_only_include_unknown_or_up_nodes_at_init() { + Mockito.when(node1.getState()).thenReturn(NodeState.DOWN); + Mockito.when(node2.getState()).thenReturn(NodeState.UP); + Mockito.when(node3.getState()).thenReturn(NodeState.UNKNOWN); + + policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); + + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + + assertThat(policy.newQueryPlan()).containsExactly(node2, node3); + } + + @Test + public void should_remove_node_from_query_plan_if_down() { + policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); + + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + + assertThat(policy.newQueryPlan()).containsExactly(node1, node2, node3); + + policy.onDown(node1); + + assertThat(policy.newQueryPlan()).containsExactly(node3, node2); + Mockito.verifyNoMoreInteractions(distanceReporter); + } + + @Test + public void should_add_node_to_query_plan_if_up() { + Mockito.when(node1.getState()).thenReturn(NodeState.DOWN); + + policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); + + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + + assertThat(policy.newQueryPlan()).containsExactly(node2, node3); + + policy.onUp(node1); + + assertThat(policy.newQueryPlan()).containsExactly(node3, node1, node2); + Mockito.verifyNoMoreInteractions(distanceReporter); + } +} From 8ab5ae66bd2cb5a0c465c42f95fdb28d8bb06fb6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 9 May 2017 16:44:31 -0700 Subject: [PATCH 048/742] Pass the node in response callbacks This will simplify request handler implementations. --- .../UnsupportedProtocolVersionException.java | 25 +-- .../adminrequest/AdminRequestHandler.java | 5 +- .../internal/core/channel/ChannelFactory.java | 29 ++-- .../channel/ClusterNameMismatchException.java | 10 +- .../core/channel/InFlightHandler.java | 12 +- .../core/channel/InternalRequest.java | 5 +- .../core/channel/ProtocolInitHandler.java | 11 +- .../core/channel/ResponseCallback.java | 18 ++- .../core/control/ControlConnection.java | 2 +- .../internal/core/pool/ChannelPool.java | 3 +- .../ChannelFactoryAvailableIdsTest.java | 12 +- .../ChannelFactoryClusterNameTest.java | 8 +- ...ChannelFactoryProtocolNegotiationTest.java | 10 +- .../core/channel/ChannelFactoryTestBase.java | 13 +- .../core/channel/DriverChannelTest.java | 6 +- .../core/channel/InFlightHandlerTest.java | 3 + .../channel/MockChannelFactoryHelper.java | 46 +++--- .../core/channel/MockResponseCallback.java | 7 +- .../core/channel/ProtocolInitHandlerTest.java | 38 +++-- .../control/ControlConnectionEventsTest.java | 10 +- .../core/control/ControlConnectionTest.java | 90 +++++------ .../control/ControlConnectionTestBase.java | 3 +- .../internal/core/pool/ChannelPoolTest.java | 150 +++++++++--------- 23 files changed, 282 insertions(+), 234 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 26335a356da..6d417cf3340 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableList; import java.net.SocketAddress; import java.util.Collections; @@ -27,37 +28,37 @@ public class UnsupportedProtocolVersionException extends RuntimeException { private static final long serialVersionUID = 0; - private final SocketAddress address; + private final Node node; private final List attemptedVersions; public static UnsupportedProtocolVersionException forSingleAttempt( - SocketAddress address, ProtocolVersion attemptedVersion) { + Node node, ProtocolVersion attemptedVersion) { String message = - String.format("[%s] Host does not support protocol version %s", address, attemptedVersion); + String.format("[%s] Host does not support protocol version %s", node, attemptedVersion); return new UnsupportedProtocolVersionException( - address, message, Collections.singletonList(attemptedVersion)); + node, message, Collections.singletonList(attemptedVersion)); } public static UnsupportedProtocolVersionException forNegotiation( - SocketAddress address, List attemptedVersions) { + Node node, List attemptedVersions) { String message = String.format( "[%s] Protocol negotiation failed: could not find a common version (attempted: %s)", - address, attemptedVersions); + node, attemptedVersions); return new UnsupportedProtocolVersionException( - address, message, ImmutableList.copyOf(attemptedVersions)); + node, message, ImmutableList.copyOf(attemptedVersions)); } private UnsupportedProtocolVersionException( - SocketAddress address, String message, List attemptedVersions) { + Node node, String message, List attemptedVersions) { super(message); - this.address = address; + this.node = node; this.attemptedVersions = attemptedVersions; } - /** The address of the node that threw the error. */ - public SocketAddress getAddress() { - return address; + /** The node that threw the error. */ + public Node getNode() { + return node; } /** The versions that were attempted. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 6b566e29e93..b3bbdc99ac5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; @@ -113,7 +114,7 @@ private void fireTimeout() { } @Override - public void onFailure(Throwable error) { + public void onFailure(Throwable error, Node node) { if (timeoutFuture != null) { timeoutFuture.cancel(true); } @@ -121,7 +122,7 @@ public void onFailure(Throwable error) { } @Override - public void onResponse(Frame responseFrame) { + public void onResponse(Frame responseFrame, Node node) { if (timeoutFuture != null) { timeoutFuture.cancel(true); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 7b8b4f6af94..4e088bae5a9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; @@ -30,7 +31,6 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -64,8 +64,7 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } - public CompletionStage connect( - final SocketAddress address, DriverChannelOptions options) { + public CompletionStage connect(final Node node, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); AvailableIdsHolder availableIdsHolder = @@ -83,7 +82,7 @@ public CompletionStage connect( } connect( - address, + node, options, availableIdsHolder, currentVersion, @@ -94,7 +93,7 @@ public CompletionStage connect( } private void connect( - SocketAddress address, + Node node, DriverChannelOptions options, AvailableIdsHolder availableIdsHolder, final ProtocolVersion currentVersion, @@ -109,11 +108,11 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(address, currentVersion, options, availableIdsHolder)); + .handler(initializer(node, currentVersion, options, availableIdsHolder)); nettyOptions.afterBootstrapInitialized(bootstrap); - ChannelFuture connectFuture = bootstrap.connect(address); + ChannelFuture connectFuture = bootstrap.connect(getConnectAddress(node)); connectFuture.addListener( cf -> { @@ -143,7 +142,7 @@ private void connect( currentVersion, downgraded.get()); connect( - address, + node, options, availableIdsHolder, downgraded.get(), @@ -152,7 +151,7 @@ private void connect( resultFuture); } else { resultFuture.completeExceptionally( - UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); + UnsupportedProtocolVersionException.forNegotiation(node, attemptedVersions)); } } else { resultFuture.completeExceptionally(error); @@ -163,7 +162,7 @@ private void connect( @VisibleForTesting ChannelInitializer initializer( - SocketAddress address, + Node node, final ProtocolVersion protocolVersion, final DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { @@ -182,18 +181,19 @@ protected void initChannel(Channel channel) throws Exception { InFlightHandler inFlightHandler = new InFlightHandler( + node, protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, options.eventCallback); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, options); + new ProtocolInitHandler(node, context, protocolVersion, clusterName, options); ChannelPipeline pipeline = channel.pipeline(); context .sslHandlerFactory() - .map(f -> f.newSslHandler(channel, address)) + .map(f -> f.newSslHandler(channel, getConnectAddress(node))) .map(h -> pipeline.addLast("ssl", h)); pipeline .addLast("encoder", new FrameEncoder(context.frameCodec())) @@ -206,4 +206,9 @@ protected void initChannel(Channel channel) throws Exception { } }; } + + @VisibleForTesting + protected SocketAddress getConnectAddress(Node node) { + return node.getConnectAddress(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java index 2837593d9a3..0c299a3d0cf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import java.net.SocketAddress; +import com.datastax.oss.driver.api.core.metadata.Node; /** * Indicates that we've attempted to connect to a node with a cluster name doesn't match that of the @@ -35,18 +35,18 @@ public class ClusterNameMismatchException extends Exception { private static final long serialVersionUID = 0; - public final SocketAddress address; + public final Node node; public final String expectedClusterName; public final String actualClusterName; public ClusterNameMismatchException( - SocketAddress address, String actualClusterName, String expectedClusterName) { + Node node, String actualClusterName, String expectedClusterName) { super( String.format( "Node %s reports cluster name '%s' that doesn't match our cluster name '%s'. " + "It will be forced down.", - address, actualClusterName, expectedClusterName)); - this.address = address; + node, actualClusterName, expectedClusterName)); + this.node = node; this.expectedClusterName = expectedClusterName; this.actualClusterName = actualClusterName; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 2167fd2d96b..55df04757ab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; @@ -42,6 +43,7 @@ public class InFlightHandler extends ChannelDuplexHandler { private static final Logger LOG = LoggerFactory.getLogger(InFlightHandler.class); + private final Node node; private final ProtocolVersion protocolVersion; private final StreamIdGenerator streamIds; private final Map inFlight; @@ -52,11 +54,13 @@ public class InFlightHandler extends ChannelDuplexHandler { private SetKeyspaceRequest setKeyspaceRequest; InFlightHandler( + Node node, ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis, AvailableIdsHolder availableIdsHolder, EventCallback eventCallback) { + this.node = node; this.protocolVersion = protocolVersion; this.streamIds = streamIds; reportAvailableIds(); @@ -118,7 +122,7 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) writeFuture.addListener( future -> { if (future.isSuccess()) { - message.responseCallback.onStreamIdAssigned(streamId); + message.responseCallback.onStreamIdAssigned(streamId, node); } }); } @@ -148,7 +152,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception release(streamId, ctx); } try { - responseCallback.onResponse(responseFrame); + responseCallback.onResponse(responseFrame, node); } catch (Throwable t) { LOG.warn("Unexpected error while invoking response handler", t); } @@ -165,7 +169,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E // We know which request matches the failing response, fail that one only ResponseCallback responseCallback = release(streamId, ctx); try { - responseCallback.onFailure(cause.getCause()); + responseCallback.onFailure(cause.getCause(), node); } catch (Throwable t) { LOG.warn("Unexpected error while invoking failure handler", t); } @@ -221,7 +225,7 @@ private void abortAllInFlight(Throwable cause) { private void abortAllInFlight(Throwable cause, ResponseCallback ignore) { for (ResponseCallback responseCallback : inFlight.values()) { if (responseCallback != ignore) { - responseCallback.onFailure(cause); + responseCallback.onFailure(cause, node); } } inFlight.clear(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java index 2d9db51826f..daff662ff49 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -77,13 +78,13 @@ private void writeListener(Future writeFuture) { } @Override - public final void onResponse(Frame responseFrame) { + public final void onResponse(Frame responseFrame, Node node) { timeoutFuture.cancel(true); onResponse(responseFrame.message); } @Override - public final void onFailure(Throwable error) { + public final void onFailure(Throwable error, Node node) { timeoutFuture.cancel(true); fail(describe() + ": unexpected failure", error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 5942683e4e4..37f7ff02509 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -58,6 +59,7 @@ class ProtocolInitHandler extends ConnectInitHandler { private static final Query CLUSTER_NAME_QUERY = new Query("SELECT cluster_name FROM system.local"); + private final Node node; private final InternalDriverContext internalDriverContext; private final long timeoutMillis; private final ProtocolVersion initialProtocolVersion; @@ -66,11 +68,13 @@ class ProtocolInitHandler extends ConnectInitHandler { private final String expectedClusterName; ProtocolInitHandler( + Node node, InternalDriverContext internalDriverContext, ProtocolVersion protocolVersion, String expectedClusterName, DriverChannelOptions options) { + this.node = node; this.internalDriverContext = internalDriverContext; DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); @@ -208,9 +212,7 @@ void onResponse(Message response) { List row = rows.data.poll(); String actualClusterName = getString(row, 0); if (expectedClusterName != null && !expectedClusterName.equals(actualClusterName)) { - fail( - new ClusterNameMismatchException( - channel.remoteAddress(), actualClusterName, expectedClusterName)); + fail(new ClusterNameMismatchException(node, actualClusterName, expectedClusterName)); } else { if (expectedClusterName == null) { // Store the actual name so that it can be retrieved from the factory @@ -245,8 +247,7 @@ void onResponse(Message response) { || error.code == ProtocolConstants.ErrorCode.SERVER_ERROR) && error.message.contains("Invalid or unsupported protocol version")) { fail( - UnsupportedProtocolVersionException.forSingleAttempt( - channel.remoteAddress(), initialProtocolVersion)); + UnsupportedProtocolVersionException.forSingleAttempt(node, initialProtocolVersion)); } else if (step == Step.SET_KEYSPACE && error.code == ProtocolConstants.ErrorCode.INVALID) { fail(new InvalidKeyspaceException(error.message)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java index f9a6399bb9c..bb550044d18 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; /** @@ -27,7 +28,7 @@ public interface ResponseCallback { /** * Invoked when the server replies (note that the response frame might contain an error message). */ - void onResponse(Frame responseFrame); + void onResponse(Frame responseFrame, Node node); /** * Invoked if there was an error while waiting for the response. @@ -35,19 +36,20 @@ public interface ResponseCallback { *

This is generally triggered when a channel fails (for example because of a heartbeat * failure) and all pending requests are aborted. */ - void onFailure(Throwable error); + void onFailure(Throwable error, Node node); /** * Whether to hold the stream id beyond the first response. * *

By default, this is false, and the channel will release the stream id (and make it available - * for other requests) as soon as {@link #onResponse(Frame)} or {@link #onFailure(Throwable)} gets - * invoked. + * for other requests) as soon as {@link #onResponse(Frame, Node)} or {@link #onFailure(Throwable, + * Node)} gets invoked. * *

If this is true, the channel will keep the stream id assigned to this request, and {@code - * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int)} will be called to - * notify the caller of the stream id, and it is the caller's responsibility to determine when the - * request is over, and then call {@link DriverChannel#release(int)} to release the stream id. + * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int, Node)} will be + * called to notify the caller of the stream id, and it is the caller's responsibility to + * determine when the request is over, and then call {@link DriverChannel#release(int)} to release + * the stream id. * *

This is intended to allow streaming requests, that would send multiple chunks of data in * response to a single request (this feature does not exist yet in Cassandra but might be @@ -62,7 +64,7 @@ default boolean holdStreamId() { * *

By default, this will never get called. */ - default void onStreamIdAssigned(int streamId) { + default void onStreamIdAssigned(int streamId, Node node) { // nothing to do by default } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 07ee114968f..e1724f1142e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -244,7 +244,7 @@ private void connect( LOG.debug("Trying to establish a connection to {}", node); context .channelFactory() - .connect(node.getConnectAddress(), channelOptions) + .connect(node, channelOptions) .whenCompleteAsync( (channel, error) -> { try { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 67c98cd18aa..6363a21fb65 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -219,8 +219,7 @@ private CompletionStage addMissingChannels() { .reportAvailableIds(wantedCount > 1) .build(); for (int i = 0; i < missing; i++) { - CompletionStage channelFuture = - channelFactory.connect(node.getConnectAddress(), options); + CompletionStage channelFuture = channelFactory.connect(node, options); pendingChannels.add(channelFuture); } return CompletableFutures.allDone(pendingChannels) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 83b9d0202f7..558d2359dd9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -55,8 +56,7 @@ public void should_report_available_ids_if_requested() { // When CompletionStage channelFuture = - factory.connect( - SERVER_ADDRESS, DriverChannelOptions.builder().reportAvailableIds(true).build()); + factory.connect(node, DriverChannelOptions.builder().reportAvailableIds(true).build()); completeSimpleChannelInit(); // Then @@ -75,7 +75,8 @@ public void should_report_available_ids_if_requested() { // Complete the request, should increase again writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + Mockito.verify(responseCallback, timeout(100)) + .onResponse(any(Frame.class), any(Node.class)); assertThat(channel.availableIds()).isEqualTo(128); }); }); @@ -88,7 +89,7 @@ public void should_not_report_available_ids_if_not_requested() { // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); // Then @@ -106,7 +107,8 @@ public void should_not_report_available_ids_if_not_requested() { assertThat(channel.availableIds()).isEqualTo(-1); writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + Mockito.verify(responseCallback, timeout(100)) + .onResponse(any(Frame.class), any(Node.class)); assertThat(channel.availableIds()).isEqualTo(-1); }); }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 0754095dd1b..bba9b44467f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -37,7 +37,7 @@ public void should_set_cluster_name_from_first_connection() { // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -57,13 +57,13 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); // open a first connection that will define the cluster name writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); assertThat(channelFuture).isSuccess(); // open a second connection that returns the same cluster name - channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + channelFuture = factory.connect(node, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -72,7 +72,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When // open a third connection that returns a different cluster name - channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + channelFuture = factory.connect(node, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index dc72bea11bc..559d347f62a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -45,7 +45,7 @@ public void should_succeed_if_version_specified_and_supported_by_server() { // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); @@ -67,7 +67,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -97,7 +97,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -124,7 +124,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -159,7 +159,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) // When CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); + factory.connect(node, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 7a26ddeba73..14f8508b6fb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -77,6 +78,7 @@ abstract class ChannelFactoryTestBase { DefaultEventLoopGroup serverGroup; DefaultEventLoopGroup clientGroup; + @Mock Node node; @Mock InternalDriverContext context; @Mock DriverConfig driverConfig; @Mock DriverConfigProfile defaultConfigProfile; @@ -101,6 +103,7 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); + Mockito.when(node.getConnectAddress()).thenAnswer(i -> SERVER_ADDRESS); Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) @@ -215,7 +218,7 @@ private TestChannelFactory(InternalDriverContext internalDriverContext) { @Override ChannelInitializer initializer( - SocketAddress address, + Node node, ProtocolVersion protocolVersion, DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { @@ -232,17 +235,23 @@ protected void initChannel(Channel channel) throws Exception { InFlightHandler inFlightHandler = new InFlightHandler( + node, protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, null); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, options); + new ProtocolInitHandler(node, context, protocolVersion, clusterName, options); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; } + + @Override + protected SocketAddress getConnectAddress(Node node) { + return SERVER_ADDRESS; + } } @AfterMethod diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 351929aace2..55ad1609020 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -36,11 +37,12 @@ import static com.datastax.oss.driver.Assertions.assertThat; public class DriverChannelTest extends ChannelHandlerTestBase { - public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; private DriverChannel driverChannel; private MockWriteCoalescer writeCoalescer; + @Mock private Node node; @Mock private StreamIdGenerator streamIds; @BeforeMethod @@ -52,7 +54,7 @@ public void setup() { .pipeline() .addLast( new InFlightHandler( - CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); + node, CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); writeCoalescer = new MockWriteCoalescer(); driverChannel = new DriverChannel(channel, writeCoalescer, null, CoreProtocolVersion.V3); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 99d7b94322a..9e2a7cdcaf5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -46,6 +47,7 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { private static final Query QUERY = new Query("select * from foo"); private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + @Mock private Node node; @Mock private StreamIdGenerator streamIds; @BeforeMethod @@ -353,6 +355,7 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { .pipeline() .addLast( new InFlightHandler( + node, CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index 5244a63d7b6..a3bd127133d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; -import java.net.SocketAddress; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; @@ -45,7 +45,7 @@ * methods throughout the test to check that each call has been performed. * *

This class handles asynchronous calls to the thread factory, but it must be used from a single - * thread (see {@link #waitForCalls(SocketAddress, int)}). + * thread (see {@link #waitForCalls(Node, int)}). */ public class MockChannelFactoryHelper { @@ -56,15 +56,15 @@ public static Builder builder(ChannelFactory channelFactory) { private final ChannelFactory channelFactory; private final InOrder inOrder; // If waitForCalls sees more invocations than expected, the difference is stored here - private final Map previous = new HashMap<>(); + private final Map previous = new HashMap<>(); public MockChannelFactoryHelper(ChannelFactory channelFactory) { this.channelFactory = channelFactory; this.inOrder = Mockito.inOrder(channelFactory); } - public void waitForCall(SocketAddress address) { - waitForCalls(address, 1); + public void waitForCall(Node node) { + waitForCalls(node, 1); } /** @@ -74,10 +74,10 @@ public void waitForCall(SocketAddress address) { * expected when this method is called. If so, the extra calls are stored and stored and will be * taken into account next time. */ - public void waitForCalls(SocketAddress address, int expected) { - int fromLastTime = previous.getOrDefault(address, 0); + public void waitForCalls(Node node, int expected) { + int fromLastTime = previous.getOrDefault(node, 0); if (fromLastTime >= expected) { - previous.put(address, fromLastTime - expected); + previous.put(node, fromLastTime - expected); return; } expected -= fromLastTime; @@ -88,19 +88,19 @@ public void waitForCalls(SocketAddress address, int expected) { ArgumentCaptor.forClass(DriverChannelOptions.class); inOrder .verify(channelFactory, timeout(100).atLeast(expected)) - .connect(eq(address), optionsCaptor.capture()); + .connect(eq(node), optionsCaptor.capture()); int actual = optionsCaptor.getAllValues().size(); int extras = actual - expected; if (extras > 0) { - previous.compute(address, (k, v) -> (v == null) ? extras : v + extras); + previous.compute(node, (k, v) -> (v == null) ? extras : v + extras); } } public void verifyNoMoreCalls() { inOrder .verify(channelFactory, timeout(100).times(0)) - .connect(any(SocketAddress.class), any(DriverChannelOptions.class)); + .connect(any(Node.class), any(DriverChannelOptions.class)); Set counts = Sets.newHashSet(previous.values()); if (!counts.isEmpty()) { @@ -110,7 +110,7 @@ public void verifyNoMoreCalls() { public static class Builder { private final ChannelFactory channelFactory; - private final ListMultimap invocations = + private final ListMultimap invocations = MultimapBuilder.hashKeys().arrayListValues().build(); public Builder(ChannelFactory channelFactory) { @@ -119,23 +119,23 @@ public Builder(ChannelFactory channelFactory) { this.channelFactory = channelFactory; } - public Builder success(SocketAddress address, DriverChannel channel) { - invocations.put(address, channel); + public Builder success(Node node, DriverChannel channel) { + invocations.put(node, channel); return this; } - public Builder failure(SocketAddress address, String error) { - invocations.put(address, new Exception(error)); + public Builder failure(Node node, String error) { + invocations.put(node, new Exception(error)); return this; } - public Builder failure(SocketAddress address, Throwable error) { - invocations.put(address, error); + public Builder failure(Node node, Throwable error) { + invocations.put(node, error); return this; } - public Builder pending(SocketAddress address, CompletableFuture future) { - invocations.put(address, future); + public Builder pending(Node node, CompletableFuture future) { + invocations.put(node, future); return this; } @@ -145,9 +145,9 @@ public MockChannelFactoryHelper build() { } private void stub() { - for (SocketAddress address : invocations.keySet()) { + for (Node node : invocations.keySet()) { LinkedList> results = new LinkedList<>(); - for (Object object : invocations.get(address)) { + for (Object object : invocations.get(node)) { if (object instanceof DriverChannel) { results.add(CompletableFuture.completedFuture(((DriverChannel) object))); } else if (object instanceof Throwable) { @@ -163,7 +163,7 @@ private void stub() { if (results.size() > 0) { CompletionStage first = results.poll(); OngoingStubbing> ongoingStubbing = - Mockito.when(channelFactory.connect(eq(address), any(DriverChannelOptions.class))) + Mockito.when(channelFactory.connect(eq(node), any(DriverChannelOptions.class))) .thenReturn(first); for (CompletionStage result : results) { ongoingStubbing.thenReturn(result); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java index 366ac580460..890f48b6b3c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import java.util.LinkedList; import java.util.Queue; @@ -34,12 +35,12 @@ class MockResponseCallback implements ResponseCallback { } @Override - public void onResponse(Frame responseFrame) { + public void onResponse(Frame responseFrame, Node node) { responses.offer(responseFrame); } @Override - public void onFailure(Throwable error) { + public void onFailure(Throwable error, Node node) { responses.offer(error); } @@ -49,7 +50,7 @@ public boolean holdStreamId() { } @Override - public void onStreamIdAssigned(int streamId) { + public void onStreamIdAssigned(int streamId, Node node) { this.streamId = streamId; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 477d5ae25a5..aa33b64ea48 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -57,6 +58,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { private static final long QUERY_TIMEOUT_MILLIS = 100L; + @Mock private Node node; @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; @@ -81,7 +83,7 @@ public void setup() { .addLast( "inflight", new InFlightHandler( - CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); + node, CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); } @Test @@ -91,7 +93,11 @@ public void should_initialize_without_authentication() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + node, + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -120,7 +126,8 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler( + node, internalDriverContext, CoreProtocolVersion.V4, null, null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -140,7 +147,11 @@ public void should_initialize_with_authentication() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + node, + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -198,7 +209,11 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + node, + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -238,6 +253,7 @@ public void should_check_cluster_name_if_provided() { .addLast( "init", new ProtocolInitHandler( + node, internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", @@ -266,6 +282,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th .addLast( "init", new ProtocolInitHandler( + node, internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", @@ -283,7 +300,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th assertThat(e) .isInstanceOf(ClusterNameMismatchException.class) .hasMessageContaining( - "Node embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); + "Node node reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); } @Test @@ -294,7 +311,8 @@ public void should_initialize_with_keyspace() { .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, options)); + new ProtocolInitHandler( + node, internalDriverContext, CoreProtocolVersion.V4, null, options)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -320,7 +338,7 @@ public void should_initialize_with_events() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -349,7 +367,7 @@ public void should_initialize_with_keyspace_and_events() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -378,7 +396,7 @@ public void should_fail_to_initialize_if_keyspace_is_invalid() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 92434360c0c..1d885df7914 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -41,7 +41,7 @@ public void should_register_for_all_events_if_topology_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -64,7 +64,7 @@ public void should_register_for_schema_events_only_if_topology_not_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -84,7 +84,7 @@ public void should_process_status_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true); waitForPendingAdminTasks(); @@ -106,7 +106,7 @@ public void should_process_topology_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true); waitForPendingAdminTasks(); @@ -128,7 +128,7 @@ public void should_process_schema_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(false); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 18b8ced41e7..71eaa64e1da 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -46,11 +46,11 @@ public void should_init_with_first_contact_point_if_reachable() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); // Then @@ -66,11 +66,11 @@ public void should_always_return_same_init_future() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); // When CompletionStage initFuture1 = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); CompletionStage initFuture2 = controlConnection.init(false); // Then @@ -85,14 +85,14 @@ public void should_init_with_second_contact_point_if_first_one_fails() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS1, "mock failure") - .success(ADDRESS2, channel2) + .failure(NODE1, "mock failure") + .success(NODE2, channel2) .build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(NODE2); waitForPendingAdminTasks(); // Then @@ -110,14 +110,14 @@ public void should_fail_to_init_if_all_contact_points_fail() { // Given MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS1, "mock failure") - .failure(ADDRESS2, "mock failure") + .failure(NODE1, "mock failure") + .failure(NODE2, "mock failure") .build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(NODE2); waitForPendingAdminTasks(); // Then @@ -137,13 +137,13 @@ public void should_reconnect_if_channel_goes_down() throws Exception { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS1, channel1) - .failure(ADDRESS1, "mock failure") - .success(ADDRESS2, channel2) + .success(NODE1, channel1) + .failure(NODE1, "mock failure") + .success(NODE2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -157,8 +157,8 @@ public void should_reconnect_if_channel_goes_down() throws Exception { // Then // a reconnection was started Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCall(ADDRESS1); - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(NODE2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); @@ -178,13 +178,13 @@ public void should_force_reconnection_if_pending() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS1, channel1) - .failure(ADDRESS1, "mock failure") - .success(ADDRESS2, channel2) + .success(NODE1, channel1) + .failure(NODE1, "mock failure") + .success(NODE2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -198,8 +198,8 @@ public void should_force_reconnection_if_pending() { // When controlConnection.reconnectNow(); - factoryHelper.waitForCall(ADDRESS1); - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(NODE2); waitForPendingAdminTasks(); // Then @@ -216,13 +216,13 @@ public void should_force_reconnection_even_if_connected() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS1, channel1) - .failure(ADDRESS1, "mock failure") - .success(ADDRESS2, channel2) + .success(NODE1, channel1) + .failure(NODE1, "mock failure") + .success(NODE2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -232,8 +232,8 @@ public void should_force_reconnection_even_if_connected() { controlConnection.reconnectNow(); // Then - factoryHelper.waitForCall(ADDRESS1); - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(NODE2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(channel1).forceClose(); @@ -258,9 +258,9 @@ public void should_not_force_reconnection_if_closed() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CompletionStage closeFuture = controlConnection.forceCloseAsync(); @@ -281,10 +281,10 @@ public void should_close_channel_when_closing() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -310,13 +310,13 @@ public void should_close_channel_if_closed_during_reconnection() { CompletableFuture channel2Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS1, channel1) - .failure(ADDRESS1, "mock failure") - .pending(ADDRESS2, channel2Future) + .success(NODE1, channel1) + .failure(NODE1, "mock failure") + .pending(NODE2, channel2Future) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -327,9 +327,9 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); // channel2 starts initializing (but the future is not completed yet) - factoryHelper.waitForCall(ADDRESS2); + factoryHelper.waitForCall(NODE2); // When // the control connection gets closed before channel2 initialization is complete @@ -357,13 +357,13 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { CompletableFuture channel1Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS1, channel1) - .pending(ADDRESS1, channel1Future) - .success(ADDRESS2, channel2) + .success(NODE1, channel1) + .pending(NODE1, channel1Future) + .success(NODE2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -375,7 +375,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); // channel1 starts initializing (but the future is not completed yet) - factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(NODE1); // When // the control connection gets closed before channel1 initialization fails diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index f68af5a6f62..986c1e665ce 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -34,7 +34,6 @@ import io.netty.channel.EventLoop; import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -85,7 +84,7 @@ public void setup() { Mockito.when(context.channelFactory()).thenReturn(channelFactory); channelFactoryFuture = new Exchanger<>(); - Mockito.when(channelFactory.connect(any(SocketAddress.class), any(DriverChannelOptions.class))) + Mockito.when(channelFactory.connect(any(Node.class), any(DriverChannelOptions.class))) .thenAnswer( invocation -> { CompletableFuture channelFuture = new CompletableFuture<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 861fec9b8d0..5625a8d229c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -109,15 +109,15 @@ public void should_initialize_when_all_channels_succeed() throws Exception { DriverChannel channel3 = newMockDriverChannel(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) + .success(NODE, channel1) + .success(NODE, channel2) + .success(NODE, channel3) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); assertThat(poolFuture) @@ -133,15 +133,15 @@ public void should_initialize_when_all_channels_fail() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") + .failure(NODE, "mock channel init failure") + .failure(NODE, "mock channel init failure") + .failure(NODE, "mock channel init failure") .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); @@ -156,15 +156,15 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) + .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) + .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); } @@ -174,17 +174,17 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); ClusterNameMismatchException error = - new ClusterNameMismatchException(ADDRESS, "actual", "expected"); + new ClusterNameMismatchException(NODE, "actual", "expected"); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, error) - .failure(ADDRESS, error) - .failure(ADDRESS, error) + .failure(NODE, error) + .failure(NODE, error) + .failure(NODE, error) .build(); ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); @@ -206,17 +206,17 @@ public void should_reconnect_when_init_incomplete() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // Init: 1 channel fails, the other succeeds - .failure(ADDRESS, "mock channel init failure") - .success(ADDRESS, channel1) + .failure(NODE, "mock channel init failure") + .success(NODE, channel1) // 1st reconnection - .pending(ADDRESS, channel2Future) + .pending(NODE, channel2Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -229,7 +229,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); channel2Future.complete(channel2); - factoryHelper.waitForCalls(ADDRESS, 1); + factoryHelper.waitForCalls(NODE, 1); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); @@ -252,17 +252,17 @@ public void should_reconnect_when_channel_dies() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) + .success(NODE, channel1) + .success(NODE, channel2) // reconnection - .pending(ADDRESS, channel3Future) + .pending(NODE, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -278,7 +278,7 @@ public void should_reconnect_when_channel_dies() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCall(ADDRESS); + factoryHelper.waitForCall(NODE); channel3Future.complete(channel3); waitForPendingAdminTasks(); @@ -301,17 +301,17 @@ public void should_shrink_outside_of_reconnection() throws Exception { DriverChannel channel4 = newMockDriverChannel(4); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) - .success(ADDRESS, channel4) + .success(NODE, channel1) + .success(NODE, channel2) + .success(NODE, channel3) + .success(NODE, channel4) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); - factoryHelper.waitForCalls(ADDRESS, 4); + factoryHelper.waitForCalls(NODE, 4); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -345,20 +345,20 @@ public void should_shrink_during_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") + .success(NODE, channel1) + .success(NODE, channel2) + .failure(NODE, "mock channel init failure") + .failure(NODE, "mock channel init failure") // reconnection - .pending(ADDRESS, channel3Future) - .pending(ADDRESS, channel4Future) + .pending(NODE, channel3Future) + .pending(NODE, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); - factoryHelper.waitForCalls(ADDRESS, 4); + factoryHelper.waitForCalls(NODE, 4); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -378,7 +378,7 @@ public void should_shrink_during_reconnection() throws Exception { channel3Future.complete(channel3); channel4Future.complete(channel4); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. @@ -404,18 +404,18 @@ public void should_grow_outside_of_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) + .success(NODE, channel1) + .success(NODE, channel2) // growth attempt - .success(ADDRESS, channel3) - .success(ADDRESS, channel4) + .success(NODE, channel3) + .success(NODE, channel4) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -430,7 +430,7 @@ public void should_grow_outside_of_reconnection() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); @@ -457,20 +457,20 @@ public void should_grow_during_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .failure(ADDRESS, "mock channel init failure") + .success(NODE, channel1) + .failure(NODE, "mock channel init failure") // first reconnection attempt - .pending(ADDRESS, channel2Future) + .pending(NODE, channel2Future) // extra reconnection attempt after we realize the pool must grow - .pending(ADDRESS, channel3Future) - .pending(ADDRESS, channel4Future) + .pending(NODE, channel3Future) + .pending(NODE, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); @@ -488,7 +488,7 @@ public void should_grow_during_reconnection() throws Exception { // Complete the channel for the first reconnection, bringing the count to 2 channel2Future.complete(channel2); - factoryHelper.waitForCall(ADDRESS); + factoryHelper.waitForCall(NODE); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); @@ -501,7 +501,7 @@ public void should_grow_during_reconnection() throws Exception { inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); // Two more channels get opened, bringing us to the target count - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); channel3Future.complete(channel3); channel4Future.complete(channel4); waitForPendingAdminTasks(); @@ -521,14 +521,14 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) + .success(NODE, channel1) + .success(NODE, channel2) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -560,17 +560,17 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") + .failure(NODE, "mock channel init failure") + .failure(NODE, "mock channel init failure") // reconnection - .pending(ADDRESS, channel1Future) - .pending(ADDRESS, channel2Future) + .pending(NODE, channel1Future) + .pending(NODE, channel2Future) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -579,7 +579,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { // Check that reconnection has kicked in, but do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCalls(ADDRESS, 2); + factoryHelper.waitForCalls(NODE, 2); // Switch keyspace, it succeeds immediately since there is no active channel CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); @@ -612,18 +612,18 @@ public void should_close_all_channels_when_closed() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") + .success(NODE, channel1) + .success(NODE, channel2) + .failure(NODE, "mock channel init failure") // reconnection - .pending(ADDRESS, channel3Future) + .pending(NODE, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -632,7 +632,7 @@ public void should_close_all_channels_when_closed() throws Exception { // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(ADDRESS, 1); + factoryHelper.waitForCalls(NODE, 1); CompletionStage closeFuture = pool.closeAsync(); waitForPendingAdminTasks(); @@ -675,18 +675,18 @@ public void should_force_close_all_channels_when_force_closed() throws Exception MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") + .success(NODE, channel1) + .success(NODE, channel2) + .failure(NODE, "mock channel init failure") // reconnection - .pending(ADDRESS, channel3Future) + .pending(NODE, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(ADDRESS, 3); + factoryHelper.waitForCalls(NODE, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -695,7 +695,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(ADDRESS, 1); + factoryHelper.waitForCalls(NODE, 1); CompletionStage closeFuture = pool.forceCloseAsync(); waitForPendingAdminTasks(); From cc1ade736992eefa1bc54dba69769a1d893e2f13 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 9 May 2017 16:50:44 -0700 Subject: [PATCH 049/742] Remove unnecessary imports --- .../main/java/com/datastax/oss/driver/api/core/Cluster.java | 1 - .../driver/api/core/UnsupportedProtocolVersionException.java | 1 - .../java/com/datastax/oss/driver/api/core/cql/CqlSession.java | 2 +- .../com/datastax/oss/driver/internal/core/ContactPoints.java | 2 -- .../oss/driver/internal/core/channel/ChannelEvent.java | 1 - .../oss/driver/internal/core/channel/DriverChannel.java | 1 - .../internal/core/util/concurrent/ReplayingEventFilter.java | 1 - .../driver/internal/core/control/ControlConnectionTest.java | 2 -- .../datastax/oss/driver/internal/type/codec/TimeUuidCodec.java | 1 - .../datastax/oss/driver/internal/type/codec/SetCodecTest.java | 3 --- .../oss/driver/internal/type/codec/TimestampCodecTest.java | 1 - .../oss/driver/internal/type/codec/TupleCodecTest.java | 1 - 12 files changed, 1 insertion(+), 16 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index effa046fdc4..b4ff2dbd8b2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.concurrent.CompletionStage; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 6d417cf3340..2d99a0bf8e1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableList; -import java.net.SocketAddress; import java.util.Collections; import java.util.List; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java index a4974889de5..d98268770fe 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; public interface CqlSession extends Session { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java index 308bb66a8f3..3e94bc75a05 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -16,8 +16,6 @@ package com.datastax.oss.driver.internal.core; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java index 59895bac1d3..ad3a7af8706 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.metadata.Node; -import java.net.SocketAddress; import java.util.Objects; /** Events relating to driver channels. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 6f3cc550f54..5cd10e1403e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -24,7 +24,6 @@ import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.Map; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java index dc3c295e983..cdeea63d0b5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java @@ -17,7 +17,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReadWriteLock; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 71eaa64e1da..26ba28e7e9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -21,14 +21,12 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; public class ControlConnectionTest extends ControlConnectionTestBase { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java index 128a4b64e7b..f00fcd283ec 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.type.DataType; import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.reflect.GenericType; import java.nio.ByteBuffer; import java.util.UUID; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java index 09575d117be..4c8f90c6ebd 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java @@ -21,11 +21,8 @@ import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.api.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import java.util.ArrayList; import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; import org.mockito.Mock; import org.mockito.Mockito; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java index 63a2ab2f21d..c33758edc3b 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.type.codec.TypeCodecs; import java.time.Instant; -import java.time.LocalDate; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java index d49d2a082e4..b0485b2c7e4 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java +++ b/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java @@ -24,7 +24,6 @@ import com.datastax.oss.driver.api.type.codec.TypeCodec; import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.internal.core.data.DefaultTupleValue; import com.datastax.oss.driver.internal.type.DefaultTupleType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; From 1f873daeef8043549f69c2262a2ed88ad8961679 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 10 May 2017 13:08:53 -0700 Subject: [PATCH 050/742] Revert "Pass the node in response callbacks" This reverts commit 8ab5ae66bd2cb5a0c465c42f95fdb28d8bb06fb6. --- .../UnsupportedProtocolVersionException.java | 25 ++- .../adminrequest/AdminRequestHandler.java | 5 +- .../internal/core/channel/ChannelFactory.java | 29 ++-- .../channel/ClusterNameMismatchException.java | 10 +- .../core/channel/InFlightHandler.java | 12 +- .../core/channel/InternalRequest.java | 5 +- .../core/channel/ProtocolInitHandler.java | 11 +- .../core/channel/ResponseCallback.java | 18 +-- .../core/control/ControlConnection.java | 2 +- .../internal/core/pool/ChannelPool.java | 3 +- .../ChannelFactoryAvailableIdsTest.java | 12 +- .../ChannelFactoryClusterNameTest.java | 8 +- ...ChannelFactoryProtocolNegotiationTest.java | 10 +- .../core/channel/ChannelFactoryTestBase.java | 13 +- .../core/channel/DriverChannelTest.java | 6 +- .../core/channel/InFlightHandlerTest.java | 3 - .../channel/MockChannelFactoryHelper.java | 46 +++--- .../core/channel/MockResponseCallback.java | 7 +- .../core/channel/ProtocolInitHandlerTest.java | 38 ++--- .../control/ControlConnectionEventsTest.java | 10 +- .../core/control/ControlConnectionTest.java | 90 +++++------ .../control/ControlConnectionTestBase.java | 3 +- .../internal/core/pool/ChannelPoolTest.java | 150 +++++++++--------- 23 files changed, 234 insertions(+), 282 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 2d99a0bf8e1..a946fd8560e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.List; @@ -27,37 +26,37 @@ public class UnsupportedProtocolVersionException extends RuntimeException { private static final long serialVersionUID = 0; - private final Node node; + private final SocketAddress address; private final List attemptedVersions; public static UnsupportedProtocolVersionException forSingleAttempt( - Node node, ProtocolVersion attemptedVersion) { + SocketAddress address, ProtocolVersion attemptedVersion) { String message = - String.format("[%s] Host does not support protocol version %s", node, attemptedVersion); + String.format("[%s] Host does not support protocol version %s", address, attemptedVersion); return new UnsupportedProtocolVersionException( - node, message, Collections.singletonList(attemptedVersion)); + address, message, Collections.singletonList(attemptedVersion)); } public static UnsupportedProtocolVersionException forNegotiation( - Node node, List attemptedVersions) { + SocketAddress address, List attemptedVersions) { String message = String.format( "[%s] Protocol negotiation failed: could not find a common version (attempted: %s)", - node, attemptedVersions); + address, attemptedVersions); return new UnsupportedProtocolVersionException( - node, message, ImmutableList.copyOf(attemptedVersions)); + address, message, ImmutableList.copyOf(attemptedVersions)); } private UnsupportedProtocolVersionException( - Node node, String message, List attemptedVersions) { + SocketAddress address, String message, List attemptedVersions) { super(message); - this.node = node; + this.address = address; this.attemptedVersions = attemptedVersions; } - /** The node that threw the error. */ - public Node getNode() { - return node; + /** The address of the node that threw the error. */ + public SocketAddress getAddress() { + return address; } /** The versions that were attempted. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index b3bbdc99ac5..6b566e29e93 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; @@ -114,7 +113,7 @@ private void fireTimeout() { } @Override - public void onFailure(Throwable error, Node node) { + public void onFailure(Throwable error) { if (timeoutFuture != null) { timeoutFuture.cancel(true); } @@ -122,7 +121,7 @@ public void onFailure(Throwable error, Node node) { } @Override - public void onResponse(Frame responseFrame, Node node) { + public void onResponse(Frame responseFrame) { if (timeoutFuture != null) { timeoutFuture.cancel(true); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 4e088bae5a9..7b8b4f6af94 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; @@ -31,6 +30,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -64,7 +64,8 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } - public CompletionStage connect(final Node node, DriverChannelOptions options) { + public CompletionStage connect( + final SocketAddress address, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); AvailableIdsHolder availableIdsHolder = @@ -82,7 +83,7 @@ public CompletionStage connect(final Node node, DriverChannelOpti } connect( - node, + address, options, availableIdsHolder, currentVersion, @@ -93,7 +94,7 @@ public CompletionStage connect(final Node node, DriverChannelOpti } private void connect( - Node node, + SocketAddress address, DriverChannelOptions options, AvailableIdsHolder availableIdsHolder, final ProtocolVersion currentVersion, @@ -108,11 +109,11 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(node, currentVersion, options, availableIdsHolder)); + .handler(initializer(address, currentVersion, options, availableIdsHolder)); nettyOptions.afterBootstrapInitialized(bootstrap); - ChannelFuture connectFuture = bootstrap.connect(getConnectAddress(node)); + ChannelFuture connectFuture = bootstrap.connect(address); connectFuture.addListener( cf -> { @@ -142,7 +143,7 @@ private void connect( currentVersion, downgraded.get()); connect( - node, + address, options, availableIdsHolder, downgraded.get(), @@ -151,7 +152,7 @@ private void connect( resultFuture); } else { resultFuture.completeExceptionally( - UnsupportedProtocolVersionException.forNegotiation(node, attemptedVersions)); + UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); } } else { resultFuture.completeExceptionally(error); @@ -162,7 +163,7 @@ private void connect( @VisibleForTesting ChannelInitializer initializer( - Node node, + SocketAddress address, final ProtocolVersion protocolVersion, final DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { @@ -181,19 +182,18 @@ protected void initChannel(Channel channel) throws Exception { InFlightHandler inFlightHandler = new InFlightHandler( - node, protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, options.eventCallback); ProtocolInitHandler initHandler = - new ProtocolInitHandler(node, context, protocolVersion, clusterName, options); + new ProtocolInitHandler(context, protocolVersion, clusterName, options); ChannelPipeline pipeline = channel.pipeline(); context .sslHandlerFactory() - .map(f -> f.newSslHandler(channel, getConnectAddress(node))) + .map(f -> f.newSslHandler(channel, address)) .map(h -> pipeline.addLast("ssl", h)); pipeline .addLast("encoder", new FrameEncoder(context.frameCodec())) @@ -206,9 +206,4 @@ protected void initChannel(Channel channel) throws Exception { } }; } - - @VisibleForTesting - protected SocketAddress getConnectAddress(Node node) { - return node.getConnectAddress(); - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java index 0c299a3d0cf..2837593d9a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.metadata.Node; +import java.net.SocketAddress; /** * Indicates that we've attempted to connect to a node with a cluster name doesn't match that of the @@ -35,18 +35,18 @@ public class ClusterNameMismatchException extends Exception { private static final long serialVersionUID = 0; - public final Node node; + public final SocketAddress address; public final String expectedClusterName; public final String actualClusterName; public ClusterNameMismatchException( - Node node, String actualClusterName, String expectedClusterName) { + SocketAddress address, String actualClusterName, String expectedClusterName) { super( String.format( "Node %s reports cluster name '%s' that doesn't match our cluster name '%s'. " + "It will be forced down.", - node, actualClusterName, expectedClusterName)); - this.node = node; + address, actualClusterName, expectedClusterName)); + this.address = address; this.expectedClusterName = expectedClusterName; this.actualClusterName = actualClusterName; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 55df04757ab..2167fd2d96b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; @@ -43,7 +42,6 @@ public class InFlightHandler extends ChannelDuplexHandler { private static final Logger LOG = LoggerFactory.getLogger(InFlightHandler.class); - private final Node node; private final ProtocolVersion protocolVersion; private final StreamIdGenerator streamIds; private final Map inFlight; @@ -54,13 +52,11 @@ public class InFlightHandler extends ChannelDuplexHandler { private SetKeyspaceRequest setKeyspaceRequest; InFlightHandler( - Node node, ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis, AvailableIdsHolder availableIdsHolder, EventCallback eventCallback) { - this.node = node; this.protocolVersion = protocolVersion; this.streamIds = streamIds; reportAvailableIds(); @@ -122,7 +118,7 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) writeFuture.addListener( future -> { if (future.isSuccess()) { - message.responseCallback.onStreamIdAssigned(streamId, node); + message.responseCallback.onStreamIdAssigned(streamId); } }); } @@ -152,7 +148,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception release(streamId, ctx); } try { - responseCallback.onResponse(responseFrame, node); + responseCallback.onResponse(responseFrame); } catch (Throwable t) { LOG.warn("Unexpected error while invoking response handler", t); } @@ -169,7 +165,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E // We know which request matches the failing response, fail that one only ResponseCallback responseCallback = release(streamId, ctx); try { - responseCallback.onFailure(cause.getCause(), node); + responseCallback.onFailure(cause.getCause()); } catch (Throwable t) { LOG.warn("Unexpected error while invoking failure handler", t); } @@ -225,7 +221,7 @@ private void abortAllInFlight(Throwable cause) { private void abortAllInFlight(Throwable cause, ResponseCallback ignore) { for (ResponseCallback responseCallback : inFlight.values()) { if (responseCallback != ignore) { - responseCallback.onFailure(cause, node); + responseCallback.onFailure(cause); } } inFlight.clear(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java index daff662ff49..2d9db51826f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -78,13 +77,13 @@ private void writeListener(Future writeFuture) { } @Override - public final void onResponse(Frame responseFrame, Node node) { + public final void onResponse(Frame responseFrame) { timeoutFuture.cancel(true); onResponse(responseFrame.message); } @Override - public final void onFailure(Throwable error, Node node) { + public final void onFailure(Throwable error) { timeoutFuture.cancel(true); fail(describe() + ": unexpected failure", error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 37f7ff02509..5942683e4e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ConnectionException; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -59,7 +58,6 @@ class ProtocolInitHandler extends ConnectInitHandler { private static final Query CLUSTER_NAME_QUERY = new Query("SELECT cluster_name FROM system.local"); - private final Node node; private final InternalDriverContext internalDriverContext; private final long timeoutMillis; private final ProtocolVersion initialProtocolVersion; @@ -68,13 +66,11 @@ class ProtocolInitHandler extends ConnectInitHandler { private final String expectedClusterName; ProtocolInitHandler( - Node node, InternalDriverContext internalDriverContext, ProtocolVersion protocolVersion, String expectedClusterName, DriverChannelOptions options) { - this.node = node; this.internalDriverContext = internalDriverContext; DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); @@ -212,7 +208,9 @@ void onResponse(Message response) { List row = rows.data.poll(); String actualClusterName = getString(row, 0); if (expectedClusterName != null && !expectedClusterName.equals(actualClusterName)) { - fail(new ClusterNameMismatchException(node, actualClusterName, expectedClusterName)); + fail( + new ClusterNameMismatchException( + channel.remoteAddress(), actualClusterName, expectedClusterName)); } else { if (expectedClusterName == null) { // Store the actual name so that it can be retrieved from the factory @@ -247,7 +245,8 @@ void onResponse(Message response) { || error.code == ProtocolConstants.ErrorCode.SERVER_ERROR) && error.message.contains("Invalid or unsupported protocol version")) { fail( - UnsupportedProtocolVersionException.forSingleAttempt(node, initialProtocolVersion)); + UnsupportedProtocolVersionException.forSingleAttempt( + channel.remoteAddress(), initialProtocolVersion)); } else if (step == Step.SET_KEYSPACE && error.code == ProtocolConstants.ErrorCode.INVALID) { fail(new InvalidKeyspaceException(error.message)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java index bb550044d18..f9a6399bb9c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; /** @@ -28,7 +27,7 @@ public interface ResponseCallback { /** * Invoked when the server replies (note that the response frame might contain an error message). */ - void onResponse(Frame responseFrame, Node node); + void onResponse(Frame responseFrame); /** * Invoked if there was an error while waiting for the response. @@ -36,20 +35,19 @@ public interface ResponseCallback { *

This is generally triggered when a channel fails (for example because of a heartbeat * failure) and all pending requests are aborted. */ - void onFailure(Throwable error, Node node); + void onFailure(Throwable error); /** * Whether to hold the stream id beyond the first response. * *

By default, this is false, and the channel will release the stream id (and make it available - * for other requests) as soon as {@link #onResponse(Frame, Node)} or {@link #onFailure(Throwable, - * Node)} gets invoked. + * for other requests) as soon as {@link #onResponse(Frame)} or {@link #onFailure(Throwable)} gets + * invoked. * *

If this is true, the channel will keep the stream id assigned to this request, and {@code - * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int, Node)} will be - * called to notify the caller of the stream id, and it is the caller's responsibility to - * determine when the request is over, and then call {@link DriverChannel#release(int)} to release - * the stream id. + * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int)} will be called to + * notify the caller of the stream id, and it is the caller's responsibility to determine when the + * request is over, and then call {@link DriverChannel#release(int)} to release the stream id. * *

This is intended to allow streaming requests, that would send multiple chunks of data in * response to a single request (this feature does not exist yet in Cassandra but might be @@ -64,7 +62,7 @@ default boolean holdStreamId() { * *

By default, this will never get called. */ - default void onStreamIdAssigned(int streamId, Node node) { + default void onStreamIdAssigned(int streamId) { // nothing to do by default } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index e1724f1142e..07ee114968f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -244,7 +244,7 @@ private void connect( LOG.debug("Trying to establish a connection to {}", node); context .channelFactory() - .connect(node, channelOptions) + .connect(node.getConnectAddress(), channelOptions) .whenCompleteAsync( (channel, error) -> { try { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 6363a21fb65..67c98cd18aa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -219,7 +219,8 @@ private CompletionStage addMissingChannels() { .reportAvailableIds(wantedCount > 1) .build(); for (int i = 0; i < missing; i++) { - CompletionStage channelFuture = channelFactory.connect(node, options); + CompletionStage channelFuture = + channelFactory.connect(node.getConnectAddress(), options); pendingChannels.add(channelFuture); } return CompletableFutures.allDone(pendingChannels) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 558d2359dd9..83b9d0202f7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -56,7 +55,8 @@ public void should_report_available_ids_if_requested() { // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.builder().reportAvailableIds(true).build()); + factory.connect( + SERVER_ADDRESS, DriverChannelOptions.builder().reportAvailableIds(true).build()); completeSimpleChannelInit(); // Then @@ -75,8 +75,7 @@ public void should_report_available_ids_if_requested() { // Complete the request, should increase again writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)) - .onResponse(any(Frame.class), any(Node.class)); + Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); assertThat(channel.availableIds()).isEqualTo(128); }); }); @@ -89,7 +88,7 @@ public void should_not_report_available_ids_if_not_requested() { // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); // Then @@ -107,8 +106,7 @@ public void should_not_report_available_ids_if_not_requested() { assertThat(channel.availableIds()).isEqualTo(-1); writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)) - .onResponse(any(Frame.class), any(Node.class)); + Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); assertThat(channel.availableIds()).isEqualTo(-1); }); }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index bba9b44467f..0754095dd1b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -37,7 +37,7 @@ public void should_set_cluster_name_from_first_connection() { // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -57,13 +57,13 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); // open a first connection that will define the cluster name writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); assertThat(channelFuture).isSuccess(); // open a second connection that returns the same cluster name - channelFuture = factory.connect(node, DriverChannelOptions.DEFAULT); + channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); @@ -72,7 +72,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // When // open a third connection that returns a different cluster name - channelFuture = factory.connect(node, DriverChannelOptions.DEFAULT); + channelFuture = factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index 559d347f62a..dc72bea11bc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -45,7 +45,7 @@ public void should_succeed_if_version_specified_and_supported_by_server() { // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); completeSimpleChannelInit(); @@ -67,7 +67,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -97,7 +97,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -124,7 +124,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); @@ -159,7 +159,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) // When CompletionStage channelFuture = - factory.connect(node, DriverChannelOptions.DEFAULT); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 14f8508b6fb..7a26ddeba73 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -78,7 +77,6 @@ abstract class ChannelFactoryTestBase { DefaultEventLoopGroup serverGroup; DefaultEventLoopGroup clientGroup; - @Mock Node node; @Mock InternalDriverContext context; @Mock DriverConfig driverConfig; @Mock DriverConfigProfile defaultConfigProfile; @@ -103,7 +101,6 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); - Mockito.when(node.getConnectAddress()).thenAnswer(i -> SERVER_ADDRESS); Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) @@ -218,7 +215,7 @@ private TestChannelFactory(InternalDriverContext internalDriverContext) { @Override ChannelInitializer initializer( - Node node, + SocketAddress address, ProtocolVersion protocolVersion, DriverChannelOptions options, AvailableIdsHolder availableIdsHolder) { @@ -235,23 +232,17 @@ protected void initChannel(Channel channel) throws Exception { InFlightHandler inFlightHandler = new InFlightHandler( - node, protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, null); ProtocolInitHandler initHandler = - new ProtocolInitHandler(node, context, protocolVersion, clusterName, options); + new ProtocolInitHandler(context, protocolVersion, clusterName, options); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; } - - @Override - protected SocketAddress getConnectAddress(Node node) { - return SERVER_ADDRESS; - } } @AfterMethod diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 55ad1609020..351929aace2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.connection.ConnectionException; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -37,12 +36,11 @@ import static com.datastax.oss.driver.Assertions.assertThat; public class DriverChannelTest extends ChannelHandlerTestBase { - private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; private DriverChannel driverChannel; private MockWriteCoalescer writeCoalescer; - @Mock private Node node; @Mock private StreamIdGenerator streamIds; @BeforeMethod @@ -54,7 +52,7 @@ public void setup() { .pipeline() .addLast( new InFlightHandler( - node, CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); + CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); writeCoalescer = new MockWriteCoalescer(); driverChannel = new DriverChannel(channel, writeCoalescer, null, CoreProtocolVersion.V3); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 9e2a7cdcaf5..99d7b94322a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ConnectionException; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -47,7 +46,6 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { private static final Query QUERY = new Query("select * from foo"); private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; - @Mock private Node node; @Mock private StreamIdGenerator streamIds; @BeforeMethod @@ -355,7 +353,6 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { .pipeline() .addLast( new InFlightHandler( - node, CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index a3bd127133d..5244a63d7b6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; +import java.net.SocketAddress; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; @@ -45,7 +45,7 @@ * methods throughout the test to check that each call has been performed. * *

This class handles asynchronous calls to the thread factory, but it must be used from a single - * thread (see {@link #waitForCalls(Node, int)}). + * thread (see {@link #waitForCalls(SocketAddress, int)}). */ public class MockChannelFactoryHelper { @@ -56,15 +56,15 @@ public static Builder builder(ChannelFactory channelFactory) { private final ChannelFactory channelFactory; private final InOrder inOrder; // If waitForCalls sees more invocations than expected, the difference is stored here - private final Map previous = new HashMap<>(); + private final Map previous = new HashMap<>(); public MockChannelFactoryHelper(ChannelFactory channelFactory) { this.channelFactory = channelFactory; this.inOrder = Mockito.inOrder(channelFactory); } - public void waitForCall(Node node) { - waitForCalls(node, 1); + public void waitForCall(SocketAddress address) { + waitForCalls(address, 1); } /** @@ -74,10 +74,10 @@ public void waitForCall(Node node) { * expected when this method is called. If so, the extra calls are stored and stored and will be * taken into account next time. */ - public void waitForCalls(Node node, int expected) { - int fromLastTime = previous.getOrDefault(node, 0); + public void waitForCalls(SocketAddress address, int expected) { + int fromLastTime = previous.getOrDefault(address, 0); if (fromLastTime >= expected) { - previous.put(node, fromLastTime - expected); + previous.put(address, fromLastTime - expected); return; } expected -= fromLastTime; @@ -88,19 +88,19 @@ public void waitForCalls(Node node, int expected) { ArgumentCaptor.forClass(DriverChannelOptions.class); inOrder .verify(channelFactory, timeout(100).atLeast(expected)) - .connect(eq(node), optionsCaptor.capture()); + .connect(eq(address), optionsCaptor.capture()); int actual = optionsCaptor.getAllValues().size(); int extras = actual - expected; if (extras > 0) { - previous.compute(node, (k, v) -> (v == null) ? extras : v + extras); + previous.compute(address, (k, v) -> (v == null) ? extras : v + extras); } } public void verifyNoMoreCalls() { inOrder .verify(channelFactory, timeout(100).times(0)) - .connect(any(Node.class), any(DriverChannelOptions.class)); + .connect(any(SocketAddress.class), any(DriverChannelOptions.class)); Set counts = Sets.newHashSet(previous.values()); if (!counts.isEmpty()) { @@ -110,7 +110,7 @@ public void verifyNoMoreCalls() { public static class Builder { private final ChannelFactory channelFactory; - private final ListMultimap invocations = + private final ListMultimap invocations = MultimapBuilder.hashKeys().arrayListValues().build(); public Builder(ChannelFactory channelFactory) { @@ -119,23 +119,23 @@ public Builder(ChannelFactory channelFactory) { this.channelFactory = channelFactory; } - public Builder success(Node node, DriverChannel channel) { - invocations.put(node, channel); + public Builder success(SocketAddress address, DriverChannel channel) { + invocations.put(address, channel); return this; } - public Builder failure(Node node, String error) { - invocations.put(node, new Exception(error)); + public Builder failure(SocketAddress address, String error) { + invocations.put(address, new Exception(error)); return this; } - public Builder failure(Node node, Throwable error) { - invocations.put(node, error); + public Builder failure(SocketAddress address, Throwable error) { + invocations.put(address, error); return this; } - public Builder pending(Node node, CompletableFuture future) { - invocations.put(node, future); + public Builder pending(SocketAddress address, CompletableFuture future) { + invocations.put(address, future); return this; } @@ -145,9 +145,9 @@ public MockChannelFactoryHelper build() { } private void stub() { - for (Node node : invocations.keySet()) { + for (SocketAddress address : invocations.keySet()) { LinkedList> results = new LinkedList<>(); - for (Object object : invocations.get(node)) { + for (Object object : invocations.get(address)) { if (object instanceof DriverChannel) { results.add(CompletableFuture.completedFuture(((DriverChannel) object))); } else if (object instanceof Throwable) { @@ -163,7 +163,7 @@ private void stub() { if (results.size() > 0) { CompletionStage first = results.poll(); OngoingStubbing> ongoingStubbing = - Mockito.when(channelFactory.connect(eq(node), any(DriverChannelOptions.class))) + Mockito.when(channelFactory.connect(eq(address), any(DriverChannelOptions.class))) .thenReturn(first); for (CompletionStage result : results) { ongoingStubbing.thenReturn(result); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java index 890f48b6b3c..366ac580460 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import java.util.LinkedList; import java.util.Queue; @@ -35,12 +34,12 @@ class MockResponseCallback implements ResponseCallback { } @Override - public void onResponse(Frame responseFrame, Node node) { + public void onResponse(Frame responseFrame) { responses.offer(responseFrame); } @Override - public void onFailure(Throwable error, Node node) { + public void onFailure(Throwable error) { responses.offer(error); } @@ -50,7 +49,7 @@ public boolean holdStreamId() { } @Override - public void onStreamIdAssigned(int streamId, Node node) { + public void onStreamIdAssigned(int streamId) { this.streamId = streamId; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index aa33b64ea48..477d5ae25a5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -58,7 +57,6 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { private static final long QUERY_TIMEOUT_MILLIS = 100L; - @Mock private Node node; @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; @@ -83,7 +81,7 @@ public void setup() { .addLast( "inflight", new InFlightHandler( - node, CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); + CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); } @Test @@ -93,11 +91,7 @@ public void should_initialize_without_authentication() { .addLast( "init", new ProtocolInitHandler( - node, - internalDriverContext, - CoreProtocolVersion.V4, - null, - DriverChannelOptions.DEFAULT)); + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -126,8 +120,7 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt .pipeline() .addLast( "init", - new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -147,11 +140,7 @@ public void should_initialize_with_authentication() { .addLast( "init", new ProtocolInitHandler( - node, - internalDriverContext, - CoreProtocolVersion.V4, - null, - DriverChannelOptions.DEFAULT)); + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -209,11 +198,7 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa .addLast( "init", new ProtocolInitHandler( - node, - internalDriverContext, - CoreProtocolVersion.V4, - null, - DriverChannelOptions.DEFAULT)); + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -253,7 +238,6 @@ public void should_check_cluster_name_if_provided() { .addLast( "init", new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", @@ -282,7 +266,6 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th .addLast( "init", new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", @@ -300,7 +283,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th assertThat(e) .isInstanceOf(ClusterNameMismatchException.class) .hasMessageContaining( - "Node node reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); + "Node embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); } @Test @@ -311,8 +294,7 @@ public void should_initialize_with_keyspace() { .pipeline() .addLast( "init", - new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, null, options)); + new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, options)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -338,7 +320,7 @@ public void should_initialize_with_events() { .addLast( "init", new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -367,7 +349,7 @@ public void should_initialize_with_keyspace_and_events() { .addLast( "init", new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -396,7 +378,7 @@ public void should_fail_to_initialize_if_keyspace_is_invalid() { .addLast( "init", new ProtocolInitHandler( - node, internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 1d885df7914..92434360c0c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -41,7 +41,7 @@ public void should_register_for_all_events_if_topology_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -64,7 +64,7 @@ public void should_register_for_schema_events_only_if_topology_not_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -84,7 +84,7 @@ public void should_process_status_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true); waitForPendingAdminTasks(); @@ -106,7 +106,7 @@ public void should_process_topology_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true); waitForPendingAdminTasks(); @@ -128,7 +128,7 @@ public void should_process_schema_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(NODE1), optionsCaptor.capture())) + Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(false); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 26ba28e7e9a..79f64099f77 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -44,11 +44,11 @@ public void should_init_with_first_contact_point_if_reachable() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); // Then @@ -64,11 +64,11 @@ public void should_always_return_same_init_future() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); // When CompletionStage initFuture1 = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); CompletionStage initFuture2 = controlConnection.init(false); // Then @@ -83,14 +83,14 @@ public void should_init_with_second_contact_point_if_first_one_fails() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(NODE1, "mock failure") - .success(NODE2, channel2) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) .build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); // Then @@ -108,14 +108,14 @@ public void should_fail_to_init_if_all_contact_points_fail() { // Given MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(NODE1, "mock failure") - .failure(NODE2, "mock failure") + .failure(ADDRESS1, "mock failure") + .failure(ADDRESS2, "mock failure") .build(); // When CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); // Then @@ -135,13 +135,13 @@ public void should_reconnect_if_channel_goes_down() throws Exception { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE1, channel1) - .failure(NODE1, "mock failure") - .success(NODE2, channel2) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -155,8 +155,8 @@ public void should_reconnect_if_channel_goes_down() throws Exception { // Then // a reconnection was started Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCall(NODE1); - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); @@ -176,13 +176,13 @@ public void should_force_reconnection_if_pending() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE1, channel1) - .failure(NODE1, "mock failure") - .success(NODE2, channel2) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -196,8 +196,8 @@ public void should_force_reconnection_if_pending() { // When controlConnection.reconnectNow(); - factoryHelper.waitForCall(NODE1); - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); // Then @@ -214,13 +214,13 @@ public void should_force_reconnection_even_if_connected() { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE1, channel1) - .failure(NODE1, "mock failure") - .success(NODE2, channel2) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .success(ADDRESS2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -230,8 +230,8 @@ public void should_force_reconnection_even_if_connected() { controlConnection.reconnectNow(); // Then - factoryHelper.waitForCall(NODE1); - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS1); + factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(channel1).forceClose(); @@ -256,9 +256,9 @@ public void should_not_force_reconnection_if_closed() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CompletionStage closeFuture = controlConnection.forceCloseAsync(); @@ -279,10 +279,10 @@ public void should_close_channel_when_closing() { // Given DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory).success(NODE1, channel1).build(); + MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -308,13 +308,13 @@ public void should_close_channel_if_closed_during_reconnection() { CompletableFuture channel2Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE1, channel1) - .failure(NODE1, "mock failure") - .pending(NODE2, channel2Future) + .success(ADDRESS1, channel1) + .failure(ADDRESS1, "mock failure") + .pending(ADDRESS2, channel2Future) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -325,9 +325,9 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); // channel2 starts initializing (but the future is not completed yet) - factoryHelper.waitForCall(NODE2); + factoryHelper.waitForCall(ADDRESS2); // When // the control connection gets closed before channel2 initialization is complete @@ -355,13 +355,13 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { CompletableFuture channel1Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE1, channel1) - .pending(NODE1, channel1Future) - .success(NODE2, channel2) + .success(ADDRESS1, channel1) + .pending(ADDRESS1, channel1Future) + .success(ADDRESS2, channel2) .build(); CompletionStage initFuture = controlConnection.init(false); - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); @@ -373,7 +373,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); // channel1 starts initializing (but the future is not completed yet) - factoryHelper.waitForCall(NODE1); + factoryHelper.waitForCall(ADDRESS1); // When // the control connection gets closed before channel1 initialization fails diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 986c1e665ce..f68af5a6f62 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -34,6 +34,7 @@ import io.netty.channel.EventLoop; import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -84,7 +85,7 @@ public void setup() { Mockito.when(context.channelFactory()).thenReturn(channelFactory); channelFactoryFuture = new Exchanger<>(); - Mockito.when(channelFactory.connect(any(Node.class), any(DriverChannelOptions.class))) + Mockito.when(channelFactory.connect(any(SocketAddress.class), any(DriverChannelOptions.class))) .thenAnswer( invocation -> { CompletableFuture channelFuture = new CompletableFuture<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 5625a8d229c..861fec9b8d0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -109,15 +109,15 @@ public void should_initialize_when_all_channels_succeed() throws Exception { DriverChannel channel3 = newMockDriverChannel(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE, channel1) - .success(NODE, channel2) - .success(NODE, channel3) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture) @@ -133,15 +133,15 @@ public void should_initialize_when_all_channels_fail() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(NODE, "mock channel init failure") - .failure(NODE, "mock channel init failure") - .failure(NODE, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); @@ -156,15 +156,15 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) - .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) - .failure(NODE, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); } @@ -174,17 +174,17 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); ClusterNameMismatchException error = - new ClusterNameMismatchException(NODE, "actual", "expected"); + new ClusterNameMismatchException(ADDRESS, "actual", "expected"); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .failure(NODE, error) - .failure(NODE, error) - .failure(NODE, error) + .failure(ADDRESS, error) + .failure(ADDRESS, error) + .failure(ADDRESS, error) .build(); ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); @@ -206,17 +206,17 @@ public void should_reconnect_when_init_incomplete() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // Init: 1 channel fails, the other succeeds - .failure(NODE, "mock channel init failure") - .success(NODE, channel1) + .failure(ADDRESS, "mock channel init failure") + .success(ADDRESS, channel1) // 1st reconnection - .pending(NODE, channel2Future) + .pending(ADDRESS, channel2Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -229,7 +229,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); channel2Future.complete(channel2); - factoryHelper.waitForCalls(NODE, 1); + factoryHelper.waitForCalls(ADDRESS, 1); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); @@ -252,17 +252,17 @@ public void should_reconnect_when_channel_dies() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .success(NODE, channel2) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) // reconnection - .pending(NODE, channel3Future) + .pending(ADDRESS, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -278,7 +278,7 @@ public void should_reconnect_when_channel_dies() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCall(NODE); + factoryHelper.waitForCall(ADDRESS); channel3Future.complete(channel3); waitForPendingAdminTasks(); @@ -301,17 +301,17 @@ public void should_shrink_outside_of_reconnection() throws Exception { DriverChannel channel4 = newMockDriverChannel(4); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE, channel1) - .success(NODE, channel2) - .success(NODE, channel3) - .success(NODE, channel4) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); - factoryHelper.waitForCalls(NODE, 4); + factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -345,20 +345,20 @@ public void should_shrink_during_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .success(NODE, channel2) - .failure(NODE, "mock channel init failure") - .failure(NODE, "mock channel init failure") + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") // reconnection - .pending(NODE, channel3Future) - .pending(NODE, channel4Future) + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); - factoryHelper.waitForCalls(NODE, 4); + factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -378,7 +378,7 @@ public void should_shrink_during_reconnection() throws Exception { channel3Future.complete(channel3); channel4Future.complete(channel4); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. @@ -404,18 +404,18 @@ public void should_grow_outside_of_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .success(NODE, channel2) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) // growth attempt - .success(NODE, channel3) - .success(NODE, channel4) + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -430,7 +430,7 @@ public void should_grow_outside_of_reconnection() throws Exception { Mockito.verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); @@ -457,20 +457,20 @@ public void should_grow_during_reconnection() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .failure(NODE, "mock channel init failure") + .success(ADDRESS, channel1) + .failure(ADDRESS, "mock channel init failure") // first reconnection attempt - .pending(NODE, channel2Future) + .pending(ADDRESS, channel2Future) // extra reconnection attempt after we realize the pool must grow - .pending(NODE, channel3Future) - .pending(NODE, channel4Future) + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); @@ -488,7 +488,7 @@ public void should_grow_during_reconnection() throws Exception { // Complete the channel for the first reconnection, bringing the count to 2 channel2Future.complete(channel2); - factoryHelper.waitForCall(NODE); + factoryHelper.waitForCall(ADDRESS); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); @@ -501,7 +501,7 @@ public void should_grow_during_reconnection() throws Exception { inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); // Two more channels get opened, bringing us to the target count - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); channel3Future.complete(channel3); channel4Future.complete(channel4); waitForPendingAdminTasks(); @@ -521,14 +521,14 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) - .success(NODE, channel1) - .success(NODE, channel2) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -560,17 +560,17 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .failure(NODE, "mock channel init failure") - .failure(NODE, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") // reconnection - .pending(NODE, channel1Future) - .pending(NODE, channel2Future) + .pending(ADDRESS, channel1Future) + .pending(ADDRESS, channel2Future) .build(); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -579,7 +579,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { // Check that reconnection has kicked in, but do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCalls(NODE, 2); + factoryHelper.waitForCalls(ADDRESS, 2); // Switch keyspace, it succeeds immediately since there is no active channel CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); @@ -612,18 +612,18 @@ public void should_close_all_channels_when_closed() throws Exception { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .success(NODE, channel2) - .failure(NODE, "mock channel init failure") + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") // reconnection - .pending(NODE, channel3Future) + .pending(ADDRESS, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); @@ -632,7 +632,7 @@ public void should_close_all_channels_when_closed() throws Exception { // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(NODE, 1); + factoryHelper.waitForCalls(ADDRESS, 1); CompletionStage closeFuture = pool.closeAsync(); waitForPendingAdminTasks(); @@ -675,18 +675,18 @@ public void should_force_close_all_channels_when_force_closed() throws Exception MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init - .success(NODE, channel1) - .success(NODE, channel2) - .failure(NODE, "mock channel init failure") + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") // reconnection - .pending(NODE, channel3Future) + .pending(ADDRESS, channel3Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); - factoryHelper.waitForCalls(NODE, 3); + factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); @@ -695,7 +695,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(NODE, 1); + factoryHelper.waitForCalls(ADDRESS, 1); CompletionStage closeFuture = pool.forceCloseAsync(); waitForPendingAdminTasks(); From b2bd03b6da199c1af511805032995e3455a5c40a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 30 May 2017 14:04:43 -0700 Subject: [PATCH 051/742] Fix missing import --- .../oss/driver/api/core/UnsupportedProtocolVersionException.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index a946fd8560e..26335a356da 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core; import com.google.common.collect.ImmutableList; +import java.net.SocketAddress; import java.util.Collections; import java.util.List; From d3334b02d3468edeaf55bdb51eb5b3820e2732a3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 30 May 2017 14:05:20 -0700 Subject: [PATCH 052/742] wip refactor exceptions --- .../internal/core/protocol/FrameDecodingException.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java index 28781214011..ce160c22563 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecodingException.java @@ -17,6 +17,11 @@ import io.netty.handler.codec.DecoderException; +/** + * Wraps an error while decoding an incoming protocol frame. + * + *

This is only used internally, never exposed to the client. + */ public class FrameDecodingException extends DecoderException { public final int streamId; From 556a800a3faa386ba483df826b5ff6abd7ed2ce0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 May 2017 11:17:56 -0700 Subject: [PATCH 053/742] Refactor exceptions --- .../oss/driver/api/core/DriverException.java | 20 ++++++++-- .../api/core/DriverExecutionException.java | 29 ++++++++++++++ ...ption.java => DriverTimeoutException.java} | 12 ++---- .../UnsupportedProtocolVersionException.java | 6 ++- .../core/auth/AuthenticationException.java | 14 ++++++- .../connection/BusyConnectionException.java | 17 ++++++-- .../connection/ClosedConnectionException.java | 39 +++++++++++++++++++ .../connection/ConnectionInitException.java | 32 +++++++++++++++ .../core/connection/HeartbeatException.java | 25 +++++++++--- .../driver/internal/core/DefaultCluster.java | 5 ++- .../adminrequest/AdminRequestHandler.java | 12 ++++-- ...equest.java => ChannelHandlerRequest.java} | 26 ++++++------- .../channel/ClusterNameMismatchException.java | 10 +++-- .../core/channel/HeartbeatHandler.java | 4 +- .../core/channel/InFlightHandler.java | 19 ++++----- .../core/channel/ProtocolInitHandler.java | 6 +-- .../internal/core/session/DefaultSession.java | 3 +- .../util/concurrent/CompletableFutures.java | 4 +- .../core/channel/DriverChannelTest.java | 4 +- .../core/channel/InFlightHandlerTest.java | 6 +-- 20 files changed, 225 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java rename core/src/main/java/com/datastax/oss/driver/api/core/{connection/ConnectionException.java => DriverTimeoutException.java} (67%) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java rename core/src/main/java/com/datastax/oss/driver/internal/core/channel/{InternalRequest.java => ChannelHandlerRequest.java} (79%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java index 57d793148a9..19e317dd38a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java @@ -15,16 +15,28 @@ */ package com.datastax.oss.driver.api.core; -public class DriverException extends RuntimeException { - public DriverException(String message) { +/** + * Base class for all exceptions thrown by the driver. + * + *

Note that, for obvious programming errors (for example, calling {@link Cluster#connect()} on a + * cluster instance that was previously closed), the driver might throw JDK runtime exceptions, such + * as {@link IllegalArgumentException} or {@link IllegalStateException}. In all other cases, it will + * be an instance of this class. + * + *

One special case is when the driver tried multiple nodes to complete a request, and they all + * failed; the error returned to the client will be an {@link AllNodesFailedException}, which wraps + * a map of errors per node. + */ +public abstract class DriverException extends RuntimeException { + protected DriverException(String message) { super(message); } - public DriverException(String message, Throwable cause) { + protected DriverException(String message, Throwable cause) { super(message, cause); } - public DriverException(Throwable cause) { + protected DriverException(Throwable cause) { super(cause); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java new file mode 100644 index 00000000000..0d524ffb0f4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +/** + * Thrown by synchronous wrapper methods (such as {@link Cluster#connect()}, when the underlying + * future was completed with a checked exception. + * + *

This exception should be rarely thrown (if ever). Most of the time, the driver uses unchecked + * exceptions, which will be rethrown directly instead of being wrapped in this class. + */ +public class DriverExecutionException extends DriverException { + public DriverExecutionException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java similarity index 67% rename from core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java rename to core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java index 9d91e183201..e22054fa1d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.connection; +package com.datastax.oss.driver.api.core; -/** A generic error on a connection to a node. */ -public class ConnectionException extends RuntimeException { - public ConnectionException(String message) { +/** Thrown when a driver request timed out. */ +public class DriverTimeoutException extends DriverException { + public DriverTimeoutException(String message) { super(message); } - - public ConnectionException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 26335a356da..e8b67558056 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -23,8 +23,12 @@ /** * Indicates that we've attempted to connect to a Cassandra node with a protocol version that it * cannot handle (e.g., connecting to a C* 2.1 node with protocol version 4). + * + *

The only time when this is returned directly to the client (wrapped in a {@link + * AllNodesFailedException}) is at initialization. If it happens later when the driver is already + * connected, it is just logged an the corresponding node is forced down. */ -public class UnsupportedProtocolVersionException extends RuntimeException { +public class UnsupportedProtocolVersionException extends DriverException { private static final long serialVersionUID = 0; private final SocketAddress address; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java index b8c6e6597f6..31ddf17ae9d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java @@ -15,9 +15,16 @@ */ package com.datastax.oss.driver.api.core.auth; +import com.datastax.oss.driver.api.core.AllNodesFailedException; import java.net.SocketAddress; -/** Indicates an error during the authentication phase while connecting to a node. */ +/** + * Indicates an error during the authentication phase while connecting to a node. + * + *

The only time when this is returned directly to the client (wrapped in a {@link + * AllNodesFailedException}) is at initialization. If it happens later when the driver is already + * connected, it is just logged and the connection will be reattempted. + */ public class AuthenticationException extends RuntimeException { private static final long serialVersionUID = 0; @@ -31,4 +38,9 @@ public AuthenticationException(SocketAddress address, String message, Throwable super(String.format("Authentication error on host %s: %s", address, message), cause); this.address = address; } + + /** The address of the node that encountered the error. */ + public SocketAddress getAddress() { + return address; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java index e05f3267672..cc68871b68f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java @@ -15,16 +15,27 @@ */ package com.datastax.oss.driver.api.core.connection; +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; + /** * Indicates that a write was attempted on a connection that already handles too many simultaneous * requests. + * + *

This might happen under heavy load. The driver will automatically try the next node in the + * query plan. Therefore the only way that the client can observe this exception is as part of a + * {@link AllNodesFailedException}. */ -public class BusyConnectionException extends ConnectionException { +public class BusyConnectionException extends DriverException { public BusyConnectionException(int maxAvailableIds) { super( String.format( - "" + "Connection has exceeded its maximum of %d simultaneous requests", - maxAvailableIds)); + "Connection has exceeded its maximum of %d simultaneous requests", maxAvailableIds)); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java new file mode 100644 index 00000000000..fdba7a43f9f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; + +/** + * Thrown when the connection on which a request was executing is closed due to an unrelated event. + * + *

For example, this can happen if the node is unresponsive and a heartbeat query failed, or if + * the node was forced down. + * + *

The driver will always retry these requests on the next node transparently. Therefore, the + * only way to observe this exception is as part of an {@link AllNodesFailedException}. + */ +public class ClosedConnectionException extends DriverException { + + public ClosedConnectionException(String message) { + super(message); + } + + public ClosedConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java new file mode 100644 index 00000000000..43e1a93de93 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; + +/** + * Indicates a generic error while initializing a connection. + * + *

The only time when this is returned directly to the client (wrapped in a {@link + * AllNodesFailedException}) is at initialization. If it happens later when the driver is already + * connected, it is just logged an the connection is reattempted. + */ +public class ConnectionInitException extends DriverException { + public ConnectionInitException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java index 36620f784d1..9484389d046 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java @@ -15,13 +15,28 @@ */ package com.datastax.oss.driver.api.core.connection; -public class HeartbeatException extends ConnectionException { +import com.datastax.oss.driver.api.core.DriverException; +import java.net.SocketAddress; - public HeartbeatException(String message) { - super(message); - } +/** + * Thrown when a heartbeat query fails. + * + *

Heartbeat queries are sent automatically on idle connections, to ensure that they are still + * alive. If a heartbeat query fails, the connection is closed, and all pending queries are aborted. + * Depending on the retry policy, the heartbeat exception can either be rethrown directly to the + * client, or the driver tries the next host in the query plan. + */ +public class HeartbeatException extends DriverException { - public HeartbeatException(String message, Throwable cause) { + private final SocketAddress address; + + public HeartbeatException(SocketAddress address, String message, Throwable cause) { super(message, cause); + this.address = address; + } + + /** The address of the node that encountered the error. */ + public SocketAddress getAddress() { + return address; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 29ca42dcf71..0b0ec84a129 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -171,7 +171,7 @@ private void init() { private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { - connectFuture.completeExceptionally(new DriverException("Cluster was closed")); + connectFuture.completeExceptionally(new IllegalStateException("Cluster was closed")); } else { DefaultSession.init(context, keyspace) .whenCompleteAsync( @@ -180,7 +180,8 @@ private void connect(CqlIdentifier keyspace, CompletableFuture conne connectFuture.completeExceptionally(error); } else if (closeWasCalled) { connectFuture.completeExceptionally( - new DriverException("Cluster was closed while session was initializing")); + new IllegalStateException( + "Cluster was closed while session was initializing")); session.forceCloseAsync(); } else { sessions.add(session); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 6b566e29e93..cc726db0ce5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.adminrequest; -import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -44,7 +44,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Handles the lifecyle of an admin request. */ +/** Handles the lifecyle of an admin request (such as a node refresh or schema refresh query). */ public class AdminRequestHandler implements ResponseCallback { private static final Logger LOG = LoggerFactory.getLogger(AdminRequestHandler.class); @@ -109,7 +109,8 @@ private void onWriteComplete(Future future) { } private void fireTimeout() { - result.completeExceptionally(new DriverException("Query timed out")); + result.completeExceptionally( + new DriverTimeoutException(String.format("%s timed out after %s", debugString, timeout))); } @Override @@ -137,7 +138,10 @@ public void onResponse(Frame responseFrame) { result.complete(null); } else { result.completeExceptionally( - new DriverException("Unexpected response to control query: " + message)); + // The actual exception type does not really matters, this is only logged, never + // returned to the client + new IllegalArgumentException( + String.format("%s got unexpected response %s", debugString, message))); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java similarity index 79% rename from core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java index 2d9db51826f..8d399250809 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InternalRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -25,10 +26,9 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** Common infrastructure to send a native protocol request from a channel handler. */ -abstract class InternalRequest implements ResponseCallback { +abstract class ChannelHandlerRequest implements ResponseCallback { final Channel channel; final ChannelHandlerContext ctx; @@ -36,7 +36,7 @@ abstract class InternalRequest implements ResponseCallback { private ScheduledFuture timeoutFuture; - InternalRequest(ChannelHandlerContext ctx, long timeoutMillis) { + ChannelHandlerRequest(ChannelHandlerContext ctx, long timeoutMillis) { this.ctx = ctx; this.channel = ctx.channel(); this.timeoutMillis = timeoutMillis; @@ -51,10 +51,6 @@ abstract class InternalRequest implements ResponseCallback { /** either message or cause can be null */ abstract void fail(String message, Throwable cause); - void fail(String message) { - fail(message, null); - } - void fail(Throwable cause) { fail(null, cause); } @@ -89,21 +85,23 @@ public final void onFailure(Throwable error) { } private void onTimeout() { - fail(new TimeoutException(describe() + ": timed out after " + timeoutMillis + " ms")); + fail(new DriverTimeoutException(describe() + ": timed out after " + timeoutMillis + " ms")); } void failOnUnexpected(Message response) { if (response instanceof Error) { Error error = (Error) response; fail( - String.format( - "%s: unexpected server error [%s] %s", - describe(), ProtocolUtils.errorCodeString(error.code), error.message)); + new IllegalArgumentException( + String.format( + "%s: unexpected server error [%s] %s", + describe(), ProtocolUtils.errorCodeString(error.code), error.message))); } else { fail( - String.format( - "%s: unexpected server response opcode=%s", - describe(), ProtocolUtils.opcodeString(response.opcode))); + new IllegalArgumentException( + String.format( + "%s: unexpected server response opcode=%s", + describe(), ProtocolUtils.opcodeString(response.opcode)))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java index 2837593d9a3..bb8621bb9ff 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -18,8 +18,8 @@ import java.net.SocketAddress; /** - * Indicates that we've attempted to connect to a node with a cluster name doesn't match that of the - * other nodes known to the driver. + * Indicates that we've attempted to connect to a node with a cluster name that doesn't match that + * of the other nodes known to the driver. * *

The driver runs the following query on each newly established connection: * @@ -30,8 +30,12 @@ * The first connection sets the cluster name for this driver instance, all subsequent connections * must match it or they will get rejected. This is intended to filter out errors in the discovery * process (for example, stale entries in {@code system.peers}). + * + *

This error is never returned directly to the client. If we detect a mismatch, it will always + * be after the driver has connected successfully; the error will be logged and the offending node + * forced down. */ -public class ClusterNameMismatchException extends Exception { +public class ClusterNameMismatchException extends RuntimeException { private static final long serialVersionUID = 0; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index 5d949cbac47..33d1101b208 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -70,7 +70,7 @@ protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws } } - private class HeartbeatRequest extends InternalRequest { + private class HeartbeatRequest extends ChannelHandlerRequest { HeartbeatRequest(ChannelHandlerContext ctx, long timeoutMillis) { super(ctx, timeoutMillis); @@ -105,7 +105,7 @@ void fail(String message, Throwable cause) { } LOG.debug(ctx.channel().toString() + " Heartbeat query failed: " + message, cause); // Notify InFlightHandler (fireExceptionCaught wouldn't work because the error has to go downstream) - ctx.write(new HeartbeatException(message, cause)); + ctx.write(new HeartbeatException(ctx.channel().remoteAddress(), message, cause)); HeartbeatHandler.this.request = null; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 2167fd2d96b..af034e2cc24 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; -import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; @@ -76,11 +76,12 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) } return; } else if (in == DriverChannel.FORCEFUL_CLOSE_MESSAGE) { - abortAllInFlight(new ConnectionException("Channel was force-closed")); + abortAllInFlight(new ClosedConnectionException("Channel was force-closed")); ctx.channel().close(); return; } else if (in instanceof HeartbeatException) { - abortAllInFlight((HeartbeatException) in); + abortAllInFlight( + new ClosedConnectionException("Heartbeat query failed", ((HeartbeatException) in))); ctx.close(); } @@ -174,7 +175,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } } else { // Otherwise fail all pending requests - abortAllInFlight(new ConnectionException("Unexpected error on channel", cause)); + abortAllInFlight(new ClosedConnectionException("Unexpected error on channel", cause)); ctx.close(); } } @@ -210,7 +211,7 @@ private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { return responseCallback; } - private void abortAllInFlight(Throwable cause) { + private void abortAllInFlight(ClosedConnectionException cause) { abortAllInFlight(cause, null); } @@ -218,7 +219,7 @@ private void abortAllInFlight(Throwable cause) { * @param ignore the ResponseCallback that called this method, if applicable (avoids a recursive * loop) */ - private void abortAllInFlight(Throwable cause, ResponseCallback ignore) { + private void abortAllInFlight(ClosedConnectionException cause, ResponseCallback ignore) { for (ResponseCallback responseCallback : inFlight.values()) { if (responseCallback != ignore) { responseCallback.onFailure(cause); @@ -235,7 +236,7 @@ private void reportAvailableIds() { } } - private class SetKeyspaceRequest extends InternalRequest { + private class SetKeyspaceRequest extends ChannelHandlerRequest { private final CqlIdentifier keyspaceName; private final Promise promise; @@ -269,8 +270,8 @@ void onResponse(Message response) { @Override void fail(String message, Throwable cause) { - Throwable setKeyspaceException = - (message == null) ? cause : new ConnectionException(message, cause); + ClosedConnectionException setKeyspaceException = + new ClosedConnectionException(message, cause); if (promise.tryFailure(setKeyspaceException)) { InFlightHandler.this.setKeyspaceRequest = null; // setKeyspace queries are not triggered directly by the user, but only as a response to a diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 5942683e4e4..06b963314a8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.auth.Authenticator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.connection.ConnectionInitException; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -96,7 +96,7 @@ private enum Step { REGISTER, } - private class InitRequest extends InternalRequest { + private class InitRequest extends ChannelHandlerRequest { // This class is a finite-state automaton, that sends a different query depending on the step // in the initialization sequence. private Step step; @@ -264,7 +264,7 @@ void onResponse(Message response) { @Override void fail(String message, Throwable cause) { Throwable finalException = - (message == null) ? cause : new ConnectionException(message, cause); + (message == null) ? cause : new ConnectionInitException(message, cause); setConnectFailure(finalException); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 895cc4cc83d..78e63fa84a8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -379,7 +378,7 @@ private void onAllPoolsClosed(List> closePoolStages) { } } if (firstError != null) { - closeFuture.completeExceptionally(new DriverException("Error closing pool(s)", firstError)); + closeFuture.completeExceptionally(firstError); } else { closeFuture.complete(null); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index e5c6f9b9a8b..d92755992cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; -import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.DriverExecutionException; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import java.util.List; @@ -104,7 +104,7 @@ public static T getUninterruptibly(CompletionStage stage) { } catch (ExecutionException e) { Throwable cause = e.getCause(); Throwables.throwIfUnchecked(cause); - throw new DriverException(cause); + throw new DriverExecutionException(cause); } } } finally { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 351929aace2..3c900ddcbe6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.CoreProtocolVersion; -import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -129,7 +129,7 @@ public void should_wait_for_coalesced_writes_when_closing_forcefully() { // Then assertThat(closeFuture).isSuccess(); assertThat(responseCallback.getFailure()) - .isInstanceOf(ConnectionException.class) + .isInstanceOf(ClosedConnectionException.class) .hasMessageContaining("Channel was force-closed"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 99d7b94322a..e9763b03e30 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; -import com.datastax.oss.driver.api.core.connection.ConnectionException; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -213,7 +213,7 @@ public void should_fail_all_pending_when_force_closed() throws Throwable { assertThat(channel.closeFuture()).isSuccess(); for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { assertThat(callback.getFailure()) - .isInstanceOf(ConnectionException.class) + .isInstanceOf(ClosedConnectionException.class) .hasMessageContaining("Channel was force-closed"); } } @@ -238,7 +238,7 @@ public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() assertThat(channel.closeFuture()).isSuccess(); for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { Throwable failure = callback.getFailure(); - assertThat(failure).isInstanceOf(ConnectionException.class); + assertThat(failure).isInstanceOf(ClosedConnectionException.class); assertThat(failure.getCause()).isSameAs(mockException); } } From 2eb5811b6522c3755ef5f17aff80a44839585db2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Jun 2017 11:08:13 -0700 Subject: [PATCH 054/742] Use suppressed exceptions in case multiple pools fail --- .../oss/driver/internal/core/session/DefaultSession.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 78e63fa84a8..0a5d75a0e54 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -370,10 +370,7 @@ private void onAllPoolsClosed(List> closePoolStages) { if (firstError == null) { firstError = error; } else { - LOG.error( - "Error closing multiple pools in the same session, logging because only " - + "the first one is included in the session's failed future", - error); + firstError.addSuppressed(error); } } } From b3f52c681524d06f66687a215d14e275f1d1d2f2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Jun 2017 15:21:10 -0700 Subject: [PATCH 055/742] Add copy method for exceptions --- .../api/core/AllNodesFailedException.java | 7 ++++- .../oss/driver/api/core/DriverException.java | 31 +++++++++++++------ .../api/core/DriverExecutionException.java | 7 ++++- .../api/core/DriverTimeoutException.java | 7 ++++- .../api/core/InvalidKeyspaceException.java | 7 ++++- .../api/core/NoNodeAvailableException.java | 5 +++ .../UnsupportedProtocolVersionException.java | 7 ++++- .../connection/BusyConnectionException.java | 13 +++++--- .../connection/ClosedConnectionException.java | 13 ++++++-- .../connection/ConnectionInitException.java | 7 ++++- .../core/connection/HeartbeatException.java | 7 ++++- .../util/concurrent/CompletableFutures.java | 4 +++ 12 files changed, 93 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java index 6241d71296d..16fda357f31 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java @@ -37,7 +37,7 @@ public static AllNodesFailedException fromErrors(Map errors) { private final Map errors; protected AllNodesFailedException(String message, Map errors) { - super(message); + super(message, null, true); this.errors = errors; } @@ -59,4 +59,9 @@ private static String buildMessage(Map errors) { public Map getErrors() { return errors; } + + @Override + public DriverException copy() { + return new AllNodesFailedException(getMessage(), errors); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java index 19e317dd38a..c8857897011 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java @@ -26,17 +26,30 @@ *

One special case is when the driver tried multiple nodes to complete a request, and they all * failed; the error returned to the client will be an {@link AllNodesFailedException}, which wraps * a map of errors per node. + * + *

Some implementations make the stack trace not writable to improve performance (see {@link + * Throwable#Throwable(String, Throwable, boolean, boolean)}). This is only done when the exception + * is thrown in a small number of well-known cases, and the stack trace wouldn't add any useful + * information (for example, server error responses). Instances returned by {@link #copy()} always + * have a stack trace. */ public abstract class DriverException extends RuntimeException { - protected DriverException(String message) { - super(message); - } - - protected DriverException(String message, Throwable cause) { - super(message, cause); + protected DriverException(String message, Throwable cause, boolean writableStackTrace) { + super(message, cause, true, writableStackTrace); } - protected DriverException(Throwable cause) { - super(cause); - } + /** + * Copy the exception. + * + *

This returns a new exception, equivalent to the original one, except that because a new + * object is created in the current thread, the top-most element in the stacktrace of the + * exception will refer to the current thread. The original exception may or may not be included + * as the copy's cause, depending on whether that is deemed useful (this is left to the discretion + * of each implementation). + * + *

This is intended for the synchronous wrapper methods of the driver, in order to produce a + * more user-friendly stack trace (that includes the line in the user code where the driver + * rethrew the error). + */ + public abstract DriverException copy(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java index 0d524ffb0f4..197126fcbb9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java @@ -24,6 +24,11 @@ */ public class DriverExecutionException extends DriverException { public DriverExecutionException(Throwable cause) { - super(cause); + super(null, cause, true); + } + + @Override + public DriverException copy() { + return new DriverExecutionException(getCause()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java index e22054fa1d2..e208e034ec2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java @@ -18,6 +18,11 @@ /** Thrown when a driver request timed out. */ public class DriverTimeoutException extends DriverException { public DriverTimeoutException(String message) { - super(message); + super(message, null, true); + } + + @Override + public DriverException copy() { + return new DriverTimeoutException(getMessage()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java index 6e184fc52e9..fe851d36199 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java @@ -18,6 +18,11 @@ /** Thrown when a session gets created with an invalid keyspace. */ public class InvalidKeyspaceException extends DriverException { public InvalidKeyspaceException(String message) { - super(message); + super(message, null, true); + } + + @Override + public DriverException copy() { + return new InvalidKeyspaceException(getMessage()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java index 0ffdd8ef567..7c94c04491e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java @@ -27,4 +27,9 @@ public class NoNodeAvailableException extends AllNodesFailedException { public NoNodeAvailableException() { super("No node was available to execute the query", Collections.emptyMap()); } + + @Override + public DriverException copy() { + return new NoNodeAvailableException(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index e8b67558056..512cdfe6b9b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -54,7 +54,7 @@ public static UnsupportedProtocolVersionException forNegotiation( private UnsupportedProtocolVersionException( SocketAddress address, String message, List attemptedVersions) { - super(message); + super(message, null, true); this.address = address; this.attemptedVersions = attemptedVersions; } @@ -68,4 +68,9 @@ public SocketAddress getAddress() { public List getAttemptedVersions() { return attemptedVersions; } + + @Override + public DriverException copy() { + return new UnsupportedProtocolVersionException(address, getMessage(), attemptedVersions); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java index cc68871b68f..61117288f8b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java @@ -29,13 +29,18 @@ public class BusyConnectionException extends DriverException { public BusyConnectionException(int maxAvailableIds) { - super( + this( String.format( - "Connection has exceeded its maximum of %d simultaneous requests", maxAvailableIds)); + "Connection has exceeded its maximum of %d simultaneous requests", maxAvailableIds), + false); + } + + private BusyConnectionException(String message, boolean writableStackTrace) { + super(message, null, writableStackTrace); } @Override - public synchronized Throwable fillInStackTrace() { - return this; + public DriverException copy() { + return new BusyConnectionException(getMessage(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java index fdba7a43f9f..a5036f59bb0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java @@ -30,10 +30,19 @@ public class ClosedConnectionException extends DriverException { public ClosedConnectionException(String message) { - super(message); + this(message, null, false); } public ClosedConnectionException(String message, Throwable cause) { - super(message, cause); + this(message, cause, false); + } + + private ClosedConnectionException(String message, Throwable cause, boolean writableStackTrace) { + super(message, cause, writableStackTrace); + } + + @Override + public DriverException copy() { + return new ClosedConnectionException(getMessage(), getCause(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java index 43e1a93de93..db7cbb7b0a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java @@ -27,6 +27,11 @@ */ public class ConnectionInitException extends DriverException { public ConnectionInitException(String message, Throwable cause) { - super(message, cause); + super(message, cause, true); + } + + @Override + public DriverException copy() { + return new ConnectionInitException(getMessage(), getCause()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java index 9484389d046..079f2e4308a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java @@ -31,7 +31,7 @@ public class HeartbeatException extends DriverException { private final SocketAddress address; public HeartbeatException(SocketAddress address, String message, Throwable cause) { - super(message, cause); + super(message, cause, true); this.address = address; } @@ -39,4 +39,9 @@ public HeartbeatException(SocketAddress address, String message, Throwable cause public SocketAddress getAddress() { return address; } + + @Override + public DriverException copy() { + return new HeartbeatException(address, getMessage(), getCause()); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index d92755992cb..8b29e0f83a9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; @@ -103,6 +104,9 @@ public static T getUninterruptibly(CompletionStage stage) { interrupted = true; } catch (ExecutionException e) { Throwable cause = e.getCause(); + if (cause instanceof DriverException) { + throw ((DriverException) cause).copy(); + } Throwables.throwIfUnchecked(cause); throw new DriverExecutionException(cause); } From 574dd08a8be599d641787e4952bf5bcfc252945a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 6 Jun 2017 16:44:52 -0700 Subject: [PATCH 056/742] Fix bug in LoadBalancingPolicyWrapperTest --- .../internal/core/metadata/LoadBalancingPolicyWrapperTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index e60033f3c68..6e7d01e4c88 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -199,6 +199,7 @@ public void should_accumulate_events_during_init_and_replay() { throw new RuntimeException(e); } eventBus.fire(NodeStateEvent.changed(NodeState.UNKNOWN, NodeState.DOWN, node1)); + initLatch.countDown(); }; Thread thread = new Thread(runnable); thread.start(); From 7044739916fcbc6e5a6675c247c4720a0978a062 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 27 Apr 2017 17:02:46 -0700 Subject: [PATCH 057/742] Add CQL request handler implementation --- .../api/core/AllNodesFailedException.java | 17 +- .../oss/driver/api/core/ClusterBuilder.java | 10 +- .../oss/driver/api/core/ConsistencyLevel.java | 65 +++ .../api/core/config/CoreDriverOption.java | 9 + .../api/core/config/DriverConfigProfile.java | 3 + .../api/core/context/DriverContext.java | 9 +- .../driver/api/core/cql/AsyncResultSet.java | 32 +- .../driver/api/core/cql/ColumnDefinition.java | 33 ++ .../api/core/cql/ColumnDefinitions.java | 61 +++ .../oss/driver/api/core/cql/CqlSession.java | 16 +- .../driver/api/core/cql/ExecutionInfo.java | 92 ++++ .../driver/api/core/cql/PrepareRequest.java | 1 + .../datastax/oss/driver/api/core/cql/Row.java | 26 ++ .../driver/api/core/cql/SimpleStatement.java | 25 ++ .../oss/driver/api/core/cql/Statement.java | 7 +- .../api/core/retry/DefaultRetryPolicy.java | 127 ++++++ .../driver/api/core/retry/RetryDecision.java | 29 ++ .../driver/api/core/retry/RetryPolicy.java | 55 +++ .../oss/driver/api/core/retry/WriteType.java | 54 +++ .../servererrors/AlreadyExistsException.java | 56 +++ .../servererrors/BootstrappingException.java | 44 ++ .../servererrors/CoordinatorException.java | 34 ++ .../FunctionFailureException.java | 42 ++ .../InvalidConfigurationInQueryException.java | 46 ++ .../servererrors/InvalidQueryException.java | 42 ++ .../servererrors/OverloadedException.java | 46 ++ .../api/core/servererrors/ProtocolError.java | 46 ++ .../QueryConsistencyException.java | 69 +++ .../servererrors/QueryExecutionException.java | 26 ++ .../QueryValidationException.java | 29 ++ .../servererrors/ReadFailureException.java | 136 ++++++ .../servererrors/ReadTimeoutException.java | 101 +++++ .../api/core/servererrors/ServerError.java | 48 ++ .../api/core/servererrors/SyntaxError.java | 42 ++ .../core/servererrors/TruncateException.java | 46 ++ .../servererrors/UnauthorizedException.java | 43 ++ .../servererrors/UnavailableException.java | 91 ++++ .../servererrors/WriteFailureException.java | 130 ++++++ .../servererrors/WriteTimeoutException.java | 84 ++++ .../oss/driver/api/core/session/Request.java | 6 +- .../specex/NoSpeculativeExecutionPolicy.java | 34 ++ .../specex/SpeculativeExecutionPolicy.java | 48 ++ .../internal/core/channel/ChannelFactory.java | 9 +- .../core/channel/ResponseCallback.java | 11 +- .../typesafe/TypesafeDriverConfigProfile.java | 7 + .../core/context/DefaultDriverContext.java | 70 ++- .../core/context/InternalDriverContext.java | 4 + .../driver/internal/core/cql/Conversions.java | 208 +++++++++ .../internal/core/cql/CqlRequestHandler.java | 418 ++++++++++++++++++ .../core/cql/CqlRequestProcessor.java | 47 ++ .../core/cql/DefaultAsyncResultSet.java | 108 +++++ .../core/cql/DefaultColumnDefinition.java | 86 ++++ .../core/cql/DefaultColumnDefinitions.java | 169 +++++++ .../core/cql/DefaultExecutionInfo.java | 93 ++++ .../internal/core/cql/DefaultResultSet.java | 23 + .../driver/internal/core/cql/DefaultRow.java | 143 ++++++ .../core/cql/DefaultSimpleStatement.java | 47 ++ .../internal/core/pool/ChannelPool.java | 2 +- .../internal/core/session/DefaultSession.java | 17 +- .../internal/core/session/RequestHandler.java | 28 ++ .../core/session/RequestHandlerBase.java | 53 +++ .../core/session/RequestProcessor.java | 51 +++ .../session/RequestProcessorRegistry.java | 57 +++ core/src/main/resources/reference.conf | 29 ++ .../oss/driver/TestDataProviders.java | 28 ++ .../datastax/oss/driver/TestInterceptor.java | 44 ++ .../core/cql/CqlRequestHandlerRetryTest.java | 325 ++++++++++++++ ...equestHandlerSpeculativeExecutionTest.java | 199 +++++++++ .../core/cql/CqlRequestHandlerTest.java | 92 ++++ .../core/cql/CqlRequestHandlerTestBase.java | 79 ++++ .../internal/core/cql/PoolBehavior.java | 94 ++++ .../core/cql/RequestHandlerTestHarness.java | 228 ++++++++++ .../ScheduledTaskCapturingEventLoop.java | 104 +++++ .../ScheduledTaskCapturingEventLoopTest.java | 66 +++ .../services/org.testng.ITestNGListener | 1 + .../api/core/data/AccessibleByName.java | 2 +- .../internal/core/data/DefaultUdtValue.java | 1 + .../driver/internal/type/DataTypeHelper.java | 109 +++++ 78 files changed, 4993 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryDecision.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java create mode 100644 core/src/test/java/com/datastax/oss/driver/TestDataProviders.java create mode 100644 core/src/test/java/com/datastax/oss/driver/TestInterceptor.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java create mode 100644 core/src/test/resources/META-INF/services/org.testng.ITestNGListener create mode 100644 types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java index 16fda357f31..06083e0624c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java @@ -19,6 +19,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import java.util.List; import java.util.Map; /** @@ -27,13 +28,27 @@ */ public class AllNodesFailedException extends DriverException { public static AllNodesFailedException fromErrors(Map errors) { - if (errors == null || errors.size() == 0) { + if (errors == null || errors.isEmpty()) { return new NoNodeAvailableException(); } else { return new AllNodesFailedException(ImmutableMap.copyOf(errors)); } } + public static AllNodesFailedException fromErrors(List> errors) { + Map map; + if (errors == null || errors.isEmpty()) { + map = null; + } else { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map.Entry entry : errors) { + builder.put(entry); + } + map = builder.build(); + } + return fromErrors(map); + } + private final Map errors; protected AllNodesFailedException(String message, Map errors) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 1611f3dbe5a..c3b8092775c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; @@ -37,6 +38,7 @@ public class ClusterBuilder { private DriverConfig config; private Set programmaticContactPoints = Collections.emptySet(); + private List> typeCodecs = Collections.emptyList(); /** * Sets the configuration to use. @@ -97,6 +99,12 @@ public ClusterBuilder withContactPoints(Set contactPoints) { return this; } + /** Registers additional codecs for custom type mappings. */ + public ClusterBuilder withTypeCodecs(List> typeCodecs) { + this.typeCodecs = typeCodecs; + return this; + } + /** * Creates the cluster with the options set by this builder. * @@ -114,7 +122,7 @@ public CompletionStage buildAsync() { Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); - InternalDriverContext context = new DefaultDriverContext(config); + InternalDriverContext context = new DefaultDriverContext(config, typeCodecs); return DefaultCluster.init(context, contactPoints); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java new file mode 100644 index 00000000000..7c2802736ea --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +/** The consistency level of a request. */ +public enum ConsistencyLevel { + ANY(ProtocolConstants.ConsistencyLevel.ANY), + ONE(ProtocolConstants.ConsistencyLevel.ONE), + TWO(ProtocolConstants.ConsistencyLevel.TWO), + THREE(ProtocolConstants.ConsistencyLevel.THREE), + QUORUM(ProtocolConstants.ConsistencyLevel.QUORUM), + ALL(ProtocolConstants.ConsistencyLevel.ALL), + LOCAL_ONE(ProtocolConstants.ConsistencyLevel.LOCAL_ONE), + LOCAL_QUORUM(ProtocolConstants.ConsistencyLevel.LOCAL_QUORUM), + EACH_QUORUM(ProtocolConstants.ConsistencyLevel.EACH_QUORUM), + + SERIAL(ProtocolConstants.ConsistencyLevel.SERIAL), + LOCAL_SERIAL(ProtocolConstants.ConsistencyLevel.LOCAL_SERIAL), + ; + + private final int protocolCode; + + ConsistencyLevel(int protocolCode) { + this.protocolCode = protocolCode; + } + + public int getProtocolCode() { + return protocolCode; + } + + public static ConsistencyLevel fromCode(int code) { + ConsistencyLevel level = BY_CODE.get(code); + if (level == null) { + throw new IllegalArgumentException("Unknown code: " + code); + } + return level; + } + + private static Map BY_CODE = mapByCode(values()); + + private static Map mapByCode(ConsistencyLevel[] levels) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ConsistencyLevel level : levels) { + builder.put(level.protocolCode, level); + } + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 10cf933dd5d..5d6f21e448e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -32,11 +32,20 @@ public enum CoreDriverOption implements DriverOption { CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), + REQUEST_TIMEOUT("request.timeout", true), + REQUEST_CONSISTENCY("request.consistency", true), + REQUEST_PAGE_SIZE("request.page-size", true), + REQUEST_SERIAL_CONSISTENCY("request.serial-consistency", true), + CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), + RETRY_POLICY_CLASS("retry.policy-class", true), + LOAD_BALANCING_POLICY_CLASS("load-balancing.policy-class", true), + SPECULATIVE_EXECUTION_POLICY_CLASS("speculative-execution.policy-class", true), + RECONNECTION_POLICY_CLASS("connection.reconnection.policy-class", true), RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection.config.base-delay", true), RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index e4a12fab8d0..35d4ad333cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.config; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -40,4 +41,6 @@ public interface DriverConfigProfile { Duration getDuration(DriverOption option); long getDuration(DriverOption option, TimeUnit targetUnit); + + ConsistencyLevel getConsistencyLevel(DriverOption option); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index c0267622119..3ef352e68b1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -19,12 +19,15 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import java.util.Optional; /** Holds common components that are shared throughout a driver instance. */ -public interface DriverContext { +public interface DriverContext extends AttachmentPoint { DriverConfig config(); @@ -32,6 +35,10 @@ public interface DriverContext { ReconnectionPolicy reconnectionPolicy(); + RetryPolicy retryPolicy(); + + SpeculativeExecutionPolicy speculativeExecutionPolicy(); + AddressTranslator addressTranslator(); /** The authentication provider, if authentication was configured. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index e92256b6ee5..c6fcf26b499 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,4 +15,34 @@ */ package com.datastax.oss.driver.api.core.cql; -public interface AsyncResultSet {} +import java.util.concurrent.CompletionStage; + +/** + * The result of an asynchronous CQL query. + * + *

If the query is paged, the rows returned by {@link #iterator()} represent only the current + * page. To keep iterating beyond that, use {@link #fetchNextPage()}. + * + *

Note that this object can only be iterated once: rows are "consumed" as they are read, + * subsequent calls to {@code iterator()} will return an empty iterator. + */ +public interface AsyncResultSet extends Iterable { + + ColumnDefinitions getColumnDefinitions(); + + ExecutionInfo getExecutionInfo(); + + /** + * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next + * one asynchronously. + */ + boolean hasMorePages(); + + /** + * Fetch the next page of results asynchronously. + * + * @throws IllegalStateException if there are no more pages. Use {@link #hasMorePages()} to check + * if you can call this method. + */ + CompletionStage fetchNextPage() throws IllegalStateException; +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java new file mode 100644 index 00000000000..0ef19f88eae --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.detach.Detachable; +import com.datastax.oss.driver.api.type.DataType; +import java.io.Serializable; + +/** Metadata about a CQL column. */ +public interface ColumnDefinition extends Detachable, Serializable { + + CqlIdentifier getKeyspace(); + + CqlIdentifier getTable(); + + CqlIdentifier getName(); + + DataType getType(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java new file mode 100644 index 00000000000..c3ead2c586c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.AccessibleByName; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.core.detach.Detachable; +import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; + +/** Metadata about a set of CQL columns. */ +public interface ColumnDefinitions extends Iterable, Detachable, Serializable { + int size(); + + ColumnDefinition get(int i); + + /** + * Whether there is a definition using the given name. + * + *

Because raw strings are ambiguous with regard to case-sensitivity, the argument will be + * interpreted according to the rules described in {@link AccessibleByName}. + */ + boolean contains(String name); + + /** Whether there is a definition using the given CQL identifier. */ + boolean contains(CqlIdentifier id); + + /** + * Returns the index of the first column that uses the given name. + * + *

Because raw strings are ambiguous with regard to case-sensitivity, the argument will be + * interpreted according to the rules described in {@link AccessibleByName}. + * + *

Also, note that if multiple columns use the same name, there is no way to find the index for + * the next occurrences. One way to avoid this is to use aliases in your CQL queries. + */ + int firstIndexOf(String name); + + /** + * Returns the index of the first column that uses the given identifier. + * + *

Note that if multiple columns use the same identifier, there is no way to find the index for + * the next occurrences. One way to avoid this is to use aliases in your CQL queries. + */ + int firstIndexOf(CqlIdentifier id); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java index d98268770fe..99fdd0a36e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -17,18 +17,28 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; +import java.util.Collections; import java.util.concurrent.CompletionStage; public interface CqlSession extends Session { // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion default ResultSet execute(Statement statement) { - return execute((Request) statement); + return execute((Request>) statement); } // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion - default AsyncResultSet executeAsync(Statement statement) { - return executeAsync((Request) statement); + default CompletionStage executeAsync(Statement statement) { + return executeAsync((Request>) statement); + } + + default ResultSet execute(String query) { + return execute(new DefaultSimpleStatement(query, Collections.emptyList(), null)); + } + + default CompletionStage executeAsync(String query) { + return executeAsync(new DefaultSimpleStatement(query, Collections.emptyList(), null)); } default PreparedStatement prepare(String query) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java new file mode 100644 index 00000000000..8866d84fcfb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +/** Information about the execution of a query. */ +public interface ExecutionInfo { + + /** The node that was used as a coordinator to successfully complete the query. */ + Node getCoordinator(); + + /** + * The number of speculative executions that were started for this query. + * + *

This does not include the initial, normal execution of the query. Therefore, if speculative + * executions are disabled, this will always be 0. If they are enabled and one speculative + * execution was triggered in addition to the initial execution, this will be 1, etc. + * + * @see SpeculativeExecutionPolicy + */ + int getSpeculativeExecutionCount(); + + /** + * The index of the execution that completed this query. + * + *

0 represents the initial, normal execution of the query, 1 the first speculative execution, + * etc. + * + * @see SpeculativeExecutionPolicy + */ + int getSuccessfulExecutionIndex(); + + /** + * The errors encountered on previous coordinators, if any. + * + *

The list is in chronological order, based on the time that the driver processed the error + * responses. If speculative executions are enabled, they run concurrently so their errors will be + * interleaved. A node can appear multiple times (if the retry policy decided to retry on the same + * node). + */ + List> getErrors(); + + /** + * The paging state of the query. + * + *

This represents the next page to be fetched if this query has multiple page of results. It + * can be saved and reused later on the same statement. + * + * @return the paging state, or {@code null} if there is no next page. + */ + ByteBuffer getPagingState(); + + /** + * The server-side warnings for this query, if any (otherwise the list will be empty). + * + *

This feature is only available with {@link CoreProtocolVersion#V4} or above; with lower + * versions, this list will always be empty. + */ + List getWarnings(); + + /** + * The custom payload sent back by the server with the response, if any (otherwise the map will be + * empty). + * + *

This method returns a read-only view of the original map, but its values remain inherently + * mutable. If multiple clients will read these values, care should be taken not to corrupt the + * data (in particular, preserve the indices by calling {@link ByteBuffer#duplicate()}). + * + *

This feature is only available with {@link CoreProtocolVersion#V4} or above; with lower + * versions, this map will always be empty. + */ + Map getIncomingPayload(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 10b092e14f6..d8bcc8febc2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.session.Request; import java.util.concurrent.CompletionStage; +/** A request to prepare a CQL query. */ public interface PrepareRequest extends Request> { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java new file mode 100644 index 00000000000..164a3cf1c49 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.data.GettableById; +import com.datastax.oss.driver.api.core.data.GettableByIndex; +import com.datastax.oss.driver.api.core.data.GettableByName; +import com.datastax.oss.driver.api.core.detach.Detachable; +import java.io.Serializable; + +/** A row from a CQL table. */ +public interface Row + extends GettableByIndex, GettableByName, GettableById, Detachable, Serializable {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java new file mode 100644 index 00000000000..b6f5fe2b24e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import java.util.List; + +public interface SimpleStatement extends Statement { + + String getQuery(); + + List getValues(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index ad174891877..0de6800edf0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -16,5 +16,10 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.session.Request; +import java.util.concurrent.CompletionStage; -public interface Statement extends Request {} +/** A request to execute a CQL query. */ +public interface Statement extends Request> { + // Implementation note: "CqlRequest" would be a better name, but we keep "Statement" to match + // previous driver versions. +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java new file mode 100644 index 00000000000..785a471798a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * The default retry policy. This is a very conservative implementation: it triggers a maximum of + * one retry per request, and only in cases that have a high chance of success (see the method + * javadocs for detailed explanations of each case). + */ +public class DefaultRetryPolicy implements RetryPolicy { + + public DefaultRetryPolicy(DriverContext context) { + // nothing to do + } + + /** + * {@inheritDoc} + * + *

This implementation triggers a maximum of one retry (to the same node), and only if enough + * replicas had responded to the read request but data was not retrieved amongst those. That + * usually means that enough replicas are alive to satisfy the consistency, but the coordinator + * picked a dead one for data retrieval, not having detected that replica as dead yet. The + * reasoning is that by the time we get the timeout, the dead replica will likely have been + * detected as dead and the retry has a high chance of success. + * + *

Otherwise, the exception is rethrown. + */ + @Override + public RetryDecision onReadTimeout( + Request request, + ConsistencyLevel cl, + int blockFor, + int received, + boolean dataPresent, + int retryCount) { + + return (retryCount == 0 && received >= blockFor && !dataPresent) + ? RetryDecision.RETRY_SAME + : RetryDecision.RETHROW; + } + + /** + * {@inheritDoc} + * + *

This implementation triggers a maximum of one retry (to the same node), and only for a + * {@code WriteType.BATCH_LOG} write. The reasoning is that the coordinator tries to write the + * distributed batch log against a small subset of nodes in the local datacenter; a timeout + * usually means that none of these nodes were alive but the coordinator hadn't detected them as + * dead yet. By the time we get the timeout, the dead nodes will likely have been detected as + * dead, and the retry has thus a high chance of success. + * + *

Otherwise, the exception is rethrown. + */ + @Override + public RetryDecision onWriteTimeout( + Request request, + ConsistencyLevel cl, + WriteType writeType, + int blockFor, + int received, + int retryCount) { + + return (retryCount == 0 && writeType == WriteType.BATCH_LOG) + ? RetryDecision.RETRY_SAME + : RetryDecision.RETHROW; + } + + /** + * {@inheritDoc} + * + *

This implementation triggers a maximum of one retry, to the next node in the query plan. The + * rationale is that the first coordinator might have been network-isolated from all other nodes + * (thinking they're down), but still able to communicate with the client; in that case, retrying + * on the same host has almost no chance of success, but moving to the next host might solve the + * issue. + * + *

Otherwise, the exception is rethrown. + */ + @Override + public RetryDecision onUnavailable( + Request request, ConsistencyLevel cl, int required, int alive, int retryCount) { + + return (retryCount == 0) ? RetryDecision.RETRY_NEXT : RetryDecision.RETHROW; + } + + /** + * {@inheritDoc} + * + *

This implementation always retries on the next node. + */ + @Override + public RetryDecision onRequestAborted(Request request, Throwable error, int retryCount) { + return RetryDecision.RETRY_NEXT; + } + + /** + * {@inheritDoc} + * + *

This implementation always retries on the next node. + */ + @Override + public RetryDecision onErrorResponse(Request request, Throwable error, int retryCount) { + return RetryDecision.RETRY_NEXT; + } + + @Override + public void close() { + // nothing to do + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryDecision.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryDecision.java new file mode 100644 index 00000000000..62769912bdd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryDecision.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +/** A decision from the {@link RetryPolicy} on how to handle a retry. */ +public enum RetryDecision { + /** Retry the operation on the same node. */ + RETRY_SAME, + /** Retry the operation on the next available node in the query plan (if any). */ + RETRY_NEXT, + /** Rethrow to the calling code, as the result of the execute operation. */ + RETHROW, + /** Don't retry and return an empty result set to the calling code. */ + IGNORE, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java new file mode 100644 index 00000000000..a86e904a348 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * Defines the behavior to adopt when a request fails. + * + *

For each request, the driver gets a "query plan" (a list of coordinators to try) from the + * {@link LoadBalancingPolicy}, and tries each node in sequence. This policy is invoked if the + * request to that node fails. + */ +public interface RetryPolicy { + + RetryDecision onReadTimeout( + Request request, + ConsistencyLevel cl, + int blockFor, + int received, + boolean dataPresent, + int retryCount); + + RetryDecision onWriteTimeout( + Request request, + ConsistencyLevel cl, + WriteType writeType, + int blockFor, + int received, + int retryCount); + + RetryDecision onUnavailable( + Request request, ConsistencyLevel cl, int required, int alive, int retryCount); + + RetryDecision onRequestAborted(Request request, Throwable error, int retryCount); + + RetryDecision onErrorResponse(Request request, Throwable error, int retryCount); + + void close(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java new file mode 100644 index 00000000000..723e8fe7072 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +/** + * The type of a Cassandra write query. + * + *

This information is returned by Cassandra when a write timeout is raised, to indicate what + * type of write timed out. It is useful to decide which retry decision to adopt. + */ +public enum WriteType { + /** A write to a single partition key. Such writes are guaranteed to be atomic and isolated. */ + SIMPLE, + /** + * A write to a multiple partition key that used the distributed batch log to ensure atomicity + * (atomicity meaning that if any statement in the batch succeeds, all will eventually succeed). + */ + BATCH, + /** + * A write to a multiple partition key that doesn't use the distributed batch log. Atomicity for + * such writes is not guaranteed + */ + UNLOGGED_BATCH, + /** + * A counter write (that can be for one or multiple partition key). Such write should not be + * replayed to avoid over-counting. + */ + COUNTER, + /** + * The initial write to the distributed batch log that Cassandra performs internally before a + * BATCH write. + */ + BATCH_LOG, + /** + * A conditional write. If a timeout has this {@code WriteType}, the timeout has happened while + * doing the compare-and-swap for an conditional update. In this case, the update may or may not + * have been applied. + */ + CAS, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java new file mode 100644 index 00000000000..017d0c827ba --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Thrown when a query attempts to create a keyspace or table that already exists. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class AlreadyExistsException extends QueryValidationException { + + private final String keyspace; + private final String table; + + public AlreadyExistsException(Node coordinator, String keyspace, String table) { + this(coordinator, makeMessage(keyspace, table), keyspace, table, false); + } + + private AlreadyExistsException( + Node coordinator, String message, String keyspace, String table, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + this.keyspace = keyspace; + this.table = table; + } + + private static String makeMessage(String keyspace, String table) { + if (table == null || table.isEmpty()) { + return String.format("Keyspace %s already exists", keyspace); + } else { + return String.format("Object %s.%s already exists", keyspace, table); + } + } + + @Override + public DriverException copy() { + return new AlreadyExistsException(getCoordinator(), getMessage(), keyspace, table, true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java new file mode 100644 index 00000000000..36cf806f5e2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Thrown when the coordinator was bootstrapping when it received a query. + * + *

This exception does not go through the {@link RetryPolicy}, the query is always retried on the + * next node. Therefore the only way the client can observe this exception is in an {@link + * AllNodesFailedException}. + */ +public class BootstrappingException extends QueryExecutionException { + + public BootstrappingException(Node coordinator) { + super(coordinator, String.format("%s is bootstrapping", coordinator), false); + } + + private BootstrappingException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new BootstrappingException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java new file mode 100644 index 00000000000..c150d109772 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; + +/** A server-side error thrown by the coordinator node in response to a driver request. */ +public abstract class CoordinatorException extends DriverException { + + private final Node coordinator; + + protected CoordinatorException(Node coordinator, String message, boolean writableStackTrace) { + super(message, null, writableStackTrace); + this.coordinator = coordinator; + } + + public Node getCoordinator() { + return coordinator; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java new file mode 100644 index 00000000000..485d191b77d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * An error during the execution of a CQL function. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class FunctionFailureException extends QueryExecutionException { + + public FunctionFailureException(Node coordinator, String message) { + this(coordinator, message, false); + } + + private FunctionFailureException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new FunctionFailureException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java new file mode 100644 index 00000000000..79753bb752e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Indicates that a query is invalid because of some configuration problem. + * + *

This is generally throw by queries that manipulate the schema (CREATE and ALTER) when the + * required configuration options are invalid. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class InvalidConfigurationInQueryException extends QueryValidationException { + + public InvalidConfigurationInQueryException(Node coordinator, String message) { + this(coordinator, message, false); + } + + private InvalidConfigurationInQueryException( + Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new InvalidConfigurationInQueryException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java new file mode 100644 index 00000000000..ae5ef5de98b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Indicates a syntactically correct, but invalid query. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class InvalidQueryException extends QueryValidationException { + + public InvalidQueryException(Node coordinator, String message) { + this(coordinator, message, false); + } + + private InvalidQueryException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new InvalidQueryException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java new file mode 100644 index 00000000000..5468366bbf8 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * Thrown when the coordinator reported itself as being overloaded. + * + *

This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, + * which will decide if it is rethrown directly to the client or if the request should be retried. + * If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class OverloadedException extends QueryExecutionException { + + public OverloadedException(Node coordinator) { + super(coordinator, String.format("%s is bootstrapping", coordinator), false); + } + + private OverloadedException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new OverloadedException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java new file mode 100644 index 00000000000..7470e67fd43 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Indicates that the contacted node reported a protocol error. + * + *

Protocol errors indicate that the client triggered a protocol violation (for instance, a + * {@code QUERY} message is sent before a {@code STARTUP} one has been sent). Protocol errors should + * be considered as a bug in the driver and reported as such. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class ProtocolError extends CoordinatorException { + + public ProtocolError(Node coordinator, String message) { + this(coordinator, message, false); + } + + private ProtocolError(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new ProtocolError(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java new file mode 100644 index 00000000000..c58d66e4e1a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.metadata.Node; + +/** + * A failure to reach the required consistency level during the execution of a query. + * + *

Such an exception is returned when the query has been tried by Cassandra but cannot be + * achieved with the requested consistency level because either: + * + *

    + *
  • the coordinator did not receive enough replica responses within the rpc timeout set for + * Cassandra; + *
  • some replicas replied with an error. + *
+ */ +public abstract class QueryConsistencyException extends QueryExecutionException { + + private final ConsistencyLevel consistencyLevel; + private final int received; + private final int blockFor; + + protected QueryConsistencyException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + this.consistencyLevel = consistencyLevel; + this.received = received; + this.blockFor = blockFor; + } + + /** The consistency level of the operation that failed. */ + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + /** The number of replica that had acknowledged/responded to the operation before it failed. */ + public int getReceived() { + return received; + } + + /** + * The minimum number of replica acknowledgements/responses that were required to fulfill the + * operation. + */ + public int getBlockFor() { + return blockFor; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java new file mode 100644 index 00000000000..c386ea5625c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.metadata.Node; + +/** A server-side error thrown when a valid query cannot be executed. */ +public abstract class QueryExecutionException extends CoordinatorException { + + protected QueryExecutionException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java new file mode 100644 index 00000000000..b121b9896d9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.metadata.Node; + +/** + * A server-side error thrown when a query cannot be executed because it is syntactically incorrect, + * invalid or unauthorized. + */ +public abstract class QueryValidationException extends CoordinatorException { + + protected QueryValidationException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java new file mode 100644 index 00000000000..d2884fa9036 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; +import java.net.InetAddress; +import java.util.Map; + +/** + * A non-timeout error during a read query. + * + *

This happens when some of the replicas that were contacted by the coordinator replied with an + * error. + * + *

This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, + * which will decide if it is rethrown directly to the client or if the request should be retried. + * If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class ReadFailureException extends QueryConsistencyException { + + private final int numFailures; + private final boolean dataPresent; + private final Map reasonMap; + + public ReadFailureException( + Node coordinator, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + int numFailures, + boolean dataPresent, + Map reasonMap) { + this( + coordinator, + String.format( + "Cassandra failure during read query at consistency %s " + + "(%d responses were required but only %d replica responded, %d failed)", + consistencyLevel, blockFor, received, numFailures), + consistencyLevel, + received, + blockFor, + numFailures, + dataPresent, + reasonMap, + false); + } + + private ReadFailureException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + int numFailures, + boolean dataPresent, + Map reasonMap, + boolean writableStackTrace) { + super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + this.numFailures = numFailures; + this.dataPresent = dataPresent; + this.reasonMap = reasonMap; + } + + /** Returns the number of replicas that experienced a failure while executing the request. */ + public int getNumFailures() { + return numFailures; + } + + /** + * Whether the actual data was amongst the received replica responses. + * + *

During reads, Cassandra doesn't request data from every replica to minimize internal network + * traffic. Instead, some replicas are only asked for a checksum of the data. A read failure may + * occur even if enough replicas have responded to fulfill the consistency level, if only checksum + * responses have been received. This method allows to detect that case. + */ + public boolean wasDataPresent() { + return dataPresent; + } + + /** + * Returns the a failure reason code for each node that failed. + * + *

At the time of writing, the existing reason codes are: + * + *

    + *
  • {@code 0x0000}: the error does not have a specific code assigned yet, or the cause is + * unknown. + *
  • {@code 0x0001}: The read operation scanned too many tombstones (as defined by {@code + * tombstone_failure_threshold} in {@code cassandra.yaml}, causing a {@code + * TombstoneOverwhelmingException}. + *
+ * + * (please refer to the Cassandra documentation for your version for the most up-to-date list of + * errors) + * + *

This feature is available for protocol v5 or above only. With lower protocol versions, the + * map will always be empty. + */ + public Map getReasonMap() { + return reasonMap; + } + + @Override + public DriverException copy() { + return new ReadFailureException( + getCoordinator(), + getMessage(), + getConsistencyLevel(), + getReceived(), + getBlockFor(), + numFailures, + dataPresent, + reasonMap, + true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java new file mode 100644 index 00000000000..06c191010b7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * A server-side timeout during a read query. + * + *

This exception is processed by {@link RetryPolicy#onReadTimeout(Request, ConsistencyLevel, + * int, int, boolean, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. + */ +public class ReadTimeoutException extends QueryConsistencyException { + + private final boolean dataPresent; + + public ReadTimeoutException( + Node coordinator, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + boolean dataPresent) { + this( + coordinator, + String.format( + "Cassandra timeout during read query at consistency %s (%s)", + consistencyLevel, formatDetails(received, blockFor, dataPresent)), + consistencyLevel, + received, + blockFor, + dataPresent, + false); + } + + private ReadTimeoutException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + boolean dataPresent, + boolean writableStackTrace) { + super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + this.dataPresent = dataPresent; + } + + private static String formatDetails(int received, int blockFor, boolean dataPresent) { + if (received < blockFor) { + return String.format( + "%d responses were required but only %d replica responded", blockFor, received); + } else if (!dataPresent) { + return "the replica queried for data didn't respond"; + } else { + return "timeout while waiting for repair of inconsistent replica"; + } + } + + /** + * Whether the actual data was amongst the received replica responses. + * + *

During reads, Cassandra doesn't request data from every replica to minimize internal network + * traffic. Instead, some replicas are only asked for a checksum of the data. A read timeout may + * occur even if enough replicas have responded to fulfill the consistency level, if only checksum + * responses have been received. This method allows to detect that case. + */ + public boolean wasDataPresent() { + return dataPresent; + } + + @Override + public DriverException copy() { + return new ReadTimeoutException( + getCoordinator(), + getMessage(), + getConsistencyLevel(), + getReceived(), + getBlockFor(), + dataPresent, + true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java new file mode 100644 index 00000000000..bbd5b3c9933 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * Indicates that the contacted node reported an internal error. + * + *

This should be considered as a server bug and reported as such. + * + *

This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, + * which will decide if it is rethrown directly to the client or if the request should be retried. + * If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class ServerError extends CoordinatorException { + + public ServerError(Node coordinator, String message) { + this(coordinator, message, false); + } + + private ServerError(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new ServerError(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java new file mode 100644 index 00000000000..778393e4578 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * A syntax error in a query. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class SyntaxError extends QueryValidationException { + + public SyntaxError(Node coordinator, String message) { + this(coordinator, message, false); + } + + private SyntaxError(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new SyntaxError(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java new file mode 100644 index 00000000000..a0463477dcf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * An error during a truncation operation. + * + *

This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, + * which will decide if it is rethrown directly to the client or if the request should be retried. + * If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class TruncateException extends QueryExecutionException { + + public TruncateException(Node coordinator, String message) { + this(coordinator, message, false); + } + + private TruncateException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new TruncateException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java new file mode 100644 index 00000000000..3a5523c1c37 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; + +/** + * Indicates that a query cannot be performed due to the authorization restrictions of the logged + * user. + * + *

This exception does not go through the {@link RetryPolicy}, it is always rethrown directly to + * the client. + */ +public class UnauthorizedException extends QueryValidationException { + + public UnauthorizedException(Node coordinator, String message) { + this(coordinator, message, false); + } + + private UnauthorizedException(Node coordinator, String message, boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + } + + @Override + public DriverException copy() { + return new UnauthorizedException(getCoordinator(), getMessage(), true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java new file mode 100644 index 00000000000..d07638c6e27 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * Thrown when the coordinator knows there is not enough replicas alive to perform a query with the + * requested consistency level. + * + *

This exception is processed by {@link RetryPolicy#onUnavailable(Request, ConsistencyLevel, + * int, int, int)}, which will decide if it is rethrown directly to the client or if the request + * should be retried. If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class UnavailableException extends QueryExecutionException { + private final ConsistencyLevel consistencyLevel; + private final int required; + private final int alive; + + public UnavailableException( + Node coordinator, ConsistencyLevel consistencyLevel, int required, int alive) { + this( + coordinator, + String.format( + "Not enough replicas available for query at consistency %s (%d required but only %d alive)", + consistencyLevel, required, alive), + consistencyLevel, + required, + alive, + false); + } + + private UnavailableException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int required, + int alive, + boolean writableStackTrace) { + super(coordinator, message, writableStackTrace); + this.consistencyLevel = consistencyLevel; + this.required = required; + this.alive = alive; + } + + /** The consistency level of the operation triggering this exception. */ + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + /** + * The number of replica acknowledgements/responses required to perform the operation (with its + * required consistency level). + */ + public int getRequired() { + return required; + } + + /** + * The number of replicas that were known to be alive by the coordinator node when it tried to + * execute the operation. + */ + public int getAlive() { + return alive; + } + + @Override + public DriverException copy() { + return new UnavailableException( + getCoordinator(), getMessage(), consistencyLevel, required, alive, true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java new file mode 100644 index 00000000000..6607af9c2ef --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.retry.WriteType; +import com.datastax.oss.driver.api.core.session.Request; +import java.net.InetAddress; +import java.util.Map; + +/** + * A non-timeout error during a write query. + * + *

This happens when some of the replicas that were contacted by the coordinator replied with an + * error. + * + *

This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, + * which will decide if it is rethrown directly to the client or if the request should be retried. + * If all other tried nodes also fail, this exception will appear in the {@link + * AllNodesFailedException} thrown to the client. + */ +public class WriteFailureException extends QueryConsistencyException { + + private final WriteType writeType; + private final int numFailures; + private final Map reasonMap; + + public WriteFailureException( + Node coordinator, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + WriteType writeType, + int numFailures, + Map reasonMap) { + this( + coordinator, + String.format( + "Cassandra failure during write query at consistency %s " + + "(%d responses were required but only %d replica responded, %d failed)", + consistencyLevel, blockFor, received, numFailures), + consistencyLevel, + received, + blockFor, + writeType, + numFailures, + reasonMap, + false); + } + + private WriteFailureException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + WriteType writeType, + int numFailures, + Map reasonMap, + boolean writableStackTrace) { + super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + this.writeType = writeType; + this.numFailures = numFailures; + this.reasonMap = reasonMap; + } + + /** The type of the write for which this failure was raised. */ + public WriteType getWriteType() { + return writeType; + } + + /** Returns the number of replicas that experienced a failure while executing the request. */ + public int getNumFailures() { + return numFailures; + } + + /** + * Returns the a failure reason code for each node that failed. + * + *

At the time of writing, the existing reason codes are: + * + *

    + *
  • {@code 0x0000}: the error does not have a specific code assigned yet, or the cause is + * unknown. + *
  • {@code 0x0001}: The read operation scanned too many tombstones (as defined by {@code + * tombstone_failure_threshold} in {@code cassandra.yaml}, causing a {@code + * TombstoneOverwhelmingException}. + *
+ * + * (please refer to the Cassandra documentation for your version for the most up-to-date list of + * errors) + * + *

This feature is available for protocol v5 or above only. With lower protocol versions, the + * map will always be empty. + */ + public Map getReasonMap() { + return reasonMap; + } + + @Override + public DriverException copy() { + return new WriteFailureException( + getCoordinator(), + getMessage(), + getConsistencyLevel(), + getReceived(), + getBlockFor(), + writeType, + numFailures, + reasonMap, + true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java new file mode 100644 index 00000000000..e04a96864d7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.retry.WriteType; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * A server-side timeout during a write query. + * + *

This exception is processed by {@link RetryPolicy#onWriteTimeout(Request, ConsistencyLevel, + * WriteType, int, int, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. + */ +public class WriteTimeoutException extends QueryConsistencyException { + + private final WriteType writeType; + + public WriteTimeoutException( + Node coordinator, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + WriteType writeType) { + this( + coordinator, + String.format( + "Cassandra timeout during write query at consistency %s (%d replica were required but only %d acknowledged the write)", + consistencyLevel, blockFor, received), + consistencyLevel, + received, + blockFor, + writeType, + false); + } + + private WriteTimeoutException( + Node coordinator, + String message, + ConsistencyLevel consistencyLevel, + int received, + int blockFor, + WriteType writeType, + boolean writableStackTrace) { + super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + this.writeType = writeType; + } + + /** The type of the write for which a timeout was raised. */ + public WriteType getWriteType() { + return writeType; + } + + @Override + public DriverException copy() { + return new WriteTimeoutException( + getCoordinator(), + getMessage(), + getConsistencyLevel(), + getReceived(), + getBlockFor(), + writeType, + true); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index d894d129bba..31520f90a3b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -25,4 +25,8 @@ * @param the type of response when this request is executed synchronously. * @param the type of response when this request is executed asynchronously. */ -public interface Request {} +public interface Request { + + /** The name of the configuration profile that will be used for execution. */ + String getConfigProfile(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java new file mode 100644 index 00000000000..1daf35df945 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.specex; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.session.Request; + +/** A policy that never triggers speculative executions. */ +public class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { + + @Override + public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { + // never start speculative executions + return 0; + } + + @Override + public void close() { + // nothing to do + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java new file mode 100644 index 00000000000..6c14b891ea0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.specex; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * The policy that decides if the driver will send speculative queries to the next nodes when the + * current node takes too long to respond. + */ +public interface SpeculativeExecutionPolicy { + + /** + * @param keyspace the CQL keyspace currently associated to the session. This is set either + * through {@link Cluster#connect(CqlIdentifier)} or by manually executing a {@code USE} CQL + * statement. It can be {@code null} if the session has no keyspace. + * @param request the request to execute. + * @param runningExecutions the number of executions that are already running (including the + * initial, non-speculative request). For example, if this is 2 it means the initial attempt + * was sent, then the driver scheduled a first speculative execution, and it is now asking for + * the delay until the second speculative execution. + * @return the time (in milliseconds) until a speculative request is sent to the next node, or + * zero or a negative value to stop sending requests. + */ + long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions); + + /** + * Gets invoked at cluster shutdown. + * + *

This gives the policy the opportunity to clean up any resource it might have acquired. + */ + void close(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 7b8b4f6af94..8d2e8162945 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -24,13 +24,13 @@ import com.datastax.oss.driver.internal.core.protocol.FrameDecoder; import com.datastax.oss.driver.internal.core.protocol.FrameEncoder; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -64,6 +64,13 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } + public ProtocolVersion protocolVersion() { + ProtocolVersion result = this.protocolVersion; + Preconditions.checkState( + result != null, "Protocol version not known yet, this should only be called after init"); + return result; + } + public CompletionStage connect( final SocketAddress address, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java index f9a6399bb9c..8dbf5c0e0c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -30,10 +30,15 @@ public interface ResponseCallback { void onResponse(Frame responseFrame); /** - * Invoked if there was an error while waiting for the response. + * Invoked if we couldn't get the response. * - *

This is generally triggered when a channel fails (for example because of a heartbeat - * failure) and all pending requests are aborted. + *

This can be triggered in two cases: + * + *

    + *
  • the connection was closed (for example, because of a heartbeat failure) before the + * response was received; + *
  • the response was received but there was an error while decoding it. + *
*/ void onFailure(Throwable error); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 1d3e8c3980e..bc348aeeda1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.typesafe.config.Config; @@ -68,4 +69,10 @@ public List getStringList(DriverOption option) { public long getBytes(DriverOption option) { return config.getBytes(option.getPath()); } + + @Override + public ConsistencyLevel getConsistencyLevel(DriverOption option) { + String name = getString(option); + return ConsistencyLevel.valueOf(name); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 05c24b0800d..804dec6e6fc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -15,13 +15,18 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; @@ -33,14 +38,17 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.driver.internal.core.util.Reflection; import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector; import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; +import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; +import java.util.List; import java.util.Optional; /** @@ -69,6 +77,11 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("loadBalancingPolicy", this::buildLoadBalancingPolicy, cycleDetector); private final LazyReference reconnectionPolicyRef = new LazyReference<>("reconnectionPolicy", this::buildReconnectionPolicy, cycleDetector); + private final LazyReference retryPolicyRef = + new LazyReference<>("retryPolicy", this::buildRetryPolicy, cycleDetector); + private final LazyReference speculativeExecutionPolicyRef = + new LazyReference<>( + "speculativeExecutionPolicy", this::buildSpeculativeExecutionPolicy, cycleDetector); private final LazyReference addressTranslatorRef = new LazyReference<>("addressTranslator", this::buildAddressTranslator, cycleDetector); private final LazyReference> authProviderRef = @@ -105,9 +118,11 @@ public class DefaultDriverContext implements InternalDriverContext { private final DriverConfig config; private final ChannelPoolFactory channelPoolFactory = new ChannelPoolFactory(); + private final CodecRegistry codecRegistry; - public DefaultDriverContext(DriverConfig config) { + public DefaultDriverContext(DriverConfig config, List> typeCodecs) { this.config = config; + this.codecRegistry = buildCodecRegistry(typeCodecs); } protected LoadBalancingPolicy buildLoadBalancingPolicy() { @@ -132,6 +147,27 @@ protected ReconnectionPolicy buildReconnectionPolicy() { classOption))); } + protected RetryPolicy buildRetryPolicy() { + CoreDriverOption classOption = CoreDriverOption.RETRY_POLICY_CLASS; + return Reflection.buildFromConfig(this, classOption, RetryPolicy.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing retry policy, check your configuration (%s)", classOption))); + } + + protected SpeculativeExecutionPolicy buildSpeculativeExecutionPolicy() { + CoreDriverOption classOption = CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS; + return Reflection.buildFromConfig(this, classOption, SpeculativeExecutionPolicy.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing speculative execution policy, check your configuration (%s)", + classOption))); + } + protected AddressTranslator buildAddressTranslator() { CoreDriverOption classOption = CoreDriverOption.ADDRESS_TRANSLATOR_CLASS; return Reflection.buildFromConfig(this, classOption, AddressTranslator.class) @@ -175,7 +211,7 @@ protected NettyOptions buildNettyOptions() { } protected Optional buildSslHandlerFactory() { - // If a JDK-based factory was provided through the public API, wrap it + // If a JDK-based factory was provided through the public API, syncWrapper it return buildSslEngineFactory().map(JdkSslHandlerFactory::new); // For more advanced options (like using Netty's native OpenSSL support instead of the JDK), @@ -206,6 +242,11 @@ protected ControlConnection buildControlConnection() { return new ControlConnection(this); } + protected CodecRegistry buildCodecRegistry(List> codecs) { + TypeCodec[] array = new TypeCodec[codecs.size()]; + return new DefaultCodecRegistry(codecs.toArray(array)); + } + @Override public DriverConfig config() { return config; @@ -221,6 +262,16 @@ public ReconnectionPolicy reconnectionPolicy() { return reconnectionPolicyRef.get(); } + @Override + public RetryPolicy retryPolicy() { + return retryPolicyRef.get(); + } + + @Override + public SpeculativeExecutionPolicy speculativeExecutionPolicy() { + return speculativeExecutionPolicyRef.get(); + } + @Override public AddressTranslator addressTranslator() { return addressTranslatorRef.get(); @@ -300,4 +351,19 @@ public LoadBalancingPolicyWrapper loadBalancingPolicyWrapper() { public ControlConnection controlConnection() { return controlConnectionRef.get(); } + + @Override + public RequestProcessorRegistry requestProcessorRegistry() { + return RequestProcessorRegistry.DEFAULT; + } + + @Override + public CodecRegistry codecRegistry() { + return codecRegistry; + } + + @Override + public ProtocolVersion protocolVersion() { + return channelFactory().protocolVersion(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 51851d343d4..13b65928250 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.context; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; @@ -24,6 +25,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; @@ -58,4 +60,6 @@ public interface InternalDriverContext extends DriverContext { LoadBalancingPolicyWrapper loadBalancingPolicyWrapper(); ControlConnection controlConnection(); + + RequestProcessorRegistry requestProcessorRegistry(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java new file mode 100644 index 00000000000..67c9130f3f0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.auth.AuthenticationException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.WriteType; +import com.datastax.oss.driver.api.core.servererrors.AlreadyExistsException; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; +import com.datastax.oss.driver.api.core.servererrors.InvalidConfigurationInQueryException; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.servererrors.OverloadedException; +import com.datastax.oss.driver.api.core.servererrors.ProtocolError; +import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.servererrors.SyntaxError; +import com.datastax.oss.driver.api.core.servererrors.TruncateException; +import com.datastax.oss.driver.api.core.servererrors.UnauthorizedException; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; +import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.request.query.QueryOptions; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.Result; +import com.datastax.oss.protocol.internal.response.error.AlreadyExists; +import com.datastax.oss.protocol.internal.response.error.ReadFailure; +import com.datastax.oss.protocol.internal.response.error.ReadTimeout; +import com.datastax.oss.protocol.internal.response.error.Unavailable; +import com.datastax.oss.protocol.internal.response.error.WriteFailure; +import com.datastax.oss.protocol.internal.response.error.WriteTimeout; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.Prepared; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Utility methods to convert to/from protocol messages. + * + *

The main goal of this class is to move this code out of the request handlers. + */ +class Conversions { + + static Message toMessage( + Statement statement, DriverConfigProfile config, InternalDriverContext context) { + if (statement instanceof SimpleStatement) { + SimpleStatement simpleStatement = (SimpleStatement) statement; + + CodecRegistry codecRegistry = context.codecRegistry(); + List values = simpleStatement.getValues(); + List encodedValues = new ArrayList<>(values.size()); + for (Object value : values) { + encodedValues.add(codecRegistry.codecFor(value).encode(value, context.protocolVersion())); + } + int consistency = + config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); + boolean skipMetadata = false; // TODO set for bound statements + int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); + ByteBuffer pagingState = null; // TODO paging + int serialConsistency = + config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); + long timestamp = Long.MIN_VALUE; // TODO timestamp generator + QueryOptions queryOptions = + new QueryOptions( + consistency, + encodedValues, + Collections.emptyMap(), + skipMetadata, + pageSize, + pagingState, + serialConsistency, + timestamp); + return new Query(simpleStatement.getQuery(), queryOptions); + } + // TODO handle other types of statements + throw new UnsupportedOperationException("TODO handle other types of statements"); + } + + static AsyncResultSet toResultSet( + Result result, ExecutionInfo executionInfo, InternalDriverContext context) { + if (result instanceof Rows) { + Rows rows = (Rows) result; + ImmutableList.Builder definitions = ImmutableList.builder(); + for (ColumnSpec columnSpec : rows.metadata.columnSpecs) { + definitions.add(new DefaultColumnDefinition(columnSpec, context)); + } + return new DefaultAsyncResultSet( + new DefaultColumnDefinitions(definitions.build()), executionInfo, rows.data, context); + } else if (result instanceof Prepared) { + // This should never happen + throw new IllegalArgumentException("Unexpected PREPARED response to a CQL query"); + } else { + // Void, SetKeyspace, SchemaChange + return DefaultAsyncResultSet.empty(executionInfo); + } + } + + static Throwable toThrowable(Node node, Error errorMessage) { + switch (errorMessage.code) { + case ProtocolConstants.ErrorCode.UNPREPARED: + throw new AssertionError( + "UNPREPARED should be handled as a special case, not turned into an exception"); + case ProtocolConstants.ErrorCode.SERVER_ERROR: + return new ServerError(node, errorMessage.message); + case ProtocolConstants.ErrorCode.PROTOCOL_ERROR: + return new ProtocolError(node, errorMessage.message); + case ProtocolConstants.ErrorCode.AUTH_ERROR: + // This method is used for query execution, authentication errors are unlikely to happen at + // this stage (more during connection init), but there's no harm in handling them anyway + return new AuthenticationException(node.getConnectAddress(), errorMessage.message); + case ProtocolConstants.ErrorCode.UNAVAILABLE: + Unavailable unavailable = (Unavailable) errorMessage; + return new UnavailableException( + node, + ConsistencyLevel.fromCode(unavailable.consistencyLevel), + unavailable.required, + unavailable.alive); + case ProtocolConstants.ErrorCode.OVERLOADED: + return new OverloadedException(node); + case ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING: + return new BootstrappingException(node); + case ProtocolConstants.ErrorCode.TRUNCATE_ERROR: + return new TruncateException(node, errorMessage.message); + case ProtocolConstants.ErrorCode.WRITE_TIMEOUT: + WriteTimeout writeTimeout = (WriteTimeout) errorMessage; + return new WriteTimeoutException( + node, + ConsistencyLevel.fromCode(writeTimeout.consistencyLevel), + writeTimeout.received, + writeTimeout.blockFor, + WriteType.valueOf(writeTimeout.writeType)); + case ProtocolConstants.ErrorCode.READ_TIMEOUT: + ReadTimeout readTimeout = (ReadTimeout) errorMessage; + return new ReadTimeoutException( + node, + ConsistencyLevel.fromCode(readTimeout.consistencyLevel), + readTimeout.received, + readTimeout.blockFor, + readTimeout.dataPresent); + case ProtocolConstants.ErrorCode.READ_FAILURE: + ReadFailure readFailure = (ReadFailure) errorMessage; + return new ReadFailureException( + node, + ConsistencyLevel.fromCode(readFailure.consistencyLevel), + readFailure.received, + readFailure.blockFor, + readFailure.numFailures, + readFailure.dataPresent, + readFailure.reasonMap); + case ProtocolConstants.ErrorCode.FUNCTION_FAILURE: + return new FunctionFailureException(node, errorMessage.message); + case ProtocolConstants.ErrorCode.WRITE_FAILURE: + WriteFailure writeFailure = (WriteFailure) errorMessage; + return new WriteFailureException( + node, + ConsistencyLevel.fromCode(writeFailure.consistencyLevel), + writeFailure.received, + writeFailure.blockFor, + WriteType.valueOf(writeFailure.writeType), + writeFailure.numFailures, + writeFailure.reasonMap); + case ProtocolConstants.ErrorCode.SYNTAX_ERROR: + return new SyntaxError(node, errorMessage.message); + case ProtocolConstants.ErrorCode.UNAUTHORIZED: + return new UnauthorizedException(node, errorMessage.message); + case ProtocolConstants.ErrorCode.INVALID: + return new InvalidQueryException(node, errorMessage.message); + case ProtocolConstants.ErrorCode.CONFIG_ERROR: + return new InvalidConfigurationInQueryException(node, errorMessage.message); + case ProtocolConstants.ErrorCode.ALREADY_EXISTS: + AlreadyExists alreadyExists = (AlreadyExists) errorMessage; + return new AlreadyExistsException(node, alreadyExists.keyspace, alreadyExists.table); + default: + return new ProtocolError(node, "Unknown error code: " + errorMessage.code); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java new file mode 100644 index 00000000000..56e3de9fda3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; +import com.datastax.oss.driver.api.core.servererrors.ProtocolError; +import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.Result; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.datastax.oss.protocol.internal.response.result.Void; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.ScheduledFuture; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Handles execution of a {@link Statement}. */ +public class CqlRequestHandler + extends RequestHandlerBase> { + + private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandler.class); + + private final CompletableFuture result; + private final Message message; + private final EventExecutor scheduler; + // How many speculative executions are currently running (not counting the initial execution). + // All executions share the same query plan, they stop either when the request completes or the + // query plan is empty. + private final AtomicInteger executions; + private final ScheduledFuture timeoutFuture; + private final List> pendingExecutions; + private final RetryPolicy retryPolicy; + private final SpeculativeExecutionPolicy speculativeExecutionPolicy; + + // The errors on the nodes that were already tried (lazily initialized on the first error). + // We don't use a map because nodes can appear multiple times. + private volatile List> errors; + + CqlRequestHandler( + Statement statement, Map pools, InternalDriverContext context) { + super(statement, pools, context); + this.result = new CompletableFuture<>(); + this.result.exceptionally( + t -> { + try { + if (t instanceof CancellationException) { + cancelScheduledTasks(); + } + } catch (Throwable t2) { + LOG.warn("Uncaught exception", t2); + } + return null; + }); + this.message = Conversions.toMessage(statement, configProfile, context); + this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); + + Duration timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeoutFuture = scheduleTimeout(timeout); + + this.retryPolicy = context.retryPolicy(); + this.speculativeExecutionPolicy = context.speculativeExecutionPolicy(); + this.executions = new AtomicInteger(0); + + // Start the initial execution + CqlIdentifier keyspace = null; // TODO pull keyspace from session + long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); + if (nextExecution > 0) { + LOG.trace("Scheduling first speculative execution in {} ms", nextExecution); + this.pendingExecutions = new CopyOnWriteArrayList<>(); + this.pendingExecutions.add( + scheduler.schedule(this::startExecution, nextExecution, TimeUnit.MILLISECONDS)); + } else { + LOG.trace("Speculative execution policy returned {}, no next execution", nextExecution); + this.pendingExecutions = null; // we'll never need this so avoid allocation + } + sendRequest(null, 0, 0); + } + + @Override + public CompletionStage asyncResult() { + return result; + } + + @Override + public ResultSet syncResult() { + BlockingOperation.checkNotDriverThread(); + AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(asyncResult()); + return new DefaultResultSet(asyncResultSet); + } + + private ScheduledFuture scheduleTimeout(Duration timeout) { + if (timeout.toNanos() > 0) { + return scheduler.schedule( + () -> setFinalError(new DriverTimeoutException("Query timed out after " + timeout)), + timeout.toNanos(), + TimeUnit.NANOSECONDS); + } else { + return null; + } + } + + private void startExecution() { + if (!result.isDone()) { + int execution = executions.incrementAndGet(); + LOG.trace("Starting speculative execution {}", execution); + CqlIdentifier keyspace = null; // TODO pull keyspace from session + long nextDelay = speculativeExecutionPolicy.nextExecution(keyspace, request, execution + 1); + if (nextDelay > 0) { + LOG.trace("Scheduling {}th speculative execution in {} ms", execution + 1, nextDelay); + this.pendingExecutions.add( + scheduler.schedule(this::startExecution, nextDelay, TimeUnit.MILLISECONDS)); + } else { + LOG.trace("Speculative execution policy returned {}, no next execution", nextDelay); + } + sendRequest(null, execution, 0); + } + } + + /** + * Sends the request to the next available node. + * + * @param node if not null, it will be attempted first before the rest of the query plan. + */ + private void sendRequest(Node node, int execution, int retryCount) { + if (result.isDone()) { + return; + } + DriverChannel channel = null; + if (node == null || (channel = getChannel(node)) == null) { + while (!result.isDone() && (node = queryPlan.poll()) != null) { + channel = getChannel(node); + if (channel != null) { + break; + } + } + } + if (channel == null) { + // We've reached the end of the query plan without finding any node to write to + if (!result.isDone() && executions.decrementAndGet() == 0) { + // We're the last execution so fail the result + setFinalError(AllNodesFailedException.fromErrors(this.errors)); + } + } else { + NodeResponseCallback nodeResponseCallback = + new NodeResponseCallback(node, execution, retryCount); + channel + .write(message, false, Frame.NO_PAYLOAD, nodeResponseCallback) + .addListener(nodeResponseCallback); + } + } + + private DriverChannel getChannel(Node node) { + ChannelPool pool = pools.get(node); + if (pool == null) { + LOG.trace("No pool to {}, skipping", node); + return null; + } else { + DriverChannel channel = pool.next(); + if (channel == null) { + LOG.trace("Pool returned no channel for {}, skipping", node); + return null; + } else if (channel.closeFuture().isDone()) { + LOG.trace("Pool returned closed connection to {}, skipping", node); + return null; + } else { + return channel; + } + } + } + + private void recordError(Node node, Throwable error) { + // Use a local variable to do only a single single volatile read in the nominal case + List> errorsSnapshot = this.errors; + if (errorsSnapshot == null) { + synchronized (CqlRequestHandler.this) { + errorsSnapshot = this.errors; + if (errorsSnapshot == null) { + this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); + } + } + } + errorsSnapshot.add(new AbstractMap.SimpleEntry<>(node, error)); + } + + private void cancelScheduledTasks() { + if (this.timeoutFuture != null) { + this.timeoutFuture.cancel(false); + } + List> pendingExecutionsSnapshot = this.pendingExecutions; + if (pendingExecutionsSnapshot != null) { + for (ScheduledFuture future : pendingExecutionsSnapshot) { + future.cancel(false); + } + } + } + + private void setFinalResult( + Result resultMessage, Frame responseFrame, NodeResponseCallback callback) { + try { + ExecutionInfo executionInfo = buildExecutionInfo(callback, resultMessage, responseFrame); + AsyncResultSet resultSet = Conversions.toResultSet(resultMessage, executionInfo, context); + if (result.complete(resultSet)) { + cancelScheduledTasks(); + if (resultMessage instanceof SetKeyspace) { + CqlIdentifier keyspace = + CqlIdentifier.fromInternal(((SetKeyspace) resultMessage).keyspace); + // TODO set keyspace on the session + throw new UnsupportedOperationException("TODO set keyspace on session after USE query"); + } + } + } catch (Throwable error) { + setFinalError(error); + } + } + + private ExecutionInfo buildExecutionInfo( + NodeResponseCallback callback, Result resultMessage, Frame responseFrame) { + ByteBuffer pagingState = + (resultMessage instanceof Rows) ? ((Rows) resultMessage).metadata.pagingState : null; + return new DefaultExecutionInfo( + callback.node, callback.execution, executions.get(), errors, pagingState, responseFrame); + } + + private void setFinalError(Throwable error) { + if (result.completeExceptionally(error)) { + cancelScheduledTasks(); + } + } + + /** + * Handles the interaction with a single node in the query plan. + * + *

An instance of this class is created each time we (re)try a node. + */ + private class NodeResponseCallback + implements ResponseCallback, GenericFutureListener> { + + private final Node node; + // The identifier of the current execution (0 for the initial execution, 1 for the first + // speculative execution, etc.) + private final int execution; + // How many times we've invoked the retry policy and it has returned a "retry" decision (0 for + // the first attempt of each execution). + private final int retryCount; + + private NodeResponseCallback(Node node, int execution, int retryCount) { + this.node = node; + this.execution = execution; + this.retryCount = retryCount; + } + + // this gets invoked once the write completes. + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + recordError(node, future.cause()); + sendRequest(null, execution, retryCount); // try next host + } + } + + @Override + public void onResponse(Frame responseFrame) { + if (result.isDone()) { + return; + } + try { + Message responseMessage = responseFrame.message; + if (responseMessage instanceof SetKeyspace) { + // TODO schema agreement, and chain setFinalResult to the result + setFinalError(new UnsupportedOperationException("TODO handle schema agreement")); + } else if (responseMessage instanceof Result) { + setFinalResult((Result) responseMessage, responseFrame, this); + } else if (responseMessage instanceof Error) { + processErrorResponse((Error) responseMessage); + } else { + result.completeExceptionally( + new IllegalStateException("Unexpected response " + responseMessage)); + } + } catch (Throwable t) { + setFinalError(t); + } + } + + private void processErrorResponse(Error errorMessage) { + if (errorMessage.code == ProtocolConstants.ErrorCode.UNPREPARED) { + // TODO reprepare on the fly + throw new UnsupportedOperationException("TODO handle prepare-and-retry"); + } + Throwable error = Conversions.toThrowable(node, errorMessage); + if (error instanceof BootstrappingException) { + // Do not call the retry policy, always try the next node + recordError(node, error); + sendRequest(null, execution, retryCount); + } else if (error instanceof QueryValidationException + || error instanceof FunctionFailureException + || error instanceof ProtocolError) { + // Do not call the retry policy, always rethrow + setFinalError(error); + } else { + RetryDecision decision; + if (error instanceof ReadTimeoutException) { + ReadTimeoutException readTimeout = (ReadTimeoutException) error; + decision = + retryPolicy.onReadTimeout( + request, + readTimeout.getConsistencyLevel(), + readTimeout.getBlockFor(), + readTimeout.getReceived(), + readTimeout.wasDataPresent(), + retryCount); + } else if (error instanceof WriteTimeoutException) { + WriteTimeoutException writeTimeout = (WriteTimeoutException) error; + decision = + retryPolicy.onWriteTimeout( + request, + writeTimeout.getConsistencyLevel(), + writeTimeout.getWriteType(), + writeTimeout.getBlockFor(), + writeTimeout.getReceived(), + retryCount); + } else if (error instanceof UnavailableException) { + UnavailableException unavailable = (UnavailableException) error; + decision = + retryPolicy.onUnavailable( + request, + unavailable.getConsistencyLevel(), + unavailable.getRequired(), + unavailable.getAlive(), + retryCount); + } else { + decision = retryPolicy.onErrorResponse(request, error, retryCount); + } + processRetryDecision(decision, error); + } + } + + private void processRetryDecision(RetryDecision decision, Throwable error) { + switch (decision) { + case RETRY_SAME: + recordError(node, error); + sendRequest(node, execution, retryCount + 1); + break; + case RETRY_NEXT: + recordError(node, error); + sendRequest(null, execution, retryCount + 1); + break; + case RETHROW: + setFinalError(error); + break; + case IGNORE: + setFinalResult(Void.INSTANCE, null, this); + break; + } + } + + @Override + public void onFailure(Throwable error) { + if (result.isDone()) { + return; + } + RetryDecision decision = retryPolicy.onRequestAborted(request, error, retryCount); + processRetryDecision(decision, error); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java new file mode 100644 index 00000000000..ccb11bc53c6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentMap; + +public class CqlRequestProcessor + implements RequestProcessor> { + + @Override + public > boolean canProcess(T request) { + return request instanceof Statement; + } + + @Override + public RequestHandler> newHandler( + Request> request, + Map pools, + InternalDriverContext context) { + return new CqlRequestHandler((Statement) request, pools, context); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java new file mode 100644 index 00000000000..29c7bfbedd9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.AbstractIterator; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CompletionStage; + +public class DefaultAsyncResultSet implements AsyncResultSet { + + private final ColumnDefinitions definitions; + private final ExecutionInfo executionInfo; + private final Queue> data; + private final InternalDriverContext context; + + public DefaultAsyncResultSet( + ColumnDefinitions definitions, + ExecutionInfo executionInfo, + Queue> data, + InternalDriverContext context) { + this.definitions = definitions; + this.executionInfo = executionInfo; + this.data = data; + this.context = context; + } + + @Override + public ColumnDefinitions getColumnDefinitions() { + return definitions; + } + + @Override + public ExecutionInfo getExecutionInfo() { + return executionInfo; + } + + @Override + public Iterator iterator() { + return new AbstractIterator() { + @Override + protected Row computeNext() { + List rowData = data.poll(); + return (rowData == null) ? endOfData() : new DefaultRow(definitions, rowData, context); + } + }; + } + + @Override + public boolean hasMorePages() { + throw new UnsupportedOperationException("TODO implement paging"); + } + + @Override + public CompletionStage fetchNextPage() throws IllegalStateException { + throw new UnsupportedOperationException("TODO implement paging"); + } + + static AsyncResultSet empty(final ExecutionInfo executionInfo) { + return new AsyncResultSet() { + @Override + public ColumnDefinitions getColumnDefinitions() { + return DefaultColumnDefinitions.EMPTY; + } + + @Override + public ExecutionInfo getExecutionInfo() { + return executionInfo; + } + + @Override + public boolean hasMorePages() { + return false; + } + + @Override + public CompletionStage fetchNextPage() throws IllegalStateException { + throw new IllegalStateException("Empty result set has no next page"); + } + + @Override + public Iterator iterator() { + return Collections.emptyList().iterator(); + } + }; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java new file mode 100644 index 00000000000..a6eac502837 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.internal.type.DataTypeHelper; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; + +public class DefaultColumnDefinition implements ColumnDefinition { + + private static final long serialVersionUID = 1; + + /** @serial */ + private final CqlIdentifier keyspace; + /** @serial */ + private final CqlIdentifier table; + /** @serial */ + private final CqlIdentifier name; + /** @serial */ + private final DataType type; + + /** @param spec the raw data decoded by the protocol layer */ + public DefaultColumnDefinition(ColumnSpec spec, AttachmentPoint attachmentPoint) { + this.keyspace = CqlIdentifier.fromInternal(spec.ksName); + this.table = CqlIdentifier.fromInternal(spec.tableName); + this.name = CqlIdentifier.fromInternal(spec.name); + this.type = DataTypeHelper.fromProtocolSpec(spec.type, attachmentPoint); + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getTable() { + return table; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public DataType getType() { + return type; + } + + @Override + public boolean isDetached() { + return type.isDetached(); + } + + @Override + public void attach(AttachmentPoint attachmentPoint) { + type.attach(attachmentPoint); + } + + @Override + public String toString() { + return keyspace.asPrettyCql() + + "." + + table.asPrettyCql() + + "." + + name.asPrettyCql() + + " " + + type; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java new file mode 100644 index 00000000000..0327ebb9469 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.internal.core.data.IdentifierIndex; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class DefaultColumnDefinitions implements ColumnDefinitions { + + private final List definitions; + private final IdentifierIndex index; + + public DefaultColumnDefinitions(List definitions) { + assert definitions != null && definitions.size() > 0; + this.definitions = definitions; + this.index = buildIndex(definitions); + } + + @Override + public int size() { + return definitions.size(); + } + + @Override + public ColumnDefinition get(int i) { + return definitions.get(i); + } + + @Override + public Iterator iterator() { + return definitions.iterator(); + } + + @Override + public boolean contains(String name) { + return index.firstIndexOf(name) >= 0; + } + + @Override + public boolean contains(CqlIdentifier id) { + return index.firstIndexOf(id) >= 0; + } + + @Override + public int firstIndexOf(String name) { + return index.firstIndexOf(name); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return index.firstIndexOf(id); + } + + @Override + public boolean isDetached() { + return definitions.get(0).isDetached(); + } + + @Override + public void attach(AttachmentPoint attachmentPoint) { + for (ColumnDefinition definition : definitions) { + definition.attach(attachmentPoint); + } + } + + private static IdentifierIndex buildIndex(List definitions) { + List identifiers = new ArrayList<>(definitions.size()); + for (ColumnDefinition definition : definitions) { + identifiers.add(definition.getName()); + } + return new IdentifierIndex(identifiers); + } + + /** + * @serialData The list of definitions (the identifier index is reconstructed at deserialization). + */ + private Object writeReplace() { + return new SerializationProxy(this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + // Should never be called since we serialized a proxy + throw new InvalidObjectException("Proxy required"); + } + + private static class SerializationProxy implements Serializable { + + private static final long serialVersionUID = 1; + + private final List definitions; + + private SerializationProxy(DefaultColumnDefinitions columnDefinitions) { + this.definitions = columnDefinitions.definitions; + } + + private Object readResolve() { + return new DefaultColumnDefinitions(this.definitions); + } + } + + static final ColumnDefinitions EMPTY = + new ColumnDefinitions() { + @Override + public int size() { + return 0; + } + + @Override + public ColumnDefinition get(int i) { + throw new ArrayIndexOutOfBoundsException(); + } + + @Override + public boolean contains(String name) { + return false; + } + + @Override + public boolean contains(CqlIdentifier id) { + return false; + } + + @Override + public int firstIndexOf(String name) { + return -1; + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return -1; + } + + @Override + public boolean isDetached() { + return false; + } + + @Override + public void attach(AttachmentPoint attachmentPoint) {} + + @Override + public Iterator iterator() { + return Collections.emptyList().iterator(); + } + }; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java new file mode 100644 index 00000000000..f60d25140cb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.protocol.internal.Frame; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class DefaultExecutionInfo implements ExecutionInfo { + + private final Node coordinator; + private final int speculativeExecutionCount; + private final int successfulExecutionIndex; + private final List> errors; + private final ByteBuffer pagingState; + private final UUID tracingId; + private final List warnings; + private final Map customPayload; + + public DefaultExecutionInfo( + Node coordinator, + int speculativeExecutionCount, + int successfulExecutionIndex, + List> errors, + ByteBuffer pagingState, + Frame frame) { + this.coordinator = coordinator; + this.speculativeExecutionCount = speculativeExecutionCount; + this.successfulExecutionIndex = successfulExecutionIndex; + this.errors = errors; + this.pagingState = pagingState; + + this.tracingId = (frame == null) ? null : frame.tracingId; + // Note: the collections returned by the protocol layer are already unmodifiable + this.warnings = (frame == null) ? Collections.emptyList() : frame.warnings; + this.customPayload = (frame == null) ? Collections.emptyMap() : frame.customPayload; + } + + @Override + public Node getCoordinator() { + return coordinator; + } + + @Override + public int getSpeculativeExecutionCount() { + return speculativeExecutionCount; + } + + @Override + public int getSuccessfulExecutionIndex() { + return successfulExecutionIndex; + } + + @Override + public List> getErrors() { + // Assume this method will be called 0 or 1 time, so we create the unmodifiable wrapper on + // demand. + return (errors == null) ? Collections.emptyList() : Collections.unmodifiableList(errors); + } + + @Override + public ByteBuffer getPagingState() { + return pagingState; + } + + @Override + public List getWarnings() { + return warnings; + } + + @Override + public Map getIncomingPayload() { + return customPayload; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java new file mode 100644 index 00000000000..8293a41372b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ResultSet; + +public class DefaultResultSet implements ResultSet { + public DefaultResultSet(AsyncResultSet asyncResultSet) {} +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java new file mode 100644 index 00000000000..ccd35fc6225 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class DefaultRow implements Row { + + private final ColumnDefinitions definitions; + private final List data; + private transient volatile AttachmentPoint attachmentPoint; + + public DefaultRow( + ColumnDefinitions definitions, List data, AttachmentPoint attachmentPoint) { + this.definitions = definitions; + this.data = data; + this.attachmentPoint = attachmentPoint; + } + + public DefaultRow(ColumnDefinitions definitions, List data) { + this(definitions, data, AttachmentPoint.NONE); + } + + @Override + public int size() { + return definitions.size(); + } + + @Override + public DataType getType(int i) { + return definitions.get(i).getType(); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return definitions.firstIndexOf(id); + } + + @Override + public DataType getType(CqlIdentifier id) { + return definitions.get(firstIndexOf(id)).getType(); + } + + @Override + public int firstIndexOf(String name) { + return definitions.firstIndexOf(name); + } + + @Override + public DataType getType(String name) { + return definitions.get(firstIndexOf(name)).getType(); + } + + @Override + public CodecRegistry codecRegistry() { + return attachmentPoint.codecRegistry(); + } + + @Override + public ProtocolVersion protocolVersion() { + return attachmentPoint.protocolVersion(); + } + + @Override + public boolean isDetached() { + return attachmentPoint != AttachmentPoint.NONE; + } + + @Override + public void attach(AttachmentPoint attachmentPoint) { + this.attachmentPoint = attachmentPoint; + this.definitions.attach(attachmentPoint); + } + + @Override + public ByteBuffer getBytesUnsafe(int i) { + return data.get(i); + } + /** + * @serialData The column definitions, followed by an array of byte arrays representing the column + * values (null values are represented by {@code null}). + */ + private Object writeReplace() { + return new SerializationProxy(this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + // Should never be called since we serialized a proxy + throw new InvalidObjectException("Proxy required"); + } + + private static class SerializationProxy implements Serializable { + + private static final long serialVersionUID = 1; + + private final ColumnDefinitions definitions; + private final byte[][] values; + + SerializationProxy(DefaultRow row) { + this.definitions = row.definitions; + this.values = new byte[row.data.size()][]; + int i = 0; + for (ByteBuffer buffer : row.data) { + this.values[i] = (buffer == null) ? null : Bytes.getArray(buffer); + i += 1; + } + } + + private Object readResolve() { + List data = new ArrayList<>(this.values.length); + for (byte[] value : this.values) { + data.add((value == null) ? null : ByteBuffer.wrap(value)); + } + return new DefaultRow(this.definitions, data); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java new file mode 100644 index 00000000000..84ef79fe034 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import java.util.List; + +public class DefaultSimpleStatement implements SimpleStatement { + + private final String query; + private final List values; + private final String configProfile; + + public DefaultSimpleStatement(String query, List values, String configProfile) { + this.query = query; + this.values = values; + this.configProfile = configProfile; + } + + @Override + public String getQuery() { + return query; + } + + @Override + public List getValues() { + return values; + } + + @Override + public String getConfigProfile() { + return configProfile; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 67c98cd18aa..612687cc02d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -124,7 +124,7 @@ public void resize(NodeDistance newDistance) { /** * Changes the keyspace name on all the channels in this pool. * - *

Note that this is not called directly by the user, but happens only on a SetKeypsace + *

Note that this is not called directly by the user, but happens only on a SetKeyspace * response after a successful "USE ..." query, so the name should be valid. If the keyspace * switch fails on any channel, that channel is closed and a reconnection is started. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 0a5d75a0e54..1d6788a9b05 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -71,8 +73,11 @@ public static CompletionStage init( return new DefaultSession(context, keyspace).init(); } + private final InternalDriverContext context; + private final DriverConfig config; private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; + private final RequestProcessorRegistry processorRegistry; @VisibleForTesting final ConcurrentMap pools = @@ -84,7 +89,10 @@ public static CompletionStage init( private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.context = context; + this.config = context.config(); this.singleThreaded = new SingleThreaded(context, keyspace); + this.processorRegistry = context.requestProcessorRegistry(); } private CompletionStage init() { @@ -95,13 +103,18 @@ private CompletionStage init() { @Override public SyncResultT execute( Request request) { - throw new UnsupportedOperationException("TODO"); + return newHandler(request).syncResult(); } @Override public AsyncResultT executeAsync( Request request) { - throw new UnsupportedOperationException("TODO"); + return newHandler(request).asyncResult(); + } + + private RequestHandler newHandler( + Request request) { + return processorRegistry.processorFor(request).newHandler(request, pools, context); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java new file mode 100644 index 00000000000..d5f1b55fe94 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +/** Manages the execution of a given request. */ +public interface RequestHandler { + /** + * Immediately returns an object that represents the pending request; that object will complete + * when the request is done. + */ + AsyncResultT asyncResult(); + + /** Blocks until the request is done, and returns an object representing the completed result. */ + SyncResultT syncResult(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java new file mode 100644 index 00000000000..80c6551fa8c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import java.util.Map; +import java.util.Queue; + +/** Factors code that should be common to most request handler implementations. */ +public abstract class RequestHandlerBase + implements RequestHandler { + + protected final Request request; + protected final Map pools; + protected final InternalDriverContext context; + protected final Queue queryPlan; + protected final DriverConfigProfile configProfile; + + protected RequestHandlerBase( + Request request, + Map pools, + InternalDriverContext context) { + this.request = request; + this.pools = pools; + this.context = context; + this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); + + DriverConfig config = context.config(); + String profileName = request.getConfigProfile(); + configProfile = + (profileName == null || profileName.isEmpty()) + ? config.defaultProfile() + : config.getProfile(profileName); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java new file mode 100644 index 00000000000..b0d169cbd26 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import java.util.Map; + +/** + * Handles a type of request in the driver. + * + *

By default, the driver supports CQL {@link Statement queries} and {@link PrepareRequest + * preparation requests}. New processors can be plugged in to handle new types of requests. + * + * @param the type of result when a request is executed synchronously. + * @param the type of result when a request is executed asynchronously. + */ +public interface RequestProcessor { + + /** + * Whether the processor can handle a given request. + * + *

Processors will be tried in the order they were registered. The first processor for which + * this method returns true will be used. + */ + > boolean canProcess(T request); + + /** Builds a new handler to process a given request. */ + RequestHandler newHandler( + Request request, + Map pools, + InternalDriverContext context); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java new file mode 100644 index 00000000000..166accf960f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.cql.CqlRequestProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RequestProcessorRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); + + public static final RequestProcessorRegistry DEFAULT = + new RequestProcessorRegistry( + new CqlRequestProcessor() + // TODO add CqlPrepareProcessor + ); + + // Effectively immutable: the contents are never modified after construction + private final RequestProcessor[] processors; + + public RequestProcessorRegistry(RequestProcessor... processors) { + this.processors = processors; + } + + public RequestProcessor processorFor( + Request request) { + + for (RequestProcessor processor : processors) { + if (processor.canProcess(request)) { + LOG.trace("Using {} to process {}", processor, request); + // The cast is safe provided that the processor implements canProcess correctly + @SuppressWarnings("unchecked") + RequestProcessor result = + (RequestProcessor) processor; + return result; + } else { + LOG.trace("{} cannot process {}, trying next", processor, request); + } + } + throw new IllegalArgumentException("No request processor found for " + request); + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 6459ac5097b..a363f43f8eb 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -38,10 +38,18 @@ datastax-java-driver { // version = V4 } + retry { + policy-class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy + } + load-balancing { policy-class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy } + speculative-execution { + policy-class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy + } + connection { # The timeout to use for internal queries that run as part of the initialization process, just # after we open a connection. If this timeout fires, the initialization of the connection will @@ -89,6 +97,27 @@ datastax-java-driver { } } + request { + # How long the driver waits for a request to complete. This is a global limit on the duration + # of a session.execute() call, including any internal retries the driver might do. + timeout = 500 milliseconds + + # The consistency level. + consistency = ONE + + # The page size. This controls how many rows will be retrieved simultaneously in a single + # network roundtrip (the goal being to avoid loading too many results in memory at the same + # time). If there are more results, additional requests will be used to retrieve them (either + # automatically if you iterate with the sync API, or explicitly with the async API's + # fetchNextPage method). + # If the value is 0 or negative, it will be ignored and the request will not be paged. + page-size = 5000 + + # The serial consistency level. + # The allowed values are SERIAL and LOCAL_SERIAL. + serial-consistency = SERIAL + } + # The driver maintains a connection pool to each node, according to the distance assigned to it # by the load balancing policy. If the distance is IGNORED, no connections are maintained. pooling { diff --git a/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java new file mode 100644 index 00000000000..0bbb21d14f3 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver; + +public class TestDataProviders { + + public static Object[][] fromList(Object... l) { + Object[][] result = new Object[l.length][]; + for (int i = 0; i < l.length; i++) { + result[i] = new Object[1]; + result[i][0] = l[i]; + } + return result; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/TestInterceptor.java b/core/src/test/java/com/datastax/oss/driver/TestInterceptor.java new file mode 100644 index 00000000000..392e5ce35ff --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/TestInterceptor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver; + +import org.testng.IHookCallBack; +import org.testng.IHookable; +import org.testng.ITestResult; + +/** + * Intercepts the execution of each test, in order to perform additional tests. + * + * @see "src/test/resources/META-INF/services/org.testng.ITestNGListener" + */ +public class TestInterceptor implements IHookable { + + @Override + public void run(IHookCallBack callback, ITestResult result) { + + // If a test interrupts the main thread silently, this can make later tests fail. Instead, we + // fail the test and clear the interrupt status. + boolean wasInterrupted = Thread.currentThread().isInterrupted(); + + callback.runTestMethod(result); + + // Note: Thread.interrupted() also clears the flag, which is what we want. + if (!wasInterrupted && Thread.interrupted()) { + result.setStatus(ITestResult.FAILURE); + result.setThrowable(new IllegalStateException("The test interrupted the main thread")); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java new file mode 100644 index 00000000000..e6b4ea93cec --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.TestDataProviders; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.retry.WriteType; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.error.ReadTimeout; +import com.datastax.oss.protocol.internal.response.error.Unavailable; +import com.datastax.oss.protocol.internal.response.error.WriteTimeout; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { + + @Test + public void should_always_try_next_node_if_bootstrapping() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withResponse( + node1, + defaultFrameOf( + new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))) + .withResponse(node2, defaultFrameOf(singleRow())) + .build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.iterator(); + assertThat(rows.hasNext()).isTrue(); + assertThat(rows.next().getString("message")).isEqualTo("hello, world"); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node2); + assertThat(executionInfo.getErrors()).hasSize(1); + assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + assertThat(executionInfo.getErrors().get(0).getValue()) + .isInstanceOf(BootstrappingException.class); + assertThat(executionInfo.getIncomingPayload()).isEmpty(); + assertThat(executionInfo.getPagingState()).isNull(); + assertThat(executionInfo.getSpeculativeExecutionCount()).isEqualTo(0); + assertThat(executionInfo.getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(executionInfo.getWarnings()).isEmpty(); + + Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + }); + } + } + + @Test + public void should_always_rethrow_query_validation_error() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withResponse( + node1, + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.INVALID, "mock message"))) + .build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isFailed( + error -> { + assertThat(error) + .isInstanceOf(InvalidQueryException.class) + .hasMessage("mock message"); + Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + }); + } + } + + @Test(dataProvider = "failureScenarios") + public void should_try_next_node_if_retry_policy_decides_so(FailureScenario failureScenario) { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + failureScenario.mockRequestError(harnessBuilder, node1); + harnessBuilder.withResponse(node2, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + failureScenario.mockRetryPolicyDecision( + harness.getContext().retryPolicy(), RetryDecision.RETRY_NEXT); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.iterator(); + assertThat(rows.hasNext()).isTrue(); + assertThat(rows.next().getString("message")).isEqualTo("hello, world"); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node2); + assertThat(executionInfo.getErrors()).hasSize(1); + assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + }); + } + } + + @Test(dataProvider = "failureScenarios") + public void should_try_same_node_if_retry_policy_decides_so(FailureScenario failureScenario) { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + failureScenario.mockRequestError(harnessBuilder, node1); + harnessBuilder.withResponse(node1, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + failureScenario.mockRetryPolicyDecision( + harness.getContext().retryPolicy(), RetryDecision.RETRY_SAME); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.iterator(); + assertThat(rows.hasNext()).isTrue(); + assertThat(rows.next().getString("message")).isEqualTo("hello, world"); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node1); + assertThat(executionInfo.getErrors()).hasSize(1); + assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + }); + } + } + + @Test(dataProvider = "failureScenarios") + public void should_ignore_error_if_retry_policy_decides_so(FailureScenario failureScenario) { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + failureScenario.mockRequestError(harnessBuilder, node1); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + failureScenario.mockRetryPolicyDecision( + harness.getContext().retryPolicy(), RetryDecision.IGNORE); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.iterator(); + assertThat(rows.hasNext()).isFalse(); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node1); + assertThat(executionInfo.getErrors()).hasSize(0); + }); + } + } + + @Test(dataProvider = "failureScenarios") + public void should_rethrow_error_if_retry_policy_decides_so(FailureScenario failureScenario) { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + failureScenario.mockRequestError(harnessBuilder, node1); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + failureScenario.mockRetryPolicyDecision( + harness.getContext().retryPolicy(), RetryDecision.RETHROW); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isFailed( + error -> assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass)); + } + } + + /** + * Sets up the mocks to simulate an error from a node, and make the retry policy return a given + * decision for that error. + */ + private abstract static class FailureScenario { + private final Class expectedExceptionClass; + + protected FailureScenario(Class expectedExceptionClass) { + this.expectedExceptionClass = expectedExceptionClass; + } + + abstract void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node); + + abstract void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision); + } + + @DataProvider + public static Object[][] failureScenarios() { + return TestDataProviders.fromList( + new FailureScenario(ReadTimeoutException.class) { + @Override + public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { + builder.withResponse( + node, + defaultFrameOf( + new ReadTimeout( + "mock message", ProtocolConstants.ConsistencyLevel.LOCAL_ONE, 1, 2, true))); + } + + @Override + public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { + Mockito.when( + policy.onReadTimeout( + SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, 2, 1, true, 0)) + .thenReturn(decision); + } + }, + new FailureScenario(WriteTimeoutException.class) { + @Override + public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { + builder.withResponse( + node, + defaultFrameOf( + new WriteTimeout( + "mock message", + ProtocolConstants.ConsistencyLevel.LOCAL_ONE, + 1, + 2, + ProtocolConstants.WriteType.SIMPLE))); + } + + @Override + public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { + Mockito.when( + policy.onWriteTimeout( + SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, WriteType.SIMPLE, 2, 1, 0)) + .thenReturn(decision); + } + }, + new FailureScenario(UnavailableException.class) { + @Override + public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { + builder.withResponse( + node, + defaultFrameOf( + new Unavailable( + "mock message", ProtocolConstants.ConsistencyLevel.LOCAL_ONE, 2, 1))); + } + + @Override + public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { + Mockito.when( + policy.onUnavailable(SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, 2, 1, 0)) + .thenReturn(decision); + } + }, + new FailureScenario(ServerError.class) { + @Override + public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { + builder.withResponse( + node, + defaultFrameOf( + new Error(ProtocolConstants.ErrorCode.SERVER_ERROR, "mock server error"))); + } + + @Override + public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { + Mockito.when( + policy.onErrorResponse(eq(SIMPLE_STATEMENT), any(ServerError.class), eq(0))) + .thenReturn(decision); + } + }, + new FailureScenario(HeartbeatException.class) { + @Override + public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { + builder.withResponseFailure(node, Mockito.mock(HeartbeatException.class)); + } + + @Override + public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { + Mockito.when( + policy.onRequestAborted( + eq(SIMPLE_STATEMENT), any(HeartbeatException.class), eq(0))) + .thenReturn(decision); + } + }); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java new file mode 100644 index 00000000000..c22b9b262ff --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class CqlRequestHandlerSpeculativeExecutionTest extends CqlRequestHandlerTestBase { + + @Test + public void should_schedule_speculative_executions() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + long secondExecutionDelay = 200L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + .thenReturn(firstExecutionDelay); + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 2)) + .thenReturn(secondExecutionDelay); + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 3)) + .thenReturn(0L); + + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + node1Behavior.verifyWrite(); + + harness.nextScheduledTask(); // Discard the timeout task + + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + firstExecutionTask.run(); + node2Behavior.verifyWrite(); + + ScheduledTaskCapturingEventLoop.CapturedTask secondExecutionTask = + harness.nextScheduledTask(); + assertThat(secondExecutionTask.getDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(secondExecutionDelay); + secondExecutionTask.run(); + node3Behavior.verifyWrite(); + + // No more scheduled tasks since the policy returns 0 on the third call. + assertThat(harness.nextScheduledTask()).isNull(); + + // Note that we don't need to complete any response, the test is just about checking that + // executions are started. + } + } + + @Test + public void should_not_start_execution_if_result_complete() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + node1Behavior.verifyWrite(); + + harness.nextScheduledTask(); // Discard the timeout task + + // Check that the first execution was scheduled but don't run it yet + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + + // Complete the request from the initial execution + node1Behavior.setWriteSuccess(); + node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); + assertThat(resultSetFuture).isSuccess(); + + // The speculative execution should have been cancelled + assertThat(firstExecutionTask.isCancelled()).isTrue(); + + // Run the task anyway; we're bending reality a bit here since the task is already cancelled + // and wouldn't run, but this allows us to test the case where the completion races with the + // start of the task. + firstExecutionTask.run(); + node2Behavior.verifyNoWrite(); + } + } + + @Test + public void should_retry_in_speculative_executions() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + harnessBuilder.withResponse(node3, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + node1Behavior.verifyWrite(); + // do not simulate a response from node1. The request will stay hanging for the rest of this test + + harness.nextScheduledTask(); // Discard the timeout task + + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + firstExecutionTask.run(); + node2Behavior.verifyWrite(); + node2Behavior.setWriteSuccess(); + + // node2 replies with a response that triggers a RETRY_NEXT + node2Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + // The second execution should move to node3 and complete the request + assertThat(resultSetFuture).isSuccess(); + } + } + + @Test + public void should_stop_retrying_other_executions_if_result_complete() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + node1Behavior.verifyWrite(); + + harness.nextScheduledTask(); // Discard the timeout task + + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + firstExecutionTask.run(); + node2Behavior.verifyWrite(); + node2Behavior.setWriteSuccess(); + + // Complete the request from the initial execution + node1Behavior.setWriteSuccess(); + node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); + assertThat(resultSetFuture).isSuccess(); + + // node2 replies with a response that would trigger a RETRY_NEXT if the request was still running + node2Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + // The speculative execution should not move to node3 because it is stopped + node3Behavior.verifyNoWrite(); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java new file mode 100644 index 00000000000..9479ddc39ff --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import java.time.Duration; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class CqlRequestHandlerTest extends CqlRequestHandlerTestBase { + + @Test + public void should_complete_result_if_first_node_replies_immediately() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withResponse(node1, defaultFrameOf(singleRow())) + .build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.iterator(); + assertThat(rows.hasNext()).isTrue(); + assertThat(rows.next().getString("message")).isEqualTo("hello, world"); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node1); + assertThat(executionInfo.getErrors()).isEmpty(); + assertThat(executionInfo.getIncomingPayload()).isEmpty(); + assertThat(executionInfo.getPagingState()).isNull(); + assertThat(executionInfo.getSpeculativeExecutionCount()).isEqualTo(0); + assertThat(executionInfo.getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(executionInfo.getWarnings()).isEmpty(); + }); + } + } + + @Test + public void should_time_out_if_first_node_takes_too_long_to_respond() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + node1Behavior.setWriteSuccess(); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + .asyncResult(); + + // First scheduled task is the timeout, run it before node1 has responded + ScheduledTaskCapturingEventLoop.CapturedTask scheduledTask = harness.nextScheduledTask(); + Duration configuredTimeout = + harness + .getContext() + .config() + .defaultProfile() + .getDuration(CoreDriverOption.REQUEST_TIMEOUT); + assertThat(scheduledTask.getDelay(TimeUnit.NANOSECONDS)) + .isEqualTo(configuredTimeout.toNanos()); + scheduledTask.run(); + + assertThat(resultSetFuture) + .isFailed(t -> assertThat(t).isInstanceOf(DriverTimeoutException.class)); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java new file mode 100644 index 00000000000..3a3ff688c78 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.RawType; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.RowsMetadata; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; + +abstract class CqlRequestHandlerTestBase { + + protected static final DefaultSimpleStatement SIMPLE_STATEMENT = + new DefaultSimpleStatement("mock query", Collections.emptyList(), null); + + @Mock protected Node node1; + @Mock protected Node node2; + @Mock protected Node node3; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + } + + protected static Frame defaultFrameOf(Message responseMessage) { + return Frame.forResponse( + CoreProtocolVersion.V4.getCode(), + 0, + null, + Frame.NO_PAYLOAD, + Collections.emptyList(), + responseMessage); + } + + // Returns a single row, with a single "message" column with the value "hello, world" + protected static Message singleRow() { + RowsMetadata metadata = + new RowsMetadata( + ImmutableList.of( + new ColumnSpec( + "ks", + "table", + "message", + 0, + RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), + null, + new int[] {}); + Queue> data = new LinkedList<>(); + data.add(ImmutableList.of(Bytes.fromHexString("0x68656C6C6F2C20776F726C64"))); + return new Rows(metadata, data); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java new file mode 100644 index 00000000000..fefba8d7340 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import io.netty.channel.ChannelFuture; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Promise; +import java.util.concurrent.CompletableFuture; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.never; + +/** + * The simulated behavior of the connection pool for a given node in a {@link + * RequestHandlerTestHarness}. + * + *

This only covers a single attempt, if the node is to be tried multiple times there will be + * multiple instances of this class. + */ +public class PoolBehavior { + + final Node node; + final DriverChannel channel; + private final Promise writePromise; + private final CompletableFuture callbackFuture = new CompletableFuture<>(); + + public PoolBehavior(Node node, boolean createChannel, EventExecutor executor) { + this.node = node; + if (!createChannel) { + this.channel = null; + this.writePromise = null; + } else { + this.channel = Mockito.mock(DriverChannel.class); + this.writePromise = executor.newPromise(); + Mockito.when( + channel.write( + any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class))) + .thenAnswer( + invocation -> { + callbackFuture.complete(invocation.getArgument(3)); + return writePromise; + }); + ChannelFuture closeFuture = Mockito.mock(ChannelFuture.class); + Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + } + } + + public void verifyWrite() { + Mockito.verify(channel) + .write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class)); + } + + public void verifyNoWrite() { + Mockito.verify(channel, never()) + .write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class)); + } + + public void setWriteSuccess() { + writePromise.setSuccess(null); + } + + public void setWriteFailure(Throwable cause) { + writePromise.setFailure(cause); + } + + public void setResponseSuccess(Frame responseFrame) { + callbackFuture.thenAccept(callback -> callback.onResponse(responseFrame)); + } + + public void setResponseFailure(Throwable cause) { + callbackFuture.thenAccept(callback -> callback.onFailure(cause)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java new file mode 100644 index 00000000000..f2651b3754c --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.protocol.internal.Frame; +import io.netty.channel.EventLoopGroup; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.OngoingStubbing; + +import static org.mockito.ArgumentMatchers.any; + +/** + * Provides the environment to test a request handler, where a query plan can be defined, and the + * behavior of each successive node simulated. + */ +public class RequestHandlerTestHarness implements AutoCloseable { + + public static Builder builder() { + return new Builder(); + } + + private final ScheduledTaskCapturingEventLoop schedulingEventGroup; + private final Map pools; + + @Mock private InternalDriverContext context; + @Mock private EventLoopGroup eventLoopGroup; + @Mock private NettyOptions nettyOptions; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; + @Mock private RetryPolicy retryPolicy; + @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; + + private RequestHandlerTestHarness(Builder builder) { + MockitoAnnotations.initMocks(this); + + this.schedulingEventGroup = builder.schedulingEventLoop; + Mockito.when(eventLoopGroup.next()).thenReturn(schedulingEventGroup); + Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + + // TODO make configurable in the test, also handle profiles + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT)) + .thenReturn(Duration.ofMillis(500)); + Mockito.when(defaultConfigProfile.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY)) + .thenReturn(ConsistencyLevel.LOCAL_ONE); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); + Mockito.when( + defaultConfigProfile.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) + .thenReturn(ConsistencyLevel.SERIAL); + + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(context.config()).thenReturn(config); + + Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()).thenReturn(builder.buildQueryPlan()); + Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + + Mockito.when(context.retryPolicy()).thenReturn(retryPolicy); + Mockito.when(context.speculativeExecutionPolicy()).thenReturn(speculativeExecutionPolicy); + + Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry()); + + pools = builder.buildMockPools(); + } + + public Map getPools() { + return pools; + } + + public InternalDriverContext getContext() { + return context; + } + + /** + * Returns the next task that was scheduled on the request handler's admin executor. The test must + * run it manually. + */ + public ScheduledTaskCapturingEventLoop.CapturedTask nextScheduledTask() { + return schedulingEventGroup.nextTask(); + } + + @Override + public void close() { + schedulingEventGroup.shutdownGracefully().getNow(); + } + + public static class Builder { + private final ScheduledTaskCapturingEventLoop schedulingEventLoop; + private final List poolBehaviors = new ArrayList<>(); + + public Builder() { + this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(null); + } + + /** + * Sets the given node as the next one in the query plan; an empty pool will be simulated when + * it gets used. + */ + public Builder withEmptyPool(Node node) { + poolBehaviors.add(new PoolBehavior(node, false, schedulingEventLoop)); + return this; + } + + /** + * Sets the given node as the next one in the query plan; a channel write failure will be + * simulated when it gets used. + */ + public Builder withWriteFailure(Node node, Throwable cause) { + PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + behavior.setWriteFailure(cause); + poolBehaviors.add(behavior); + return this; + } + + /** + * Sets the given node as the next one in the query plan; the write to the channel will succeed, + * but a response failure will be simulated immediately after. + */ + public Builder withResponseFailure(Node node, Throwable cause) { + PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + behavior.setWriteSuccess(); + behavior.setResponseFailure(cause); + poolBehaviors.add(behavior); + return this; + } + + /** + * Sets the given node as the next one in the query plan; the write to the channel will succeed, + * and the given response will be simulated immediately after. + */ + public Builder withResponse(Node node, Frame response) { + PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + behavior.setWriteSuccess(); + behavior.setResponseSuccess(response); + poolBehaviors.add(behavior); + return this; + } + + /** + * Sets the given node as the next one in the query plan; the test code is responsible of + * calling the methods on the returned object to complete the write and the query. + */ + public PoolBehavior customBehavior(Node node) { + PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + poolBehaviors.add(behavior); + return behavior; + } + + public RequestHandlerTestHarness build() { + return new RequestHandlerTestHarness(this); + } + + private Queue buildQueryPlan() { + ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); + for (PoolBehavior behavior : poolBehaviors) { + // We don't want duplicates in the query plan: the only way a node is tried multiple times + // is if the retry policy returns a RETRY_SAME, the request handler does not re-read from + // the plan. + if (!queryPlan.contains(behavior.node)) { + queryPlan.offer(behavior.node); + } + } + return queryPlan; + } + + private Map buildMockPools() { + Map pools = new ConcurrentHashMap<>(); + Map> stubbings = new HashMap<>(); + for (PoolBehavior behavior : poolBehaviors) { + Node node = behavior.node; + ChannelPool pool = pools.computeIfAbsent(node, n -> Mockito.mock(ChannelPool.class)); + + // The goal of the code below is to generate the equivalent of: + // + // Mockito.when(pool.next()) + // .thenReturn(behavior1.channel) + // .thenReturn(behavior2.channel) + // ... + stubbings.compute( + node, + (sameNode, previous) -> { + if (previous == null) { + previous = Mockito.when(pool.next()); + } + return previous.thenReturn(behavior.channel); + }); + } + return pools; + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java new file mode 100644 index 00000000000..c4bf0830710 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import io.netty.channel.DefaultEventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.ScheduledFuture; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; + +/** + * Extend Netty's default event loop to capture scheduled tasks instead of running them. The tasks + * can be checked later, and run manually. + * + *

This is used to make unit tests independent of time. + */ +public class ScheduledTaskCapturingEventLoop extends DefaultEventLoop { + + private final Queue capturedTasks = new ConcurrentLinkedQueue<>(); + + public ScheduledTaskCapturingEventLoop(EventLoopGroup parent) { + super(parent); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + CapturedTask task = new CapturedTask<>(callable, delay, unit); + boolean added = capturedTasks.offer(task); + assertThat(added).isTrue(); + return task.scheduledFuture; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return schedule( + () -> { + command.run(); + return null; + }, + delay, + unit); + } + + public CapturedTask nextTask() { + return capturedTasks.poll(); + } + + public static class CapturedTask { + private final FutureTask futureTask; + private final long delay; + private final TimeUnit unit; + + @SuppressWarnings("unchecked") + private final ScheduledFuture scheduledFuture = Mockito.mock(ScheduledFuture.class); + + CapturedTask(Callable task, long delay, TimeUnit unit) { + this.futureTask = new FutureTask<>(task); + this.delay = delay; + this.unit = unit; + + // If the code under test cancels the scheduled future, cancel our task + Mockito.when(scheduledFuture.cancel(anyBoolean())) + .thenAnswer(invocation -> futureTask.cancel(invocation.getArgument(0))); + + // Delegate methods of the scheduled future to our task (to be extended to more methods if + // needed) + Mockito.when(scheduledFuture.isDone()).thenAnswer(invocation -> futureTask.isDone()); + Mockito.when(scheduledFuture.isCancelled()) + .thenAnswer(invocation -> futureTask.isCancelled()); + } + + public void run() { + futureTask.run(); + } + + public boolean isCancelled() { + return futureTask.isCancelled(); + } + + public long getDelay(TimeUnit targetUnit) { + return targetUnit.convert(delay, unit); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java new file mode 100644 index 00000000000..9d3ba18d7c3 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.concurrent; + +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop.CapturedTask; +import io.netty.util.concurrent.ScheduledFuture; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.fail; + +public class ScheduledTaskCapturingEventLoopTest { + + @Test + public void should_capture_task_and_let_test_complete_it_manually() { + ScheduledTaskCapturingEventLoop eventLoop = new ScheduledTaskCapturingEventLoop(null); + final AtomicBoolean ran = new AtomicBoolean(); + ScheduledFuture future = eventLoop.schedule(() -> ran.set(true), 1, TimeUnit.NANOSECONDS); + + assertThat(future.isDone()).isFalse(); + assertThat(future.isCancelled()).isFalse(); + assertThat(ran.get()).isFalse(); + + CapturedTask task = eventLoop.nextTask(); + assertThat(task.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(1); + + task.run(); + + assertThat(future.isDone()).isTrue(); + assertThat(future.isCancelled()).isFalse(); + assertThat(ran.get()).isTrue(); + } + + @Test + public void should_let_tested_code_cancel_future() { + ScheduledTaskCapturingEventLoop eventLoop = new ScheduledTaskCapturingEventLoop(null); + final AtomicBoolean ran = new AtomicBoolean(); + ScheduledFuture future = eventLoop.schedule(() -> ran.set(true), 1, TimeUnit.NANOSECONDS); + + assertThat(future.isDone()).isFalse(); + assertThat(future.isCancelled()).isFalse(); + assertThat(ran.get()).isFalse(); + + future.cancel(true); + + assertThat(future.isDone()).isTrue(); + assertThat(future.isCancelled()).isTrue(); + assertThat(ran.get()).isFalse(); + } +} diff --git a/core/src/test/resources/META-INF/services/org.testng.ITestNGListener b/core/src/test/resources/META-INF/services/org.testng.ITestNGListener new file mode 100644 index 00000000000..d5149d1ba9d --- /dev/null +++ b/core/src/test/resources/META-INF/services/org.testng.ITestNGListener @@ -0,0 +1 @@ +com.datastax.oss.driver.TestInterceptor \ No newline at end of file diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java index c500083bc31..173bb7650e9 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java +++ b/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java @@ -22,7 +22,7 @@ * A data structure where the values are accessible via a name string. * *

This is an optimized version of {@link AccessibleById}, in case the overhead of having to - * create a {@link CqlIdentifier} for each value is too much overhead. + * create a {@link CqlIdentifier} for each value is too much. * *

By default, case is ignored when matching names. If multiple names only differ by their case, * then the first one is chosen. You can force an exact match by double-quoting the name. diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index 4bbca108a6c..e213fb96aba 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -90,6 +90,7 @@ public CodecRegistry codecRegistry() { public ProtocolVersion protocolVersion() { return type.getAttachmentPoint().protocolVersion(); } + /** * @serialData The type of the tuple, followed by an array of byte arrays representing the values * (null values are represented by {@code null}). diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java b/types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java new file mode 100644 index 00000000000..eaf4a3486a3 --- /dev/null +++ b/types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.type; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.result.RawType; +import com.datastax.oss.protocol.internal.util.IntMap; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.Map; + +public class DataTypeHelper { + + public static DataType fromProtocolSpec(RawType rawType, AttachmentPoint attachmentPoint) { + DataType type = PRIMITIVE_TYPES_BY_CODE.get(rawType.id); + if (type != null) { + return type; + } else { + switch (rawType.id) { + case ProtocolConstants.DataType.CUSTOM: + RawType.RawCustom rawCustom = (RawType.RawCustom) rawType; + return DataTypes.custom(rawCustom.className); + case ProtocolConstants.DataType.LIST: + RawType.RawList rawList = (RawType.RawList) rawType; + return DataTypes.listOf(fromProtocolSpec(rawList.elementType, attachmentPoint)); + case ProtocolConstants.DataType.SET: + RawType.RawSet rawSet = (RawType.RawSet) rawType; + return DataTypes.setOf(fromProtocolSpec(rawSet.elementType, attachmentPoint)); + case ProtocolConstants.DataType.MAP: + RawType.RawMap rawMap = (RawType.RawMap) rawType; + return DataTypes.mapOf( + fromProtocolSpec(rawMap.keyType, attachmentPoint), + fromProtocolSpec(rawMap.valueType, attachmentPoint)); + case ProtocolConstants.DataType.TUPLE: + RawType.RawTuple rawTuple = (RawType.RawTuple) rawType; + List rawFieldsList = rawTuple.fieldTypes; + ImmutableList.Builder fields = ImmutableList.builder(); + for (RawType rawField : rawFieldsList) { + fields.add(fromProtocolSpec(rawField, attachmentPoint)); + } + return new DefaultTupleType(fields.build(), attachmentPoint); + case ProtocolConstants.DataType.UDT: + RawType.RawUdt rawUdt = (RawType.RawUdt) rawType; + ImmutableList.Builder fieldNames = ImmutableList.builder(); + ImmutableList.Builder fieldTypes = ImmutableList.builder(); + for (Map.Entry entry : rawUdt.fields.entrySet()) { + fieldNames.add(CqlIdentifier.fromInternal(entry.getKey())); + fieldTypes.add(fromProtocolSpec(entry.getValue(), attachmentPoint)); + } + return new DefaultUserDefinedType( + CqlIdentifier.fromInternal(rawUdt.keyspace), + CqlIdentifier.fromInternal(rawUdt.typeName), + fieldNames.build(), + fieldTypes.build(), + attachmentPoint); + default: + throw new IllegalArgumentException("Unsupported type: " + rawType.id); + } + } + } + + private static IntMap PRIMITIVE_TYPES_BY_CODE = + sortByProtocolCode( + DataTypes.ASCII, + DataTypes.BIGINT, + DataTypes.BLOB, + DataTypes.BOOLEAN, + DataTypes.COUNTER, + DataTypes.DECIMAL, + DataTypes.DOUBLE, + DataTypes.FLOAT, + DataTypes.INT, + DataTypes.TIMESTAMP, + DataTypes.UUID, + DataTypes.VARINT, + DataTypes.TIMEUUID, + DataTypes.INET, + DataTypes.DATE, + DataTypes.TEXT, + DataTypes.TIME, + DataTypes.SMALLINT, + DataTypes.TINYINT, + DataTypes.DURATION); + + private static IntMap sortByProtocolCode(DataType... types) { + IntMap.Builder builder = IntMap.builder(); + for (DataType type : types) { + builder.put(type.getProtocolCode(), type); + } + return builder.build(); + } +} From 20c88f44cc56e3ca212bd16e5480607e26ac5e3b Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Jun 2017 13:26:20 -0700 Subject: [PATCH 058/742] Remove `types` module Move classes back into the core module. --- .../oss/driver/api/core/ClusterBuilder.java | 2 +- .../driver/api/core/CoreProtocolVersion.java | 0 .../oss/driver/api/core/CqlIdentifier.java | 0 .../oss/driver/api/core/ProtocolVersion.java | 0 .../driver/api/core/cql/ColumnDefinition.java | 2 +- .../driver/api/core/data/AccessibleById.java | 2 +- .../api/core/data/AccessibleByIndex.java | 2 +- .../api/core/data/AccessibleByName.java | 2 +- .../oss/driver/api/core/data/CqlDuration.java | 0 .../oss/driver/api/core/data/Data.java | 2 +- .../driver/api/core/data/GettableById.java | 6 +- .../driver/api/core/data/GettableByIndex.java | 22 ++--- .../driver/api/core/data/GettableByName.java | 6 +- .../driver/api/core/data/SettableById.java | 8 +- .../driver/api/core/data/SettableByIndex.java | 22 ++--- .../driver/api/core/data/SettableByName.java | 8 +- .../oss/driver/api/core/data/TupleValue.java | 2 +- .../oss/driver/api/core/data/UdtValue.java | 2 +- .../api/core/detach/AttachmentPoint.java | 2 +- .../driver/api/core/detach/Detachable.java | 4 +- .../oss/driver/api/core}/type/CustomType.java | 2 +- .../oss/driver/api/core}/type/DataType.java | 2 +- .../oss/driver/api/core}/type/DataTypes.java | 14 +-- .../oss/driver/api/core}/type/ListType.java | 2 +- .../oss/driver/api/core}/type/MapType.java | 2 +- .../oss/driver/api/core}/type/SetType.java | 2 +- .../oss/driver/api/core}/type/TupleType.java | 2 +- .../api/core}/type/UserDefinedType.java | 2 +- .../type/codec/CodecNotFoundException.java | 8 +- .../type/codec/PrimitiveBooleanCodec.java | 2 +- .../core}/type/codec/PrimitiveByteCodec.java | 2 +- .../type/codec/PrimitiveDoubleCodec.java | 2 +- .../core}/type/codec/PrimitiveFloatCodec.java | 2 +- .../core}/type/codec/PrimitiveIntCodec.java | 2 +- .../core}/type/codec/PrimitiveLongCodec.java | 2 +- .../core}/type/codec/PrimitiveShortCodec.java | 2 +- .../api/core}/type/codec/TypeCodec.java | 6 +- .../api/core}/type/codec/TypeCodecs.java | 62 ++++++------ .../type/codec/registry/CodecRegistry.java | 12 +-- .../api/core}/type/reflect/GenericType.java | 2 +- .../adminrequest/AdminRequestHandler.java | 2 +- .../core/adminrequest/AdminResult.java | 4 +- .../core/context/DefaultDriverContext.java | 6 +- .../core/context/InternalDriverContext.java | 1 - .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/DefaultColumnDefinition.java | 4 +- .../driver/internal/core/cql/DefaultRow.java | 4 +- .../internal/core/data/DefaultTupleValue.java | 6 +- .../internal/core/data/DefaultUdtValue.java | 6 +- .../internal/core/data/IdentifierIndex.java | 0 .../internal/core}/type/DataTypeHelper.java | 6 +- .../core}/type/DefaultCustomType.java | 4 +- .../internal/core}/type/DefaultListType.java | 6 +- .../internal/core}/type/DefaultMapType.java | 6 +- .../internal/core}/type/DefaultSetType.java | 6 +- .../internal/core}/type/DefaultTupleType.java | 6 +- .../core}/type/DefaultUserDefinedType.java | 6 +- .../internal/core}/type/PrimitiveType.java | 4 +- .../core}/type/UserDefinedTypeBuilder.java | 6 +- .../core}/type/codec/BigIntCodec.java | 10 +- .../internal/core}/type/codec/BlobCodec.java | 10 +- .../core}/type/codec/BooleanCodec.java | 10 +- .../core}/type/codec/CounterCodec.java | 6 +- .../core}/type/codec/CqlDurationCodec.java | 12 +-- .../core}/type/codec/CustomCodec.java | 10 +- .../internal/core}/type/codec/DateCodec.java | 12 +-- .../core}/type/codec/DecimalCodec.java | 10 +- .../core}/type/codec/DoubleCodec.java | 10 +- .../internal/core}/type/codec/FloatCodec.java | 10 +- .../internal/core}/type/codec/InetCodec.java | 10 +- .../internal/core}/type/codec/IntCodec.java | 10 +- .../internal/core}/type/codec/ListCodec.java | 10 +- .../internal/core}/type/codec/MapCodec.java | 8 +- .../internal/core}/type/codec/ParseUtils.java | 2 +- .../internal/core}/type/codec/SetCodec.java | 10 +- .../core}/type/codec/SmallIntCodec.java | 10 +- .../core}/type/codec/StringCodec.java | 8 +- .../internal/core}/type/codec/TimeCodec.java | 12 +-- .../core}/type/codec/TimeUuidCodec.java | 6 +- .../core}/type/codec/TimestampCodec.java | 12 +-- .../core}/type/codec/TinyIntCodec.java | 10 +- .../internal/core}/type/codec/TupleCodec.java | 12 +-- .../internal/core}/type/codec/UdtCodec.java | 12 +-- .../internal/core}/type/codec/UuidCodec.java | 10 +- .../core}/type/codec/VarIntCodec.java | 10 +- .../codec/registry/CachingCodecRegistry.java | 26 ++--- .../codec/registry/DefaultCodecRegistry.java | 8 +- .../internal/core}/type/util/VIntCoding.java | 2 +- .../driver/internal/core/util/Strings.java | 0 .../driver/api/core/CqlIdentifierTest.java | 0 .../driver/api/core/data/CqlDurationTest.java | 0 .../core}/type/reflect/GenericTypeTest.java | 2 +- .../driver/internal/SerializationHelper.java | 0 .../core/cql/RequestHandlerTestHarness.java | 2 +- .../core/data/AccessibleByIdTestBase.java | 6 +- .../core/data/AccessibleByIndexTestBase.java | 16 ++-- .../core/data/DefaultTupleValueTest.java | 6 +- .../core/data/DefaultUdtValueTest.java | 8 +- .../core/data/IdentifierIndexTest.java | 0 .../core}/type/DataTypeDetachableTest.java | 16 ++-- .../core}/type/DataTypeSerializationTest.java | 10 +- .../core}/type/codec/BigIntCodecTest.java | 4 +- .../core}/type/codec/BlobCodecTest.java | 4 +- .../core}/type/codec/BooleanCodecTest.java | 4 +- .../core}/type/codec/CodecTestBase.java | 4 +- .../core}/type/codec/CounterCodecTest.java | 4 +- .../type/codec/CqlDurationCodecTest.java | 4 +- .../core}/type/codec/CqlIntToStringCodec.java | 12 +-- .../core}/type/codec/CustomCodecTest.java | 6 +- .../core}/type/codec/DateCodecTest.java | 4 +- .../core}/type/codec/DecimalCodecTest.java | 4 +- .../core}/type/codec/DoubleCodecTest.java | 4 +- .../core}/type/codec/FloatCodecTest.java | 4 +- .../core}/type/codec/InetCodecTest.java | 4 +- .../core}/type/codec/IntCodecTest.java | 4 +- .../core}/type/codec/ListCodecTest.java | 10 +- .../core}/type/codec/MapCodecTest.java | 10 +- .../core}/type/codec/SetCodecTest.java | 10 +- .../core}/type/codec/SmallIntCodecTest.java | 4 +- .../core}/type/codec/StringCodecTest.java | 4 +- .../core}/type/codec/TimeCodecTest.java | 4 +- .../core}/type/codec/TimeUuidCodecTest.java | 4 +- .../core}/type/codec/TimestampCodecTest.java | 4 +- .../core}/type/codec/TinyIntCodecTest.java | 4 +- .../core}/type/codec/TupleCodecTest.java | 16 ++-- .../core}/type/codec/UdtCodecTest.java | 16 ++-- .../core}/type/codec/UuidCodecTest.java | 4 +- .../core}/type/codec/VarintCodecTest.java | 4 +- .../registry/CachingCodecRegistryTest.java | 28 +++--- pom.xml | 1 - types/pom.xml | 96 ------------------- 131 files changed, 426 insertions(+), 524 deletions(-) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java (100%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java (100%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java (100%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java (96%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java (94%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java (97%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java (100%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/Data.java (96%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java (99%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java (95%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java (99%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java (98%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java (94%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java (98%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java (95%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java (94%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java (93%) rename {types => core}/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java (94%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/CustomType.java (95%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/DataType.java (95%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/DataTypes.java (89%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/ListType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/MapType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/SetType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/TupleType.java (95%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/UserDefinedType.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/CodecNotFoundException.java (87%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveBooleanCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveByteCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveDoubleCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveFloatCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveIntCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveLongCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/PrimitiveShortCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/TypeCodec.java (95%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/TypeCodecs.java (64%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/codec/registry/CodecRegistry.java (84%) rename {types/src/main/java/com/datastax/oss/driver/api => core/src/main/java/com/datastax/oss/driver/api/core}/type/reflect/GenericType.java (99%) rename {types => core}/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java (94%) rename {types => core}/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java (94%) rename {types => core}/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java (100%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DataTypeHelper.java (96%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultCustomType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultListType.java (93%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultMapType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultSetType.java (93%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultTupleType.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/DefaultUserDefinedType.java (96%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/PrimitiveType.java (96%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/UserDefinedTypeBuilder.java (92%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/BigIntCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/BlobCodec.java (85%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/BooleanCodec.java (88%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/CounterCodec.java (81%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/CqlDurationCodec.java (89%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/CustomCodec.java (86%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/DateCodec.java (92%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/DecimalCodec.java (90%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/DoubleCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/FloatCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/InetCodec.java (89%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/IntCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/ListCodec.java (95%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/MapCodec.java (96%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/ParseUtils.java (98%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/SetCodec.java (95%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/SmallIntCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/StringCodec.java (90%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/TimeCodec.java (89%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/TimeUuidCodec.java (91%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/TimestampCodec.java (91%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/TinyIntCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/TupleCodec.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/UdtCodec.java (95%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/UuidCodec.java (88%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/VarIntCodec.java (87%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/registry/CachingCodecRegistry.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/codec/registry/DefaultCodecRegistry.java (94%) rename {types/src/main/java/com/datastax/oss/driver/internal => core/src/main/java/com/datastax/oss/driver/internal/core}/type/util/VIntCoding.java (99%) rename {types => core}/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java (100%) rename {types => core}/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java (100%) rename {types => core}/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java (100%) rename {types/src/test/java/com/datastax/oss/driver/api => core/src/test/java/com/datastax/oss/driver/api/core}/type/reflect/GenericTypeTest.java (97%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java (100%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java (98%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java (94%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java (91%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java (91%) rename {types => core}/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java (100%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/DataTypeDetachableTest.java (92%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/DataTypeSerializationTest.java (88%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/BigIntCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/BlobCodecTest.java (95%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/BooleanCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/CodecTestBase.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/CounterCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/CqlDurationCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/CqlIntToStringCodec.java (82%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/CustomCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/DateCodecTest.java (96%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/DecimalCodecTest.java (95%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/DoubleCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/FloatCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/InetCodecTest.java (96%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/IntCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/ListCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/MapCodecTest.java (95%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/SetCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/SmallIntCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/StringCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/TimeCodecTest.java (95%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/TimeUuidCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/TimestampCodecTest.java (95%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/TinyIntCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/TupleCodecTest.java (91%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/UdtCodecTest.java (91%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/UuidCodecTest.java (94%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/VarintCodecTest.java (93%) rename {types/src/test/java/com/datastax/oss/driver/internal => core/src/test/java/com/datastax/oss/driver/internal/core}/type/codec/registry/CachingCodecRegistryTest.java (95%) delete mode 100644 types/pom.xml diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index c3b8092775c..d17f5fbb5ef 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java rename to core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java rename to core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java rename to core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java index 0ef19f88eae..caedd2b0015 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinition.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.detach.Detachable; -import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.core.type.DataType; import java.io.Serializable; /** Metadata about a CQL column. */ diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java index f290e908ddb..6c14cefa52d 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.core.type.DataType; /** * A data structure where the values are accessible via a CQL identifier. diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java index f16e665f3fe..8fe626eb012 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByIndex.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.core.type.DataType; /** A data structure where the values are accessible via an integer index. */ public interface AccessibleByIndex extends Data { diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java similarity index 97% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java index 173bb7650e9..194470e2f0e 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.core.type.DataType; /** * A data structure where the values are accessible via a name string. diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java index dd760f5ac79..aeab4cdd7de 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/Data.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.detach.Detachable; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** A data structure containing CQL values. */ public interface Data { diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java similarity index 99% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java index f23665b3305..f72ddcb3853 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java index aa9fb71ee53..4fb907edc45 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java @@ -15,17 +15,17 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveBooleanCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveByteCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveDoubleCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveFloatCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveLongCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveShortCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java similarity index 99% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java index adcc8111102..e56841fc93f 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java similarity index 98% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index 6cd02691f20..8462690f7b8 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -16,10 +16,10 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java index ce76ef02327..41cda0ee105 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -15,17 +15,17 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec; -import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveBooleanCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveByteCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveDoubleCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveFloatCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveLongCodec; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveShortCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java similarity index 98% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index ac52a70557f..8ac4fefa412 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java index 4c8af45b351..d4aeb870375 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/TupleValue.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.detach.Detachable; -import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.core.type.TupleType; import java.io.Serializable; /** diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java rename to core/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java index c94701bcc6e..ea50785aed8 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/UdtValue.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.detach.Detachable; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import java.io.Serializable; /** diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java b/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java similarity index 93% rename from types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java rename to core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java index 336b5cd3304..ff19d12823a 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.detach; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** @see Detachable */ public interface AttachmentPoint { diff --git a/types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java b/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java rename to core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java index 2541b71194e..8822b52e388 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java @@ -17,8 +17,8 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.Data; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Defines the contract of an object that can be detached and reattached to a driver instance. diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java index 481ef548ea9..3bd3389ccbc 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/CustomType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.protocol.internal.ProtocolConstants; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/type/DataType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java index e1ad8358463..8f317e80ad4 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/DataType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.driver.api.core.detach.Detachable; import java.io.Serializable; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java similarity index 89% rename from types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java index 196dded5aa4..5a0aab753b0 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/DataTypes.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.driver.api.core.detach.Detachable; -import com.datastax.oss.driver.internal.type.DefaultCustomType; -import com.datastax.oss.driver.internal.type.DefaultListType; -import com.datastax.oss.driver.internal.type.DefaultMapType; -import com.datastax.oss.driver.internal.type.DefaultSetType; -import com.datastax.oss.driver.internal.type.DefaultTupleType; -import com.datastax.oss.driver.internal.type.PrimitiveType; +import com.datastax.oss.driver.internal.core.type.DefaultCustomType; +import com.datastax.oss.driver.internal.core.type.DefaultListType; +import com.datastax.oss.driver.internal.core.type.DefaultMapType; +import com.datastax.oss.driver.internal.core.type.DefaultSetType; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; +import com.datastax.oss.driver.internal.core.type.PrimitiveType; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.google.common.collect.ImmutableList; import java.util.Arrays; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/type/ListType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java index e6fcb3e3620..abd8c552496 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/ListType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.protocol.internal.ProtocolConstants; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/type/MapType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java index d2786dcc9bf..09877917116 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/MapType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.protocol.internal.ProtocolConstants; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/api/type/SetType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java index 1e9482757fb..3eea41176b1 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/SetType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.protocol.internal.ProtocolConstants; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java index c84aff98262..25e6d931175 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/TupleType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java index fa92804fbd9..51ce25bccc9 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/UserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type; +package com.datastax.oss.driver.api.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/CodecNotFoundException.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/CodecNotFoundException.java index ab86b7c936a..cf95bd8047a 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/CodecNotFoundException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/CodecNotFoundException.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; /** Thrown when a suitable {@link TypeCodec} cannot be found by the {@link CodecRegistry}. */ public class CodecNotFoundException extends RuntimeException { diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveBooleanCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveBooleanCodec.java index cb80cdb04d0..d29b62d5211 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveBooleanCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveBooleanCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveByteCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveByteCodec.java index 627c4c4614c..3ecfacd2d88 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveByteCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveByteCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveDoubleCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveDoubleCodec.java index b901f55936c..e1db70580c3 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveDoubleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveDoubleCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveFloatCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveFloatCodec.java index af5daec4d13..377717103da 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveFloatCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveFloatCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveIntCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveIntCodec.java index 1fd3a20a542..bfc3fd03f6b 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveIntCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveLongCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveLongCodec.java index b098bd7ff3e..1f9853b90bf 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveLongCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveLongCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveShortCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveShortCodec.java index abeb4289f3a..6edfa3fc83b 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/PrimitiveShortCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/PrimitiveShortCodec.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java index 0e2dccd2252..43f00889058 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.google.common.base.Preconditions; import com.google.common.reflect.TypeToken; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java similarity index 64% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java index b3541d5a88e..38a7eb0ec58 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/TypeCodecs.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java @@ -13,41 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec; +package com.datastax.oss.driver.api.core.type.codec; import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.type.CustomType; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.internal.type.codec.BigIntCodec; -import com.datastax.oss.driver.internal.type.codec.BlobCodec; -import com.datastax.oss.driver.internal.type.codec.BooleanCodec; -import com.datastax.oss.driver.internal.type.codec.CounterCodec; -import com.datastax.oss.driver.internal.type.codec.CqlDurationCodec; -import com.datastax.oss.driver.internal.type.codec.CustomCodec; -import com.datastax.oss.driver.internal.type.codec.DateCodec; -import com.datastax.oss.driver.internal.type.codec.DecimalCodec; -import com.datastax.oss.driver.internal.type.codec.DoubleCodec; -import com.datastax.oss.driver.internal.type.codec.FloatCodec; -import com.datastax.oss.driver.internal.type.codec.InetCodec; -import com.datastax.oss.driver.internal.type.codec.IntCodec; -import com.datastax.oss.driver.internal.type.codec.ListCodec; -import com.datastax.oss.driver.internal.type.codec.MapCodec; -import com.datastax.oss.driver.internal.type.codec.SetCodec; -import com.datastax.oss.driver.internal.type.codec.SmallIntCodec; -import com.datastax.oss.driver.internal.type.codec.StringCodec; -import com.datastax.oss.driver.internal.type.codec.TimeCodec; -import com.datastax.oss.driver.internal.type.codec.TimeUuidCodec; -import com.datastax.oss.driver.internal.type.codec.TimestampCodec; -import com.datastax.oss.driver.internal.type.codec.TinyIntCodec; -import com.datastax.oss.driver.internal.type.codec.TupleCodec; -import com.datastax.oss.driver.internal.type.codec.UdtCodec; -import com.datastax.oss.driver.internal.type.codec.UuidCodec; -import com.datastax.oss.driver.internal.type.codec.VarIntCodec; +import com.datastax.oss.driver.api.core.type.CustomType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.type.codec.BigIntCodec; +import com.datastax.oss.driver.internal.core.type.codec.BlobCodec; +import com.datastax.oss.driver.internal.core.type.codec.BooleanCodec; +import com.datastax.oss.driver.internal.core.type.codec.CounterCodec; +import com.datastax.oss.driver.internal.core.type.codec.CqlDurationCodec; +import com.datastax.oss.driver.internal.core.type.codec.CustomCodec; +import com.datastax.oss.driver.internal.core.type.codec.DateCodec; +import com.datastax.oss.driver.internal.core.type.codec.DecimalCodec; +import com.datastax.oss.driver.internal.core.type.codec.DoubleCodec; +import com.datastax.oss.driver.internal.core.type.codec.FloatCodec; +import com.datastax.oss.driver.internal.core.type.codec.InetCodec; +import com.datastax.oss.driver.internal.core.type.codec.IntCodec; +import com.datastax.oss.driver.internal.core.type.codec.ListCodec; +import com.datastax.oss.driver.internal.core.type.codec.MapCodec; +import com.datastax.oss.driver.internal.core.type.codec.SetCodec; +import com.datastax.oss.driver.internal.core.type.codec.SmallIntCodec; +import com.datastax.oss.driver.internal.core.type.codec.StringCodec; +import com.datastax.oss.driver.internal.core.type.codec.TimeCodec; +import com.datastax.oss.driver.internal.core.type.codec.TimeUuidCodec; +import com.datastax.oss.driver.internal.core.type.codec.TimestampCodec; +import com.datastax.oss.driver.internal.core.type.codec.TinyIntCodec; +import com.datastax.oss.driver.internal.core.type.codec.TupleCodec; +import com.datastax.oss.driver.internal.core.type.codec.UdtCodec; +import com.datastax.oss.driver.internal.core.type.codec.UuidCodec; +import com.datastax.oss.driver.internal.core.type.codec.VarIntCodec; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import java.math.BigDecimal; diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java similarity index 84% rename from types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java index d85fb84fc00..36b6a0374c0 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/codec/registry/CodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.codec.registry; +package com.datastax.oss.driver.api.core.type.codec.registry; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; -import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; public interface CodecRegistry { /** diff --git a/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java similarity index 99% rename from types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index 32aa43741fb..83897cb6870 100644 --- a/types/src/main/java/com/datastax/oss/driver/api/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.reflect; +package com.datastax.oss.driver.api.core.type.reflect; import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.data.TupleValue; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index cc726db0ce5..89d3bc5fb31 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java index 58b2ee933a6..78f7e0b89ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.internal.core.adminrequest; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.Rows; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 804dec6e6fc..bc8c79aa8e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -25,8 +25,8 @@ import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; @@ -44,7 +44,7 @@ import com.datastax.oss.driver.internal.core.util.Reflection; import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector; import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; -import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 13b65928250..cc264f59be7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.context; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 67c9130f3f0..016cceca823 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -42,7 +42,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java index a6eac502837..8f94e11968f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.internal.type.DataTypeHelper; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.internal.core.type.DataTypeHelper; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; public class DefaultColumnDefinition implements ColumnDefinition { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java index ccd35fc6225..d87918e2de8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.protocol.internal.util.Bytes; import java.io.InvalidObjectException; import java.io.ObjectInputStream; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index 919e734423a..5d813562e39 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -17,9 +17,9 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.base.Preconditions; import java.io.InvalidObjectException; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index e213fb96aba..17dfc68e3d1 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -18,9 +18,9 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.base.Preconditions; import java.io.InvalidObjectException; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/data/IdentifierIndex.java diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java index eaf4a3486a3..1ee63a4c5d1 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DataTypeHelper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.result.RawType; import com.datastax.oss.protocol.internal.util.IntMap; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultCustomType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultCustomType.java index 7e47ae8bbfc..6abf44dbc7f 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultCustomType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultCustomType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.CustomType; +import com.datastax.oss.driver.api.core.type.CustomType; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java similarity index 93% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java index 8fa6ad81440..b2f45219b64 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultListType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.ListType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.ListType; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java index 67645d7c9f2..763a0641d11 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultMapType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.MapType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.MapType; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java similarity index 93% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java index 55e8df6bba2..a8869326b0a 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultSetType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.SetType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.SetType; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultTupleType.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultTupleType.java index f8c7ca3b579..aa633f6829a 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultTupleType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultTupleType.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.TupleType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.internal.core.data.DefaultTupleValue; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java index 1e1dc8e2434..28931fd6245 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/DefaultUserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.core.data.DefaultUdtValue; import com.datastax.oss.driver.internal.core.data.IdentifierIndex; import com.google.common.base.Preconditions; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java index ee461f58dbe..d5dbc5ae70e 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/PrimitiveType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; +import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.protocol.internal.ProtocolConstants; public class PrimitiveType implements DataType { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java similarity index 92% rename from types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java index 5c5725a1261..c9a9e85cb48 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/UserDefinedTypeBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.google.common.collect.ImmutableList; /** diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java index 19df4f389e1..62fc46039b7 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BigIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveLongCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveLongCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class BigIntCodec implements PrimitiveLongCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java similarity index 85% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java index 7756f1c6fc9..32a98c01279 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BlobCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java similarity index 88% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java index 92c6f6c9369..bbc9976cdbf 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/BooleanCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveBooleanCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveBooleanCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class BooleanCodec implements PrimitiveBooleanCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodec.java similarity index 81% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodec.java index 671eaa81c99..47ebc07ed21 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CounterCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodec.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; public class CounterCodec extends BigIntCodec { @Override diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java similarity index 89% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java index bbf13b801b4..edad3067ffd 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.CqlDuration; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; -import com.datastax.oss.driver.internal.type.util.VIntCoding; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.type.util.VIntCoding; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java similarity index 86% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java index 75d79454396..3ccbfcb37b1 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/CustomCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.CustomType; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.CustomType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java similarity index 92% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java index da77b7a0f87..1421b2053d8 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DateCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.Strings; import java.nio.ByteBuffer; import java.time.LocalDate; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java similarity index 90% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java index c728fe87537..ef2ed52f521 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DecimalCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java index f0a251673f7..0a62ef00bee 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/DoubleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveDoubleCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveDoubleCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class DoubleCodec implements PrimitiveDoubleCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java index bd817fcedf0..6c238c0d912 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/FloatCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveFloatCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveFloatCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class FloatCodec implements PrimitiveFloatCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java similarity index 89% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java index d2cfd6bf636..a5a3b4c7d37 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/InetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.Strings; import com.datastax.oss.protocol.internal.util.Bytes; import java.net.InetAddress; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java index 8c7cd44b47a..d97e38d843a 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/IntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class IntCodec implements PrimitiveIntCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java index a193f8a7067..38f08fa61d9 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ListCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.ListType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.google.common.base.Preconditions; import java.nio.ByteBuffer; import java.util.ArrayList; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java similarity index 96% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java index f254aae0eee..5328a5f29a1 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/MapCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.google.common.collect.Maps; import java.nio.ByteBuffer; import java.util.LinkedHashMap; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java similarity index 98% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java index ed0a70b64d1..6360179bbb6 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/ParseUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; public class ParseUtils { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java index c34ab5683ed..874f92a909b 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.SetType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java index 5709c7d268f..a7f389afea4 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveShortCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveShortCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class SmallIntCodec implements PrimitiveShortCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java similarity index 90% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java index 4944f8991f2..24cd40b2c7f 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/StringCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.Strings; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java similarity index 89% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java index 0a8fae74165..0932a689878 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.Strings; import java.nio.ByteBuffer; import java.time.LocalTime; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java similarity index 91% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java index f00fcd283ec..8336c6c302b 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; import java.nio.ByteBuffer; import java.util.UUID; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java similarity index 91% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java index ca6d2b71275..76eb7748657 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TimestampCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.Strings; import java.nio.ByteBuffer; import java.time.Instant; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java index 27d5431c217..6a59409cfd7 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveByteCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveByteCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; public class TinyIntCodec implements PrimitiveByteCodec { diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java index 0f2cb6682db..dc18cf2e463 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/TupleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java similarity index 95% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 6c380fce859..9d8653da334 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java similarity index 88% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java index 85d7833c06e..1155ac0f3c9 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/UuidCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; import java.util.UUID; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java similarity index 87% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java index fa770bb9f8a..4433392c4e5 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/VarIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index d5c44c2281d..6ec567dc498 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -13,22 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec.registry; +package com.datastax.oss.driver.internal.core.type.codec.registry; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.type.CustomType; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.ListType; -import com.datastax.oss.driver.api.type.MapType; -import com.datastax.oss.driver.api.type.SetType; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.api.type.codec.CodecNotFoundException; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.CustomType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.IntMap; import com.google.common.base.Preconditions; import com.google.common.reflect.TypeToken; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java similarity index 94% rename from types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java index e39cd447661..74bcc460b6c 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/codec/registry/DefaultCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec.registry; +package com.datastax.oss.driver.internal.core.type.codec.registry; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java similarity index 99% rename from types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java index 9905c13907a..a620e13ab91 100644 --- a/types/src/main/java/com/datastax/oss/driver/internal/type/util/VIntCoding.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java @@ -42,7 +42,7 @@ // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package com.datastax.oss.driver.internal.type.util; +package com.datastax.oss.driver.internal.core.type.util; import java.io.DataInput; import java.io.DataOutput; diff --git a/types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java similarity index 100% rename from types/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java diff --git a/types/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java similarity index 100% rename from types/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java rename to core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java diff --git a/types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java similarity index 100% rename from types/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java rename to core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java diff --git a/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java similarity index 97% rename from types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java rename to core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java index 9d325cc7d5f..ce7d9f66898 100644 --- a/types/src/test/java/com/datastax/oss/driver/api/type/reflect/GenericTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.type.reflect; +package com.datastax.oss.driver.api.core.type.reflect; import com.google.common.reflect.TypeToken; import java.util.List; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java similarity index 100% rename from types/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java rename to core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index f2651b3754c..2d83a8b6b6a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -28,7 +28,7 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; -import com.datastax.oss.driver.internal.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.protocol.internal.Frame; import io.netty.channel.EventLoopGroup; import java.time.Duration; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java similarity index 98% rename from types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index f9e38ac4270..f610af68d18 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -21,9 +21,9 @@ import com.datastax.oss.driver.api.core.data.GettableByName; import com.datastax.oss.driver.api.core.data.SettableById; import com.datastax.oss.driver.api.core.data.SettableByName; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.reflect.GenericType; -import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index ebf25a07575..f7ba7fc55c8 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -19,14 +19,14 @@ import com.datastax.oss.driver.api.core.data.GettableByIndex; import com.datastax.oss.driver.api.core.data.SettableByIndex; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; -import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java similarity index 91% rename from types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index b9afd6680a5..d8d1ed7bf0f 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -17,10 +17,10 @@ import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.SerializationHelper; -import com.datastax.oss.driver.internal.type.DefaultTupleType; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.util.List; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java similarity index 91% rename from types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index c5eab0b753d..0ea7081e5c1 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -18,11 +18,11 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.SerializationHelper; -import com.datastax.oss.driver.internal.type.UserDefinedTypeBuilder; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.datastax.oss.protocol.internal.util.Bytes; import java.util.List; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java similarity index 100% rename from types/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java similarity index 92% rename from types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java index 6e59f7870a0..d3ff735c0ac 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeDetachableTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.ListType; -import com.datastax.oss.driver.api.type.MapType; -import com.datastax.oss.driver.api.type.SetType; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.SerializationHelper; import com.google.common.collect.ImmutableList; import org.mockito.Mock; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java similarity index 88% rename from types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java index 08dcc585df3..8c8bf398155 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/DataTypeSerializationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type; +package com.datastax.oss.driver.internal.core.type; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.SerializationHelper; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java index f654a90b16e..4f8c8f54329 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BigIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java index 1f5ec1ea27f..bb78d110080 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BlobCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java index 51e74c540ba..59211226066 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/BooleanCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java index 4482274e2de..36cf55f5e3c 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CodecTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java index 0705c8aee4b..6938afd8973 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CounterCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java index 87644271446..7f6ba160a7b 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlDurationCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.data.CqlDuration; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlIntToStringCodec.java similarity index 82% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlIntToStringCodec.java index 4beb828b085..182be621539 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CqlIntToStringCodec.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlIntToStringCodec.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; /** diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java index fcaf063a1f2..3598e0e5bcf 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/CustomCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java similarity index 96% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java index e561168b653..463f17bd0d7 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DateCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalDate; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java index bd33b8a3fd7..7a37b6aeca3 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DecimalCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigDecimal; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java index 609f10378d4..3afcddaa789 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/DoubleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java index 77b62b91d29..adcf6af4563 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/FloatCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java similarity index 96% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java index 19aaf37bfe7..8cfa5f4555b 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/InetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.google.common.base.Strings; import java.net.InetAddress; import java.net.UnknownHostException; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java index c64c28ac95b..fa34dd54123 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/IntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java index bddaed59797..24f74019625 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/ListCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.util.ArrayList; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java index fa4d090bda3..2d2758465f0 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/MapCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableMap; import java.util.LinkedHashMap; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java index 4c8f90c6ebd..6686151ecb7 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableSet; import java.util.LinkedHashSet; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java index a9e5f492a53..099441bbfdb 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/SmallIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java index 9e7459dee1a..e2e0a62866f 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/StringCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java index 0f0e1e43f35..3098662b58b 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalTime; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java index 8f1678d16a0..1de96f682c8 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimeUuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java index c33758edc3b..17c276d9a7d 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.Instant; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java index bada793de10..838bd50475c 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TinyIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java similarity index 91% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index b0485b2c7e4..bc8c112cbcf 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.internal.type.DefaultTupleType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import org.mockito.Mock; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java similarity index 91% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index ccd83444caa..9e340135767 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.api.type.codec.PrimitiveIntCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.internal.type.DefaultUserDefinedType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.PrimitiveIntCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import org.mockito.Mock; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java similarity index 94% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java index 0b0e820ac65..9a4e51d84d7 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/UuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java similarity index 93% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java index 927ecd87981..b57ce0ff3b8 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/VarintCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec; +package com.datastax.oss.driver.internal.core.type.codec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigInteger; import org.testng.annotations.Test; diff --git a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java similarity index 95% rename from types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 9c5bf9ff24c..ef478281c0a 100644 --- a/types/src/test/java/com/datastax/oss/driver/internal/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -13,26 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.type.codec.registry; +package com.datastax.oss.driver.internal.core.type.codec.registry; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.type.DataType; -import com.datastax.oss.driver.api.type.DataTypes; -import com.datastax.oss.driver.api.type.ListType; -import com.datastax.oss.driver.api.type.MapType; -import com.datastax.oss.driver.api.type.SetType; -import com.datastax.oss.driver.api.type.TupleType; -import com.datastax.oss.driver.api.type.UserDefinedType; -import com.datastax.oss.driver.api.type.codec.TypeCodec; -import com.datastax.oss.driver.api.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.type.reflect.GenericType; -import com.datastax.oss.driver.internal.type.UserDefinedTypeBuilder; -import com.datastax.oss.driver.internal.type.codec.CqlIntToStringCodec; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; +import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; diff --git a/pom.xml b/pom.xml index bf7df9e219e..1353f192340 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,6 @@ 2017 - types core diff --git a/types/pom.xml b/types/pom.xml deleted file mode 100644 index f486f7fc76f..00000000000 --- a/types/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - - com.datastax.oss - java-driver-parent - 4.0.0-SNAPSHOT - - - java-driver-types - jar - - DataStax Java driver for Apache Cassandra® - data types - - - - com.datastax.oss - native-protocol - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - test - - - org.testng - testng - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - - - - - maven-shade-plugin - - - - shade - - - - - com.google.guava:guava - - - - - com.google - com.datastax.oss.driver.shaded.guava - - - - - - - - - From f4680e68b861f2942dc3b124d1c80fe86f163592 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Jun 2017 15:05:43 -0700 Subject: [PATCH 059/742] Add more contribution guidelines --- CONTRIBUTING.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c83e861d09c..7a386d5d9e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,6 @@ in the documented item's signature. Builder withLimit(int limit) { ``` - ## Coding style -- test code Static imports are permitted in a couple of places: @@ -94,6 +93,11 @@ Don't try to generify at all cost: a bit of duplication is acceptable, if that h simple to understand (a newcomer should be able to understand how to fix a failing test without having to read too much code). +Test classes can be a bit longer, since they often enumerate similar test cases. You can also +factor some common code in a parent abstract class named with "XxxTestBase", and then split +different families of tests into separate child classes. For example, `CqlRequestHandlerTestBase`, +`CqlRequestHandlerRetryTest`, `CqlRequestHandlerSpeculativeExecutionTest`... + ## License headers The build will fail if some license headers are missing. To update all files from the command line, @@ -118,3 +122,68 @@ Note: the tests run on the current state of the working directory. I tried to ad the script to only test what's actually being committed, but I couldn't get it to run reliably (it's still in there but commented). Keep this in mind when you commit, and don't forget to re-add the changes if the first attempt failed and you fixed the tests. + +## Commits + +Keep your changes **focused**. Each commit should have a single, clear purpose expressed in its +message. (Note: these rules can be somewhat relaxed during the initial developement phase, when +adding a feature sometimes requires other semi-related features). + +Resist the urge to "fix" cosmetic issues (add/remove blank lines, etc.) in existing code. This adds +cognitive load for reviewers, who have to figure out which changes are relevant to the actual +issue. If you see legitimate issues, like typos, address them in a separate commit (it's fine to +group multiple typo fixes in a single commit). + +Commit message subjects start with a capital letter, use the imperative form and do **not** end +with a period: + +* correct: "Add test for CQL request handler" +* incorrect: "~~Added test for CQL request handler~~" +* incorrect: "~~New test for CQL request handler~~" + +Avoid catch-all messages like "Minor cleanup", "Various fixes", etc. They don't provide any useful +information to reviewers, and might be a sign that your commit contains unrelated changes. + +We don't enforce a particular subject line length limit, but try to keep it short. + +You can add more details after the subject line, separated by a blank line. The following pattern +(inspired by [Netty](http://netty.io/wiki/writing-a-commit-message.html)) is not mandatory, but +welcome for complex changes: + +``` +One line description of your change + +Motivation: + +Explain here the context, and why you're making that change. +What is the problem you're trying to solve. + +Modifications: + +Describe the modifications you've done. + +Result: + +After your change, what will change. +``` + +## Pull requests + +Like commits, pull requests should be focused on a single, clearly stated goal. + +Avoid basing a pull request onto another one. If possible, the first one should be merged in first, +and only then should the second one be created. + +If you have to address feedback, avoid rebasing your branch and force-pushing (this makes the +reviewers' job harder, because they have to re-read the full diff and figure out where your new +changes are). Instead, push a new commit on top of the existing history; it will be squashed later +when the PR gets merged. If the history is complex, it's a good idea to indicate in the message +where the changes should be squashed: + +``` +* 20c88f4 - Address feedback (to squash with "Add metadata parsing logic") (36 minutes ago) +* 7044739 - Fix various typos in Javadocs (2 days ago) +* 574dd08 - Add metadata parsing logic (2 days ago) +``` + +(Note that the message refers to the other commit's subject line, not the SHA-1.) From d022c5428bcf23b0cc978183dfa4c1cffa099b8f Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Jun 2017 18:15:33 -0700 Subject: [PATCH 060/742] Add missing NoSpeculativeExecutionPolicy constructor --- .../driver/api/core/specex/NoSpeculativeExecutionPolicy.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java index 1daf35df945..333a56fe805 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -16,11 +16,16 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; /** A policy that never triggers speculative executions. */ public class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { + public NoSpeculativeExecutionPolicy(@SuppressWarnings("unused") DriverContext context) { + // nothing to do + } + @Override public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { // never start speculative executions From fe97e04156a6e992db2c7296054501abe2eb24af Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Jun 2017 18:16:03 -0700 Subject: [PATCH 061/742] Suppress warning in DefaultRetryPolicy --- .../datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index 785a471798a..134596b92be 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -26,7 +26,7 @@ */ public class DefaultRetryPolicy implements RetryPolicy { - public DefaultRetryPolicy(DriverContext context) { + public DefaultRetryPolicy(@SuppressWarnings("unused") DriverContext context) { // nothing to do } From 979ded95ab3c14fad405be6f738e8a7d2d5c0b09 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Jun 2017 18:11:03 -0700 Subject: [PATCH 062/742] Handle keyspace change on a session --- .../oss/driver/api/core/session/Session.java | 34 +++++ .../internal/core/cql/CqlRequestHandler.java | 18 ++- .../core/cql/CqlRequestProcessor.java | 10 +- .../internal/core/pool/ChannelPool.java | 10 ++ .../internal/core/session/DefaultSession.java | 57 +++++++-- .../core/session/RequestHandlerBase.java | 11 +- .../core/session/RequestProcessor.java | 6 +- .../core/cql/CqlRequestHandlerRetryTest.java | 12 +- ...equestHandlerSpeculativeExecutionTest.java | 8 +- .../core/cql/CqlRequestHandlerTest.java | 26 +++- .../core/cql/RequestHandlerTestHarness.java | 10 +- .../core/session/DefaultSessionTest.java | 120 +++++++++++++++--- 12 files changed, 250 insertions(+), 72 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 831e6b1b71c..166e8e82f78 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.api.core.session; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.CqlSession; /** @@ -26,7 +28,39 @@ * CqlSession}. */ public interface Session extends AsyncAutoCloseable { + + /** + * The keyspace that this session is currently connected to. + * + *

There are two ways that this can be set: + * + *

    + *
  • during initialization, if the session was created with {@link + * Cluster#connect(CqlIdentifier)} or {@link Cluster#connectAsync(CqlIdentifier)}; + *
  • at runtime, if the client issues a request that changes the keyspace (such as a CQL + * {@code USE} query). Note that this second method is inherently unsafe, since other + * requests expecting the old keyspace might be executing concurrently. Therefore it is + * highly discouraged, aside from trivial cases (such as a cqlsh-style program where + * requests are never concurrent). + *
+ */ + CqlIdentifier getKeyspace(); + + /** + * Executes a request, and blocks until the result is available. + * + * @return a synchronous result, that provides immediate access to the data as soon as the method + * returns. + */ SyncResultT execute(Request request); + /** + * Executes a request, returning as soon as it has been scheduled, but generally before the result + * is available. + * + * @return an asynchronous result, that represents the future completion of the request. The + * client either wait, or schedule a callback to be executed on completion (this is + * implementation-specific). + */ AsyncResultT executeAsync(Request request); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 56e3de9fda3..d75e1b22b76 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -38,6 +38,7 @@ import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -47,6 +48,7 @@ import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.Result; import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.SchemaChange; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.response.result.Void; import io.netty.util.concurrent.EventExecutor; @@ -89,9 +91,8 @@ public class CqlRequestHandler // We don't use a map because nodes can appear multiple times. private volatile List> errors; - CqlRequestHandler( - Statement statement, Map pools, InternalDriverContext context) { - super(statement, pools, context); + CqlRequestHandler(Statement statement, DefaultSession session, InternalDriverContext context) { + super(statement, session, context); this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -115,7 +116,6 @@ public class CqlRequestHandler this.executions = new AtomicInteger(0); // Start the initial execution - CqlIdentifier keyspace = null; // TODO pull keyspace from session long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); if (nextExecution > 0) { LOG.trace("Scheduling first speculative execution in {} ms", nextExecution); @@ -156,7 +156,6 @@ private void startExecution() { if (!result.isDone()) { int execution = executions.incrementAndGet(); LOG.trace("Starting speculative execution {}", execution); - CqlIdentifier keyspace = null; // TODO pull keyspace from session long nextDelay = speculativeExecutionPolicy.nextExecution(keyspace, request, execution + 1); if (nextDelay > 0) { LOG.trace("Scheduling {}th speculative execution in {} ms", execution + 1, nextDelay); @@ -203,7 +202,7 @@ private void sendRequest(Node node, int execution, int retryCount) { } private DriverChannel getChannel(Node node) { - ChannelPool pool = pools.get(node); + ChannelPool pool = session.getPools().get(node); if (pool == null) { LOG.trace("No pool to {}, skipping", node); return null; @@ -255,10 +254,9 @@ private void setFinalResult( if (result.complete(resultSet)) { cancelScheduledTasks(); if (resultMessage instanceof SetKeyspace) { - CqlIdentifier keyspace = + CqlIdentifier newKeyspace = CqlIdentifier.fromInternal(((SetKeyspace) resultMessage).keyspace); - // TODO set keyspace on the session - throw new UnsupportedOperationException("TODO set keyspace on session after USE query"); + session.setKeyspace(newKeyspace); } } } catch (Throwable error) { @@ -318,7 +316,7 @@ public void onResponse(Frame responseFrame) { } try { Message responseMessage = responseFrame.message; - if (responseMessage instanceof SetKeyspace) { + if (responseMessage instanceof SchemaChange) { // TODO schema agreement, and chain setFinalResult to the result setFinalError(new UnsupportedOperationException("TODO handle schema agreement")); } else if (responseMessage instanceof Result) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java index ccb11bc53c6..7d8d0fce061 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java @@ -15,19 +15,15 @@ */ package com.datastax.oss.driver.internal.core.cql; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; -import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentMap; public class CqlRequestProcessor implements RequestProcessor> { @@ -40,8 +36,8 @@ public class CqlRequestProcessor @Override public RequestHandler> newHandler( Request> request, - Map pools, + DefaultSession session, InternalDriverContext context) { - return new CqlRequestHandler((Statement) request, pools, context); + return new CqlRequestHandler((Statement) request, session, context); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 612687cc02d..d45590e01c5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -78,6 +78,7 @@ public static CompletionStage init( @VisibleForTesting final ChannelSet channels = new ChannelSet(); private final Node node; + private final CqlIdentifier initialKeyspaceName; private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; private volatile boolean invalidKeyspace; @@ -85,6 +86,7 @@ public static CompletionStage init( private ChannelPool( Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { this.node = node; + this.initialKeyspaceName = keyspaceName; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(keyspaceName, distance, context); } @@ -98,6 +100,14 @@ public Node getNode() { return node; } + /** + * The keyspace with which the pool was initialized (therefore a constant, it's not updated if the + * keyspace is switched later). + */ + public CqlIdentifier getInitialKeyspaceName() { + return initialKeyspaceName; + } + /** * Whether all channels failed due to an invalid keyspace. This is only used at initialization. We * don't make the decision to close the pool here yet, that's done at the session level. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 1d6788a9b05..a68bea0d2b7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -35,7 +34,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; -import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.EventExecutor; import java.util.ArrayList; import java.util.Collection; @@ -79,8 +77,10 @@ public static CompletionStage init( private final SingleThreaded singleThreaded; private final RequestProcessorRegistry processorRegistry; - @VisibleForTesting - final ConcurrentMap pools = + // This is read concurrently, but only updated from adminExecutor + private volatile CqlIdentifier keyspace; + + private final ConcurrentMap pools = new ConcurrentHashMap<>( 16, 0.75f, @@ -91,8 +91,9 @@ private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.context = context; this.config = context.config(); - this.singleThreaded = new SingleThreaded(context, keyspace); + this.singleThreaded = new SingleThreaded(context); this.processorRegistry = context.requestProcessorRegistry(); + this.keyspace = keyspace; } private CompletionStage init() { @@ -100,6 +101,29 @@ private CompletionStage init() { return singleThreaded.initFuture; } + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + /** + * INTERNAL USE ONLY -- switches the session to a new keyspace. + * + *

This is called by the driver when a {@code USE} query is successfully executed through the + * session. Calling it from anywhere else is highly discouraged, as an invalid keyspace would + * wreak havoc (close all connections and make the session unusable). + */ + public void setKeyspace(CqlIdentifier newKeyspace) { + if (!this.keyspace.equals(newKeyspace)) { + this.keyspace = newKeyspace; + RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace)); + } + } + + public Map getPools() { + return pools; + } + @Override public SyncResultT execute( Request request) { @@ -114,7 +138,7 @@ public AsyncResultT executeAsync( private RequestHandler newHandler( Request request) { - return processorRegistry.processorFor(request).newHandler(request, pools, context); + return processorRegistry.processorFor(request).newHandler(request, this, context); } @Override @@ -155,12 +179,9 @@ private class SingleThreaded { private final Map pendingDistanceEvents = new HashMap<>(); private final Map pendingStateEvents = new HashMap<>(); - private CqlIdentifier keyspace; - - private SingleThreaded(InternalDriverContext context, CqlIdentifier keyspace) { + private SingleThreaded(InternalDriverContext context) { this.context = context; this.channelPoolFactory = context.channelPoolFactory(); - this.keyspace = keyspace; this.distanceListenerKey = context .eventBus() @@ -317,6 +338,12 @@ private void onPoolAdded(ChannelPool pool) { pool.forceCloseAsync(); } else { LOG.debug("New pool to {} initialized", node); + // If the session's keyspace changed while the pool was initializing, switch it now. Don't + // try too hard to wait until we expose the pool to clients, switching keyspaces is + // inherently unsafe anyway. + if (!keyspace.equals(pool.getInitialKeyspaceName())) { + pool.setKeyspace(keyspace); + } pending.remove(node); pools.put(node, pool); DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); @@ -332,6 +359,16 @@ private void onPoolAdded(ChannelPool pool) { } } + private void setKeyspace(CqlIdentifier newKeyspace) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + for (ChannelPool pool : pools.values()) { + pool.setKeyspace(newKeyspace); + } + } + private void close() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index 80c6551fa8c..3617034f1c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -15,13 +15,12 @@ */ package com.datastax.oss.driver.internal.core.session; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import java.util.Map; import java.util.Queue; /** Factors code that should be common to most request handler implementations. */ @@ -29,17 +28,19 @@ public abstract class RequestHandlerBase implements RequestHandler { protected final Request request; - protected final Map pools; + protected final DefaultSession session; + protected final CqlIdentifier keyspace; protected final InternalDriverContext context; protected final Queue queryPlan; protected final DriverConfigProfile configProfile; protected RequestHandlerBase( Request request, - Map pools, + DefaultSession session, InternalDriverContext context) { this.request = request; - this.pools = pools; + this.session = session; + this.keyspace = session.getKeyspace(); this.context = context; this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index b0d169cbd26..aace09845bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -15,14 +15,10 @@ */ package com.datastax.oss.driver.internal.core.session; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import java.util.Map; /** * Handles a type of request in the driver. @@ -46,6 +42,6 @@ public interface RequestProcessor { /** Builds a new handler to process a given request. */ RequestHandler newHandler( Request request, - Map pools, + DefaultSession session, InternalDriverContext context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index e6b4ea93cec..d882cde51c6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -60,7 +60,7 @@ public void should_always_try_next_node_if_bootstrapping() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -97,7 +97,7 @@ public void should_always_rethrow_query_validation_error() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -122,7 +122,7 @@ public void should_try_next_node_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -151,7 +151,7 @@ public void should_try_same_node_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -179,7 +179,7 @@ public void should_ignore_error_if_retry_policy_decides_so(FailureScenario failu harness.getContext().retryPolicy(), RetryDecision.IGNORE); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -206,7 +206,7 @@ public void should_rethrow_error_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETHROW); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index c22b9b262ff..c01f610358e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -48,7 +48,7 @@ public void should_schedule_speculative_executions() { Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 3)) .thenReturn(0L); - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); @@ -90,7 +90,7 @@ public void should_not_start_execution_if_result_complete() { .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); @@ -132,7 +132,7 @@ public void should_retry_in_speculative_executions() { .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); // do not simulate a response from node1. The request will stay hanging for the rest of this test @@ -170,7 +170,7 @@ public void should_stop_retrying_other_executions_if_result_complete() { .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 9479ddc39ff..3d49c543c11 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -15,16 +15,19 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import java.time.Duration; import java.util.Iterator; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; +import org.mockito.Mockito; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -39,7 +42,7 @@ public void should_complete_result_if_first_node_replies_immediately() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -70,7 +73,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getPools(), harness.getContext()) + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); // First scheduled task is the timeout, run it before node1 has responded @@ -89,4 +92,23 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { .isFailed(t -> assertThat(t).isInstanceOf(DriverTimeoutException.class)); } } + + @Test + public void should_switch_keyspace_on_session_after_successful_use_statement() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withResponse(node1, defaultFrameOf(new SetKeyspace("newKeyspace"))) + .build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isSuccess( + resultSet -> + Mockito.verify(harness.getSession()) + .setKeyspace(CqlIdentifier.fromInternal("newKeyspace"))); + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 2d83a8b6b6a..34179a63de3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.protocol.internal.Frame; @@ -57,9 +58,9 @@ public static Builder builder() { } private final ScheduledTaskCapturingEventLoop schedulingEventGroup; - private final Map pools; @Mock private InternalDriverContext context; + @Mock private DefaultSession session; @Mock private EventLoopGroup eventLoopGroup; @Mock private NettyOptions nettyOptions; @Mock private DriverConfig config; @@ -97,11 +98,12 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry()); - pools = builder.buildMockPools(); + Map pools = builder.buildMockPools(); + Mockito.when(session.getPools()).thenReturn(pools); } - public Map getPools() { - return pools; + public DefaultSession getSession() { + return session; } public InternalDriverContext getContext() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index d5867ba8cb6..41a0a34e294 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -127,7 +127,8 @@ public void should_initialize_pools_with_distances() { assertThat(initFuture) .isSuccess( session -> - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3)); + assertThat(((DefaultSession) session).getPools()) + .containsValues(pool1, pool2, pool3)); } @Test @@ -150,7 +151,8 @@ public void should_not_connect_to_ignored_nodes() { waitForPendingAdminTasks(); assertThat(initFuture) .isSuccess( - session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + session -> + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); } @Test @@ -173,7 +175,8 @@ public void should_not_connect_to_forced_down_nodes() { waitForPendingAdminTasks(); assertThat(initFuture) .isSuccess( - session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + session -> + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); } @Test @@ -213,7 +216,8 @@ public void should_adjust_distance_if_changed_while_init() { assertThat(initFuture) .isSuccess( session -> - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3)); + assertThat(((DefaultSession) session).getPools()) + .containsValues(pool1, pool2, pool3)); } @Test @@ -252,7 +256,8 @@ public void should_remove_pool_if_ignored_while_init() { assertThat(initFuture) .isSuccess( - session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + session -> + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); } @Test @@ -291,7 +296,8 @@ public void should_remove_pool_if_forced_down_while_init() { assertThat(initFuture) .isSuccess( - session -> assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3)); + session -> + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); } @Test @@ -342,7 +348,7 @@ public void should_remove_pool_if_node_becomes_ignored() { Mockito.verify(pool2, timeout(100)).closeAsync(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @Test @@ -368,13 +374,13 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool2, pool3); } @Test @@ -401,7 +407,7 @@ public void should_remove_pool_if_node_is_forced_down() { Mockito.verify(pool2, timeout(100)).closeAsync(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @Test @@ -427,12 +433,12 @@ public void should_recreate_pool_if_node_is_forced_back_up() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool2, pool3); } @Test @@ -459,7 +465,7 @@ public void should_adjust_distance_if_changed_while_recreating() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -475,7 +481,7 @@ public void should_adjust_distance_if_changed_while_recreating() { // Pool should have been adjusted Mockito.verify(pool2).resize(NodeDistance.REMOTE); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool2, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool2, pool3); } @Test @@ -502,7 +508,7 @@ public void should_remove_pool_if_ignored_while_recreating() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -518,7 +524,7 @@ public void should_remove_pool_if_ignored_while_recreating() { // Pool should have been closed Mockito.verify(pool2).closeAsync(); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @Test @@ -545,7 +551,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -561,7 +567,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { // Pool should have been closed Mockito.verify(pool2).closeAsync(); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @Test @@ -648,7 +654,7 @@ public void should_close_pool_if_recreated_while_closing() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); - assertThat(((DefaultSession) session).pools).containsValues(pool1, pool3); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); // node2 comes back up, start initializing a pool for it eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); @@ -667,6 +673,82 @@ public void should_close_pool_if_recreated_while_closing() { Mockito.verify(pool2).forceCloseAsync(); } + @Test + public void should_set_keyspace_on_all_pools() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + + CqlIdentifier newKeyspace = CqlIdentifier.fromInternal("newKeyspace"); + ((DefaultSession) session).setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + + Mockito.verify(pool1).setKeyspace(newKeyspace); + Mockito.verify(pool2).setKeyspace(newKeyspace); + Mockito.verify(pool3).setKeyspace(newKeyspace); + } + + @Test + public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() { + Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + CompletableFuture pool2Future = new CompletableFuture<>(); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // init + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + // when node2 comes back up + .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + DefaultSession session = + (DefaultSession) CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(session.getPools()).containsValues(pool1, pool3); + + // node2 comes back up, start initializing a pool for it + eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + + // Keyspace gets changed on the session in the meantime, node2's pool will miss it + CqlIdentifier newKeyspace = CqlIdentifier.fromInternal("newKeyspace"); + session.setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + Mockito.verify(pool1).setKeyspace(newKeyspace); + Mockito.verify(pool3).setKeyspace(newKeyspace); + + // now pool init completes + pool2Future.complete(pool2); + waitForPendingAdminTasks(); + + // Pool should have been closed + Mockito.verify(pool2).setKeyspace(newKeyspace); + } + private ChannelPool mockPool(Node node) { ChannelPool pool = Mockito.mock(ChannelPool.class); Mockito.when(pool.getNode()).thenReturn(node); From ef2b0b98e07f3740d44f248816a2627fc1d75463 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Jun 2017 10:46:39 -0700 Subject: [PATCH 063/742] Implement synchronous CQL result set Paging is temporarily disabled because it would cause a failure when the result set initializes. It will be reestablished in the next commit. --- .../driver/api/core/cql/AsyncResultSet.java | 6 + .../oss/driver/api/core/cql/ResultSet.java | 92 ++++++- .../internal/core/cql/CqlRequestHandler.java | 4 +- .../core/cql/DefaultAsyncResultSet.java | 33 ++- .../internal/core/cql/MultiPageResultSet.java | 124 ++++++++++ ...{DefaultResultSet.java => ResultSets.java} | 8 +- .../core/cql/SinglePageResultSet.java | 70 ++++++ .../internal/core/util/CountingIterator.java | 109 ++++++++ .../internal/core/cql/ResultSetsTest.java | 233 ++++++++++++++++++ 9 files changed, 663 insertions(+), 16 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{DefaultResultSet.java => ResultSets.java} (77%) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index c6fcf26b499..99b82fa231b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -25,6 +25,9 @@ * *

Note that this object can only be iterated once: rows are "consumed" as they are read, * subsequent calls to {@code iterator()} will return an empty iterator. + * + * @see CqlSession#executeAsync(Statement) + * @see CqlSession#executeAsync(String) */ public interface AsyncResultSet extends Iterable { @@ -32,6 +35,9 @@ public interface AsyncResultSet extends Iterable { ExecutionInfo getExecutionInfo(); + /** How many rows are left before the current page is exhausted. */ + int remaining(); + /** * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next * one asynchronously. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 9fcf7488fa5..2f552862127 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -15,4 +15,94 @@ */ package com.datastax.oss.driver.api.core.cql; -public interface ResultSet {} +import java.util.List; + +/** + * The result of a synchronous CQL query. + * + *

It uses {@link AsyncResultSet asynchronous calls} internally, but blocks on the results in + * order to provide a synchronous API to its clients. If the query is paged, only the first page + * will be fetched initially, and iteration will trigger background fetches of the next pages when + * necessary. + * + *

Note that this object can only be iterated once: rows are "consumed" as they are read, + * subsequent calls to {@code iterator()} will the same iterator instance. + * + *

Implementations of this type are not thread-safe. They can only be iterated by the + * thread that invoked {@code session.execute}. + * + * @see CqlSession#execute(Statement) + * @see CqlSession#execute(String) + */ +public interface ResultSet extends Iterable { + + ColumnDefinitions getColumnDefinitions(); + + /** + * The execution information for the last query performed for this result set. + * + *

This is a shortcut for: + * + *

+   * getExecutionInfos().get(getExecutionInfos().size() - 1)
+   * 
+ * + * @see #getExecutionInfos() + */ + default ExecutionInfo getExecutionInfo() { + List infos = getExecutionInfos(); + return infos.get(infos.size() - 1); + } + + /** + * The execution information for all the queries that have been performed so far to assemble this + * result set. + * + *

This will have multiple elements if the query is paged, since the driver performs blocking + * background queries to fetch additional pages transparently as the result set is being iterated. + */ + List getExecutionInfos(); + + /** + * Whether all pages have been fetched from the database. + * + *

If this is {@code false}, it means that more blocking background queries will be triggered + * as iteration continues. + */ + boolean isFullyFetched(); + + /** + * The number of rows that can be returned from this result set before a blocking background query + * needs to be performed to retrieve more results. In other words, this is the number of rows + * remaining in the current page. + */ + int getAvailableWithoutFetching(); + + /** + * Forces the driver to fetch the next page of results, regardless of whether the current page is + * exhausted. + * + *

If all pages have already been fetched ({@code isFullyFetched() == true}), this method has + * no effect. + * + *

This can be used to pre-fetch the next page early to improve performance. For example, if + * you want to start fetching the next page as soon as you reach the last 100 rows of the current + * one, you can use: + * + *

{@code
+   * Iterator iterator = rs.iterator();
+   * while (iterator.hasNext()) {
+   *   if (rs.getAvailableWithoutFetching() == 100) {
+   *     rs.fetchNextPage();
+   *   }
+   *   Row row = iterator.next();
+   *   ... process the row ...
+   * }
+   * }
+ * + *

Note that, contrary to version 3.x of the driver, this method deliberately avoids returning + * a future. If you want to iterate a multi-page result asynchronously with callbacks, use {@link + * AsyncResultSet}. + */ + void fetchNextPage(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index d75e1b22b76..a80682b2701 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -137,8 +137,8 @@ public CompletionStage asyncResult() { @Override public ResultSet syncResult() { BlockingOperation.checkNotDriverThread(); - AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(asyncResult()); - return new DefaultResultSet(asyncResultSet); + AsyncResultSet firstPage = CompletableFutures.getUninterruptibly(asyncResult()); + return ResultSets.newInstance(firstPage); } private ScheduledFuture scheduleTimeout(Duration timeout) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 29c7bfbedd9..2510aeff988 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.google.common.collect.AbstractIterator; +import com.datastax.oss.driver.internal.core.util.CountingIterator; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Iterator; @@ -32,7 +32,7 @@ public class DefaultAsyncResultSet implements AsyncResultSet { private final ColumnDefinitions definitions; private final ExecutionInfo executionInfo; - private final Queue> data; + private final CountingIterator iterator; private final InternalDriverContext context; public DefaultAsyncResultSet( @@ -42,7 +42,14 @@ public DefaultAsyncResultSet( InternalDriverContext context) { this.definitions = definitions; this.executionInfo = executionInfo; - this.data = data; + this.iterator = + new CountingIterator(data.size()) { + @Override + protected Row computeNext() { + List rowData = data.poll(); + return (rowData == null) ? endOfData() : new DefaultRow(definitions, rowData, context); + } + }; this.context = context; } @@ -58,18 +65,17 @@ public ExecutionInfo getExecutionInfo() { @Override public Iterator iterator() { - return new AbstractIterator() { - @Override - protected Row computeNext() { - List rowData = data.poll(); - return (rowData == null) ? endOfData() : new DefaultRow(definitions, rowData, context); - } - }; + return iterator; + } + + @Override + public int remaining() { + return iterator.remaining(); } @Override public boolean hasMorePages() { - throw new UnsupportedOperationException("TODO implement paging"); + return false; } @Override @@ -89,6 +95,11 @@ public ExecutionInfo getExecutionInfo() { return executionInfo; } + @Override + public int remaining() { + return 0; + } + @Override public boolean hasMorePages() { return false; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java new file mode 100644 index 00000000000..f37c1a8706e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.util.CountingIterator; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.google.common.collect.AbstractIterator; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class MultiPageResultSet implements ResultSet { + + // Reminder: by contract this is not thread-safe, so we don't need any synchronization. + + private final RowIterator iterator; + private final ColumnDefinitions columnDefinitions; + private final List executionInfos = new ArrayList<>(); + + public MultiPageResultSet(AsyncResultSet firstPage) { + assert firstPage.hasMorePages(); + this.iterator = new RowIterator(firstPage); + this.executionInfos.add(firstPage.getExecutionInfo()); + // This is the same for all pages + this.columnDefinitions = firstPage.getColumnDefinitions(); + } + + @Override + public ColumnDefinitions getColumnDefinitions() { + return columnDefinitions; + } + + @Override + public List getExecutionInfos() { + return executionInfos; + } + + @Override + public boolean isFullyFetched() { + return iterator.isFullyFetched(); + } + + @Override + public int getAvailableWithoutFetching() { + return iterator.remaining(); + } + + @Override + public void fetchNextPage() { + iterator.fetchNextPage(); + } + + @Override + public Iterator iterator() { + return iterator; + } + + private class RowIterator extends CountingIterator { + // The pages fetched so far. The first is the one we're currently iterating. + private LinkedList pages = new LinkedList<>(); + private Iterator currentRows; + + private RowIterator(AsyncResultSet firstPage) { + super(firstPage.remaining()); + this.pages.add(firstPage); + this.currentRows = firstPage.iterator(); + } + + @Override + protected Row computeNext() { + maybeMoveToNextPage(); + return currentRows.hasNext() ? currentRows.next() : endOfData(); + } + + private void maybeMoveToNextPage() { + if (!currentRows.hasNext()) { + fetchNextPage(); + // We've just finished iterating the current page, remove it + pages.removeFirst(); + if (!pages.isEmpty()) { + currentRows = pages.getFirst().iterator(); + } + } + } + + private boolean isFullyFetched() { + return pages.isEmpty() || !pages.getLast().hasMorePages(); + } + + private void fetchNextPage() { + if (!pages.isEmpty()) { + AsyncResultSet lastPage = pages.getLast(); + if (lastPage.hasMorePages()) { + BlockingOperation.checkNotDriverThread(); + AsyncResultSet nextPage = + CompletableFutures.getUninterruptibly(pages.getLast().fetchNextPage()); + executionInfos.add(nextPage.getExecutionInfo()); + pages.offer(nextPage); + remaining += nextPage.remaining(); + } + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/ResultSets.java similarity index 77% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/ResultSets.java index 8293a41372b..14c6c43cd74 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/ResultSets.java @@ -18,6 +18,10 @@ import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ResultSet; -public class DefaultResultSet implements ResultSet { - public DefaultResultSet(AsyncResultSet asyncResultSet) {} +public class ResultSets { + public static ResultSet newInstance(AsyncResultSet firstPage) { + return (firstPage.hasMorePages()) + ? new MultiPageResultSet(firstPage) + : new SinglePageResultSet(firstPage); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java new file mode 100644 index 00000000000..5663af060be --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.google.common.collect.ImmutableList; +import java.util.Iterator; +import java.util.List; + +public class SinglePageResultSet implements ResultSet { + private final AsyncResultSet onlyPage; + + public SinglePageResultSet(AsyncResultSet onlyPage) { + this.onlyPage = onlyPage; + assert !onlyPage.hasMorePages(); + } + + @Override + public ColumnDefinitions getColumnDefinitions() { + return onlyPage.getColumnDefinitions(); + } + + @Override + public ExecutionInfo getExecutionInfo() { + return onlyPage.getExecutionInfo(); + } + + @Override + public List getExecutionInfos() { + // Assuming this will be called 0 or 1 time, avoid creating the list if it's 0. + return ImmutableList.of(onlyPage.getExecutionInfo()); + } + + @Override + public boolean isFullyFetched() { + return true; + } + + @Override + public int getAvailableWithoutFetching() { + return onlyPage.remaining(); + } + + @Override + public void fetchNextPage() { + // nothing to do + } + + @Override + public Iterator iterator() { + return onlyPage.iterator(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java new file mode 100644 index 00000000000..47deb3b8472 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.google.common.base.Preconditions; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An iterator that knows in advance how many elements it will return, and maintains a counter as + * elements get returned. + */ +public abstract class CountingIterator implements Iterator { + + protected int remaining; + + public CountingIterator(int remaining) { + this.remaining = remaining; + } + + public int remaining() { + return remaining; + } + + /* + * The rest of this class was adapted from Guava's `AbstractIterator` (which we can't extend + * because its `next` method is final). Guava copyright notice follows: + * + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + private enum State { + READY, + NOT_READY, + DONE, + FAILED, + } + + private State state = State.NOT_READY; + private T next; + + protected abstract T computeNext(); + + protected final T endOfData() { + state = State.DONE; + return null; + } + + @Override + public final boolean hasNext() { + Preconditions.checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + T result = next; + next = null; + // Added to original Guava code: decrement counter when we return an element + remaining -= 1; + return result; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java new file mode 100644 index 00000000000..28e19d77ef6 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.util.CountingIterator; +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ResultSetsTest { + + @Test + public void should_create_result_set_from_single_page() { + // Given + AsyncResultSet page1 = mockPage(false, 0, 1, 2); + + // When + ResultSet resultSet = ResultSets.newInstance(page1); + + // Then + assertThat(resultSet.getColumnDefinitions()).isSameAs(page1.getColumnDefinitions()); + assertThat(resultSet.getExecutionInfo()).isSameAs(page1.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()).containsExactly(page1.getExecutionInfo()); + assertThat(resultSet.isFullyFetched()).isTrue(); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + + Iterator iterator = resultSet.iterator(); + + assertNextRow(iterator, 0); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); + + assertNextRow(iterator, 1); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); + + assertNextRow(iterator, 2); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); + + assertThat(iterator.hasNext()).isFalse(); + } + + @Test + public void should_create_result_set_from_multiple_pages() { + // Given + AsyncResultSet page1 = mockPage(true, 0, 1, 2); + AsyncResultSet page2 = mockPage(true, 3, 4, 5); + AsyncResultSet page3 = mockPage(false, 6, 7, 8); + + ((CompletableFuture) page1.fetchNextPage()).complete(page2); + ((CompletableFuture) page2.fetchNextPage()).complete(page3); + + // When + ResultSet resultSet = ResultSets.newInstance(page1); + + // Then + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + assertThat(resultSet.iterator().hasNext()).isTrue(); + + assertThat(resultSet.getColumnDefinitions()).isSameAs(page1.getColumnDefinitions()); + assertThat(resultSet.getExecutionInfo()).isSameAs(page1.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()).containsExactly(page1.getExecutionInfo()); + assertThat(resultSet.isFullyFetched()).isFalse(); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + + Iterator iterator = resultSet.iterator(); + + assertNextRow(iterator, 0); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); + + assertNextRow(iterator, 1); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); + + assertNextRow(iterator, 2); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); + + assertThat(iterator.hasNext()).isTrue(); + // This should have triggered the fetch of page2 + assertThat(resultSet.getExecutionInfo()).isEqualTo(page2.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()) + .containsExactly(page1.getExecutionInfo(), page2.getExecutionInfo()); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + + assertNextRow(iterator, 3); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); + + assertNextRow(iterator, 4); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); + + assertNextRow(iterator, 5); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); + + assertThat(iterator.hasNext()).isTrue(); + // This should have triggered the fetch of page3 + assertThat(resultSet.getExecutionInfo()).isEqualTo(page3.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()) + .containsExactly( + page1.getExecutionInfo(), page2.getExecutionInfo(), page3.getExecutionInfo()); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + + assertNextRow(iterator, 6); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); + + assertNextRow(iterator, 7); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); + + assertNextRow(iterator, 8); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); + } + + @Test + public void should_fetch_multiple_pages_in_advance() { + // Given + AsyncResultSet page1 = mockPage(true, 0, 1, 2); + AsyncResultSet page2 = mockPage(true, 3, 4, 5); + AsyncResultSet page3 = mockPage(false, 6, 7, 8); + + ((CompletableFuture) page1.fetchNextPage()).complete(page2); + ((CompletableFuture) page2.fetchNextPage()).complete(page3); + + // When + ResultSet resultSet = ResultSets.newInstance(page1); + resultSet.fetchNextPage(); + + // Then + assertThat(resultSet.getExecutionInfo()).isEqualTo(page2.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()) + .containsExactly(page1.getExecutionInfo(), page2.getExecutionInfo()); + assertThat(resultSet.iterator().hasNext()).isTrue(); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(6); + + // When + resultSet.fetchNextPage(); + + // Then + assertThat(resultSet.getExecutionInfo()).isEqualTo(page3.getExecutionInfo()); + assertThat(resultSet.getExecutionInfos()) + .containsExactly( + page1.getExecutionInfo(), page2.getExecutionInfo(), page3.getExecutionInfo()); + assertThat(resultSet.iterator().hasNext()).isTrue(); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(9); + + Iterator iterator = resultSet.iterator(); + + assertNextRow(iterator, 0); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(8); + assertNextRow(iterator, 1); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(7); + assertNextRow(iterator, 2); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(6); + assertNextRow(iterator, 3); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(5); + assertNextRow(iterator, 4); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(4); + assertNextRow(iterator, 5); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); + assertNextRow(iterator, 6); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); + assertNextRow(iterator, 7); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); + assertNextRow(iterator, 8); + assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); + } + + private void assertNextRow(Iterator iterator, int expectedValue) { + assertThat(iterator.hasNext()).isTrue(); + Row row0 = iterator.next(); + assertThat(row0.getInt(0)).isEqualTo(expectedValue); + } + + private AsyncResultSet mockPage(boolean nextPage, Integer... data) { + AsyncResultSet page = Mockito.mock(AsyncResultSet.class); + + ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); + Mockito.when(page.getColumnDefinitions()).thenReturn(columnDefinitions); + + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(page.getExecutionInfo()).thenReturn(executionInfo); + + if (nextPage) { + Mockito.when(page.hasMorePages()).thenReturn(true); + CompletableFuture nextPageFuture = Mockito.spy(new CompletableFuture<>()); + Mockito.when(page.fetchNextPage()).thenReturn(nextPageFuture); + } else { + Mockito.when(page.hasMorePages()).thenReturn(false); + Mockito.when(page.fetchNextPage()).thenThrow(new IllegalStateException()); + } + + // Emulate DefaultAsyncResultSet's internals (this is a bit sketchy, maybe it would be better + // to use real DefaultAsyncResultSet instances) + Queue queue = Lists.newLinkedList(Arrays.asList(data)); + CountingIterator iterator = + new CountingIterator(queue.size()) { + @Override + protected Row computeNext() { + Integer index = queue.poll(); + return (index == null) ? endOfData() : mockRow(index); + } + }; + Mockito.when(page.iterator()).thenReturn(iterator); + Mockito.when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); + + return page; + } + + private Row mockRow(int index) { + Row row = Mockito.mock(Row.class); + Mockito.when(row.getInt(0)).thenReturn(index); + return row; + } +} From d730bbd04e8fdd24e457503f36e44d5a6e28a2e2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Jun 2017 12:01:04 -0700 Subject: [PATCH 064/742] Add paging to async result set --- .../driver/api/core/cql/ExecutionInfo.java | 3 + .../oss/driver/api/core/cql/Statement.java | 10 +++ .../driver/internal/core/cql/Conversions.java | 12 ++- .../internal/core/cql/CqlRequestHandler.java | 11 ++- .../core/cql/DefaultAsyncResultSet.java | 26 ++++-- .../core/cql/DefaultExecutionInfo.java | 9 ++ .../core/cql/DefaultSimpleStatement.java | 18 ++++ .../core/cql/DefaultAsyncResultSetTest.java | 90 +++++++++++++++++++ 8 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 8866d84fcfb..86e51dbab7a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -25,6 +25,9 @@ /** Information about the execution of a query. */ public interface ExecutionInfo { + /** The statement that was executed. */ + Statement getStatement(); + /** The node that was used as a coordinator to successfully complete the query. */ Node getCoordinator(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 0de6800edf0..7323f541ba1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -16,10 +16,20 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.session.Request; +import java.nio.ByteBuffer; import java.util.concurrent.CompletionStage; /** A request to execute a CQL query. */ public interface Statement extends Request> { // Implementation note: "CqlRequest" would be a better name, but we keep "Statement" to match // previous driver versions. + + ByteBuffer getPagingState(); + + /** Creates a new statement with a different paging state. */ + // Implementation note: this is a simplistic first draft in order to move forward with paging, + // however more thought is needed about statement attributes: which belong to the API and which + // belong to DriverConfig, how you override them for a specific statement, whether statement + // implementations are immutable, etc. + Statement copy(ByteBuffer newPagingState); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 016cceca823..0d571ec105a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -42,6 +42,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Message; @@ -87,7 +88,6 @@ static Message toMessage( config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); boolean skipMetadata = false; // TODO set for bound statements int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); - ByteBuffer pagingState = null; // TODO paging int serialConsistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); long timestamp = Long.MIN_VALUE; // TODO timestamp generator @@ -98,7 +98,7 @@ static Message toMessage( Collections.emptyMap(), skipMetadata, pageSize, - pagingState, + statement.getPagingState(), serialConsistency, timestamp); return new Query(simpleStatement.getQuery(), queryOptions); @@ -108,7 +108,7 @@ static Message toMessage( } static AsyncResultSet toResultSet( - Result result, ExecutionInfo executionInfo, InternalDriverContext context) { + Result result, ExecutionInfo executionInfo, Session session, InternalDriverContext context) { if (result instanceof Rows) { Rows rows = (Rows) result; ImmutableList.Builder definitions = ImmutableList.builder(); @@ -116,7 +116,11 @@ static AsyncResultSet toResultSet( definitions.add(new DefaultColumnDefinition(columnSpec, context)); } return new DefaultAsyncResultSet( - new DefaultColumnDefinitions(definitions.build()), executionInfo, rows.data, context); + new DefaultColumnDefinitions(definitions.build()), + executionInfo, + rows.data, + session, + context); } else if (result instanceof Prepared) { // This should never happen throw new IllegalArgumentException("Unexpected PREPARED response to a CQL query"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index a80682b2701..19f5fd16bab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -250,7 +250,8 @@ private void setFinalResult( Result resultMessage, Frame responseFrame, NodeResponseCallback callback) { try { ExecutionInfo executionInfo = buildExecutionInfo(callback, resultMessage, responseFrame); - AsyncResultSet resultSet = Conversions.toResultSet(resultMessage, executionInfo, context); + AsyncResultSet resultSet = + Conversions.toResultSet(resultMessage, executionInfo, session, context); if (result.complete(resultSet)) { cancelScheduledTasks(); if (resultMessage instanceof SetKeyspace) { @@ -269,7 +270,13 @@ private ExecutionInfo buildExecutionInfo( ByteBuffer pagingState = (resultMessage instanceof Rows) ? ((Rows) resultMessage).metadata.pagingState : null; return new DefaultExecutionInfo( - callback.node, callback.execution, executions.get(), errors, pagingState, responseFrame); + (Statement) request, + callback.node, + callback.execution, + executions.get(), + errors, + pagingState, + responseFrame); } private void setFinalError(Throwable error) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 2510aeff988..87bfa5d6ede 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; import java.nio.ByteBuffer; @@ -27,21 +29,27 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DefaultAsyncResultSet implements AsyncResultSet { + private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncResultSet.class); + private final ColumnDefinitions definitions; private final ExecutionInfo executionInfo; + private final Session session; private final CountingIterator iterator; - private final InternalDriverContext context; public DefaultAsyncResultSet( ColumnDefinitions definitions, ExecutionInfo executionInfo, Queue> data, + Session session, InternalDriverContext context) { this.definitions = definitions; this.executionInfo = executionInfo; + this.session = session; this.iterator = new CountingIterator(data.size()) { @Override @@ -50,7 +58,6 @@ protected Row computeNext() { return (rowData == null) ? endOfData() : new DefaultRow(definitions, rowData, context); } }; - this.context = context; } @Override @@ -75,12 +82,20 @@ public int remaining() { @Override public boolean hasMorePages() { - return false; + return executionInfo.getPagingState() != null; } @Override public CompletionStage fetchNextPage() throws IllegalStateException { - throw new UnsupportedOperationException("TODO implement paging"); + ByteBuffer nextState = executionInfo.getPagingState(); + if (nextState == null) { + throw new IllegalStateException( + "No next page. Use #hasMorePages before calling this method to avoid this error."); + } + Statement statement = executionInfo.getStatement(); + LOG.debug("Fetching next page for {}", statement); + Statement nextStatement = statement.copy(nextState); + return session.executeAsync(nextStatement); } static AsyncResultSet empty(final ExecutionInfo executionInfo) { @@ -107,7 +122,8 @@ public boolean hasMorePages() { @Override public CompletionStage fetchNextPage() throws IllegalStateException { - throw new IllegalStateException("Empty result set has no next page"); + throw new IllegalStateException( + "No next page. Use #hasMorePages before calling this method to avoid this error."); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index f60d25140cb..68a58cb2a8c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import java.nio.ByteBuffer; @@ -26,6 +27,7 @@ public class DefaultExecutionInfo implements ExecutionInfo { + private final Statement statement; private final Node coordinator; private final int speculativeExecutionCount; private final int successfulExecutionIndex; @@ -36,12 +38,14 @@ public class DefaultExecutionInfo implements ExecutionInfo { private final Map customPayload; public DefaultExecutionInfo( + Statement statement, Node coordinator, int speculativeExecutionCount, int successfulExecutionIndex, List> errors, ByteBuffer pagingState, Frame frame) { + this.statement = statement; this.coordinator = coordinator; this.speculativeExecutionCount = speculativeExecutionCount; this.successfulExecutionIndex = successfulExecutionIndex; @@ -54,6 +58,11 @@ public DefaultExecutionInfo( this.customPayload = (frame == null) ? Collections.emptyMap() : frame.customPayload; } + @Override + public Statement getStatement() { + return statement; + } + @Override public Node getCoordinator() { return coordinator; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 84ef79fe034..52a94b19a51 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import java.nio.ByteBuffer; import java.util.List; public class DefaultSimpleStatement implements SimpleStatement { @@ -23,11 +24,18 @@ public class DefaultSimpleStatement implements SimpleStatement { private final String query; private final List values; private final String configProfile; + private final ByteBuffer pagingState; public DefaultSimpleStatement(String query, List values, String configProfile) { + this(query, values, configProfile, null); + } + + private DefaultSimpleStatement( + String query, List values, String configProfile, ByteBuffer pagingState) { this.query = query; this.values = values; this.configProfile = configProfile; + this.pagingState = pagingState; } @Override @@ -44,4 +52,14 @@ public List getValues() { public String getConfigProfile() { return configProfile; } + + @Override + public ByteBuffer getPagingState() { + return pagingState; + } + + @Override + public DefaultSimpleStatement copy(ByteBuffer newPagingState) { + return new DefaultSimpleStatement(query, values, configProfile, newPagingState); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java new file mode 100644 index 00000000000..1491d09ded7 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class DefaultAsyncResultSetTest { + + @Mock private ColumnDefinitions columnDefinitions; + @Mock private ExecutionInfo executionInfo; + @Mock private Statement statement; + @Mock private Session session; + @Mock private InternalDriverContext context; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(executionInfo.getStatement()).thenReturn(statement); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void should_fail_to_fetch_next_page_if_last() { + // Given + Mockito.when(executionInfo.getPagingState()).thenReturn(null); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet( + columnDefinitions, executionInfo, new LinkedList<>(), session, context); + + // Then + assertThat(resultSet.hasMorePages()).isFalse(); + resultSet.fetchNextPage(); + } + + @Test + public void should_invoke_session_to_fetch_next_page() { + // Given + ByteBuffer mockPagingState = ByteBuffer.allocate(0); + Mockito.when(executionInfo.getPagingState()).thenReturn(mockPagingState); + + Statement mockNextStatement = Mockito.mock(Statement.class); + Mockito.when(statement.copy(mockPagingState)).thenReturn(mockNextStatement); + + CompletableFuture mockResultFuture = new CompletableFuture<>(); + Mockito.when(session.executeAsync(Mockito.any(Statement.class))).thenReturn(mockResultFuture); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet( + columnDefinitions, executionInfo, new LinkedList<>(), session, context); + assertThat(resultSet.hasMorePages()).isTrue(); + CompletionStage nextPageFuture = resultSet.fetchNextPage(); + + // Then + Mockito.verify(statement).copy(mockPagingState); + Mockito.verify(session).executeAsync(mockNextStatement); + assertThat(nextPageFuture).isEqualTo(mockResultFuture); + } +} From 9689ad8be67901f77ffc266cff56e9d5d761f0c6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 12 Jun 2017 15:07:39 -0700 Subject: [PATCH 065/742] Log a warning when keyspace changes at runtime --- .../driver/api/core/config/CoreDriverOption.java | 1 + .../internal/core/session/DefaultSession.java | 14 +++++++++++++- core/src/main/resources/reference.conf | 13 +++++++++++++ .../internal/core/session/DefaultSessionTest.java | 10 ++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 5d6f21e448e..844c0df1d7c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -36,6 +36,7 @@ public enum CoreDriverOption implements DriverOption { REQUEST_CONSISTENCY("request.consistency", true), REQUEST_PAGE_SIZE("request.page-size", true), REQUEST_SERIAL_CONSISTENCY("request.serial-consistency", true), + REQUEST_WARN_IF_SET_KEYSPACE("request.warn-if-set-keyspace", true), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index a68bea0d2b7..87b7810865c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; @@ -114,7 +116,17 @@ public CqlIdentifier getKeyspace() { * wreak havoc (close all connections and make the session unusable). */ public void setKeyspace(CqlIdentifier newKeyspace) { - if (!this.keyspace.equals(newKeyspace)) { + CqlIdentifier oldKeyspace = this.keyspace; + if (!Objects.equals(oldKeyspace, newKeyspace)) { + if (config.defaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { + LOG.warn( + "Detected a keyspace change at runtime ({} => {}). " + + "This is an anti-pattern that should be avoided in production " + + "(see '{}' in the configuration).", + (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), + newKeyspace.asInternal(), + CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); + } this.keyspace = newKeyspace; RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace)); } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index a363f43f8eb..1e996ca41b4 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -116,6 +116,19 @@ datastax-java-driver { # The serial consistency level. # The allowed values are SERIAL and LOCAL_SERIAL. serial-consistency = SERIAL + + # Whether a warning is logged when a request (such as a CQL `USE ...`) changes the active + # keyspace. + # Switching keyspace at runtime is highly discouraged, because it is inherently unsafe (other + # requests expecting the old keyspace might be running concurrently), and may cause statements + # prepared before the change to fail. + # It should only be done in very specific use cases where there is only a single client thread + # executing synchronous queries (such as a cqlsh-like interpreter). In other cases, clients + # should prefix table names in their queries instead. + # + # Note that CASSANDRA-10145 (scheduled for C* 4.0) will introduce a per-request keyspace + # option as a workaround to this issue. + warn-if-set-keyspace = true } # The driver maintains a connection pool to each node, according to the distance assigned to it diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 41a0a34e294..4092ec4c02a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -16,6 +16,9 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -60,6 +63,8 @@ public class DefaultSessionTest { @Mock private ChannelPoolFactory channelPoolFactory; @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfigProfile; private DefaultNode node1; private DefaultNode node2; @@ -91,6 +96,11 @@ public void setup() { Mockito.when(metadata.getNodes()).thenReturn(nodes); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); Mockito.when(context.metadataManager()).thenReturn(metadataManager); + + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) + .thenReturn(true); + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(context.config()).thenReturn(config); } @Test From 1ffdcc737c34cb39c1f74be6bb48e8acb445c45b Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Jun 2017 09:44:47 -0700 Subject: [PATCH 066/742] Remove config.getDuration variant Having two variants is confusing in unit tests because you need to mock the exact one used in the test. It's easy enough to convert to the desired unit from client code. --- .../api/core/config/DriverConfigProfile.java | 2 -- .../ExponentialReconnectionPolicy.java | 5 ++--- .../internal/core/channel/ChannelFactory.java | 7 +++---- .../core/channel/HeartbeatHandler.java | 16 +++++++++------- .../core/channel/ProtocolInitHandler.java | 4 +--- .../typesafe/TypesafeDriverConfigProfile.java | 6 +----- .../core/channel/ChannelFactoryTestBase.java | 18 ++++++++---------- .../core/channel/ProtocolInitHandlerTest.java | 7 +++---- 8 files changed, 27 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 35d4ad333cb..5f2302fdab4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -40,7 +40,5 @@ public interface DriverConfigProfile { Duration getDuration(DriverOption option); - long getDuration(DriverOption option, TimeUnit targetUnit); - ConsistencyLevel getConsistencyLevel(DriverOption option); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index e853da9b932..fe17a0f9477 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -36,9 +36,8 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { public ExponentialReconnectionPolicy(DriverContext context) { DriverConfigProfile config = context.config().defaultProfile(); this.baseDelayMs = - config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY, TimeUnit.MILLISECONDS); - this.maxDelayMs = - config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY, TimeUnit.MILLISECONDS); + config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY).toMillis(); + this.maxDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY).toMillis(); Preconditions.checkArgument( baseDelayMs > 0, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 8d2e8162945..809a84820f8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -40,8 +40,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - /** Builds {@link DriverChannel} objects for an instance of the driver. */ public class ChannelFactory { @@ -180,8 +178,9 @@ protected void initChannel(Channel channel) throws Exception { DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); long setKeyspaceTimeoutMillis = - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS); + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .toMillis(); int maxFrameLength = (int) defaultConfigProfile.getBytes(CoreDriverOption.CONNECTION_MAX_FRAME_LENGTH); int maxRequestsPerConnection = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index 33d1101b208..ce106849807 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -25,7 +25,6 @@ import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,8 +39,9 @@ class HeartbeatHandler extends IdleStateHandler { HeartbeatHandler(DriverConfigProfile defaultConfigProfile) { super( (int) - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL, TimeUnit.SECONDS), + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL) + .getSeconds(), 0, 0); this.defaultConfigProfile = defaultConfigProfile; @@ -59,11 +59,13 @@ protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws } else { LOG.debug( "Connection was inactive for {} seconds, sending heartbeat", - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL, TimeUnit.SECONDS)); + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL) + .getSeconds()); long timeoutMillis = - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT, TimeUnit.MILLISECONDS); + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT) + .toMillis(); this.request = new HeartbeatRequest(ctx, timeoutMillis); this.request.send(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 06b963314a8..38e0ba54000 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -45,7 +45,6 @@ import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.List; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,8 +75,7 @@ class ProtocolInitHandler extends ConnectInitHandler { DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); this.timeoutMillis = - defaultConfig.getDuration( - CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS); + defaultConfig.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; this.options = options; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index bc348aeeda1..ee56e44e5c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.typesafe.config.Config; +import com.typesafe.config.ConfigValueFactory; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -50,11 +51,6 @@ public Duration getDuration(DriverOption option) { return config.getDuration(option.getPath()); } - @Override - public long getDuration(DriverOption option, TimeUnit targetUnit) { - return config.getDuration(option.getPath(), targetUnit); - } - @Override public String getString(DriverOption option) { return config.getString(option.getPath()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 7a26ddeba73..f1ce6968389 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -42,6 +42,7 @@ import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; import java.net.SocketAddress; +import java.time.Duration; import java.util.Collections; import java.util.Optional; import java.util.concurrent.Exchanger; @@ -105,14 +106,10 @@ public void setup() throws InterruptedException { Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) .thenReturn(false); - Mockito.when( - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS)) - .thenReturn(100L); - Mockito.when( - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS)) - .thenReturn(100L); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + .thenReturn(Duration.ofMillis(100)); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) + .thenReturn(Duration.ofMillis(100)); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); @@ -225,8 +222,9 @@ protected void initChannel(Channel channel) throws Exception { DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); long setKeyspaceTimeoutMillis = - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT, MILLISECONDS); + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .toMillis(); int maxRequestsPerConnection = defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 477d5ae25a5..f9cfdc6e2b1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -42,6 +42,7 @@ import com.google.common.collect.ImmutableList; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -69,10 +70,8 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when( - defaultConfigProfile.getDuration( - CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, TimeUnit.MILLISECONDS)) - .thenReturn(QUERY_TIMEOUT_MILLIS); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); From a6b6f68ea5252367ec6ac3c5b2c663f272602f21 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Jun 2017 10:34:08 -0700 Subject: [PATCH 067/742] Add methods to copy config profile programatically --- .../api/core/config/DriverConfigProfile.java | 26 ++++++- .../typesafe/TypesafeDriverConfigProfile.java | 73 ++++++++++++++++--- .../typesafe/TypeSafeDriverConfigTest.java | 22 ++++++ 3 files changed, 109 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 5f2302fdab4..76af88fb1d7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -18,11 +18,20 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; /** * A profile in the driver's configuration. * + *

It is a collection of typed options. + * + *

Getters (such as {@link #getBoolean(DriverOption)}) are self-explanatory. + * + *

{@code withXxx} methods (such as {@link #withBoolean(DriverOption, boolean)}) create an + * on-the-fly copy of the profile with the new value (which might be a new option, or + * overwrite an existing one). Such on-the-fly profiles should be used sparingly: each call creates + * a new instance; if you have multiple option to customize, it is better to create a profile in the + * base configuration. + * * @see DriverConfig */ public interface DriverConfigProfile { @@ -30,15 +39,30 @@ public interface DriverConfigProfile { boolean getBoolean(DriverOption option); + DriverConfigProfile withBoolean(DriverOption option, boolean value); + int getInt(DriverOption option); + DriverConfigProfile withInt(DriverOption option, int value); + String getString(DriverOption option); + DriverConfigProfile withString(DriverOption option, String value); + List getStringList(DriverOption option); + DriverConfigProfile withStringList(DriverOption option, List value); + + /** Returns a size in bytes. */ long getBytes(DriverOption option); + DriverConfigProfile withBytes(DriverOption option, long value); + Duration getDuration(DriverOption option); + DriverConfigProfile withDuration(DriverOption option, Duration value); + ConsistencyLevel getConsistencyLevel(DriverOption option); + + DriverConfigProfile withConsistencyLevel(DriverOption option, ConsistencyLevel value); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index ee56e44e5c7..ed977a541ee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -19,51 +19,92 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; public class TypesafeDriverConfigProfile implements DriverConfigProfile { - private volatile Config config; + // The base options loaded from the driver's configuration + private volatile Config base; + // Any extras that were configured manually using withXxx methods + private final Config extras; + // The actual options returned by getXxx methods (which is a merge of the previous two) + private volatile Config actual; - public TypesafeDriverConfigProfile(Config config) { - this.config = config; + public TypesafeDriverConfigProfile(Config base) { + this(base, ConfigFactory.empty()); + } + + private TypesafeDriverConfigProfile(Config base, Config extras) { + this.base = base; + this.extras = extras; + this.actual = extras.withFallback(base); } @Override public boolean isDefined(DriverOption option) { - return config.hasPath(option.getPath()); + return actual.hasPath(option.getPath()); } @Override public boolean getBoolean(DriverOption option) { - return config.getBoolean(option.getPath()); + return actual.getBoolean(option.getPath()); + } + + @Override + public DriverConfigProfile withBoolean(DriverOption option, boolean value) { + return with(option, value); } @Override public int getInt(DriverOption option) { - return config.getInt(option.getPath()); + return actual.getInt(option.getPath()); + } + + @Override + public DriverConfigProfile withInt(DriverOption option, int value) { + return with(option, value); } @Override public Duration getDuration(DriverOption option) { - return config.getDuration(option.getPath()); + return actual.getDuration(option.getPath()); + } + + @Override + public DriverConfigProfile withDuration(DriverOption option, Duration value) { + return with(option, value); } @Override public String getString(DriverOption option) { - return config.getString(option.getPath()); + return actual.getString(option.getPath()); + } + + @Override + public DriverConfigProfile withString(DriverOption option, String value) { + return with(option, value); } @Override public List getStringList(DriverOption option) { - return config.getStringList(option.getPath()); + return actual.getStringList(option.getPath()); + } + + @Override + public DriverConfigProfile withStringList(DriverOption option, List value) { + return with(option, value); } @Override public long getBytes(DriverOption option) { - return config.getBytes(option.getPath()); + return actual.getBytes(option.getPath()); + } + + @Override + public DriverConfigProfile withBytes(DriverOption option, long value) { + return with(option, value); } @Override @@ -71,4 +112,14 @@ public ConsistencyLevel getConsistencyLevel(DriverOption option) { String name = getString(option); return ConsistencyLevel.valueOf(name); } + + @Override + public DriverConfigProfile withConsistencyLevel(DriverOption option, ConsistencyLevel value) { + return with(option, value.toString()); + } + + private DriverConfigProfile with(DriverOption option, Object v) { + return new TypesafeDriverConfigProfile( + base, extras.withValue(option.getPath(), ConfigValueFactory.fromAnyRef(v))); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 0b13febf9fe..aed4446baec 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.testng.annotations.Test; @@ -69,6 +70,27 @@ public void should_load_default_driver_config() { ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); } + @Test + public void should_create_on_the_fly_profile_with_new_option() { + DriverConfig config = parse("required_int = 42"); + DriverConfigProfile base = config.defaultProfile(); + DriverConfigProfile copy = base.withInt(MockOptions.OPTIONAL_INT, 43); + + assertThat(base.isDefined(MockOptions.OPTIONAL_INT)).isFalse(); + assertThat(copy.isDefined(MockOptions.OPTIONAL_INT)).isTrue(); + assertThat(copy.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(43); + } + + @Test + public void should_create_on_the_fly_profile_overriding_option() { + DriverConfig config = parse("required_int = 42"); + DriverConfigProfile base = config.defaultProfile(); + DriverConfigProfile copy = base.withInt(MockOptions.REQUIRED_INT, 43); + + assertThat(base.getInt(MockOptions.REQUIRED_INT)).isEqualTo(42); + assertThat(copy.getInt(MockOptions.REQUIRED_INT)).isEqualTo(43); + } + private DriverConfig parse(String configString) { Config config = ConfigFactory.parseString(configString); return new TypeSafeDriverConfig(config, MockOptions.values()); From c87146511e216819e62beb40d87eeaf9ad552722 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Jun 2017 16:16:45 -0700 Subject: [PATCH 068/742] Make configuration reloadable --- .../api/core/config/DriverConfigProfile.java | 10 +- .../config/typesafe/TypeSafeDriverConfig.java | 119 +++++++++++---- .../typesafe/TypesafeDriverConfigProfile.java | 140 ++++++++++++++---- .../typesafe/TypeSafeDriverConfigTest.java | 64 ++++++-- 4 files changed, 259 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 76af88fb1d7..ff31092b4a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -26,11 +26,11 @@ * *

Getters (such as {@link #getBoolean(DriverOption)}) are self-explanatory. * - *

{@code withXxx} methods (such as {@link #withBoolean(DriverOption, boolean)}) create an - * on-the-fly copy of the profile with the new value (which might be a new option, or - * overwrite an existing one). Such on-the-fly profiles should be used sparingly: each call creates - * a new instance; if you have multiple option to customize, it is better to create a profile in the - * base configuration. + *

{@code withXxx} methods (such as {@link #withBoolean(DriverOption, boolean)}) create a + * "derived" profile, which is an on-the-fly copy of the profile with the new value (which + * might be a new option, or overwrite an existing one). If the original configuration is reloaded, + * all derived profiles get updated as well. For best performance, such derived profiles should be + * used sparingly; it is better to have built-in profiles for common scenarios. * * @see DriverConfig */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 2d8e5ba5aa0..6a9ce613e53 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -18,54 +18,115 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValue; import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.Collections; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.typesafe.config.ConfigValueType.OBJECT; public class TypeSafeDriverConfig implements DriverConfig { - private final TypesafeDriverConfigProfile defaultProfile; - private final ConcurrentMap profiles; + private static final Logger LOG = LoggerFactory.getLogger(TypeSafeDriverConfig.class); + private static final String DEFAULT_PROFILE_KEY = "__default_internal__"; + + private final Collection options; + private final TypesafeDriverConfigProfile.Base defaultProfile; + private final Map profiles; public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) { - Collection options = merge(optionArrays); - - // Process the raw configuration to extract profiles. For example: - // { - // foo = 1, bar = 2 - // profiles { - // custom1 { bar = 3 } - // } - // } - // Would produce a map with the following entries: - // "default" => { foo = 1, bar = 2 } - // "custom1" => { foo = 1, bar = 3 } - - Config defaultProfileConfig = config.withoutPath("profiles"); + this.options = merge(optionArrays); + + Map profileConfigs = extractProfiles(config); + this.defaultProfile = + new TypesafeDriverConfigProfile.Base(profileConfigs.get(DEFAULT_PROFILE_KEY)); + + if (profileConfigs.size() == 1) { + this.profiles = Collections.emptyMap(); + } else { + ImmutableMap.Builder builder = + ImmutableMap.builder(); + for (Map.Entry entry : profileConfigs.entrySet()) { + String profileName = entry.getKey(); + if (!profileName.equals(DEFAULT_PROFILE_KEY)) { + builder.put(profileName, new TypesafeDriverConfigProfile.Base(entry.getValue())); + } + } + this.profiles = builder.build(); + } + } + + public void reload(Config config) { + Map profileConfigs = extractProfiles(config); + this.defaultProfile.refresh(profileConfigs.get(DEFAULT_PROFILE_KEY)); + if (profileConfigs.size() > 1) { + for (Map.Entry entry : profileConfigs.entrySet()) { + String profileName = entry.getKey(); + if (!profileName.equals(DEFAULT_PROFILE_KEY)) { + TypesafeDriverConfigProfile.Base profile = this.profiles.get(profileName); + if (profile == null) { + LOG.warn( + String.format( + "Unknown profile '%s' while reloading configuration. " + + "Adding profiles at runtime is not supported.", + profileName)); + } else { + profile.refresh(entry.getValue()); + } + } + } + } + } + + /* + * Processes the raw configuration to extract profiles. For example: + * { + * foo = 1, bar = 2 + * profiles { + * custom1 { bar = 3 } + * } + * } + * Would produce: + * DEFAULT_PROFILE_KEY => { foo = 1, bar = 2 } + * "custom1" => { foo = 1, bar = 3 } + */ + private Map extractProfiles(Config sourceConfig) { + ImmutableMap.Builder result = ImmutableMap.builder(); + + Config defaultProfileConfig = sourceConfig.withoutPath("profiles"); validateRequired(defaultProfileConfig, options); - this.defaultProfile = new TypesafeDriverConfigProfile(defaultProfileConfig); + result.put(DEFAULT_PROFILE_KEY, defaultProfileConfig); - this.profiles = new ConcurrentHashMap<>(); - ConfigObject rootObject = config.root(); + // The rest of the method is a bit confusing because we navigate between Typesafe config's two + // APIs, see https://github.com/typesafehub/config#understanding-config-and-configobject + // In an attempt to clarify: + // xxxObject = `ConfigObject` API (config as a hierarchical structure) + // xxxConfig = `Config` API (config as a flat set of options with hierarchical paths) + ConfigObject rootObject = sourceConfig.root(); if (rootObject.containsKey("profiles") && rootObject.get("profiles").valueType() == OBJECT) { - ConfigObject profileConfigs = (ConfigObject) rootObject.get("profiles"); - for (String profileName : profileConfigs.keySet()) { - ConfigValue profileValue = profileConfigs.get(profileName); - if (profileValue.valueType() == OBJECT) { - Config profileConfig = ((ConfigObject) profileValue).toConfig(); - this.profiles.put( - profileName, - new TypesafeDriverConfigProfile(profileConfig.withFallback(defaultProfileConfig))); + ConfigObject profilesObject = (ConfigObject) rootObject.get("profiles"); + for (String profileName : profilesObject.keySet()) { + if (profileName.equals(DEFAULT_PROFILE_KEY)) { + throw new IllegalArgumentException( + String.format( + "Can't have %s as a profile name because it's used internally. Pick another name.", + profileName)); + } + ConfigValue profileObject = profilesObject.get(profileName); + if (profileObject.valueType() == OBJECT) { + Config profileConfig = ((ConfigObject) profileObject).toConfig(); + result.put(profileName, profileConfig.withFallback(defaultProfileConfig)); } } } + return result.build(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index ed977a541ee..9daf486fbcc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -18,38 +18,34 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.google.common.collect.MapMaker; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import java.time.Duration; +import java.util.Collections; import java.util.List; +import java.util.Set; -public class TypesafeDriverConfigProfile implements DriverConfigProfile { - // The base options loaded from the driver's configuration - private volatile Config base; - // Any extras that were configured manually using withXxx methods - private final Config extras; - // The actual options returned by getXxx methods (which is a merge of the previous two) - private volatile Config actual; +public abstract class TypesafeDriverConfigProfile implements DriverConfigProfile { - public TypesafeDriverConfigProfile(Config base) { - this(base, ConfigFactory.empty()); - } + /** The original profile in the driver's configuration that this profile was derived from. */ + protected abstract Base getBaseProfile(); - private TypesafeDriverConfigProfile(Config base, Config extras) { - this.base = base; - this.extras = extras; - this.actual = extras.withFallback(base); - } + /** The extra options that were added with {@code withXxx} methods. */ + protected abstract Config getAddedOptions(); + + /** The actual options that will be used to answer {@code getXxx} calls. */ + protected abstract Config getEffectiveOptions(); @Override public boolean isDefined(DriverOption option) { - return actual.hasPath(option.getPath()); + return getEffectiveOptions().hasPath(option.getPath()); } @Override public boolean getBoolean(DriverOption option) { - return actual.getBoolean(option.getPath()); + return getEffectiveOptions().getBoolean(option.getPath()); } @Override @@ -59,7 +55,7 @@ public DriverConfigProfile withBoolean(DriverOption option, boolean value) { @Override public int getInt(DriverOption option) { - return actual.getInt(option.getPath()); + return getEffectiveOptions().getInt(option.getPath()); } @Override @@ -69,7 +65,7 @@ public DriverConfigProfile withInt(DriverOption option, int value) { @Override public Duration getDuration(DriverOption option) { - return actual.getDuration(option.getPath()); + return getEffectiveOptions().getDuration(option.getPath()); } @Override @@ -79,7 +75,7 @@ public DriverConfigProfile withDuration(DriverOption option, Duration value) { @Override public String getString(DriverOption option) { - return actual.getString(option.getPath()); + return getEffectiveOptions().getString(option.getPath()); } @Override @@ -89,7 +85,7 @@ public DriverConfigProfile withString(DriverOption option, String value) { @Override public List getStringList(DriverOption option) { - return actual.getStringList(option.getPath()); + return getEffectiveOptions().getStringList(option.getPath()); } @Override @@ -99,7 +95,7 @@ public DriverConfigProfile withStringList(DriverOption option, List valu @Override public long getBytes(DriverOption option) { - return actual.getBytes(option.getPath()); + return getEffectiveOptions().getBytes(option.getPath()); } @Override @@ -118,8 +114,102 @@ public DriverConfigProfile withConsistencyLevel(DriverOption option, Consistency return with(option, value.toString()); } - private DriverConfigProfile with(DriverOption option, Object v) { - return new TypesafeDriverConfigProfile( - base, extras.withValue(option.getPath(), ConfigValueFactory.fromAnyRef(v))); + private DriverConfigProfile with(DriverOption option, Object value) { + Base base = getBaseProfile(); + // Add the new option to any already derived options + Config newAdded = + getAddedOptions().withValue(option.getPath(), ConfigValueFactory.fromAnyRef(value)); + Derived derived = new Derived(base, newAdded); + base.register(derived); + return derived; + } + + /** A profile that was loaded directly from the driver's configuration. */ + static class Base extends TypesafeDriverConfigProfile { + + private volatile Config options; + private volatile Set derivedProfiles; + + Base(Config options) { + this.options = options; + } + + @Override + protected Base getBaseProfile() { + return this; + } + + @Override + protected Config getAddedOptions() { + return ConfigFactory.empty(); + } + + @Override + protected Config getEffectiveOptions() { + return options; + } + + void refresh(Config newOptions) { + this.options = newOptions; + if (derivedProfiles != null) { + for (Derived derivedProfile : derivedProfiles) { + derivedProfile.refresh(); + } + } + } + + void register(Derived derivedProfile) { + getDerivedProfiles().add(derivedProfile); + } + + // Lazy init + private Set getDerivedProfiles() { + Set result = derivedProfiles; + if (result == null) { + synchronized (this) { + result = derivedProfiles; + if (result == null) { + derivedProfiles = + result = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); + } + } + } + return result; + } + } + + /** + * A profile that was copied from another profile programatically using {@code withXxx} methods. + */ + static class Derived extends TypesafeDriverConfigProfile { + + private final Base baseProfile; + private final Config addedOptions; + private volatile Config effectiveOptions; + + Derived(Base baseProfile, Config addedOptions) { + this.baseProfile = baseProfile; + this.addedOptions = addedOptions; + refresh(); + } + + void refresh() { + this.effectiveOptions = addedOptions.withFallback(baseProfile.getEffectiveOptions()); + } + + @Override + protected Base getBaseProfile() { + return baseProfile; + } + + @Override + protected Config getAddedOptions() { + return addedOptions; + } + + @Override + protected Config getEffectiveOptions() { + return effectiveOptions; + } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index aed4446baec..5945c0b2b4f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.config.typesafe; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -28,13 +27,13 @@ public class TypeSafeDriverConfigTest { @Test public void should_load_minimal_config_with_required_options_and_no_profiles() { - DriverConfig config = parse("required_int = 42"); + TypeSafeDriverConfig config = parse("required_int = 42"); assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); } @Test public void should_load_config_with_no_profiles_and_optional_values() { - DriverConfig config = parse("required_int = 42\n optional_int = 43"); + TypeSafeDriverConfig config = parse("required_int = 42\n optional_int = 43"); assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); assertThat(config).hasIntOption(MockOptions.OPTIONAL_INT, 43); } @@ -49,7 +48,7 @@ public void should_fail_if_required_option_is_missing() { @Test public void should_inherit_option_in_profile() { - DriverConfig config = parse("required_int = 42\n profiles { profile1 { } }"); + TypeSafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { } }"); assertThat(config) .hasIntOption(MockOptions.REQUIRED_INT, 42) .hasIntOption("profile1", MockOptions.REQUIRED_INT, 42); @@ -57,7 +56,8 @@ public void should_inherit_option_in_profile() { @Test public void should_override_option_in_profile() { - DriverConfig config = parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); + TypeSafeDriverConfig config = + parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); assertThat(config) .hasIntOption(MockOptions.REQUIRED_INT, 42) .hasIntOption("profile1", MockOptions.REQUIRED_INT, 43); @@ -71,27 +71,61 @@ public void should_load_default_driver_config() { } @Test - public void should_create_on_the_fly_profile_with_new_option() { - DriverConfig config = parse("required_int = 42"); + public void should_create_derived_profile_with_new_option() { + TypeSafeDriverConfig config = parse("required_int = 42"); DriverConfigProfile base = config.defaultProfile(); - DriverConfigProfile copy = base.withInt(MockOptions.OPTIONAL_INT, 43); + DriverConfigProfile derived = base.withInt(MockOptions.OPTIONAL_INT, 43); assertThat(base.isDefined(MockOptions.OPTIONAL_INT)).isFalse(); - assertThat(copy.isDefined(MockOptions.OPTIONAL_INT)).isTrue(); - assertThat(copy.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(43); + assertThat(derived.isDefined(MockOptions.OPTIONAL_INT)).isTrue(); + assertThat(derived.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(43); } @Test - public void should_create_on_the_fly_profile_overriding_option() { - DriverConfig config = parse("required_int = 42"); + public void should_create_derived_profile_overriding_option() { + TypeSafeDriverConfig config = parse("required_int = 42"); DriverConfigProfile base = config.defaultProfile(); - DriverConfigProfile copy = base.withInt(MockOptions.REQUIRED_INT, 43); + DriverConfigProfile derived = base.withInt(MockOptions.REQUIRED_INT, 43); assertThat(base.getInt(MockOptions.REQUIRED_INT)).isEqualTo(42); - assertThat(copy.getInt(MockOptions.REQUIRED_INT)).isEqualTo(43); + assertThat(derived.getInt(MockOptions.REQUIRED_INT)).isEqualTo(43); } - private DriverConfig parse(String configString) { + @Test + public void should_reload() { + TypeSafeDriverConfig config = + parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); + + config.reload( + ConfigFactory.parseString( + "required_int = 44\n profiles { profile1 { required_int = 45 } }")); + assertThat(config) + .hasIntOption(MockOptions.REQUIRED_INT, 44) + .hasIntOption("profile1", MockOptions.REQUIRED_INT, 45); + } + + @Test + public void should_update_derived_profiles_after_reloading() { + TypeSafeDriverConfig config = + parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); + + DriverConfigProfile derivedFromDefault = + config.defaultProfile().withInt(MockOptions.OPTIONAL_INT, 50); + DriverConfigProfile derivedFromProfile1 = + config.getProfile("profile1").withInt(MockOptions.OPTIONAL_INT, 51); + + config.reload( + ConfigFactory.parseString( + "required_int = 44\n profiles { profile1 { required_int = 45 } }")); + + assertThat(derivedFromDefault.getInt(MockOptions.REQUIRED_INT)).isEqualTo(44); + assertThat(derivedFromDefault.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(50); + + assertThat(derivedFromProfile1.getInt(MockOptions.REQUIRED_INT)).isEqualTo(45); + assertThat(derivedFromProfile1.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(51); + } + + private TypeSafeDriverConfig parse(String configString) { Config config = ConfigFactory.parseString(configString); return new TypeSafeDriverConfig(config, MockOptions.values()); } From 30121daa37df6ec32558bf2189b9cf218e236cd9 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 14 Jun 2017 16:11:08 -0700 Subject: [PATCH 069/742] Revisit request-level configuration - add missing parameters (tracing, custom payload, etc.) - add idempotence and use it in CqlRequestHandler - add named parameters to SimpleStatement and move to a builder --- .../api/core/config/CoreDriverOption.java | 1 + .../oss/driver/api/core/cql/CqlSession.java | 6 +- .../driver/api/core/cql/PrepareRequest.java | 10 + .../driver/api/core/cql/SimpleStatement.java | 102 +++++++++- .../api/core/cql/SimpleStatementBuilder.java | 182 ++++++++++++++++++ .../oss/driver/api/core/session/Request.java | 70 ++++++- .../driver/internal/core/cql/Conversions.java | 50 ++++- .../internal/core/cql/CqlRequestHandler.java | 81 +++++--- .../core/cql/DefaultSimpleStatement.java | 106 ++++++++-- .../core/session/RequestHandlerBase.java | 22 ++- core/src/main/resources/reference.conf | 4 + .../oss/driver/TestDataProviders.java | 52 +++++ .../core/cql/CqlRequestHandlerRetryTest.java | 118 +++++++++--- ...equestHandlerSpeculativeExecutionTest.java | 77 +++++--- .../core/cql/CqlRequestHandlerTest.java | 9 +- .../core/cql/CqlRequestHandlerTestBase.java | 42 +++- .../core/cql/RequestHandlerTestHarness.java | 8 + 17 files changed, 817 insertions(+), 123 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 844c0df1d7c..3952c181e41 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -37,6 +37,7 @@ public enum CoreDriverOption implements DriverOption { REQUEST_PAGE_SIZE("request.page-size", true), REQUEST_SERIAL_CONSISTENCY("request.serial-consistency", true), REQUEST_WARN_IF_SET_KEYSPACE("request.warn-if-set-keyspace", true), + REQUEST_DEFAULT_IDEMPOTENCE("request.default-idempotence", true), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java index 99fdd0a36e8..db5353373e5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -17,8 +17,6 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; -import java.util.Collections; import java.util.concurrent.CompletionStage; public interface CqlSession extends Session { @@ -34,11 +32,11 @@ default CompletionStage executeAsync(Statement statement) { } default ResultSet execute(String query) { - return execute(new DefaultSimpleStatement(query, Collections.emptyList(), null)); + return execute(SimpleStatement.newInstance(query)); } default CompletionStage executeAsync(String query) { - return executeAsync(new DefaultSimpleStatement(query, Collections.emptyList(), null)); + return executeAsync(SimpleStatement.newInstance(query)); } default PreparedStatement prepare(String query) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index d8bcc8febc2..d203a7f443f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -29,4 +29,14 @@ static PrepareRequest from(String query) { static PrepareRequest from(Statement statement) { throw new UnsupportedOperationException("TODO"); } + + @Override + default Boolean isIdempotent() { + return true; // Retrying to prepare is always safe + } + + @Override + default boolean isTracing() { + return false; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index b6f5fe2b24e..c6e3cac28f1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -15,11 +15,111 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; public interface SimpleStatement extends Statement { + /** + * The CQL query to execute. + * + *

It may contain anonymous placeholders identified by a question mark, as in: + * + *

+   *   SELECT username FROM user WHERE id = ?
+   * 
+ * + * Or named placeholders prefixed by a column, as in: + * + *
+   *   SELECT username FROM user WHERE id = :i
+   * 
+ * + * @see #getPositionalValues() + * @see #getNamedValues() + */ String getQuery(); - List getValues(); + /** + * A list of positional values to bind to anonymous placeholders. + * + *

You can use either positional or named values, but not both. Therefore if this method + * returns a non-empty list, then {@link #getNamedValues()} must return an empty map, otherwise a + * runtime error will be thrown. + * + * @see #getQuery() + */ + List getPositionalValues(); + + /** + * A list of named values to bind to named placeholders. + * + *

Names must be stripped of the leading column. + * + *

You can use either positional or named values, but not both. Therefore if this method + * returns a non-empty map, then {@link #getPositionalValues()} must return an empty list, + * otherwise a runtime error will be thrown. + * + * @see #getQuery() + */ + Map getNamedValues(); + + /** + * Shortcut to create an instance of the default implementation with only a CQL query (see {@link + * SimpleStatementBuilder} for the defaults for the other fields). + */ + static SimpleStatement newInstance(String cqlQuery) { + return new DefaultSimpleStatement( + cqlQuery, + Collections.emptyList(), + Collections.emptyMap(), + null, + null, + Collections.emptyMap(), + null, + false, + null); + } + + /** + * Shortcut to create an instance of the default implementation with only a CQL query and + * positional values (see {@link SimpleStatementBuilder} for the defaults for the other fields). + */ + static SimpleStatement newInstance(String cqlQuery, Object... positionalValues) { + return new DefaultSimpleStatement( + cqlQuery, + Arrays.asList(positionalValues), + Collections.emptyMap(), + null, + null, + Collections.emptyMap(), + null, + false, + null); + } + + /** + * Shortcut to create an instance of the default implementation with only a CQL query and named + * values (see {@link SimpleStatementBuilder} for the defaults for other fields). + */ + static SimpleStatement newInstance(String cqlQuery, Map namedValues) { + return new DefaultSimpleStatement( + cqlQuery, + Collections.emptyList(), + namedValues, + null, + null, + Collections.emptyMap(), + null, + false, + null); + } + + /** Returns a builder to create an instance of the default implementation. */ + static SimpleStatementBuilder builder(String query) { + return new SimpleStatementBuilder(query); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java new file mode 100644 index 00000000000..a8d99192ee2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; +import com.google.common.collect.ImmutableMap; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SimpleStatementBuilder { + + private final String query; + private List positionalValues = Collections.emptyList(); + private ImmutableMap.Builder namedValues; + private String configProfileName; + private DriverConfigProfile configProfile; + private ImmutableMap.Builder customPayload; + private Boolean idempotent; + private boolean tracing; + private ByteBuffer pagingState; + + public SimpleStatementBuilder(String query) { + this.query = query; + } + + /** + * Adds a value for an anonymous placeholder. + * + *

Values must be added in the order they appear in the query string. You can also use {@link + * #withPositionalValues(Object...)} to add multiple values at once. + * + * @throws IllegalArgumentException if named values were already added for this statement; you + * can't mix both. + * @see SimpleStatement#getPositionalValues() + */ + public SimpleStatementBuilder withPositionalValue(Object value) { + return withPositionalValues(value); + } + + /** + * Adds values for multiple anonymous placeholders. + * + *

Values must be added in the order they appear in the query string. + * + * @throws IllegalArgumentException if named values were already added for this statement; you + * can't mix both. + * @see SimpleStatement#getPositionalValues() + */ + public SimpleStatementBuilder withPositionalValues(Object... values) { + if (namedValues != null) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); + } + if (positionalValues.isEmpty()) { + positionalValues = new ArrayList<>(); + } + Collections.addAll(positionalValues, values); + return this; + } + + /** + * Adds a value for a named placeholder in the query string. + * + * @throws IllegalArgumentException if positional values were already added for this statement; + * you can't mix both. + * @see SimpleStatement#getPositionalValues() + */ + public SimpleStatementBuilder withNamedValue(String name, Object value) { + if (!positionalValues.isEmpty()) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); + } + if (namedValues == null) { + namedValues = ImmutableMap.builder(); + } + namedValues.put(name, value); + return this; + } + + /** + * Sets the name of the config profile to use for this statement. + * + *

Note that {@link #withConfigProfile(DriverConfigProfile)} will override this. + * + * @see SimpleStatement#getConfigProfileName() + */ + public SimpleStatementBuilder withConfigProfileName(String configProfileName) { + this.configProfileName = configProfileName; + return this; + } + + /** + * Sets the config profile to use for this statement. + * + * @see SimpleStatement#getConfigProfile() + */ + public SimpleStatementBuilder withConfigProfile(DriverConfigProfile configProfile) { + this.configProfile = configProfile; + this.configProfileName = null; + return this; + } + + /** + * Adds an entry in the custom payload of this statement. + * + * @see SimpleStatement#getCustomPayload() + */ + public SimpleStatementBuilder withCustomPayload(String key, ByteBuffer value) { + if (customPayload == null) { + customPayload = ImmutableMap.builder(); + } + customPayload.put(key, value); + return this; + } + + /** + * Indicates whether this statement is idempotent. + * + *

If this method is not called, the statement will fallback to the default defined in the + * configuration. + * + * @see SimpleStatement#isIdempotent() + */ + public SimpleStatementBuilder withIdempotence(boolean idempotent) { + this.idempotent = idempotent; + return this; + } + + /** + * Indicates that tracing information should be requested when executing this statement. + * + *

If this method is not called, the statement will not be tracing. + * + * @see SimpleStatement#isTracing() + */ + public SimpleStatementBuilder withTracing() { + this.tracing = true; + return this; + } + + /** + * Sets the paging state to use for this statement. + * + *

Note that it is also possible to set the paging state on an existing statement with {@link + * Statement#copy(ByteBuffer)}. + * + * @see Statement#getPagingState() + */ + public SimpleStatementBuilder withPagingState(ByteBuffer pagingState) { + this.pagingState = pagingState; + return this; + } + + public SimpleStatement build() { + return new DefaultSimpleStatement( + query, + positionalValues, + (namedValues == null) ? Collections.emptyMap() : namedValues.build(), + configProfileName, + configProfile, + (customPayload == null) ? Collections.emptyMap() : customPayload.build(), + idempotent, + tracing, + pagingState); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 31520f90a3b..0959d7b77f3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -15,10 +15,16 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import java.nio.ByteBuffer; +import java.util.Map; + /** * A request executed by a {@link Session}. * - *

This is a high-level abstraction, agnostic to the actual language (e.g. CQL). A request can be + *

This is a high-level abstraction, agnostic to the actual language (e.g. CQL). A request is * anything that can be converted to a protocol message, provided that you register a request * processor with the driver to do that conversion. * @@ -27,6 +33,64 @@ */ public interface Request { - /** The name of the configuration profile that will be used for execution. */ - String getConfigProfile(); + /** + * The name of the driver configuration profile that will be used for execution. + * + *

Note that this will be ignored if {@link #getConfigProfile()} returns a non-null value. + * + * @see DriverConfig + */ + String getConfigProfileName(); + + /** + * The configuration profile to use for execution. + * + *

It is generally simpler to specify a profile name with {@link #getConfigProfileName()}. + * However, this method can be used to provide a "derived" profile that was built programatically + * by the client code. If specified, it overrides the profile name. + * + * @see DriverConfigProfile + */ + DriverConfigProfile getConfigProfile(); + + /** + * NOT YET SUPPORTED -- the CQL keyspace to associate with the query. + * + *

This will be available when CASSANDRA-10145 is merged in a + * stable server release. In the meantime, the method is present to avoid breaking the API later, + * but returning any value other than {@code null} will cause a runtime exception. + */ + String getKeyspace(); + + /** + * Returns the custom payload to send alongside the request. + * + *

This is used to exchange extra information with the server. By default, Cassandra doesn't do + * anything with this, you'll only need it if you have a custom request handler on the + * server-side. + */ + Map getCustomPayload(); + + /** + * Whether the request is idempotent; that is, whether applying the request twice yields the same + * result. + * + *

This is used internally for retries and speculative executions: if a request is not + * idempotent, the driver will take extra care to ensure that it is not sent twice (for example, + * don't retry if there is the slightest chance that the request reached a coordinator). + * + * @return a boolean value, or {@code null} to use the default value defined in the configuration. + * @see CoreDriverOption#REQUEST_DEFAULT_IDEMPOTENCE + */ + Boolean isIdempotent(); + + /** + * Whether tracing information should be recorded for this request. + * + *

Tracing is rather specific to CQL, but this is exposed in this interface because it is + * available at the protocol level. Request implementations are free to use it if it is relevant + * to them, or always return {@code false} otherwise. + */ + boolean isTracing(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 0d571ec105a..9ef963dcb3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -61,10 +62,12 @@ import com.datastax.oss.protocol.internal.response.result.Prepared; import com.datastax.oss.protocol.internal.response.result.Rows; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; /** * Utility methods to convert to/from protocol messages. @@ -78,15 +81,16 @@ static Message toMessage( if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; - CodecRegistry codecRegistry = context.codecRegistry(); - List values = simpleStatement.getValues(); - List encodedValues = new ArrayList<>(values.size()); - for (Object value : values) { - encodedValues.add(codecRegistry.codecFor(value).encode(value, context.protocolVersion())); + if (!simpleStatement.getPositionalValues().isEmpty() + && !simpleStatement.getNamedValues().isEmpty()) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); } + + CodecRegistry codecRegistry = context.codecRegistry(); + ProtocolVersion protocolVersion = context.protocolVersion(); int consistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); - boolean skipMetadata = false; // TODO set for bound statements int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); int serialConsistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); @@ -94,9 +98,9 @@ static Message toMessage( QueryOptions queryOptions = new QueryOptions( consistency, - encodedValues, - Collections.emptyMap(), - skipMetadata, + encode(simpleStatement.getPositionalValues(), codecRegistry, protocolVersion), + encode(simpleStatement.getNamedValues(), codecRegistry, protocolVersion), + false, pageSize, statement.getPagingState(), serialConsistency, @@ -107,6 +111,34 @@ static Message toMessage( throw new UnsupportedOperationException("TODO handle other types of statements"); } + private static List encode( + List values, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { + if (values.isEmpty()) { + return Collections.emptyList(); + } else { + List encodedValues = new ArrayList<>(values.size()); + for (Object value : values) { + encodedValues.add(codecRegistry.codecFor(value).encode(value, protocolVersion)); + } + return encodedValues; + } + } + + private static Map encode( + Map values, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { + if (values.isEmpty()) { + return Collections.emptyMap(); + } else { + ImmutableMap.Builder encodedValues = ImmutableMap.builder(); + for (Map.Entry entry : values.entrySet()) { + encodedValues.put( + entry.getKey(), + codecRegistry.codecFor(entry.getValue()).encode(entry.getValue(), protocolVersion)); + } + return encodedValues.build(); + } + } + static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, Session session, InternalDriverContext context) { if (result instanceof Rows) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 19f5fd16bab..20c2485de60 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -115,16 +115,21 @@ public class CqlRequestHandler this.speculativeExecutionPolicy = context.speculativeExecutionPolicy(); this.executions = new AtomicInteger(0); - // Start the initial execution - long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); - if (nextExecution > 0) { - LOG.trace("Scheduling first speculative execution in {} ms", nextExecution); - this.pendingExecutions = new CopyOnWriteArrayList<>(); - this.pendingExecutions.add( - scheduler.schedule(this::startExecution, nextExecution, TimeUnit.MILLISECONDS)); + if (isIdempotent) { + // Start the initial execution + long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); + if (nextExecution > 0) { + LOG.trace("Scheduling first speculative execution in {} ms", nextExecution); + this.pendingExecutions = new CopyOnWriteArrayList<>(); + this.pendingExecutions.add( + scheduler.schedule(this::startExecution, nextExecution, TimeUnit.MILLISECONDS)); + } else { + LOG.trace("Speculative execution policy returned {}, no next execution", nextExecution); + this.pendingExecutions = null; // we'll never need this so avoid allocation + } } else { - LOG.trace("Speculative execution policy returned {}, no next execution", nextExecution); - this.pendingExecutions = null; // we'll never need this so avoid allocation + LOG.trace("Request is not idempotent, no speculative executions"); + this.pendingExecutions = null; } sendRequest(null, 0, 0); } @@ -196,7 +201,7 @@ private void sendRequest(Node node, int execution, int retryCount) { NodeResponseCallback nodeResponseCallback = new NodeResponseCallback(node, execution, retryCount); channel - .write(message, false, Frame.NO_PAYLOAD, nodeResponseCallback) + .write(message, request.isTracing(), request.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); } } @@ -359,34 +364,43 @@ private void processErrorResponse(Error errorMessage) { if (error instanceof ReadTimeoutException) { ReadTimeoutException readTimeout = (ReadTimeoutException) error; decision = - retryPolicy.onReadTimeout( - request, - readTimeout.getConsistencyLevel(), - readTimeout.getBlockFor(), - readTimeout.getReceived(), - readTimeout.wasDataPresent(), - retryCount); + isIdempotent + ? retryPolicy.onReadTimeout( + request, + readTimeout.getConsistencyLevel(), + readTimeout.getBlockFor(), + readTimeout.getReceived(), + readTimeout.wasDataPresent(), + retryCount) + : RetryDecision.RETHROW; } else if (error instanceof WriteTimeoutException) { WriteTimeoutException writeTimeout = (WriteTimeoutException) error; decision = - retryPolicy.onWriteTimeout( - request, - writeTimeout.getConsistencyLevel(), - writeTimeout.getWriteType(), - writeTimeout.getBlockFor(), - writeTimeout.getReceived(), - retryCount); + isIdempotent + ? retryPolicy.onWriteTimeout( + request, + writeTimeout.getConsistencyLevel(), + writeTimeout.getWriteType(), + writeTimeout.getBlockFor(), + writeTimeout.getReceived(), + retryCount) + : RetryDecision.RETHROW; } else if (error instanceof UnavailableException) { UnavailableException unavailable = (UnavailableException) error; decision = - retryPolicy.onUnavailable( - request, - unavailable.getConsistencyLevel(), - unavailable.getRequired(), - unavailable.getAlive(), - retryCount); + isIdempotent + ? retryPolicy.onUnavailable( + request, + unavailable.getConsistencyLevel(), + unavailable.getRequired(), + unavailable.getAlive(), + retryCount) + : RetryDecision.RETHROW; } else { - decision = retryPolicy.onErrorResponse(request, error, retryCount); + decision = + isIdempotent + ? retryPolicy.onErrorResponse(request, error, retryCount) + : RetryDecision.RETHROW; } processRetryDecision(decision, error); } @@ -416,7 +430,10 @@ public void onFailure(Throwable error) { if (result.isDone()) { return; } - RetryDecision decision = retryPolicy.onRequestAborted(request, error, retryCount); + RetryDecision decision = + isIdempotent + ? retryPolicy.onRequestAborted(request, error, retryCount) + : RetryDecision.RETHROW; processRetryDecision(decision, error); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 52a94b19a51..475f95b1c82 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -15,26 +15,70 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.List; +import java.util.Map; public class DefaultSimpleStatement implements SimpleStatement { private final String query; - private final List values; - private final String configProfile; + private final List positionalValues; + private final Map namedValues; + private final String configProfileName; + private final DriverConfigProfile configProfile; + private final Map customPayload; + private final Boolean idempotent; + private final boolean tracing; private final ByteBuffer pagingState; - public DefaultSimpleStatement(String query, List values, String configProfile) { - this(query, values, configProfile, null); + public DefaultSimpleStatement(String query, List positionalValues) { + this( + query, + positionalValues, + Collections.emptyMap(), + null, + null, + Collections.emptyMap(), + null, + false, + null); } - private DefaultSimpleStatement( - String query, List values, String configProfile, ByteBuffer pagingState) { + public DefaultSimpleStatement(String query, Map namedValues) { + this( + query, + Collections.emptyList(), + namedValues, + null, + null, + Collections.emptyMap(), + null, + false, + null); + } + + /** @see SimpleStatement#builder(String) */ + public DefaultSimpleStatement( + String query, + List positionalValues, + Map namedValues, + String configProfileName, + DriverConfigProfile configProfile, + Map customPayload, + Boolean idempotent, + boolean tracing, + ByteBuffer pagingState) { this.query = query; - this.values = values; + this.positionalValues = positionalValues; + this.namedValues = namedValues; + this.configProfileName = configProfileName; this.configProfile = configProfile; + this.customPayload = customPayload; + this.idempotent = idempotent; + this.tracing = tracing; this.pagingState = pagingState; } @@ -44,15 +88,46 @@ public String getQuery() { } @Override - public List getValues() { - return values; + public List getPositionalValues() { + return positionalValues; + } + + @Override + public Map getNamedValues() { + return namedValues; } @Override - public String getConfigProfile() { + public String getConfigProfileName() { + return configProfileName; + } + + @Override + public DriverConfigProfile getConfigProfile() { return configProfile; } + @Override + public String getKeyspace() { + // Not implemented yet, waiting for CASSANDRA-10145 to land in a release + return null; + } + + @Override + public Map getCustomPayload() { + return customPayload; + } + + @Override + public Boolean isIdempotent() { + return idempotent; + } + + @Override + public boolean isTracing() { + return tracing; + } + @Override public ByteBuffer getPagingState() { return pagingState; @@ -60,6 +135,15 @@ public ByteBuffer getPagingState() { @Override public DefaultSimpleStatement copy(ByteBuffer newPagingState) { - return new DefaultSimpleStatement(query, values, configProfile, newPagingState); + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + tracing, + newPagingState); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index 3617034f1c7..02b3c12483a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; @@ -28,6 +29,7 @@ public abstract class RequestHandlerBase implements RequestHandler { protected final Request request; + protected final boolean isIdempotent; protected final DefaultSession session; protected final CqlIdentifier keyspace; protected final InternalDriverContext context; @@ -44,11 +46,19 @@ protected RequestHandlerBase( this.context = context; this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); - DriverConfig config = context.config(); - String profileName = request.getConfigProfile(); - configProfile = - (profileName == null || profileName.isEmpty()) - ? config.defaultProfile() - : config.getProfile(profileName); + if (request.getConfigProfile() != null) { + this.configProfile = request.getConfigProfile(); + } else { + DriverConfig config = context.config(); + String profileName = request.getConfigProfileName(); + this.configProfile = + (profileName == null || profileName.isEmpty()) + ? config.defaultProfile() + : config.getProfile(profileName); + } + this.isIdempotent = + (request.isIdempotent() == null) + ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) + : request.isIdempotent(); } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 1e996ca41b4..49662962fb0 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -129,6 +129,10 @@ datastax-java-driver { # Note that CASSANDRA-10145 (scheduled for C* 4.0) will introduce a per-request keyspace # option as a workaround to this issue. warn-if-set-keyspace = true + + # The default idempotence of a request, that will be used for all `Request` instances where + # `isIdempotent()` returns null. + default-idempotence = false } # The driver maintains a connection pool to each node, according to the distance assigned to it diff --git a/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java index 0bbb21d14f3..016120a2212 100644 --- a/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java +++ b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver; +import java.util.Arrays; + public class TestDataProviders { public static Object[][] fromList(Object... l) { @@ -25,4 +27,54 @@ public static Object[][] fromList(Object... l) { } return result; } + + public static Object[][] concat(Object[][] left, Object[][] right) { + Object[][] result = Arrays.copyOf(left, left.length + right.length); + System.arraycopy(right, 0, result, left.length, right.length); + return result; + } + + // example: [ [a,b], [c,d] ], [ [1], [2] ], [ [true], [false] ] + // => [ [a,b,1,true], [a,b,1,false], [a,b,2,true], [a,b,2,false], ... ] + public static Object[][] combine(Object[][]... providers) { + int numberOfProviders = providers.length; // (ex: 3) + + // ex: 2 * 2 * 2 combinations + int numberOfCombinations = 1; + for (Object[][] provider : providers) { + numberOfCombinations *= provider.length; + } + + Object[][] result = new Object[numberOfCombinations][]; + // The current index in each provider (ex: [1,0,1] => [c,d,1,false]) + int[] indices = new int[numberOfProviders]; + + for (int c = 0; c < numberOfCombinations; c++) { + int combinationLength = 0; + for (int p = 0; p < numberOfProviders; p++) { + combinationLength += providers[p][indices[p]].length; + } + Object[] combination = new Object[combinationLength]; + int destPos = 0; + for (int p = 0; p < numberOfProviders; p++) { + Object[] src = providers[p][indices[p]]; + System.arraycopy(src, 0, combination, destPos, src.length); + destPos += src.length; + } + result[c] = combination; + + // Update indices: try to increment from the right, if it overflows reset and move left + for (int p = providers.length - 1; p >= 0; p--) { + if (indices[p] < providers[p].length - 1) { + // ex: [0,0,0], p = 2 => [0,0,1] + indices[p] += 1; + break; + } else { + // ex: [0,0,1], p = 2 => [0,0,0], loop to increment to [0,1,0] + indices[p] = 0; + } + } + } + return result; + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index d882cde51c6..411a361b59b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; @@ -48,10 +49,12 @@ public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { - @Test - public void should_always_try_next_node_if_bootstrapping() { + @Test(dataProvider = "allIdempotenceConfigs") + public void should_always_try_next_node_if_bootstrapping( + boolean defaultIdempotence, SimpleStatement statement) { try (RequestHandlerTestHarness harness = RequestHandlerTestHarness.builder() + .withDefaultIdempotence(defaultIdempotence) .withResponse( node1, defaultFrameOf( @@ -60,7 +63,7 @@ public void should_always_try_next_node_if_bootstrapping() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -87,17 +90,19 @@ public void should_always_try_next_node_if_bootstrapping() { } } - @Test - public void should_always_rethrow_query_validation_error() { + @Test(dataProvider = "allIdempotenceConfigs") + public void should_always_rethrow_query_validation_error( + boolean defaultIdempotence, SimpleStatement statement) { try (RequestHandlerTestHarness harness = RequestHandlerTestHarness.builder() + .withDefaultIdempotence(defaultIdempotence) .withResponse( node1, defaultFrameOf(new Error(ProtocolConstants.ErrorCode.INVALID, "mock message"))) .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -111,9 +116,11 @@ public void should_always_rethrow_query_validation_error() { } } - @Test(dataProvider = "failureScenarios") - public void should_try_next_node_if_retry_policy_decides_so(FailureScenario failureScenario) { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "failureAndIdempotent") + public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( + FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); failureScenario.mockRequestError(harnessBuilder, node1); harnessBuilder.withResponse(node2, defaultFrameOf(singleRow())); @@ -122,7 +129,7 @@ public void should_try_next_node_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -140,9 +147,11 @@ public void should_try_next_node_if_retry_policy_decides_so(FailureScenario fail } } - @Test(dataProvider = "failureScenarios") - public void should_try_same_node_if_retry_policy_decides_so(FailureScenario failureScenario) { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "failureAndIdempotent") + public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( + FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); failureScenario.mockRequestError(harnessBuilder, node1); harnessBuilder.withResponse(node1, defaultFrameOf(singleRow())); @@ -151,7 +160,7 @@ public void should_try_same_node_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -169,9 +178,11 @@ public void should_try_same_node_if_retry_policy_decides_so(FailureScenario fail } } - @Test(dataProvider = "failureScenarios") - public void should_ignore_error_if_retry_policy_decides_so(FailureScenario failureScenario) { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "failureAndIdempotent") + public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( + FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); failureScenario.mockRequestError(harnessBuilder, node1); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { @@ -179,7 +190,7 @@ public void should_ignore_error_if_retry_policy_decides_so(FailureScenario failu harness.getContext().retryPolicy(), RetryDecision.IGNORE); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -195,9 +206,11 @@ public void should_ignore_error_if_retry_policy_decides_so(FailureScenario failu } } - @Test(dataProvider = "failureScenarios") - public void should_rethrow_error_if_retry_policy_decides_so(FailureScenario failureScenario) { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "failureAndIdempotent") + public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( + FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); failureScenario.mockRequestError(harnessBuilder, node1); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { @@ -206,7 +219,7 @@ public void should_rethrow_error_if_retry_policy_decides_so(FailureScenario fail harness.getContext().retryPolicy(), RetryDecision.RETHROW); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -215,6 +228,29 @@ public void should_rethrow_error_if_retry_policy_decides_so(FailureScenario fail } } + @Test(dataProvider = "failureAndNotIdempotent") + public void should_rethrow_error_if_not_idempotent( + FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); + failureScenario.mockRequestError(harnessBuilder, node1); + harnessBuilder.withResponse(node2, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + CompletionStage resultSetFuture = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + .asyncResult(); + + assertThat(resultSetFuture) + .isFailed( + error -> { + assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); + // When non idempotent, the policy is bypassed completely: + Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + }); + } + } + /** * Sets up the mocks to simulate an error from a node, and make the retry policy return a given * decision for that error. @@ -232,7 +268,7 @@ protected FailureScenario(Class expectedExceptionClass) { } @DataProvider - public static Object[][] failureScenarios() { + public static Object[][] failure() { return TestDataProviders.fromList( new FailureScenario(ReadTimeoutException.class) { @Override @@ -248,7 +284,12 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { Mockito.when( policy.onReadTimeout( - SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, 2, 1, true, 0)) + any(SimpleStatement.class), + eq(ConsistencyLevel.LOCAL_ONE), + eq(2), + eq(1), + eq(true), + eq(0))) .thenReturn(decision); } }, @@ -270,7 +311,12 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { Mockito.when( policy.onWriteTimeout( - SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, WriteType.SIMPLE, 2, 1, 0)) + any(SimpleStatement.class), + eq(ConsistencyLevel.LOCAL_ONE), + eq(WriteType.SIMPLE), + eq(2), + eq(1), + eq(0))) .thenReturn(decision); } }, @@ -287,7 +333,12 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { Mockito.when( - policy.onUnavailable(SIMPLE_STATEMENT, ConsistencyLevel.LOCAL_ONE, 2, 1, 0)) + policy.onUnavailable( + any(SimpleStatement.class), + eq(ConsistencyLevel.LOCAL_ONE), + eq(2), + eq(1), + eq(0))) .thenReturn(decision); } }, @@ -303,7 +354,8 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { Mockito.when( - policy.onErrorResponse(eq(SIMPLE_STATEMENT), any(ServerError.class), eq(0))) + policy.onErrorResponse( + any(SimpleStatement.class), any(ServerError.class), eq(0))) .thenReturn(decision); } }, @@ -317,9 +369,19 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { Mockito.when( policy.onRequestAborted( - eq(SIMPLE_STATEMENT), any(HeartbeatException.class), eq(0))) + any(SimpleStatement.class), any(HeartbeatException.class), eq(0))) .thenReturn(decision); } }); } + + @DataProvider + public static Object[][] failureAndIdempotent() { + return TestDataProviders.combine(failure(), idempotentConfig()); + } + + @DataProvider + public static Object[][] failureAndNotIdempotent() { + return TestDataProviders.combine(failure(), nonIdempotentConfig()); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index c01f610358e..f11d9523d7b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -29,9 +30,33 @@ public class CqlRequestHandlerSpeculativeExecutionTest extends CqlRequestHandlerTestBase { - @Test - public void should_schedule_speculative_executions() { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "nonIdempotentConfig") + public void should_not_schedule_speculative_executions_if_not_idempotent( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()).asyncResult(); + + node1Behavior.verifyWrite(); + + harness.nextScheduledTask(); // Discard the timeout task + assertThat(harness.nextScheduledTask()).isNull(); + + Mockito.verifyNoMoreInteractions(speculativeExecutionPolicy); + } + } + + @Test(dataProvider = "idempotentConfig") + public void should_schedule_speculative_executions( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); @@ -41,15 +66,13 @@ public void should_schedule_speculative_executions() { harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; long secondExecutionDelay = 200L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) .thenReturn(firstExecutionDelay); - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 2)) + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 2)) .thenReturn(secondExecutionDelay); - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 3)) - .thenReturn(0L); + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(0L); - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) - .asyncResult(); + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()).asyncResult(); node1Behavior.verifyWrite(); @@ -76,9 +99,11 @@ public void should_schedule_speculative_executions() { } } - @Test - public void should_not_start_execution_if_result_complete() { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "idempotentConfig") + public void should_not_start_execution_if_result_complete( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); @@ -86,11 +111,11 @@ public void should_not_start_execution_if_result_complete() { SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); @@ -117,9 +142,11 @@ public void should_not_start_execution_if_result_complete() { } } - @Test - public void should_retry_in_speculative_executions() { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "idempotentConfig") + public void should_retry_in_speculative_executions( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); harnessBuilder.withResponse(node3, defaultFrameOf(singleRow())); @@ -128,11 +155,11 @@ public void should_retry_in_speculative_executions() { SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); // do not simulate a response from node1. The request will stay hanging for the rest of this test @@ -155,9 +182,11 @@ public void should_retry_in_speculative_executions() { } } - @Test - public void should_stop_retrying_other_executions_if_result_complete() { - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + @Test(dataProvider = "idempotentConfig") + public void should_stop_retrying_other_executions_if_result_complete( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); @@ -166,11 +195,11 @@ public void should_stop_retrying_other_executions_if_result_complete() { SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, SIMPLE_STATEMENT, 1)) + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) .asyncResult(); node1Behavior.verifyWrite(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 3d49c543c11..0f92b297de8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -42,7 +42,8 @@ public void should_complete_result_if_first_node_replies_immediately() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) @@ -73,7 +74,8 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); // First scheduled task is the timeout, run it before node1 has responded @@ -101,7 +103,8 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(SIMPLE_STATEMENT, harness.getSession(), harness.getContext()) + new CqlRequestHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) .asyncResult(); assertThat(resultSetFuture) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 3a3ff688c78..6e2c113bbd3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -34,11 +36,16 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; abstract class CqlRequestHandlerTestBase { - protected static final DefaultSimpleStatement SIMPLE_STATEMENT = - new DefaultSimpleStatement("mock query", Collections.emptyList(), null); + protected static final SimpleStatement UNDEFINED_IDEMPOTENCE_STATEMENT = + SimpleStatement.newInstance("mock query"); + protected static final SimpleStatement IDEMPOTENT_STATEMENT = + SimpleStatement.builder("mock query").withIdempotence(true).build(); + protected static final SimpleStatement NON_IDEMPOTENT_STATEMENT = + SimpleStatement.builder("mock query").withIdempotence(false).build(); @Mock protected Node node1; @Mock protected Node node2; @@ -76,4 +83,35 @@ protected static Message singleRow() { data.add(ImmutableList.of(Bytes.fromHexString("0x68656C6C6F2C20776F726C64"))); return new Rows(metadata, data); } + + /** + * The combination of the default idempotence option and statement setting that produce an + * idempotent statement. + */ + @DataProvider + public static Object[][] idempotentConfig() { + return new Object[][] { + new Object[] {true, UNDEFINED_IDEMPOTENCE_STATEMENT}, + new Object[] {false, IDEMPOTENT_STATEMENT}, + new Object[] {true, IDEMPOTENT_STATEMENT}, + }; + } + + /** + * The combination of the default idempotence option and statement setting that produce a non + * idempotent statement. + */ + @DataProvider + public static Object[][] nonIdempotentConfig() { + return new Object[][] { + new Object[] {false, UNDEFINED_IDEMPOTENCE_STATEMENT}, + new Object[] {true, NON_IDEMPOTENT_STATEMENT}, + new Object[] {false, NON_IDEMPOTENT_STATEMENT}, + }; + } + + @DataProvider + public static Object[][] allIdempotenceConfigs() { + return TestDataProviders.concat(idempotentConfig(), nonIdempotentConfig()); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 34179a63de3..2cfcb15c957 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -86,6 +86,8 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when( defaultConfigProfile.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) .thenReturn(ConsistencyLevel.SERIAL); + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) + .thenReturn(builder.defaultIdempotence); Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); @@ -126,6 +128,7 @@ public void close() { public static class Builder { private final ScheduledTaskCapturingEventLoop schedulingEventLoop; private final List poolBehaviors = new ArrayList<>(); + private boolean defaultIdempotence; public Builder() { this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(null); @@ -175,6 +178,11 @@ public Builder withResponse(Node node, Frame response) { return this; } + public Builder withDefaultIdempotence(boolean defaultIdempotence) { + this.defaultIdempotence = defaultIdempotence; + return this; + } + /** * Sets the given node as the next one in the query plan; the test code is responsible of * calling the methods on the returned object to complete the write and the query. From 6052bcef676daea9f54b44139aec859584f21ac8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 14 Jun 2017 16:18:48 -0700 Subject: [PATCH 070/742] Enforce rule about rejecting per-request keyspace --- .../driver/internal/core/session/DefaultSession.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 87b7810865c..19020999b1d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -59,9 +59,9 @@ *

It executes requests by: * *

    - *
  • picking the appropriate processor to convert the request into a protocol message. - *
  • getting a query plan from the load balancing policy - *
  • trying to send the message on each pool, in the order of the query plan + *
  • picking the appropriate processor to convert the request into a protocol message. + *
  • getting a query plan from the load balancing policy + *
  • trying to send the message on each pool, in the order of the query plan *
*/ public class DefaultSession implements CqlSession { @@ -150,6 +150,10 @@ public AsyncResultT executeAsync( private RequestHandler newHandler( Request request) { + if (request.getKeyspace() != null) { + // TODO CASSANDRA-10145 + throw new UnsupportedOperationException("Per-request keyspaces are not supported yet"); + } return processorRegistry.processorFor(request).newHandler(request, this, context); } From 6827760c3fb465264fb8ba96a46e885b6ef887b3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 14 Jun 2017 16:22:06 -0700 Subject: [PATCH 071/742] Fix formatting issue --- .../oss/driver/internal/core/session/DefaultSession.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 19020999b1d..703362be6ef 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -59,9 +59,9 @@ *

It executes requests by: * *

    - *
  • picking the appropriate processor to convert the request into a protocol message. - *
  • getting a query plan from the load balancing policy - *
  • trying to send the message on each pool, in the order of the query plan + *
  • picking the appropriate processor to convert the request into a protocol message. + *
  • getting a query plan from the load balancing policy + *
  • trying to send the message on each pool, in the order of the query plan *
*/ public class DefaultSession implements CqlSession { From abfe314959531d888d87dfedf49ba12fbe406d4b Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 15 Jun 2017 08:33:10 -0700 Subject: [PATCH 072/742] Fix compile errors after native-protocol change --- .../com/datastax/oss/driver/internal/core/TestResponses.java | 2 +- .../oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java index f7bf9cf0b4c..e4257795161 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -37,7 +37,7 @@ public static Rows clusterNameResponse(String actualClusterName) { "cluster_name", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR)); - RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); + RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), 1, null, null); Queue> data = Lists.newLinkedList(); data.add(Lists.newArrayList(ByteBuffer.wrap(actualClusterName.getBytes(Charsets.UTF_8)))); return new Rows(metadata, data); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 6e2c113bbd3..8f8c634e272 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -77,6 +77,7 @@ protected static Message singleRow() { "message", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), + 1, null, new int[] {}); Queue> data = new LinkedList<>(); From 02b90744be440e7df429eccc230df179baa5a10c Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 15 Jun 2017 09:11:32 -0700 Subject: [PATCH 073/742] Don't throw Exception from close methods It is not needed since the driver throws runtime exceptions only. It avoids having to catch it in try-with-resources blocks. --- .../com/datastax/oss/driver/api/core/AsyncAutoCloseable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java index b104c27e371..3837fcaefe1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java @@ -58,7 +58,7 @@ public interface AsyncAutoCloseable extends AutoCloseable { * should not be called on a driver thread. */ @Override - default void close() throws Exception { + default void close() { BlockingOperation.checkNotDriverThread(); CompletableFutures.getUninterruptibly(closeAsync().toCompletableFuture()); } From 9b5a45f90407160882a22da06fc8bb4c647bb9d7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 12 Jun 2017 14:11:36 -0700 Subject: [PATCH 074/742] JAVA-1495: Add prepared statements --- changelog/README.md | 7 + .../driver/api/core/cql/BoundStatement.java | 78 ++++ .../api/core/cql/ColumnDefinitions.java | 47 ++- .../oss/driver/api/core/cql/CqlSession.java | 13 +- .../driver/api/core/cql/PrepareRequest.java | 59 ++- .../api/core/cql/PreparedStatement.java | 25 +- .../adminrequest/AdminRequestHandler.java | 7 +- .../driver/internal/core/cql/Conversions.java | 73 +++- .../internal/core/cql/CqlPrepareHandler.java | 335 ++++++++++++++++++ .../core/cql/CqlPrepareProcessor.java | 71 ++++ .../internal/core/cql/CqlRequestHandler.java | 23 +- .../core/cql/DefaultBoundStatement.java | 192 ++++++++++ .../core/cql/DefaultPrepareRequest.java | 88 +++++ .../core/cql/DefaultPreparedStatement.java | 100 ++++++ .../internal/core/session/DefaultSession.java | 2 +- .../core/session/RequestHandlerBase.java | 25 ++ .../session/RequestProcessorRegistry.java | 6 +- .../util/concurrent/CompletableFutures.java | 22 +- .../core/cql/CqlPrepareHandlerTest.java | 324 +++++++++++++++++ 19 files changed, 1420 insertions(+), 77 deletions(-) create mode 100644 changelog/README.md create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java diff --git a/changelog/README.md b/changelog/README.md new file mode 100644 index 00000000000..e4a63652165 --- /dev/null +++ b/changelog/README.md @@ -0,0 +1,7 @@ +## Changelog + + + +### 4.0.0-alpha1 (in progress) + +- [new feature] JAVA-1495: Add prepared statements \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java new file mode 100644 index 00000000000..378dd193668 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.data.GettableById; +import com.datastax.oss.driver.api.core.data.GettableByName; +import com.datastax.oss.driver.api.core.data.SettableById; +import com.datastax.oss.driver.api.core.data.SettableByName; +import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +/** + * A prepared statement in its executable form, with values bound to the variables. + * + *

The default implementation provided by the driver is: + * + *

    + *
  • not thread-safe: all methods (setting values, etc.) must be called from the thread that + * created the instance. Besides, if you use {@link CqlSession#executeAsync(Statement)} + * asynchronous execution}, do not reuse the same instance for multiple calls, as you run the + * risk of modifying the statement while the driver internals are still processing it. + *
  • mutable: all setters that return a {@code BoundStatement} modify and return the same + * instance, instead of creating a copy. + *
+ */ +public interface BoundStatement + extends Statement, + GettableById, + GettableByName, + SettableById, + SettableByName { + + /** The prepared statement that was used to create this statement. */ + PreparedStatement getPreparedStatement(); + + /** The values to bind, in their serialized form. */ + List getValues(); + + /** + * Sets the name of the configuration profile to use. + * + *

Note that this will be ignored if {@link #getConfigProfile()} return a non-null value. + */ + BoundStatement setConfigProfileName(String configProfileName); + + /** Sets the configuration profile to use. */ + BoundStatement setConfigProfile(DriverConfigProfile configProfile); + + /** Sets the custom payload to send alongside the request. */ + BoundStatement setCustomPayload(Map customPayload); + + /** + * Indicates whether the statement is idempotent. + * + * @param idempotent true or false, or {@code null} to use the default defined in the + * configuration. + */ + BoundStatement setIdempotent(Boolean idempotent); + + /** Request tracing information for this statement. */ + BoundStatement setTracing(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java index c3ead2c586c..49b6deacc55 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ColumnDefinitions.java @@ -17,11 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.AccessibleByName; -import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.detach.Detachable; import java.io.Serializable; -import java.util.Collections; -import java.util.Iterator; /** Metadata about a set of CQL columns. */ public interface ColumnDefinitions extends Iterable, Detachable, Serializable { @@ -29,6 +26,50 @@ public interface ColumnDefinitions extends Iterable, Detachabl ColumnDefinition get(int i); + /** + * Get a definition by name. + * + *

This is the equivalent of: + * + *

+   *   get(firstIndexOf(name))
+   * 
+ * + * @throws IllegalArgumentException if the name does not exist (in other words, if {@code + * !contains(name))}). + * @see #contains(String) + * @see #firstIndexOf(String) + */ + default ColumnDefinition get(String name) { + if (!contains(name)) { + throw new IllegalArgumentException("No definition named " + name); + } else { + return get(firstIndexOf(name)); + } + } + + /** + * Get a definition by name. + * + *

This is the equivalent of: + * + *

+   *   get(firstIndexOf(name))
+   * 
+ * + * @throws IllegalArgumentException if the name does not exist (in other words, if {@code + * !contains(name))}). + * @see #contains(CqlIdentifier) + * @see #firstIndexOf(CqlIdentifier) + */ + default ColumnDefinition get(CqlIdentifier name) { + if (!contains(name)) { + throw new IllegalArgumentException("No definition named " + name); + } else { + return get(firstIndexOf(name)); + } + } + /** * Whether there is a definition using the given name. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java index db5353373e5..9dedb6d8eb2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; +import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; public interface CqlSession extends Session { @@ -40,18 +41,18 @@ default CompletionStage executeAsync(String query) { } default PreparedStatement prepare(String query) { - return execute(PrepareRequest.from(query)); + return execute(new DefaultPrepareRequest(query)); } - default PreparedStatement prepare(Statement query) { - return execute(PrepareRequest.from(query)); + default PreparedStatement prepare(SimpleStatement query) { + return execute(new DefaultPrepareRequest(query)); } default CompletionStage prepareAsync(String query) { - return executeAsync(PrepareRequest.from(query)); + return executeAsync(new DefaultPrepareRequest(query)); } - default CompletionStage prepareAsync(Statement query) { - return executeAsync(PrepareRequest.from(query)); + default CompletionStage prepareAsync(SimpleStatement query) { + return executeAsync(new DefaultPrepareRequest(query)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index d203a7f443f..5994f7b789c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -15,28 +15,69 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; +import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.CompletionStage; -/** A request to prepare a CQL query. */ +/** + * A request to prepare a CQL query. + * + *

Driver clients should rarely have to deal directly with this type, it's used internally by + * {@link CqlSession}'s prepare methods. However a {@link RetryPolicy} implementation might use it + * if it needs a custom behavior for prepare requests. + */ public interface PrepareRequest extends Request> { - static PrepareRequest from(String query) { - throw new UnsupportedOperationException("TODO"); - } - - static PrepareRequest from(Statement statement) { - throw new UnsupportedOperationException("TODO"); - } + /** The CQL query to prepare. */ + String getQuery(); + /** + * {@inheritDoc} + * + *

Note that this refers to the prepare query itself, not to the bound statements that will be + * created from the prepared statement (see {@link #areBoundStatementsIdempotent()}). + */ @Override default Boolean isIdempotent() { - return true; // Retrying to prepare is always safe + // Retrying to prepare is always safe + return true; } @Override default boolean isTracing() { + // Tracing prepare requests is unlikely to be useful, we don't expose an API for it. return false; } + + /** + * The name of the driver configuration profile to use for the bound statements that will be + * created from the prepared statement. + * + *

Note that this will be ignored if {@link #getConfigProfileForBoundStatements()} returns a + * non-null value. + */ + String getConfigProfileNameForBoundStatements(); + + /** + * The configuration profile to use for the bound statements that will be created from the + * prepared statement. + */ + DriverConfigProfile getConfigProfileForBoundStatements(); + + /** + * Returns the custom payload to send alongside the bound statements that will be created from the + * prepared statement. + */ + Map getCustomPayloadForBoundStatements(); + + /** + * Whether bound statements that will be created from the prepared statement are idempotent. + * + *

This follows the same semantics as {@link #isIdempotent()}. + */ + Boolean areBoundStatementsIdempotent(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index ae86aa22286..91aca9e3da9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -15,4 +15,27 @@ */ package com.datastax.oss.driver.api.core.cql; -public interface PreparedStatement {} +import java.nio.ByteBuffer; + +public interface PreparedStatement { + + /** + * A unique identifier for this prepared statement. + * + *

Note: the returned buffer is read-only. + */ + ByteBuffer getId(); + + String getQuery(); + + /** A description of the bind variables of this prepared statement. */ + ColumnDefinitions getVariableDefinitions(); + + /** + * A description of the result set that will be returned when this prepared statement is bound and + * executed. + */ + ColumnDefinitions getResultSetDefinitions(); + + BoundStatement bind(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 89d3bc5fb31..5c2b65dda62 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -70,11 +70,6 @@ public static AdminRequestHandler query( return query(channel, query, Collections.emptyMap(), timeout, pageSize); } - public static AdminRequestHandler prepare(DriverChannel channel, String query, Duration timeout) { - String debugString = "prepare '" + query + "'"; - return new AdminRequestHandler(channel, new Prepare(query), timeout, debugString); - } - private final DriverChannel channel; private final Message message; private final Duration timeout; @@ -84,7 +79,7 @@ public static AdminRequestHandler prepare(DriverChannel channel, String query, D // This is only ever accessed on the channel's event loop, so it doesn't need to be volatile private ScheduledFuture timeoutFuture; - private AdminRequestHandler( + public AdminRequestHandler( DriverChannel channel, Message message, Duration timeout, String debugString) { this.channel = channel; this.message = message; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 9ef963dcb3f..12656efb314 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -21,8 +21,11 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -48,6 +51,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Execute; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.request.query.QueryOptions; import com.datastax.oss.protocol.internal.response.Error; @@ -61,6 +65,8 @@ import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.Prepared; import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.RowsMetadata; +import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; @@ -78,6 +84,12 @@ class Conversions { static Message toMessage( Statement statement, DriverConfigProfile config, InternalDriverContext context) { + int consistency = + config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); + int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); + int serialConsistency = + config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); + long timestamp = Long.MIN_VALUE; // TODO timestamp generator if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; @@ -86,15 +98,8 @@ static Message toMessage( throw new IllegalArgumentException( "Can't have both positional and named values in a statement."); } - CodecRegistry codecRegistry = context.codecRegistry(); ProtocolVersion protocolVersion = context.protocolVersion(); - int consistency = - config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); - int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); - int serialConsistency = - config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); - long timestamp = Long.MIN_VALUE; // TODO timestamp generator QueryOptions queryOptions = new QueryOptions( consistency, @@ -106,6 +111,20 @@ static Message toMessage( serialConsistency, timestamp); return new Query(simpleStatement.getQuery(), queryOptions); + } else if (statement instanceof BoundStatement) { + BoundStatement boundStatement = (BoundStatement) statement; + QueryOptions queryOptions = + new QueryOptions( + consistency, + boundStatement.getValues(), + Collections.emptyMap(), + true, + pageSize, + statement.getPagingState(), + serialConsistency, + timestamp); + ByteBuffer id = boundStatement.getPreparedStatement().getId(); + return new Execute(Bytes.getArray(id), queryOptions); } // TODO handle other types of statements throw new UnsupportedOperationException("TODO handle other types of statements"); @@ -143,16 +162,13 @@ static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, Session session, InternalDriverContext context) { if (result instanceof Rows) { Rows rows = (Rows) result; - ImmutableList.Builder definitions = ImmutableList.builder(); - for (ColumnSpec columnSpec : rows.metadata.columnSpecs) { - definitions.add(new DefaultColumnDefinition(columnSpec, context)); - } + Statement statement = executionInfo.getStatement(); + ColumnDefinitions columnDefinitions = + (statement instanceof BoundStatement) + ? ((BoundStatement) statement).getPreparedStatement().getResultSetDefinitions() + : toColumnDefinitions(rows.metadata, context); return new DefaultAsyncResultSet( - new DefaultColumnDefinitions(definitions.build()), - executionInfo, - rows.data, - session, - context); + columnDefinitions, executionInfo, rows.data, session, context); } else if (result instanceof Prepared) { // This should never happen throw new IllegalArgumentException("Unexpected PREPARED response to a CQL query"); @@ -162,6 +178,31 @@ static AsyncResultSet toResultSet( } } + static DefaultPreparedStatement toPreparedStatement( + Prepared response, PrepareRequest request, InternalDriverContext context) { + return new DefaultPreparedStatement( + ByteBuffer.wrap(response.preparedQueryId).asReadOnlyBuffer(), + request.getQuery(), + toColumnDefinitions(response.variablesMetadata, context), + toColumnDefinitions(response.resultMetadata, context), + request.getConfigProfileNameForBoundStatements(), + request.getConfigProfileForBoundStatements(), + request.getKeyspace(), + request.getCustomPayloadForBoundStatements(), + request.areBoundStatementsIdempotent(), + context.codecRegistry(), + context.protocolVersion()); + } + + private static DefaultColumnDefinitions toColumnDefinitions( + RowsMetadata metadata, InternalDriverContext context) { + ImmutableList.Builder definitions = ImmutableList.builder(); + for (ColumnSpec columnSpec : metadata.columnSpecs) { + definitions.add(new DefaultColumnDefinition(columnSpec, context)); + } + return new DefaultColumnDefinitions(definitions.build()); + } + static Throwable toThrowable(Node node, Error errorMessage) { switch (errorMessage.code) { case ProtocolConstants.ErrorCode.UNPREPARED: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java new file mode 100644 index 00000000000..1d957b458c3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; +import com.datastax.oss.driver.api.core.servererrors.ProtocolError; +import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Prepare; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.result.Prepared; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.ScheduledFuture; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Handles the lifecycle of the preparation of a CQL statement. */ +public class CqlPrepareHandler + extends RequestHandlerBase> { + + private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandler.class); + + private final CompletableFuture result; + private final Message message; + private final EventExecutor scheduler; + private final Duration timeout; + private final ScheduledFuture timeoutFuture; + private final RetryPolicy retryPolicy; + private final CqlPrepareProcessor processor; + + // The errors on the nodes that were already tried (lazily initialized on the first error). + // We don't use a map because nodes can appear multiple times. + private volatile List> errors; + + CqlPrepareHandler( + PrepareRequest request, + CqlPrepareProcessor processor, + DefaultSession session, + InternalDriverContext context) { + super(request, session, context); + this.processor = processor; + this.result = new CompletableFuture<>(); + this.result.exceptionally( + t -> { + try { + if (t instanceof CancellationException) { + cancelTimeout(); + } + } catch (Throwable t2) { + LOG.warn("Uncaught exception", t2); + } + return null; + }); + this.message = new Prepare(request.getQuery()); + this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); + + timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeoutFuture = scheduleTimeout(timeout); + this.retryPolicy = context.retryPolicy(); + sendRequest(null, 0); + } + + @Override + public CompletionStage asyncResult() { + return result; + } + + @Override + public PreparedStatement syncResult() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(asyncResult()); + } + + private ScheduledFuture scheduleTimeout(Duration timeout) { + if (timeout.toNanos() > 0) { + return scheduler.schedule( + () -> setFinalError(new DriverTimeoutException("Query timed out after " + timeout)), + timeout.toNanos(), + TimeUnit.NANOSECONDS); + } else { + return null; + } + } + + private void cancelTimeout() { + if (this.timeoutFuture != null) { + this.timeoutFuture.cancel(false); + } + } + + private void sendRequest(Node node, int retryCount) { + if (result.isDone()) { + return; + } + DriverChannel channel = null; + if (node == null || (channel = getChannel(node)) == null) { + while (!result.isDone() && (node = queryPlan.poll()) != null) { + channel = getChannel(node); + if (channel != null) { + break; + } + } + } + if (channel == null) { + setFinalError(AllNodesFailedException.fromErrors(this.errors)); + } else { + InitialPrepareCallback initialPrepareCallback = new InitialPrepareCallback(node, retryCount); + channel + .write(message, false, Frame.NO_PAYLOAD, initialPrepareCallback) + .addListener(initialPrepareCallback); + } + } + + private void recordError(Node node, Throwable error) { + // Use a local variable to do only a single single volatile read in the nominal case + List> errorsSnapshot = this.errors; + if (errorsSnapshot == null) { + synchronized (CqlPrepareHandler.this) { + errorsSnapshot = this.errors; + if (errorsSnapshot == null) { + this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); + } + } + } + errorsSnapshot.add(new AbstractMap.SimpleEntry<>(node, error)); + } + + private void setFinalResult(Prepared prepared) { + DefaultPreparedStatement newStatement = + Conversions.toPreparedStatement(prepared, (PrepareRequest) request, context); + + DefaultPreparedStatement cachedStatement = processor.cache(newStatement); + + if (cachedStatement != newStatement) { + // The statement already existed in the cache, assume it's because the client called + // prepare() twice, and therefore it's already been prepared on other nodes. + result.complete(cachedStatement); + } else { + prepareOnOtherNodes() + .thenRun(() -> result.complete(cachedStatement)) + .exceptionally( + error -> { + result.completeExceptionally(error); + return null; + }); + } + } + + private CompletionStage prepareOnOtherNodes() { + List> otherNodesFutures = new ArrayList<>(); + // Only process the rest of the query plan. Any node before that is either the coordinator, or + // a node that failed (we assume that retrying right now has little chance of success). + for (Node node : queryPlan) { + otherNodesFutures.add(prepareOnOtherNode(node)); + } + return CompletableFutures.allDone(otherNodesFutures); + } + + // Try to reprepare on another node, after the initial query has succeeded. Errors are not + // blocking, the preparation will be retried later on that node. Simply warn and move on. + private CompletionStage prepareOnOtherNode(Node node) { + LOG.debug("Repreparing on {}", node); + DriverChannel channel = getChannel(node); + if (channel == null) { + LOG.warn("Could not get a channel to reprepare on {}", node); + return CompletableFuture.completedFuture(null); + } else { + AdminRequestHandler handler = + new AdminRequestHandler(channel, message, timeout, message.toString()); + return handler + .start() + .handle( + (result, error) -> { + if (error != null) { + LOG.warn("Error while repreparing on {}", node); + } + return null; + }); + } + } + + private void setFinalError(Throwable error) { + if (result.completeExceptionally(error)) { + cancelTimeout(); + } + } + + private class InitialPrepareCallback + implements ResponseCallback, GenericFutureListener> { + private final Node node; + // How many times we've invoked the retry policy and it has returned a "retry" decision (0 for + // the first attempt of each execution). + private final int retryCount; + + private InitialPrepareCallback(Node node, int retryCount) { + this.node = node; + this.retryCount = retryCount; + } + + // this gets invoked once the write completes. + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + recordError(node, future.cause()); + sendRequest(null, retryCount); // try next host + } + } + + @Override + public void onResponse(Frame responseFrame) { + if (result.isDone()) { + return; + } + try { + Message responseMessage = responseFrame.message; + if (responseMessage instanceof Prepared) { + setFinalResult((Prepared) responseMessage); + } else if (responseMessage instanceof Error) { + processErrorResponse((Error) responseMessage); + } else { + setFinalError(new IllegalStateException("Unexpected response " + responseMessage)); + } + } catch (Throwable t) { + setFinalError(t); + } + } + + private void processErrorResponse(Error errorMessage) { + if (errorMessage.code == ProtocolConstants.ErrorCode.UNPREPARED + || errorMessage.code == ProtocolConstants.ErrorCode.ALREADY_EXISTS + || errorMessage.code == ProtocolConstants.ErrorCode.READ_FAILURE + || errorMessage.code == ProtocolConstants.ErrorCode.READ_TIMEOUT + || errorMessage.code == ProtocolConstants.ErrorCode.WRITE_FAILURE + || errorMessage.code == ProtocolConstants.ErrorCode.WRITE_TIMEOUT + || errorMessage.code == ProtocolConstants.ErrorCode.UNAVAILABLE + || errorMessage.code == ProtocolConstants.ErrorCode.TRUNCATE_ERROR) { + setFinalError( + new IllegalStateException( + "Unexpected server error for a PREPARE query" + errorMessage)); + return; + } + Throwable error = Conversions.toThrowable(node, errorMessage); + if (error instanceof BootstrappingException) { + // Do not call the retry policy, always try the next node + recordError(node, error); + sendRequest(null, retryCount); + } else if (error instanceof QueryValidationException + || error instanceof FunctionFailureException + || error instanceof ProtocolError) { + // Do not call the retry policy, always rethrow + setFinalError(error); + } else { + // Because prepare requests are known to always be idempotent, we call the retry policy + // directly, without checking the flag. + RetryDecision decision = retryPolicy.onErrorResponse(request, error, retryCount); + processRetryDecision(decision, error); + } + } + + private void processRetryDecision(RetryDecision decision, Throwable error) { + switch (decision) { + case RETRY_SAME: + recordError(node, error); + sendRequest(node, retryCount + 1); + break; + case RETRY_NEXT: + recordError(node, error); + sendRequest(null, retryCount + 1); + break; + case RETHROW: + setFinalError(error); + break; + case IGNORE: + setFinalError( + new IllegalArgumentException( + "IGNORE decisions are not allowed for prepare requests, " + + "please fix your retry policy.")); + break; + } + } + + @Override + public void onFailure(Throwable error) { + if (result.isDone()) { + return; + } + RetryDecision decision = retryPolicy.onRequestAborted(request, error, retryCount); + processRetryDecision(decision, error); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java new file mode 100644 index 00000000000..ec1e65ecf4f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.google.common.collect.MapMaker; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CqlPrepareProcessor + implements RequestProcessor> { + + private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareProcessor.class); + + private final ConcurrentMap preparedStatements = + new MapMaker().weakValues().makeMap(); + + @Override + public > boolean canProcess(T request) { + return request instanceof PrepareRequest; + } + + @Override + public RequestHandler> newHandler( + Request> request, + DefaultSession session, + InternalDriverContext context) { + return new CqlPrepareHandler((PrepareRequest) request, this, session, context); + } + + DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatement) { + DefaultPreparedStatement previous = + preparedStatements.putIfAbsent(preparedStatement.getId(), preparedStatement); + if (previous != null) { + LOG.warn( + "Re-preparing already prepared query. " + + "This is generally an anti-pattern and will likely affect performance. " + + "Consider preparing the statement only once. Query='{}'", + preparedStatement.getQuery()); + + // The one object in the cache will get GCed once it's not referenced by the client anymore + // since we use a weak reference. So we need to make sure that the instance we do return to + // the user is the one that is in the cache. + return previous; + } else { + return preparedStatement; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 20c2485de60..caf51843e49 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -37,7 +37,6 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; @@ -206,25 +205,6 @@ private void sendRequest(Node node, int execution, int retryCount) { } } - private DriverChannel getChannel(Node node) { - ChannelPool pool = session.getPools().get(node); - if (pool == null) { - LOG.trace("No pool to {}, skipping", node); - return null; - } else { - DriverChannel channel = pool.next(); - if (channel == null) { - LOG.trace("Pool returned no channel for {}, skipping", node); - return null; - } else if (channel.closeFuture().isDone()) { - LOG.trace("Pool returned closed connection to {}, skipping", node); - return null; - } else { - return channel; - } - } - } - private void recordError(Node node, Throwable error) { // Use a local variable to do only a single single volatile read in the nominal case List> errorsSnapshot = this.errors; @@ -336,8 +316,7 @@ public void onResponse(Frame responseFrame) { } else if (responseMessage instanceof Error) { processErrorResponse((Error) responseMessage); } else { - result.completeExceptionally( - new IllegalStateException("Unexpected response " + responseMessage)); + setFinalError(new IllegalStateException("Unexpected response " + responseMessage)); } } catch (Throwable t) { setFinalError(t); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java new file mode 100644 index 00000000000..2f25c48c952 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DefaultBoundStatement implements BoundStatement { + + private final DefaultPreparedStatement preparedStatement; + private final ColumnDefinitions variableDefinitions; + private final List values; + private String configProfileName; + private DriverConfigProfile configProfile; + private final String keyspace; + private Map customPayload; + private Boolean idempotent; + private final CodecRegistry codecRegistry; + private final ProtocolVersion protocolVersion; + private boolean tracing; + private ByteBuffer pagingState; + + public DefaultBoundStatement( + DefaultPreparedStatement preparedStatement, + ColumnDefinitions variableDefinitions, + String configProfileName, + DriverConfigProfile configProfile, + String keyspace, + Map customPayload, + Boolean idempotent, + CodecRegistry codecRegistry, + ProtocolVersion protocolVersion) { + this.preparedStatement = preparedStatement; + this.variableDefinitions = variableDefinitions; + this.values = new ArrayList<>(variableDefinitions.size()); + for (int i = 0; i < variableDefinitions.size(); i++) { + this.values.add(null); + } + this.configProfileName = configProfileName; + this.configProfile = configProfile; + this.keyspace = keyspace; + this.customPayload = customPayload; + this.idempotent = idempotent; + this.codecRegistry = codecRegistry; + this.protocolVersion = protocolVersion; + } + + @Override + public int size() { + return variableDefinitions.size(); + } + + @Override + public DataType getType(int i) { + return variableDefinitions.get(i).getType(); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return variableDefinitions.firstIndexOf(id); + } + + @Override + public int firstIndexOf(String name) { + return variableDefinitions.firstIndexOf(name); + } + + @Override + public CodecRegistry codecRegistry() { + return codecRegistry; + } + + @Override + public ProtocolVersion protocolVersion() { + return protocolVersion; + } + + @Override + public ByteBuffer getBytesUnsafe(int i) { + return values.get(i); + } + + @Override + public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { + values.set(i, v); + return this; + } + + @Override + public DefaultPreparedStatement getPreparedStatement() { + return preparedStatement; + } + + @Override + public List getValues() { + return values; + } + + @Override + public String getConfigProfileName() { + return configProfileName; + } + + @Override + public BoundStatement setConfigProfileName(String configProfileName) { + this.configProfileName = configProfileName; + return this; + } + + @Override + public DriverConfigProfile getConfigProfile() { + return configProfile; + } + + @Override + public BoundStatement setConfigProfile(DriverConfigProfile configProfile) { + this.configProfile = configProfile; + return this; + } + + @Override + public String getKeyspace() { + return keyspace; + } + + @Override + public Map getCustomPayload() { + return customPayload; + } + + @Override + public BoundStatement setCustomPayload(Map customPayload) { + this.customPayload = customPayload; + return this; + } + + @Override + public Boolean isIdempotent() { + return idempotent; + } + + @Override + public BoundStatement setIdempotent(Boolean idempotent) { + this.idempotent = idempotent; + return this; + } + + @Override + public boolean isTracing() { + return tracing; + } + + @Override + public BoundStatement setTracing() { + this.tracing = true; + return this; + } + + @Override + public ByteBuffer getPagingState() { + return pagingState; + } + + @Override + public Statement copy(ByteBuffer newPagingState) { + this.pagingState = newPagingState; + return this; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java new file mode 100644 index 00000000000..9bbbba80728 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * For simplicity, this default implementation makes opinionated choices about {@code + * *ForBoundStatements} methods: these should be appropriate for most cases, but not all use cases + * are supported (for example, preparing with one profile and having the bound statements use + * another profile is not possible). For exotic use cases, subclass or write your own + * implementation. + */ +public class DefaultPrepareRequest implements PrepareRequest { + + private final SimpleStatement statement; + + public DefaultPrepareRequest(SimpleStatement statement) { + this.statement = statement; + } + + public DefaultPrepareRequest(String query) { + this.statement = SimpleStatement.newInstance(query); + } + + @Override + public String getQuery() { + return statement.getQuery(); + } + + @Override + public String getConfigProfileName() { + return statement.getConfigProfileName(); + } + + @Override + public DriverConfigProfile getConfigProfile() { + return statement.getConfigProfile(); + } + + @Override + public String getKeyspace() { + // Not implemented yet, waiting for CASSANDRA-10145 to land in a release + return null; + } + + @Override + public Map getCustomPayload() { + return statement.getCustomPayload(); + } + + @Override + public String getConfigProfileNameForBoundStatements() { + return statement.getConfigProfileName(); + } + + @Override + public DriverConfigProfile getConfigProfileForBoundStatements() { + return statement.getConfigProfile(); + } + + @Override + public Map getCustomPayloadForBoundStatements() { + return statement.getCustomPayload(); + } + + @Override + public Boolean areBoundStatementsIdempotent() { + return statement.isIdempotent(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java new file mode 100644 index 00000000000..0be81a2ec93 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import java.nio.ByteBuffer; +import java.util.Map; + +public class DefaultPreparedStatement implements PreparedStatement { + + private final ByteBuffer id; + private final String query; + private final ColumnDefinitions variableDefinitions; + private final ColumnDefinitions resultSetDefinitions; + // The options to propagate to the bound statements: + private final String configProfileName; + private final DriverConfigProfile configProfile; + private final String keyspace; + private final Map customPayload; + private final Boolean idempotent; + private final CodecRegistry codecRegistry; + private final ProtocolVersion protocolVersion; + + public DefaultPreparedStatement( + ByteBuffer id, + String query, + ColumnDefinitions variableDefinitions, + ColumnDefinitions resultSetDefinitions, + String configProfileName, + DriverConfigProfile configProfile, + String keyspace, + Map customPayload, + Boolean idempotent, + CodecRegistry codecRegistry, + ProtocolVersion protocolVersion) { + this.id = id; + this.query = query; + this.variableDefinitions = variableDefinitions; + this.resultSetDefinitions = resultSetDefinitions; + this.configProfileName = configProfileName; + this.configProfile = configProfile; + this.keyspace = keyspace; + this.customPayload = customPayload; + this.idempotent = idempotent; + this.codecRegistry = codecRegistry; + this.protocolVersion = protocolVersion; + } + + @Override + public ByteBuffer getId() { + return id; + } + + @Override + public String getQuery() { + return query; + } + + @Override + public ColumnDefinitions getVariableDefinitions() { + return variableDefinitions; + } + + @Override + public ColumnDefinitions getResultSetDefinitions() { + return resultSetDefinitions; + } + + @Override + public BoundStatement bind() { + return new DefaultBoundStatement( + this, + variableDefinitions, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + codecRegistry, + protocolVersion); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 703362be6ef..4a0be33af85 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -241,7 +241,7 @@ private void onPoolsInit(List> poolStages) { assert adminExecutor.inEventLoop(); LOG.debug("{}: all pools have finished initializing", DefaultSession.this); // We will only propagate an invalid keyspace error if all pools get it - boolean allInvalidKeyspaces = true; + boolean allInvalidKeyspaces = poolStages.size() > 0; for (CompletionStage poolStage : poolStages) { // Note: pool init always succeeds ChannelPool pool = CompletableFutures.getCompleted(poolStage.toCompletableFuture()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index 02b3c12483a..a7bafcefe56 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -21,13 +21,19 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; import java.util.Queue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Factors code that should be common to most request handler implementations. */ public abstract class RequestHandlerBase implements RequestHandler { + private static final Logger LOG = LoggerFactory.getLogger(RequestHandlerBase.class); + protected final Request request; protected final boolean isIdempotent; protected final DefaultSession session; @@ -61,4 +67,23 @@ protected RequestHandlerBase( ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : request.isIdempotent(); } + + protected DriverChannel getChannel(Node node) { + ChannelPool pool = session.getPools().get(node); + if (pool == null) { + LOG.trace("No pool to {}, skipping", node); + return null; + } else { + DriverChannel channel = pool.next(); + if (channel == null) { + LOG.trace("Pool returned no channel for {}, skipping", node); + return null; + } else if (channel.closeFuture().isDone()) { + LOG.trace("Pool returned closed connection to {}, skipping", node); + return null; + } else { + return channel; + } + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java index 166accf960f..acd6884362f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareProcessor; import com.datastax.oss.driver.internal.core.cql.CqlRequestProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,10 +26,7 @@ public class RequestProcessorRegistry { private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); public static final RequestProcessorRegistry DEFAULT = - new RequestProcessorRegistry( - new CqlRequestProcessor() - // TODO add CqlPrepareProcessor - ); + new RequestProcessorRegistry(new CqlRequestProcessor(), new CqlPrepareProcessor()); // Effectively immutable: the contents are never modified after construction private final RequestProcessor[] processors; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 8b29e0f83a9..619b7be5a3c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -49,15 +49,19 @@ public static void completeFrom(CompletionStage source, CompletableFuture /** @return a completion stage that completes when all inputs are done (success or failure). */ public static CompletionStage allDone(List> inputs) { CompletableFuture result = new CompletableFuture<>(); - final int todo = inputs.size(); - final AtomicInteger done = new AtomicInteger(); - for (CompletionStage input : inputs) { - input.whenComplete( - (v, error) -> { - if (done.incrementAndGet() == todo) { - result.complete(null); - } - }); + if (inputs.isEmpty()) { + result.complete(null); + } else { + final int todo = inputs.size(); + final AtomicInteger done = new AtomicInteger(); + for (CompletionStage input : inputs) { + input.whenComplete( + (v, error) -> { + if (done.incrementAndGet() == todo) { + result.complete(null); + } + }); + } } return result; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java new file mode 100644 index 00000000000..f2a23801dcb --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.servererrors.OverloadedException; +import com.datastax.oss.protocol.internal.Frame; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.Prepared; +import com.datastax.oss.protocol.internal.response.result.RawType; +import com.datastax.oss.protocol.internal.response.result.RowsMetadata; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +public class CqlPrepareHandlerTest { + + private static final DefaultPrepareRequest PREPARE_REQUEST = + new DefaultPrepareRequest("mock query"); + + @Mock private Node node1; + @Mock private Node node2; + @Mock private Node node3; + + @Mock private CqlPrepareProcessor processor; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + // By default, simulate that the prepared statement is not already in the driver's cache + Mockito.when(processor.cache(any(DefaultPreparedStatement.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void should_prepare_on_first_node_and_reprepare_on_others() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); + node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + // The future waits for the reprepare attempt on other nodes, so it's not done yet. + assertThat(prepareFuture).isNotDone(); + + // Should now reprepare on the remaining nodes: + node2Behavior.verifyWrite(); + node2Behavior.setWriteSuccess(); + node2Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + node3Behavior.verifyWrite(); + node3Behavior.setWriteSuccess(); + node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + } + } + + @Test + public void should_not_reprepare_on_other_nodes_if_already_cached() { + // Simulate an existing entry in the driver's cache: + DefaultPreparedStatement mockExistingStatement = Mockito.mock(DefaultPreparedStatement.class); + Mockito.when(processor.cache(any(DefaultPreparedStatement.class))) + .thenAnswer(invocation -> mockExistingStatement); + + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); + node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + // When the statement already existed, we don't prepare on other nodes, so the future should + // complete immediately. + assertThat(prepareFuture) + .isSuccess( + preparedStatement -> assertThat(preparedStatement).isSameAs(mockExistingStatement)); + + // And the other nodes should not be contacted: + node2Behavior.verifyNoWrite(); + node3Behavior.verifyNoWrite(); + } + } + + @Test + public void should_ignore_errors_while_repreparing_on_other_nodes() { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withResponse(node1, defaultFrameOf(simplePrepared())); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + assertThat(prepareFuture).isNotDone(); + + // Other nodes fail, the future should still succeed when all done + node2Behavior.verifyWrite(); + node2Behavior.setWriteSuccess(); + node2Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.SERVER_ERROR, "mock error"))); + + node3Behavior.verifyWrite(); + node3Behavior.setWriteFailure(new RuntimeException("mock error")); + + assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + } + } + + @Test + public void should_retry_initial_prepare_if_recoverable_error() { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder() + .withResponse( + node1, + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.OVERLOADED, "mock message"))) + .withResponse(node2, defaultFrameOf(simplePrepared())); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + // Make node1's error recoverable, will switch to node2 + Mockito.when( + harness + .getContext() + .retryPolicy() + .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + .thenReturn(RetryDecision.RETRY_NEXT); + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + // Success on node2, reprepare on node3 + assertThat(prepareFuture).isNotDone(); + node3Behavior.verifyWrite(); + node3Behavior.setWriteSuccess(); + node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + } + } + + @Test + public void should_not_retry_initial_prepare_if_unrecoverable_error() { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder() + .withResponse( + node1, + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.OVERLOADED, "mock message"))); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + // Make node1's error unrecoverable, will rethrow + Mockito.when( + harness + .getContext() + .retryPolicy() + .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + .thenReturn(RetryDecision.RETHROW); + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + // Success on node2, reprepare on node3 + assertThat(prepareFuture) + .isFailed( + error -> { + assertThat(error).isInstanceOf(OverloadedException.class); + node2Behavior.verifyNoWrite(); + node3Behavior.verifyNoWrite(); + }); + } + } + + @Test + public void should_fail_if_retry_policy_ignores_error() { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder() + .withResponse( + node1, + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.OVERLOADED, "mock message"))); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + // Make node1's error unrecoverable, will rethrow + Mockito.when( + harness + .getContext() + .retryPolicy() + .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + .thenReturn(RetryDecision.IGNORE); + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + .asyncResult(); + + // Success on node2, reprepare on node3 + assertThat(prepareFuture) + .isFailed( + error -> { + assertThat(error) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "IGNORE decisions are not allowed for prepare requests, " + + "please fix your retry policy."); + node2Behavior.verifyNoWrite(); + node3Behavior.verifyNoWrite(); + }); + } + } + + private static Frame defaultFrameOf(Message responseMessage) { + return Frame.forResponse( + CoreProtocolVersion.V4.getCode(), + 0, + null, + Frame.NO_PAYLOAD, + Collections.emptyList(), + responseMessage); + } + + private static Message simplePrepared() { + RowsMetadata variablesMetadata = + new RowsMetadata( + ImmutableList.of( + new ColumnSpec( + "ks", + "table", + "key", + 0, + RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), + 1, + null, + new int[] {0}); + RowsMetadata resultMetadata = + new RowsMetadata( + ImmutableList.of( + new ColumnSpec( + "ks", + "table", + "message", + 0, + RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), + 1, + null, + new int[] {}); + return new Prepared(Bytes.fromHexString("0xffff").array(), variablesMetadata, resultMetadata); + } + + private static void assertMatchesSimplePrepared(PreparedStatement statement) { + assertThat(Bytes.toHexString(statement.getId())).isEqualTo("0xffff"); + + ColumnDefinitions variableDefinitions = statement.getVariableDefinitions(); + assertThat(variableDefinitions).hasSize(1); + assertThat(variableDefinitions.get(0).getName().asInternal()).isEqualTo("key"); + + ColumnDefinitions resultSetDefinitions = statement.getResultSetDefinitions(); + assertThat(resultSetDefinitions).hasSize(1); + assertThat(resultSetDefinitions.get(0).getName().asInternal()).isEqualTo("message"); + } +} From 4683b78277ce4cc7f9bc099ae9774b8ea6e309cc Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 15 Jun 2017 11:18:56 -0700 Subject: [PATCH 075/742] Add documentation for statements --- .../driver/api/core/cql/PreparedStatement.java | 13 +++++++++++++ .../driver/api/core/cql/SimpleStatement.java | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 91aca9e3da9..2412be6acb5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -17,6 +17,19 @@ import java.nio.ByteBuffer; +/** + * A query with bind variables that has been pre-parsed by the database. + * + *

Client applications create instances with {@link CqlSession#prepare(SimpleStatement)}. Then + * they use {@link #bind()} to obtain a {@link BoundStatement}, an executable instance that + * associates a set of values with the bind variables. + * + *

The default prepared statement implementation returned by the driver is thread-safe. + * Client applications can -- and are expected to -- prepare each query once and store the result in + * a place where it can be accessed concurrently by application threads (for example a final field). + * Preparing the same query string twice is suboptimal and a bad practice, and will cause the driver + * to log a warning. + */ public interface PreparedStatement { /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index c6e3cac28f1..187768d520f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -21,6 +21,24 @@ import java.util.List; import java.util.Map; +/** + * A one-off CQL statement consisting of a query string with optional placeholders, and a set of + * values for these placeholders. + * + *

To create instances, client applications can use the {@code newInstance} factory methods on + * this interface for common cases, or {@link #builder(String)} for more control over the + * parameters. They can then be passed to {@link CqlSession#execute(Statement)}. + * + *

Simple statements should be reserved for queries that will only be executed a few times by an + * application. For more frequent queries, {@link PreparedStatement} provides many advantages: it is + * more efficient because the server parses the query only once and caches the result; it allows the + * server to return metadata about the bind variables, which allows the driver to validate the + * values earlier, and apply certain optimizations like token-aware routing. + * + *

The default simple statement implementation returned by the driver is immutable and + * thread-safe. If an application is going to reuse the same statement more than once, it is + * recommended to cache it (for example in a final field). + */ public interface SimpleStatement extends Statement { /** From 00193939c82a1c2627229d16e4763afc34c0267b Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 15 Jun 2017 15:49:04 -0700 Subject: [PATCH 076/742] JAVA-1499: Wait for load balancing policy at cluster initialization Motivation: In the previous version, the cluster was marked as initialized as soon as the topology monitor had initialized. This created a race condition where a session could be created before the LBP was initialized, observe all nodes in the initial IGNORED state and not create any pool. Modifications: Don't complete the cluster init future until the LBP has initialized. Result: Cluster.connect is now guaranteed to see the node distances initialized by the LBP. --- changelog/README.md | 1 + .../driver/internal/core/DefaultCluster.java | 31 +++++-------------- .../core/metadata/TopologyMonitor.java | 2 +- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index e4a63652165..1fed771cf28 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,4 +4,5 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1499: Wait for load balancing policy at cluster initialization - [new feature] JAVA-1495: Add prepared statements \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 0b0ec84a129..f76b8012c0c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -135,31 +134,17 @@ private void init() { MetadataManager metadataManager = context.metadataManager(); metadataManager .addContactPoints(initialContactPoints) - // Then initialize the topology monitor .thenCompose(v -> context.topologyMonitor().init()) - // If that succeeds, Cluster init is considered successful + .thenCompose(v -> metadataManager.refreshNodes()) .thenAccept( v -> { - initFuture.complete(DefaultCluster.this); - - // Launch a full refresh asynchronously - metadataManager - .refreshNodes() - .whenComplete( - (result, error) -> { - if (error != null) { - LOG.debug("Error while refreshing node list", error); - } else { - try { - context.loadBalancingPolicyWrapper().init(); - } catch (Throwable t) { - LOG.warn( - "Unexpected error while initializing load balancing policy", t); - } - } - }); - - // TODO schedule full schema refresh + try { + context.loadBalancingPolicyWrapper().init(); + initFuture.complete(DefaultCluster.this); + // TODO schedule full schema refresh asynchronously (does not block init) + } catch (Throwable throwable) { + initFuture.completeExceptionally(throwable); + } }) .exceptionally( error -> { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index e999734a249..9b9685c33da 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -50,7 +50,7 @@ public interface TopologyMonitor extends AsyncAutoCloseable { CompletionStage init(); /** - * Invoked when the drive needs to refresh the information about an existing node. This is called + * Invoked when the driver needs to refresh the information about an existing node. This is called * when the node was back and comes back up. * *

This will be invoked directly from a driver's internal thread; if the refresh involves From 8ba7c64b989b5ab072e0af3782e7f262481abeea Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 16 Jun 2017 13:26:43 -0700 Subject: [PATCH 077/742] Update topology monitor javadocs They were out of date after JAVA-1499. --- .../internal/core/metadata/TopologyMonitor.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index 9b9685c33da..b41024eb362 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -18,10 +18,10 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.control.ControlConnection; import java.net.InetSocketAddress; import java.util.Optional; import java.util.concurrent.CompletionStage; @@ -40,13 +40,7 @@ */ public interface TopologyMonitor extends AsyncAutoCloseable { - /** - * Triggers the initialization of the monitor. - * - *

This will be invoked at startup, and is how the driver determines when it is "successfully - * connected" to the Cassandra cluster. In particular, the initialization of the {@link Cluster} - * instance depends on the result of this method. - */ + /** Triggers the initialization of the monitor. */ CompletionStage init(); /** @@ -83,10 +77,9 @@ public interface TopologyMonitor extends AsyncAutoCloseable { *

This will be invoked directly from a driver's internal thread; if the refresh involves * blocking I/O or heavy computations, it should be scheduled on a separate thread. * - *

The driver calls this at initialization; if that initial call fails, the load balancing - * policy is not initialized, and the driver is unable to execute queries. You should schedule - * retries to ensure that the call eventually succeeds (see how the default implementation does it - * in {@link ControlConnection.SingleThreaded#onSuccessfulReconnect()}). + *

The driver calls this at initialization, and uses the result to initialize the {@link + * LoadBalancingPolicy}; successful initialization of the {@link Cluster} object depends on that + * initial call succeeding. * * @return a future that completes with the information. We assume that the full node list will * always be returned in a single message (no paging). From 589775ebf0aaea2c88d21d2bba1402b66a0da421 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 16 Jun 2017 13:19:50 -0700 Subject: [PATCH 078/742] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response --- changelog/README.md | 1 + .../api/core/cql/PreparedStatement.java | 8 ++++ .../RoundRobinLoadBalancingPolicy.java | 2 + .../adminrequest/AdminRequestHandler.java | 7 ++- .../driver/internal/core/cql/Conversions.java | 7 ++- .../internal/core/cql/CqlPrepareHandler.java | 2 +- .../internal/core/cql/CqlRequestHandler.java | 45 ++++++++++++++++--- .../core/cql/DefaultPreparedStatement.java | 14 ++++-- .../core/cql/CqlRequestHandlerTest.java | 43 ++++++++++++++++++ .../internal/core/cql/PoolBehavior.java | 18 ++++++++ 10 files changed, 134 insertions(+), 13 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 1fed771cf28..c1b7ec62930 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,5 +4,6 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response - [bug] JAVA-1499: Wait for load balancing policy at cluster initialization - [new feature] JAVA-1495: Add prepared statements \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 2412be6acb5..4e7ef28ddd2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import java.nio.ByteBuffer; +import java.util.Map; /** * A query with bind variables that has been pre-parsed by the database. @@ -51,4 +52,11 @@ public interface PreparedStatement { ColumnDefinitions getResultSetDefinitions(); BoundStatement bind(); + + /** + * The custom payload that was used for the initial prepare request. + * + *

This is mostly for internal use, if the driver needs to re-prepare the statement later. + */ + Map initialCustomPayload(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index c2b94b9d6bd..88885508a3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -75,11 +75,13 @@ public void onAdd(Node node) { @Override public void onUp(Node node) { + LOG.debug("Adding {}", node); liveNodes.add(node); } @Override public void onDown(Node node) { + LOG.debug("Removing {}", node); liveNodes.remove(node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 5c2b65dda62..57ecbf8d834 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -24,7 +24,6 @@ import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; -import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.request.query.QueryOptions; import com.datastax.oss.protocol.internal.response.result.Prepared; @@ -88,8 +87,12 @@ public AdminRequestHandler( } public CompletionStage start() { + return start(Frame.NO_PAYLOAD); + } + + public CompletionStage start(Map customPayload) { LOG.debug("Executing {}", this); - channel.write(message, false, Frame.NO_PAYLOAD, this).addListener(this::onWriteComplete); + channel.write(message, false, customPayload, this).addListener(this::onWriteComplete); return result; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 12656efb314..313b6a6854f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -52,6 +53,7 @@ import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Execute; +import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.request.query.QueryOptions; import com.datastax.oss.protocol.internal.response.Error; @@ -188,10 +190,11 @@ static DefaultPreparedStatement toPreparedStatement( request.getConfigProfileNameForBoundStatements(), request.getConfigProfileForBoundStatements(), request.getKeyspace(), - request.getCustomPayloadForBoundStatements(), + ImmutableMap.copyOf(request.getCustomPayloadForBoundStatements()), request.areBoundStatementsIdempotent(), context.codecRegistry(), - context.protocolVersion()); + context.protocolVersion(), + ImmutableMap.copyOf(request.getCustomPayload())); } private static DefaultColumnDefinitions toColumnDefinitions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 1d957b458c3..ba6413b3335 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -98,7 +98,7 @@ public class CqlPrepareHandler this.message = new Prepare(request.getQuery()); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); sendRequest(null, 0); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index caf51843e49..f2f670dd429 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -20,7 +20,9 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -34,6 +36,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -44,6 +47,7 @@ import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.Result; import com.datastax.oss.protocol.internal.response.result.Rows; @@ -81,6 +85,7 @@ public class CqlRequestHandler // All executions share the same query plan, they stop either when the request completes or the // query plan is empty. private final AtomicInteger executions; + private final Duration timeout; private final ScheduledFuture timeoutFuture; private final List> pendingExecutions; private final RetryPolicy retryPolicy; @@ -107,7 +112,7 @@ public class CqlRequestHandler this.message = Conversions.toMessage(statement, configProfile, context); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - Duration timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); @@ -198,7 +203,7 @@ private void sendRequest(Node node, int execution, int retryCount) { } } else { NodeResponseCallback nodeResponseCallback = - new NodeResponseCallback(node, execution, retryCount); + new NodeResponseCallback(node, channel, execution, retryCount); channel .write(message, request.isTracing(), request.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); @@ -279,6 +284,7 @@ private class NodeResponseCallback implements ResponseCallback, GenericFutureListener> { private final Node node; + private final DriverChannel channel; // The identifier of the current execution (0 for the initial execution, 1 for the first // speculative execution, etc.) private final int execution; @@ -286,8 +292,9 @@ private class NodeResponseCallback // the first attempt of each execution). private final int retryCount; - private NodeResponseCallback(Node node, int execution, int retryCount) { + private NodeResponseCallback(Node node, DriverChannel channel, int execution, int retryCount) { this.node = node; + this.channel = channel; this.execution = execution; this.retryCount = retryCount; } @@ -325,8 +332,36 @@ public void onResponse(Frame responseFrame) { private void processErrorResponse(Error errorMessage) { if (errorMessage.code == ProtocolConstants.ErrorCode.UNPREPARED) { - // TODO reprepare on the fly - throw new UnsupportedOperationException("TODO handle prepare-and-retry"); + if (!(request instanceof BoundStatement)) { + // TODO can we get UNPREPARED for a BATCH message? + setFinalError( + new IllegalStateException( + "Unexpected UNPREPARED response, " + + "this should only happen for a bound statement")); + } else { + LOG.debug("Statement is not prepared on {}, repreparing", node); + PreparedStatement preparedStatement = + ((BoundStatement) CqlRequestHandler.this.request).getPreparedStatement(); + Prepare reprepareMessage = new Prepare(preparedStatement.getQuery()); + AdminRequestHandler reprepareHandler = + new AdminRequestHandler( + channel, reprepareMessage, timeout, "Reprepare " + reprepareMessage.toString()); + reprepareHandler + .start(preparedStatement.initialCustomPayload()) + .handle( + (result, error) -> { + if (error != null) { + recordError(node, error); + // Try next host + sendRequest(null, execution, retryCount); + } else { + // Reprepare succeeded, retry on same node + sendRequest(node, execution, retryCount); + } + return null; + }); + return; + } } Throwable error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 0be81a2ec93..d8545c95aa3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -30,14 +30,15 @@ public class DefaultPreparedStatement implements PreparedStatement { private final String query; private final ColumnDefinitions variableDefinitions; private final ColumnDefinitions resultSetDefinitions; + private final CodecRegistry codecRegistry; + private final ProtocolVersion protocolVersion; + private final Map initialCustomPayload; // The options to propagate to the bound statements: private final String configProfileName; private final DriverConfigProfile configProfile; private final String keyspace; private final Map customPayload; private final Boolean idempotent; - private final CodecRegistry codecRegistry; - private final ProtocolVersion protocolVersion; public DefaultPreparedStatement( ByteBuffer id, @@ -50,7 +51,8 @@ public DefaultPreparedStatement( Map customPayload, Boolean idempotent, CodecRegistry codecRegistry, - ProtocolVersion protocolVersion) { + ProtocolVersion protocolVersion, + Map initialCustomPayload) { this.id = id; this.query = query; this.variableDefinitions = variableDefinitions; @@ -62,6 +64,7 @@ public DefaultPreparedStatement( this.idempotent = idempotent; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; + this.initialCustomPayload = initialCustomPayload; } @Override @@ -97,4 +100,9 @@ public BoundStatement bind() { codecRegistry, protocolVersion); } + + @Override + public Map initialCustomPayload() { + return initialCustomPayload; + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 0f92b297de8..467fca1a65c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -19,11 +19,18 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.protocol.internal.request.Prepare; +import com.datastax.oss.protocol.internal.response.error.Unprepared; +import com.datastax.oss.protocol.internal.response.result.Prepared; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import java.nio.ByteBuffer; import java.time.Duration; +import java.util.Collections; import java.util.Iterator; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @@ -114,4 +121,40 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { .setKeyspace(CqlIdentifier.fromInternal("newKeyspace"))); } } + + @Test + public void should_reprepare_on_the_fly_if_not_prepared() { + ByteBuffer mockId = ByteBuffer.wrap(new byte[0]); + PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class); + Mockito.when(preparedStatement.getId()).thenReturn(mockId); + BoundStatement boundStatement = Mockito.mock(BoundStatement.class); + Mockito.when(boundStatement.getPreparedStatement()).thenReturn(preparedStatement); + Mockito.when(boundStatement.getValues()).thenReturn(Collections.emptyList()); + + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + // For the first attempt that gets the UNPREPARED response + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + // For the second attempt that succeeds + harnessBuilder.withResponse(node1, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext()) + .asyncResult(); + + node1Behavior.setWriteSuccess(); + + // Before we proceed, mock the PREPARE exchange that will occur as soon as we complete the + // first response. + node1Behavior.mockFollowupRequest( + Prepare.class, defaultFrameOf(new Prepared(new byte[] {}, null, null))); + + node1Behavior.setResponseSuccess( + defaultFrameOf(new Unprepared("mock message", new byte[] {}))); + + // Should now re-prepare, re-execute and succeed. + assertThat(resultSetFuture).isSuccess(); + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index fefba8d7340..79f87ec8140 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -41,12 +41,14 @@ public class PoolBehavior { final Node node; + private final EventExecutor executor; final DriverChannel channel; private final Promise writePromise; private final CompletableFuture callbackFuture = new CompletableFuture<>(); public PoolBehavior(Node node, boolean createChannel, EventExecutor executor) { this.node = node; + this.executor = executor; if (!createChannel) { this.channel = null; this.writePromise = null; @@ -91,4 +93,20 @@ public void setResponseSuccess(Frame responseFrame) { public void setResponseFailure(Throwable cause) { callbackFuture.thenAccept(callback -> callback.onFailure(cause)); } + + /** Mocks a follow-up request on the same channel. */ + public void mockFollowupRequest(Class expectedMessage, Frame responseFrame) { + Promise writePromise2 = executor.newPromise(); + CompletableFuture callbackFuture2 = new CompletableFuture<>(); + Mockito.when( + channel.write( + any(expectedMessage), anyBoolean(), anyMap(), any(ResponseCallback.class))) + .thenAnswer( + invocation -> { + callbackFuture2.complete(invocation.getArgument(3)); + return writePromise2; + }); + writePromise2.setSuccess(null); + callbackFuture2.thenAccept(callback -> callback.onResponse(responseFrame)); + } } From 8d7ae3b0c51fb6634e38cb0395df3e1416c6e8c2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 16 Jun 2017 16:56:19 -0700 Subject: [PATCH 079/742] JAVA-1496: Improve log messages Add normalized prefix to every log message. Add more log messages. --- changelog/README.md | 1 + .../datastax/oss/driver/api/core/Cluster.java | 8 ++ .../oss/driver/api/core/CqlIdentifier.java | 5 ++ .../api/core/config/CoreDriverOption.java | 2 +- .../api/core/context/DriverContext.java | 7 ++ .../RoundRobinLoadBalancingPolicy.java | 9 ++- .../type/codec/registry/CodecRegistry.java | 2 +- .../driver/internal/core/DefaultCluster.java | 31 ++++++-- .../adminrequest/AdminRequestHandler.java | 21 +++-- .../internal/core/channel/ChannelFactory.java | 3 +- .../internal/core/channel/DriverChannel.java | 5 ++ .../core/channel/DriverChannelOptions.java | 16 +++- .../core/channel/InFlightHandler.java | 54 ++++++++++--- .../core/channel/ProtocolInitHandler.java | 21 ++++- .../core/context/DefaultDriverContext.java | 28 +++++-- .../internal/core/context/EventBus.java | 11 ++- .../core/control/ControlConnection.java | 59 +++++++++----- .../internal/core/cql/CqlPrepareHandler.java | 47 ++++++++--- .../core/cql/CqlPrepareProcessor.java | 6 +- .../internal/core/cql/CqlRequestHandler.java | 78 ++++++++++++++----- .../core/cql/CqlRequestProcessor.java | 5 +- .../core/metadata/AddNodeRefresh.java | 6 +- .../core/metadata/DefaultTopologyMonitor.java | 15 ++-- .../core/metadata/FullNodeListRefresh.java | 10 +-- .../metadata/InitContactPointsRefresh.java | 7 +- .../metadata/LoadBalancingPolicyWrapper.java | 7 +- .../core/metadata/MetadataManager.java | 33 ++++---- .../core/metadata/MetadataRefresh.java | 4 +- .../core/metadata/NodeStateManager.java | 55 +++++++++---- .../internal/core/metadata/NodesRefresh.java | 8 +- .../core/metadata/RemoveNodeRefresh.java | 6 +- .../internal/core/pool/ChannelPool.java | 58 ++++++++------ .../core/pool/ChannelPoolFactory.java | 8 +- .../internal/core/session/DefaultSession.java | 74 +++++++++++------- .../core/session/RequestHandlerBase.java | 8 +- .../core/session/RequestProcessor.java | 3 +- .../session/RequestProcessorRegistry.java | 14 ++-- .../codec/registry/CachingCodecRegistry.java | 38 ++++----- .../codec/registry/DefaultCodecRegistry.java | 9 ++- .../core/util/concurrent/Reconnection.java | 21 +++-- core/src/main/resources/reference.conf | 9 +++ .../core/channel/ChannelFactoryTestBase.java | 3 +- .../core/channel/DriverChannelTest.java | 7 +- .../core/channel/InFlightHandlerTest.java | 3 +- .../core/channel/ProtocolInitHandlerTest.java | 5 +- .../core/context/bus/EventBusTest.java | 2 +- .../core/control/ControlConnectionTest.java | 1 - .../core/cql/CqlPrepareHandlerTest.java | 12 +-- .../core/cql/CqlRequestHandlerRetryTest.java | 14 ++-- ...equestHandlerSpeculativeExecutionTest.java | 12 +-- .../core/cql/CqlRequestHandlerTest.java | 17 +++- .../core/cql/RequestHandlerTestHarness.java | 2 +- .../core/metadata/AddNodeRefreshTest.java | 4 +- .../metadata/FullNodeListRefreshTest.java | 4 +- .../InitContactPointsRefreshTest.java | 3 +- .../LoadBalancingPolicyWrapperTest.java | 2 +- .../core/metadata/NodeStateManagerTest.java | 2 +- .../core/metadata/RemoveNodeRefreshTest.java | 4 +- .../internal/core/pool/ChannelPoolTest.java | 28 +++---- .../core/session/DefaultSessionTest.java | 40 +++++----- .../session/MockChannelPoolFactoryHelper.java | 5 +- .../registry/CachingCodecRegistryTest.java | 2 +- .../util/concurrent/ReconnectionTest.java | 7 +- 63 files changed, 669 insertions(+), 322 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index c1b7ec62930..ef760c8ebca 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1496: Improve log messages - [new feature] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response - [bug] JAVA-1499: Wait for load balancing policy at cluster initialization - [new feature] JAVA-1495: Add prepared statements \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index b4ff2dbd8b2..d2325d3062f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -30,6 +31,13 @@ static ClusterBuilder builder() { return new ClusterBuilder(); } + /** + * The unique name identifying this cluster. + * + * @see CoreDriverOption#CLUSTER_NAME + */ + String getName(); + /** * Returns a snapshot of the Cassandra cluster's topology and schema metadata. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 71a55cb0730..695664a89ad 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -137,6 +137,11 @@ public int hashCode() { return internal.hashCode(); } + @Override + public String toString() { + return internal; + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Preconditions.checkNotNull(internal, "internal must not be null"); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 3952c181e41..4b2c750e168 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -22,8 +22,8 @@ */ public enum CoreDriverOption implements DriverOption { CONTACT_POINTS("contact-points", false), - PROTOCOL_VERSION("protocol.version", false), + CLUSTER_NAME("cluster-name", false), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), CONNECTION_SET_KEYSPACE_TIMEOUT("connection.set-keyspace-timeout", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 3ef352e68b1..8904f716b6d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.context; +import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -29,6 +30,12 @@ /** Holds common components that are shared throughout a driver instance. */ public interface DriverContext extends AttachmentPoint { + /** + * This is the same as {@link Cluster#getName()}, it's exposed here for components that only have + * a reference to the context. + */ + String clusterName(); + DriverConfig config(); LoadBalancingPolicy loadBalancingPolicy(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index 88885508a3a..40606911592 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -38,16 +38,17 @@ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; + private final String logPrefix; private final AtomicInteger startIndex = new AtomicInteger(); private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { - // nothing to do + this.logPrefix = context.clusterName(); } @Override public void init(Set nodes, DistanceReporter distanceReporter) { - LOG.debug("Initializing with {}", nodes); + LOG.debug("[{}] Initializing with {}", logPrefix, nodes); for (Node node : nodes) { distanceReporter.setDistance(node, NodeDistance.LOCAL); if (node.getState() == NodeState.UNKNOWN || node.getState() == NodeState.UP) { @@ -75,13 +76,13 @@ public void onAdd(Node node) { @Override public void onUp(Node node) { - LOG.debug("Adding {}", node); + LOG.debug("[{}] Adding {}", logPrefix, node); liveNodes.add(node); } @Override public void onDown(Node node) { - LOG.debug("Removing {}", node); + LOG.debug("[{}] Removing {}", logPrefix, node); liveNodes.remove(node); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java index 36b6a0374c0..e0f86bbeb02 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java @@ -26,7 +26,7 @@ public interface CodecRegistry { * An immutable instance, that only handles built-in driver types (that is, primitive types, and * collections, tuples, and user defined types thereof). */ - CodecRegistry DEFAULT = new DefaultCodecRegistry(); + CodecRegistry DEFAULT = new DefaultCodecRegistry("default"); /** * Returns a codec to handle the conversion between the given types. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index f76b8012c0c..6083b347899 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -54,12 +54,15 @@ public static CompletableFuture init( private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; private final MetadataManager metadataManager; + private final String logPrefix; private DefaultCluster(InternalDriverContext context, Set contactPoints) { + LOG.debug("Creating new cluster {}", context.clusterName()); this.context = context; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context, contactPoints); this.metadataManager = context.metadataManager(); + this.logPrefix = context.clusterName(); } private CompletableFuture init() { @@ -67,6 +70,11 @@ private CompletableFuture init() { return singleThreaded.initFuture; } + @Override + public String getName() { + return context.clusterName(); + } + @Override public Metadata getMetadata() { return metadataManager.getMetadata(); @@ -114,6 +122,7 @@ private class SingleThreaded { // Note: closed sessions are not removed from the list. If this creates a memory issue, there // is something really wrong in the client program private List sessions; + private int sessionCounter; private SingleThreaded(InternalDriverContext context, Set contactPoints) { this.context = context; @@ -128,6 +137,7 @@ private void init() { return; } initWasCalled = true; + LOG.debug("[{}] Starting initialization", logPrefix); // If any contact points were provided, store them in the metadata right away (the // control connection will need them if it has to initialize) @@ -140,6 +150,7 @@ private void init() { v -> { try { context.loadBalancingPolicyWrapper().init(); + LOG.debug("[{}] Initialization complete, ready", logPrefix); initFuture.complete(DefaultCluster.this); // TODO schedule full schema refresh asynchronously (does not block init) } catch (Throwable throwable) { @@ -158,7 +169,10 @@ private void connect(CqlIdentifier keyspace, CompletableFuture conne if (closeWasCalled) { connectFuture.completeExceptionally(new IllegalStateException("Cluster was closed")); } else { - DefaultSession.init(context, keyspace) + String sessionLogPrefix = logPrefix + "|s" + sessionCounter++; + LOG.debug( + "[{}] Opening new session {} to keyspace {}", logPrefix, sessionLogPrefix, keyspace); + DefaultSession.init(context, keyspace, sessionLogPrefix) .whenCompleteAsync( (session, error) -> { if (error != null) { @@ -185,10 +199,9 @@ private void close() { } closeWasCalled = true; - LOG.debug("Closing {}", this); + LOG.debug("[{}] Starting shutdown", logPrefix); List> childrenCloseStages = new ArrayList<>(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - LOG.debug("Closing {}", closeable); childrenCloseStages.add(closeable.closeAsync()); } CompletableFutures.whenAllDone( @@ -201,20 +214,20 @@ private void forceClose() { return; } forceCloseWasCalled = true; - - LOG.debug("Force-closing {} (was {}closed before)", this, (closeWasCalled ? "" : "not ")); + LOG.debug( + "[{}] Starting forced shutdown (was {}closed before)", + logPrefix, + (closeWasCalled ? "" : "not ")); if (closeWasCalled) { // onChildrenClosed has already been called for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - LOG.debug("Force-closing {}", closeable); closeable.forceCloseAsync(); } } else { closeWasCalled = true; List> childrenCloseStages = new ArrayList<>(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - LOG.debug("Force-closing {}", closeable); childrenCloseStages.add(closeable.forceCloseAsync()); } CompletableFutures.whenAllDone( @@ -235,6 +248,7 @@ private void onChildrenClosed(List> childrenCloseStages) { if (!f.isSuccess()) { closeFuture.completeExceptionally(f.cause()); } else { + LOG.debug("[{}] Shutdown complete", logPrefix); closeFuture.complete(null); } }); @@ -244,7 +258,8 @@ private void warnIfFailed(CompletionStage stage) { CompletableFuture future = stage.toCompletableFuture(); assert future.isDone(); if (future.isCompletedExceptionally()) { - LOG.warn("Unexpected error while closing", CompletableFutures.getFailed(future)); + LOG.warn( + "[{}] Unexpected error while closing", logPrefix, CompletableFutures.getFailed(future)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 57ecbf8d834..4c53c2dc8e2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -52,7 +52,8 @@ public static AdminRequestHandler query( String query, Map parameters, Duration timeout, - int pageSize) { + int pageSize, + String logPrefix) { Query message = new Query( query, @@ -61,17 +62,18 @@ public static AdminRequestHandler query( if (!parameters.isEmpty()) { debugString += " with parameters " + parameters; } - return new AdminRequestHandler(channel, message, timeout, debugString); + return new AdminRequestHandler(channel, message, timeout, logPrefix, debugString); } public static AdminRequestHandler query( - DriverChannel channel, String query, Duration timeout, int pageSize) { - return query(channel, query, Collections.emptyMap(), timeout, pageSize); + DriverChannel channel, String query, Duration timeout, int pageSize, String logPrefix) { + return query(channel, query, Collections.emptyMap(), timeout, pageSize, logPrefix); } private final DriverChannel channel; private final Message message; private final Duration timeout; + private final String logPrefix; private final String debugString; private final CompletableFuture result = new CompletableFuture<>(); @@ -79,10 +81,15 @@ public static AdminRequestHandler query( private ScheduledFuture timeoutFuture; public AdminRequestHandler( - DriverChannel channel, Message message, Duration timeout, String debugString) { + DriverChannel channel, + Message message, + Duration timeout, + String logPrefix, + String debugString) { this.channel = channel; this.message = message; this.timeout = timeout; + this.logPrefix = logPrefix; this.debugString = debugString; } @@ -91,7 +98,7 @@ public CompletionStage start() { } public CompletionStage start(Map customPayload) { - LOG.debug("Executing {}", this); + LOG.debug("[{}] Executing {}", logPrefix, this); channel.write(message, false, customPayload, this).addListener(this::onWriteComplete); return result; } @@ -150,7 +157,7 @@ private AdminRequestHandler copy(ByteBuffer pagingState) { QueryOptions newOptions = buildQueryOptions(currentOptions.pageSize, currentOptions.namedValues, pagingState); return new AdminRequestHandler( - channel, new Query(current.query, newOptions), timeout, debugString); + channel, new Query(current.query, newOptions), timeout, logPrefix, debugString); } private static QueryOptions buildQueryOptions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 809a84820f8..f2a5a9e9509 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -192,7 +192,8 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, - options.eventCallback); + options.eventCallback, + options.ownerLogPrefix); ProtocolInitHandler initHandler = new ProtocolInitHandler(context, protocolVersion, clusterName, options); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 5cd10e1403e..59f156a42e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -161,6 +161,11 @@ public ChannelFuture closeFuture() { return channel.closeFuture(); } + @Override + public String toString() { + return channel.toString(); + } + // This is essentially a stripped-down Frame. We can't materialize the frame before writing, // because we need the stream id, which is assigned from within the event loop. static class RequestMessage { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java index d978c95f447..88f234ac010 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java @@ -31,6 +31,7 @@ public static Builder builder() { } public final CqlIdentifier keyspace; + /** Whether {@link DriverChannel#availableIds()} should be maintained */ public final boolean reportAvailableIds; @@ -43,15 +44,19 @@ public static Builder builder() { public final EventCallback eventCallback; + public final String ownerLogPrefix; + private DriverChannelOptions( CqlIdentifier keyspace, boolean reportAvailableIds, List eventTypes, - EventCallback eventCallback) { + EventCallback eventCallback, + String ownerLogPrefix) { this.keyspace = keyspace; this.reportAvailableIds = reportAvailableIds; this.eventTypes = eventTypes; this.eventCallback = eventCallback; + this.ownerLogPrefix = ownerLogPrefix; } public static class Builder { @@ -59,6 +64,7 @@ public static class Builder { private boolean reportAvailableIds = false; private List eventTypes = Collections.emptyList(); private EventCallback eventCallback = null; + private String ownerLogPrefix = null; public Builder withKeyspace(CqlIdentifier keyspace) { this.keyspace = keyspace; @@ -78,8 +84,14 @@ public Builder withEvents(List eventTypes, EventCallback eventCallback) return this; } + public Builder withOwnerLogPrefix(String ownerLogPrefix) { + this.ownerLogPrefix = ownerLogPrefix; + return this; + } + public DriverChannelOptions build() { - return new DriverChannelOptions(keyspace, reportAvailableIds, eventTypes, eventCallback); + return new DriverChannelOptions( + keyspace, reportAvailableIds, eventTypes, eventCallback, ownerLogPrefix); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index af034e2cc24..8866c3bfa8f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -44,21 +44,26 @@ public class InFlightHandler extends ChannelDuplexHandler { private final ProtocolVersion protocolVersion; private final StreamIdGenerator streamIds; + private final String ownerLogPrefix; private final Map inFlight; private final long setKeyspaceTimeoutMillis; private final AvailableIdsHolder availableIdsHolder; private final EventCallback eventCallback; private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; + private String logPrefix; InFlightHandler( ProtocolVersion protocolVersion, StreamIdGenerator streamIds, long setKeyspaceTimeoutMillis, AvailableIdsHolder availableIdsHolder, - EventCallback eventCallback) { + EventCallback eventCallback, + String ownerLogPrefix) { this.protocolVersion = protocolVersion; this.streamIds = streamIds; + this.ownerLogPrefix = ownerLogPrefix; + this.logPrefix = ownerLogPrefix + "|connecting..."; reportAvailableIds(); this.inFlight = Maps.newHashMapWithExpectedSize(streamIds.getMaxAvailableIds()); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; @@ -66,16 +71,29 @@ public class InFlightHandler extends ChannelDuplexHandler { this.eventCallback = eventCallback; } + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + String channelId = ctx.channel().toString(); + this.logPrefix = ownerLogPrefix + "|" + channelId.substring(1, channelId.length() - 1); + } + @Override public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) throws Exception { if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { if (inFlight.isEmpty()) { + LOG.debug( + "[{}] Received graceful close request and no pending queries, closing now", logPrefix); ctx.channel().close(); } else { + LOG.debug( + "[{}] Received graceful close request, waiting for pending queries to complete", + logPrefix); closingGracefully = true; } return; } else if (in == DriverChannel.FORCEFUL_CLOSE_MESSAGE) { + LOG.debug("[{}] Received forceful close request, aborting pending queries", logPrefix); abortAllInFlight(new ClosedConnectionException("Channel was force-closed")); ctx.channel().close(); return; @@ -105,6 +123,7 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) reportAvailableIds(); RequestMessage message = (RequestMessage) in; + LOG.debug("[{}] Writing {} on stream id {}", logPrefix, message.responseCallback, streamId); Frame frame = Frame.forRequest( protocolVersion.getCode(), @@ -133,17 +152,22 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (streamId < 0) { Message event = responseFrame.message; if (eventCallback == null) { - LOG.debug("Received event {} but no callback was registered", event); + LOG.debug("[{}] Received event {} but no callback was registered", logPrefix, event); } else { - LOG.debug("Received event {}, notifying callback", event); + LOG.debug("[{}] Received event {}, notifying callback", logPrefix, event); try { eventCallback.onEvent(event); } catch (Throwable t) { - LOG.warn("Unexpected error while invoking event handler", t); + LOG.warn("[{}] Unexpected error while invoking event handler", logPrefix, t); } } } else { ResponseCallback responseCallback = inFlight.get(streamId); + LOG.debug( + "[{}] Got response on stream id {}, completing {}", + logPrefix, + streamId, + responseCallback); if (responseCallback != null) { if (!responseCallback.holdStreamId()) { release(streamId, ctx); @@ -151,7 +175,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception try { responseCallback.onResponse(responseFrame); } catch (Throwable t) { - LOG.warn("Unexpected error while invoking response handler", t); + LOG.warn("[{}] Unexpected error while invoking response handler", logPrefix, t); } } } @@ -162,16 +186,20 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof FrameDecodingException) { int streamId = ((FrameDecodingException) cause).streamId; + LOG.debug("[{}] Error while decoding response on stream id {}", logPrefix, streamId); if (streamId >= 0) { // We know which request matches the failing response, fail that one only ResponseCallback responseCallback = release(streamId, ctx); try { responseCallback.onFailure(cause.getCause()); } catch (Throwable t) { - LOG.warn("Unexpected error while invoking failure handler", t); + LOG.warn("[{}] Unexpected error while invoking failure handler", logPrefix, t); } } else { - LOG.warn("Unexpected error while decoding incoming event frame", cause.getCause()); + LOG.warn( + "[{}] Unexpected error while decoding incoming event frame", + logPrefix, + cause.getCause()); } } else { // Otherwise fail all pending requests @@ -183,7 +211,9 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E @Override public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception { if (event instanceof ReleaseEvent) { - release(((ReleaseEvent) event).streamId, ctx); + int streamId = ((ReleaseEvent) event).streamId; + LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); + release(streamId, ctx); } else if (event instanceof SetKeyspaceEvent) { SetKeyspaceEvent setKeyspaceEvent = (SetKeyspaceEvent) event; if (this.setKeyspaceRequest != null) { @@ -191,6 +221,8 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws E new IllegalStateException( "Can't call setKeyspace while a keyspace switch is already in progress")); } else { + LOG.debug( + "[{}] Switching to keyspace {}", logPrefix, setKeyspaceEvent.keyspaceName.asInternal()); this.setKeyspaceRequest = new SetKeyspaceRequest(ctx, setKeyspaceEvent); this.setKeyspaceRequest.send(); } @@ -200,12 +232,14 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws E } private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { + LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); ResponseCallback responseCallback = inFlight.remove(streamId); streamIds.release(streamId); reportAvailableIds(); // If we're in the middle of an orderly close and this was the last request, actually close // the channel now if (closingGracefully && inFlight.isEmpty()) { + LOG.debug("[{}] Done handling the last pending query, closing channel", logPrefix); ctx.channel().close(); } return responseCallback; @@ -249,7 +283,7 @@ private class SetKeyspaceRequest extends ChannelHandlerRequest { @Override String describe() { - return "set keyspace " + keyspaceName; + return "[" + logPrefix + "] set keyspace " + keyspaceName; } @Override @@ -279,7 +313,7 @@ void fail(String message, Throwable cause) { // keyspace switch fails, this could be due to a schema disagreement or a more serious // error. Rescheduling the switch is impractical, we can't do much better than closing the // channel and letting it reconnect. - LOG.warn("Unexpected error while switching keyspace", setKeyspaceException); + LOG.warn("[{}] Unexpected error while switching keyspace", logPrefix, setKeyspaceException); abortAllInFlight(setKeyspaceException, this); ctx.channel().close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 38e0ba54000..db715126d42 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -63,6 +63,7 @@ class ProtocolInitHandler extends ConnectInitHandler { private final DriverChannelOptions options; // might be null if this is the first channel to this cluster private final String expectedClusterName; + private String logPrefix; ProtocolInitHandler( InternalDriverContext internalDriverContext, @@ -79,10 +80,19 @@ class ProtocolInitHandler extends ConnectInitHandler { this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; this.options = options; + this.logPrefix = options.ownerLogPrefix + "|connecting..."; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + String channelId = ctx.channel().toString(); + this.logPrefix = options.ownerLogPrefix + "|" + channelId.substring(1, channelId.length() - 1); } @Override protected void onRealConnect(ChannelHandlerContext ctx) { + LOG.debug("[{}] Starting channel initialization", logPrefix); new InitRequest(ctx).send(); } @@ -108,7 +118,7 @@ private class InitRequest extends ChannelHandlerRequest { @Override String describe() { - return "init query " + step; + return "[" + logPrefix + "] init query " + step; } @Override @@ -132,8 +142,8 @@ Message getRequest() { @Override void onResponse(Message response) { LOG.trace( - "[{} {}] received response opcode={}", - channel, + "[{}] step {} received response opcode={}", + logPrefix, step, ProtocolUtils.opcodeString(response.opcode)); try { @@ -278,6 +288,11 @@ private Authenticator buildAuthenticator(SocketAddress address, String authentic "Host %s requires authentication (%s), but no authenticator configured", address, authenticator))); } + + @Override + public String toString() { + return "init query " + step; + } } // TODO we'll probably need a lightweight ResultSet implementation for internal uses, but this is good for now diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index bc8c79aa8e6..fae2751263d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.retry.RetryPolicy; @@ -41,15 +42,16 @@ import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.driver.internal.core.util.Reflection; import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector; import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference; -import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; /** * Default implementation of the driver context. @@ -70,6 +72,8 @@ */ public class DefaultDriverContext implements InternalDriverContext { + private static final AtomicInteger CLUSTER_NAME_COUNTER = new AtomicInteger(); + private final CycleDetector cycleDetector = new CycleDetector("Detected cycle in context initialization"); @@ -119,10 +123,17 @@ public class DefaultDriverContext implements InternalDriverContext { private final DriverConfig config; private final ChannelPoolFactory channelPoolFactory = new ChannelPoolFactory(); private final CodecRegistry codecRegistry; + private final String clusterName; public DefaultDriverContext(DriverConfig config, List> typeCodecs) { this.config = config; - this.codecRegistry = buildCodecRegistry(typeCodecs); + DriverConfigProfile defaultProfile = config.defaultProfile(); + if (defaultProfile.isDefined(CoreDriverOption.CLUSTER_NAME)) { + this.clusterName = defaultProfile.getString(CoreDriverOption.CLUSTER_NAME); + } else { + this.clusterName = "c" + CLUSTER_NAME_COUNTER.getAndIncrement(); + } + this.codecRegistry = buildCodecRegistry(this.clusterName, typeCodecs); } protected LoadBalancingPolicy buildLoadBalancingPolicy() { @@ -189,7 +200,7 @@ protected Optional buildSslEngineFactory() { } protected EventBus buildEventBus() { - return new EventBus(); + return new EventBus(clusterName()); } protected Compressor buildCompressor() { @@ -242,9 +253,14 @@ protected ControlConnection buildControlConnection() { return new ControlConnection(this); } - protected CodecRegistry buildCodecRegistry(List> codecs) { + protected CodecRegistry buildCodecRegistry(String logPrefix, List> codecs) { TypeCodec[] array = new TypeCodec[codecs.size()]; - return new DefaultCodecRegistry(codecs.toArray(array)); + return new DefaultCodecRegistry(logPrefix, codecs.toArray(array)); + } + + @Override + public String clusterName() { + return clusterName; } @Override @@ -354,7 +370,7 @@ public ControlConnection controlConnection() { @Override public RequestProcessorRegistry requestProcessorRegistry() { - return RequestProcessorRegistry.DEFAULT; + return RequestProcessorRegistry.defaultCqlProcessors(clusterName()); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java index b81d214c03f..eb641df05d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java @@ -36,16 +36,21 @@ public class EventBus { private static final Logger LOG = LoggerFactory.getLogger(EventBus.class); + private final String logPrefix; private final SetMultimap, Consumer> listeners = Multimaps.synchronizedSetMultimap(HashMultimap.create()); + public EventBus(String logPrefix) { + this.logPrefix = logPrefix; + } + /** * Registers a listener for an event type. * * @return a key that is needed to unregister later. */ public Object register(Class eventClass, Consumer listener) { - LOG.debug("Registering {} for {}", listener, eventClass); + LOG.debug("[{}] Registering {} for {}", logPrefix, listener, eventClass); listeners.put(eventClass, listener); // The reason for the key mechanism is that this will often be used with method references, // and you get a different object every time you reference a method, so register(Foo::bar) @@ -59,7 +64,7 @@ public Object register(Class eventClass, Consumer listener) { * @param key the key that was returned by {@link #register(Class, Consumer)} */ public boolean unregister(Object key, Class eventClass) { - LOG.debug("Unregistering {} for {}", key, eventClass); + LOG.debug("[{}] Unregistering {} for {}", logPrefix, key, eventClass); return listeners.remove(eventClass, key); } @@ -79,7 +84,7 @@ public void fire(Object event) { for (Consumer l : listeners.get(eventClass)) { @SuppressWarnings("unchecked") Consumer listener = (Consumer) l; - LOG.trace("Notifying {} of {}", listener, event); + LOG.trace("[{}] Notifying {} of {}", logPrefix, listener, event); listener.accept(event); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 07ee114968f..2dbab12112c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -62,6 +62,7 @@ public class ControlConnection implements EventCallback, AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(ControlConnection.class); private final InternalDriverContext context; + private final String logPrefix; private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; @@ -71,6 +72,7 @@ public class ControlConnection implements EventCallback, AsyncAutoCloseable { public ControlConnection(InternalDriverContext context) { this.context = context; + this.logPrefix = context.clusterName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); } @@ -124,9 +126,9 @@ public CompletionStage forceCloseAsync() { @Override public void onEvent(Message eventMessage) { if (!(eventMessage instanceof Event)) { - LOG.warn("Unsupported event class: {}", eventMessage.getClass().getName()); + LOG.warn("[{}] Unsupported event class: {}", logPrefix, eventMessage.getClass().getName()); } else { - LOG.debug("Processing incoming event {}", eventMessage); + LOG.debug("[{}] Processing incoming event {}", logPrefix, eventMessage); Event event = (Event) eventMessage; switch (event.type) { case ProtocolConstants.EventType.TOPOLOGY_CHANGE: @@ -139,7 +141,7 @@ public void onEvent(Message eventMessage) { processSchemaChange(event); break; default: - LOG.warn("Unsupported event type: {}", event.type); + LOG.warn("[{}] Unsupported event type: {}", logPrefix, event.type); } } } @@ -155,7 +157,7 @@ private void processTopologyChange(Event event) { context.eventBus().fire(TopologyEvent.suggestRemoved(address)); break; default: - LOG.warn("Unsupported topology change type: {}", tce.changeType); + LOG.warn("[{}] Unsupported topology change type: {}", logPrefix, tce.changeType); } } @@ -170,7 +172,7 @@ private void processStatusChange(Event event) { context.eventBus().fire(TopologyEvent.suggestDown(address)); break; default: - LOG.warn("Unsupported status change type: {}", sce.changeType); + LOG.warn("[{}] Unsupported status change type: {}", logPrefix, sce.changeType); } } @@ -197,7 +199,7 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; this.reconnection = - new Reconnection(adminExecutor, context.reconnectionPolicy(), this::reconnect); + new Reconnection(logPrefix, adminExecutor, context.reconnectionPolicy(), this::reconnect); } private void init(boolean listenToClusterEvents) { @@ -207,9 +209,12 @@ private void init(boolean listenToClusterEvents) { } initWasCalled = true; ImmutableList eventTypes = buildEventTypes(listenToClusterEvents); - LOG.debug("Initializing with event types {}", eventTypes); + LOG.debug("[{}] Initializing with event types {}", logPrefix, eventTypes); channelOptions = - DriverChannelOptions.builder().withEvents(eventTypes, ControlConnection.this).build(); + DriverChannelOptions.builder() + .withEvents(eventTypes, ControlConnection.this) + .withOwnerLogPrefix(logPrefix + "|control") + .build(); Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); @@ -241,7 +246,7 @@ private void connect( if (node == null) { onFailure.accept(AllNodesFailedException.fromErrors(errors)); } else { - LOG.debug("Trying to establish a connection to {}", node); + LOG.debug("[{}] Trying to establish a connection to {}", logPrefix, node); context .channelFactory() .connect(node.getConnectAddress(), channelOptions) @@ -252,7 +257,11 @@ private void connect( if (closeWasCalled) { onSuccess.run(); // abort, we don't really care about the result } else { - LOG.debug("Error connecting to " + node + ", trying next node", error); + LOG.debug( + "[{}] Error connecting to {}, trying next node", + logPrefix, + node, + error); Map newErrors = (errors == null) ? new LinkedHashMap<>() : errors; newErrors.put(node, error); @@ -260,12 +269,13 @@ private void connect( } } else if (closeWasCalled) { LOG.debug( - "New channel opened ({}) but the control connection was closed, closing it", + "[{}] New channel opened ({}) but the control connection was closed, closing it", + logPrefix, channel); channel.forceClose(); onSuccess.run(); } else { - LOG.debug("Connection established to {}", node); + LOG.debug("[{}] Connection established to {}", logPrefix, node); // Make sure previous channel gets closed (it may still be open if reconnection was forced) DriverChannel previousChannel = ControlConnection.this.channel; if (previousChannel != null) { @@ -283,7 +293,10 @@ private void connect( onSuccess.run(); } } catch (Exception e) { - LOG.warn("Unexpected exception while processing channel init result", e); + LOG.warn( + "[{}] Unexpected exception while processing channel init result", + logPrefix, + e); } }, adminExecutor); @@ -298,13 +311,16 @@ private void onSuccessfulReconnect() { .whenComplete( (result, error) -> { if (error != null) { - LOG.debug("Error while refreshing node list", error); + LOG.debug("[{}] Error while refreshing node list", logPrefix, error); } else { try { // This does nothing if the LBP is initialized already context.loadBalancingPolicyWrapper().init(); } catch (Throwable t) { - LOG.warn("Unexpected error while initializing load balancing policy", t); + LOG.warn( + "[{}] Unexpected error while initializing load balancing policy", + logPrefix, + t); } } }); @@ -314,10 +330,12 @@ private void onSuccessfulReconnect() { private void onChannelClosed(DriverChannel channel, Node node) { assert adminExecutor.inEventLoop(); - LOG.debug("Lost channel {}", channel); - context.eventBus().fire(ChannelEvent.channelClosed(node)); - if (!closeWasCalled && !reconnection.isRunning()) { - reconnection.start(); + if (!closeWasCalled) { + LOG.debug("[{}] Lost channel {}", logPrefix, channel); + context.eventBus().fire(ChannelEvent.channelClosed(node)); + if (!reconnection.isRunning()) { + reconnection.start(); + } } } @@ -334,8 +352,10 @@ private void forceClose() { return; } closeWasCalled = true; + LOG.debug("[{}] Starting shutdown", logPrefix); reconnection.stop(); if (channel == null) { + LOG.debug("[{}] Shutdown complete", logPrefix); closeFuture.complete(null); } else { channel @@ -343,6 +363,7 @@ private void forceClose() { .addListener( f -> { if (f.isSuccess()) { + LOG.debug("[{}] Shutdown complete", logPrefix); closeFuture.complete(null); } else { closeFuture.completeExceptionally(f.cause()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index ba6413b3335..12c8514a12e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -64,6 +64,7 @@ public class CqlPrepareHandler private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandler.class); + private final String logPrefix; private final CompletableFuture result; private final Message message; private final EventExecutor scheduler; @@ -80,8 +81,11 @@ public class CqlPrepareHandler PrepareRequest request, CqlPrepareProcessor processor, DefaultSession session, - InternalDriverContext context) { + InternalDriverContext context, + String sessionLogPrefix) { super(request, session, context); + this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); + LOG.debug("[{}] Creating new handler for prepare request {}", logPrefix, request); this.processor = processor; this.result = new CompletableFuture<>(); this.result.exceptionally( @@ -91,7 +95,7 @@ public class CqlPrepareHandler cancelTimeout(); } } catch (Throwable t2) { - LOG.warn("Uncaught exception", t2); + LOG.warn("[{}] Uncaught exception", logPrefix, t2); } return null; }); @@ -137,9 +141,9 @@ private void sendRequest(Node node, int retryCount) { return; } DriverChannel channel = null; - if (node == null || (channel = getChannel(node)) == null) { + if (node == null || (channel = getChannel(node, logPrefix)) == null) { while (!result.isDone() && (node = queryPlan.poll()) != null) { - channel = getChannel(node); + channel = getChannel(node, logPrefix); if (channel != null) { break; } @@ -203,20 +207,23 @@ private CompletionStage prepareOnOtherNodes() { // Try to reprepare on another node, after the initial query has succeeded. Errors are not // blocking, the preparation will be retried later on that node. Simply warn and move on. private CompletionStage prepareOnOtherNode(Node node) { - LOG.debug("Repreparing on {}", node); - DriverChannel channel = getChannel(node); + LOG.debug("[{}] Repreparing on {}", logPrefix, node); + DriverChannel channel = getChannel(node, logPrefix); if (channel == null) { - LOG.warn("Could not get a channel to reprepare on {}", node); + LOG.debug("[{}] Could not get a channel to reprepare on {}, skipping", logPrefix, node); return CompletableFuture.completedFuture(null); } else { AdminRequestHandler handler = - new AdminRequestHandler(channel, message, timeout, message.toString()); + new AdminRequestHandler(channel, message, timeout, logPrefix, message.toString()); return handler .start() .handle( (result, error) -> { - if (error != null) { - LOG.warn("Error while repreparing on {}", node); + if (error == null) { + LOG.debug("[{}] Successfully reprepared on {}", logPrefix, node); + } else { + LOG.warn( + "[{}] Error while repreparing on {}: {}", logPrefix, node, error.toString()); } return null; }); @@ -245,8 +252,15 @@ private InitialPrepareCallback(Node node, int retryCount) { @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { + LOG.debug( + "[{}] Failed to send request on {}, trying next node (cause: {})", + logPrefix, + node, + future.cause().toString()); recordError(node, future.cause()); sendRequest(null, retryCount); // try next host + } else { + LOG.debug("[{}] Request sent to {}", logPrefix, node); } } @@ -258,8 +272,10 @@ public void onResponse(Frame responseFrame) { try { Message responseMessage = responseFrame.message; if (responseMessage instanceof Prepared) { + LOG.debug("[{}] Got result, completing", logPrefix); setFinalResult((Prepared) responseMessage); } else if (responseMessage instanceof Error) { + LOG.debug("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); } else { setFinalError(new IllegalStateException("Unexpected response " + responseMessage)); @@ -285,13 +301,13 @@ private void processErrorResponse(Error errorMessage) { } Throwable error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { - // Do not call the retry policy, always try the next node + LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); sendRequest(null, retryCount); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { - // Do not call the retry policy, always rethrow + LOG.debug("[{}] Unrecoverable error, rethrowing", logPrefix); setFinalError(error); } else { // Because prepare requests are known to always be idempotent, we call the retry policy @@ -302,6 +318,7 @@ private void processErrorResponse(Error errorMessage) { } private void processRetryDecision(RetryDecision decision, Throwable error) { + LOG.debug("[{}] Processing retry decision {}", logPrefix, decision); switch (decision) { case RETRY_SAME: recordError(node, error); @@ -328,8 +345,14 @@ public void onFailure(Throwable error) { if (result.isDone()) { return; } + LOG.debug("[{}] Request failure, processing: {}", logPrefix, error.toString()); RetryDecision decision = retryPolicy.onRequestAborted(request, error, retryCount); processRetryDecision(decision, error); } + + @Override + public String toString() { + return logPrefix; + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java index ec1e65ecf4f..1b5c39fd080 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java @@ -46,8 +46,10 @@ public class CqlPrepareProcessor public RequestHandler> newHandler( Request> request, DefaultSession session, - InternalDriverContext context) { - return new CqlPrepareHandler((PrepareRequest) request, this, session, context); + InternalDriverContext context, + String sessionLogPrefix) { + return new CqlPrepareHandler( + (PrepareRequest) request, this, session, context, sessionLogPrefix); } DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatement) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index f2f670dd429..cba8e6d4feb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -78,6 +78,7 @@ public class CqlRequestHandler private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandler.class); + private final String logPrefix; private final CompletableFuture result; private final Message message; private final EventExecutor scheduler; @@ -95,8 +96,14 @@ public class CqlRequestHandler // We don't use a map because nodes can appear multiple times. private volatile List> errors; - CqlRequestHandler(Statement statement, DefaultSession session, InternalDriverContext context) { + CqlRequestHandler( + Statement statement, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { super(statement, session, context); + this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); + LOG.debug("[{}] Creating new handler for request {}", logPrefix, statement); this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -105,7 +112,7 @@ public class CqlRequestHandler cancelScheduledTasks(); } } catch (Throwable t2) { - LOG.warn("Uncaught exception", t2); + LOG.warn("[{}] Uncaught exception", logPrefix, t2); } return null; }); @@ -123,16 +130,19 @@ public class CqlRequestHandler // Start the initial execution long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); if (nextExecution > 0) { - LOG.trace("Scheduling first speculative execution in {} ms", nextExecution); + LOG.debug("[{}] Scheduling first speculative execution in {} ms", logPrefix, nextExecution); this.pendingExecutions = new CopyOnWriteArrayList<>(); this.pendingExecutions.add( scheduler.schedule(this::startExecution, nextExecution, TimeUnit.MILLISECONDS)); } else { - LOG.trace("Speculative execution policy returned {}, no next execution", nextExecution); + LOG.debug( + "[{}] Speculative execution policy returned {}, no next execution", + logPrefix, + nextExecution); this.pendingExecutions = null; // we'll never need this so avoid allocation } } else { - LOG.trace("Request is not idempotent, no speculative executions"); + LOG.debug("[{}] Request is not idempotent, no speculative executions", logPrefix); this.pendingExecutions = null; } sendRequest(null, 0, 0); @@ -164,14 +174,21 @@ private ScheduledFuture scheduleTimeout(Duration timeout) { private void startExecution() { if (!result.isDone()) { int execution = executions.incrementAndGet(); - LOG.trace("Starting speculative execution {}", execution); + LOG.trace("[{}] Starting speculative execution {}", logPrefix, execution); long nextDelay = speculativeExecutionPolicy.nextExecution(keyspace, request, execution + 1); if (nextDelay > 0) { - LOG.trace("Scheduling {}th speculative execution in {} ms", execution + 1, nextDelay); + LOG.trace( + "[{}] Scheduling {}th speculative execution in {} ms", + logPrefix, + execution + 1, + nextDelay); this.pendingExecutions.add( scheduler.schedule(this::startExecution, nextDelay, TimeUnit.MILLISECONDS)); } else { - LOG.trace("Speculative execution policy returned {}, no next execution", nextDelay); + LOG.trace( + "[{}] Speculative execution policy returned {}, no next execution", + logPrefix, + nextDelay); } sendRequest(null, execution, 0); } @@ -187,9 +204,9 @@ private void sendRequest(Node node, int execution, int retryCount) { return; } DriverChannel channel = null; - if (node == null || (channel = getChannel(node)) == null) { + if (node == null || (channel = getChannel(node, logPrefix)) == null) { while (!result.isDone() && (node = queryPlan.poll()) != null) { - channel = getChannel(node); + channel = getChannel(node, logPrefix); if (channel != null) { break; } @@ -203,7 +220,7 @@ private void sendRequest(Node node, int execution, int retryCount) { } } else { NodeResponseCallback nodeResponseCallback = - new NodeResponseCallback(node, channel, execution, retryCount); + new NodeResponseCallback(node, channel, execution, retryCount, logPrefix); channel .write(message, request.isTracing(), request.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); @@ -291,20 +308,30 @@ private class NodeResponseCallback // How many times we've invoked the retry policy and it has returned a "retry" decision (0 for // the first attempt of each execution). private final int retryCount; + private final String logPrefix; - private NodeResponseCallback(Node node, DriverChannel channel, int execution, int retryCount) { + private NodeResponseCallback( + Node node, DriverChannel channel, int execution, int retryCount, String logPrefix) { this.node = node; this.channel = channel; this.execution = execution; this.retryCount = retryCount; + this.logPrefix = logPrefix + "|" + execution; } // this gets invoked once the write completes. @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { + LOG.debug( + "[{}] Failed to send request on {}, trying next node (cause: {})", + logPrefix, + channel, + future.cause()); recordError(node, future.cause()); - sendRequest(null, execution, retryCount); // try next host + sendRequest(null, execution, retryCount); // try next node + } else { + LOG.debug("[{}] Request sent on {}", logPrefix, channel); } } @@ -319,8 +346,10 @@ public void onResponse(Frame responseFrame) { // TODO schema agreement, and chain setFinalResult to the result setFinalError(new UnsupportedOperationException("TODO handle schema agreement")); } else if (responseMessage instanceof Result) { + LOG.debug("[{}] Got result, completing", logPrefix); setFinalResult((Result) responseMessage, responseFrame, this); } else if (responseMessage instanceof Error) { + LOG.debug("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); } else { setFinalError(new IllegalStateException("Unexpected response " + responseMessage)); @@ -339,23 +368,27 @@ private void processErrorResponse(Error errorMessage) { "Unexpected UNPREPARED response, " + "this should only happen for a bound statement")); } else { - LOG.debug("Statement is not prepared on {}, repreparing", node); + LOG.debug("[{}] Statement is not prepared on {}, repreparing", logPrefix, node); PreparedStatement preparedStatement = ((BoundStatement) CqlRequestHandler.this.request).getPreparedStatement(); Prepare reprepareMessage = new Prepare(preparedStatement.getQuery()); AdminRequestHandler reprepareHandler = new AdminRequestHandler( - channel, reprepareMessage, timeout, "Reprepare " + reprepareMessage.toString()); + channel, + reprepareMessage, + timeout, + logPrefix, + "Reprepare " + reprepareMessage.toString()); reprepareHandler .start(preparedStatement.initialCustomPayload()) .handle( (result, error) -> { if (error != null) { recordError(node, error); - // Try next host + LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); sendRequest(null, execution, retryCount); } else { - // Reprepare succeeded, retry on same node + LOG.debug("[{}] Reprepare sucessful, retrying", logPrefix); sendRequest(node, execution, retryCount); } return null; @@ -365,13 +398,13 @@ private void processErrorResponse(Error errorMessage) { } Throwable error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { - // Do not call the retry policy, always try the next node + LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); sendRequest(null, execution, retryCount); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { - // Do not call the retry policy, always rethrow + LOG.debug("[{}] Unrecoverable error, rethrowing", logPrefix); setFinalError(error); } else { RetryDecision decision; @@ -421,6 +454,7 @@ private void processErrorResponse(Error errorMessage) { } private void processRetryDecision(RetryDecision decision, Throwable error) { + LOG.debug("[{}] Processing retry decision {}", logPrefix, decision); switch (decision) { case RETRY_SAME: recordError(node, error); @@ -444,11 +478,17 @@ public void onFailure(Throwable error) { if (result.isDone()) { return; } + LOG.debug("[{}] Request failure, processing: {}", logPrefix, error.toString()); RetryDecision decision = isIdempotent ? retryPolicy.onRequestAborted(request, error, retryCount) : RetryDecision.RETHROW; processRetryDecision(decision, error); } + + @Override + public String toString() { + return logPrefix; + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java index 7d8d0fce061..7bf44e8cc79 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java @@ -37,7 +37,8 @@ public class CqlRequestProcessor public RequestHandler> newHandler( Request> request, DefaultSession session, - InternalDriverContext context) { - return new CqlRequestHandler((Statement) request, session, context); + InternalDriverContext context, + String sessionLogPrefix) { + return new CqlRequestHandler((Statement) request, session, context, sessionLogPrefix); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 9f90537a09b..0bc0c6d5d3b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -25,8 +25,8 @@ public class AddNodeRefresh extends NodesRefresh { @VisibleForTesting final NodeInfo newNodeInfo; - AddNodeRefresh(DefaultMetadata oldMetadata, NodeInfo newNodeInfo) { - super(oldMetadata); + AddNodeRefresh(DefaultMetadata oldMetadata, NodeInfo newNodeInfo, String logPrefix) { + super(oldMetadata, logPrefix); this.newNodeInfo = newNodeInfo; } @@ -37,7 +37,7 @@ protected Map computeNewNodes() { return oldNodes; } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); - copyInfos(newNodeInfo, newNode); + copyInfos(newNodeInfo, newNode, logPrefix); events.add(NodeStateEvent.added(newNode)); return ImmutableMap.builder() .putAll(oldNodes) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index edf16bc94b1..f4b13d1c829 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -52,6 +52,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { // Assume topology queries never need paging private static final int INFINITE_PAGE_SIZE = -1; + private final String logPrefix; private final ControlConnection controlConnection; private final AddressTranslator addressTranslator; private final Duration timeout; @@ -60,6 +61,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { @VisibleForTesting volatile int port = -1; public DefaultTopologyMonitor(InternalDriverContext context) { + this.logPrefix = context.clusterName(); this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); DriverConfigProfile config = context.config().defaultProfile(); @@ -80,13 +82,13 @@ public CompletionStage> refreshNode(Node node) { if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - LOG.debug("Refreshing info for {}", node); + LOG.debug("[{}] Refreshing info for {}", logPrefix, node); DriverChannel channel = controlConnection.channel(); if (node.getConnectAddress().equals(channel.address())) { // refreshNode is called for nodes that just came up. If the control node just came up, it // means the control connection just reconnected, which means we did a full node refresh. So // we don't need to process this call. - LOG.debug("Ignoring refresh of control node"); + LOG.debug("[{}] Ignoring refresh of control node", logPrefix); return CompletableFuture.completedFuture(Optional.empty()); } else if (node.getBroadcastAddress().isPresent()) { return query( @@ -105,7 +107,7 @@ public CompletionStage> getNewNodeInfo(InetSocketAddress conn if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - LOG.debug("Fetching info for new node {}", connectAddress); + LOG.debug("[{}] Fetching info for new node {}", logPrefix, connectAddress); DriverChannel channel = controlConnection.channel(); return query(channel, "SELECT * FROM system.peers") .thenApply(result -> this.findInPeers(result, connectAddress)); @@ -116,7 +118,7 @@ public CompletionStage> refreshNodeList() { if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - LOG.debug("Refreshing node list"); + LOG.debug("[{}] Refreshing node list", logPrefix); DriverChannel channel = controlConnection.channel(); savePort(channel); @@ -154,7 +156,8 @@ public CompletionStage forceCloseAsync() { @VisibleForTesting protected CompletionStage query( DriverChannel channel, String queryString, Map parameters) { - return AdminRequestHandler.query(channel, queryString, parameters, timeout, INFINITE_PAGE_SIZE) + return AdminRequestHandler.query( + channel, queryString, parameters, timeout, INFINITE_PAGE_SIZE, logPrefix) .start(); } @@ -208,7 +211,7 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con return Optional.of(buildNodeInfo(row, connectAddress)); } } - LOG.debug("Could not find any peer row matching {}", connectAddress); + LOG.debug("[{}] Could not find any peer row matching {}", logPrefix, connectAddress); return Optional.empty(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 13d9659494f..1e6b50f8d16 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -33,8 +33,8 @@ class FullNodeListRefresh extends NodesRefresh { @VisibleForTesting final Iterable nodeInfos; - FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos) { - super(current); + FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos, String logPrefix) { + super(current, logPrefix); this.nodeInfos = nodeInfos; } @@ -48,17 +48,17 @@ protected Map computeNewNodes() { for (NodeInfo nodeInfo : nodeInfos) { InetSocketAddress address = nodeInfo.getConnectAddress(); if (address == null) { - LOG.warn("Got node info with no connect address, ignoring"); + LOG.warn("[{}] Got node info with no connect address, ignoring", logPrefix); continue; } seen.add(address); DefaultNode node = (DefaultNode) oldNodes.get(address); if (node == null) { node = new DefaultNode(address); - LOG.debug("Adding new node {}", node); + LOG.debug("[{}] Adding new node {}", logPrefix, node); added.put(address, node); } - copyInfos(nodeInfo, node); + copyInfos(nodeInfo, node, logPrefix); } Set removed = Sets.difference(oldNodes.keySet(), seen); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index 9cf152974f9..851cd59edf7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -29,15 +29,16 @@ class InitContactPointsRefresh extends MetadataRefresh { @VisibleForTesting final Set contactPoints; - InitContactPointsRefresh(DefaultMetadata current, Set contactPoints) { - super(current); + InitContactPointsRefresh( + DefaultMetadata current, Set contactPoints, String logPrefix) { + super(current, logPrefix); this.contactPoints = contactPoints; } @Override void compute() { assert oldMetadata == DefaultMetadata.EMPTY; - LOG.debug("Initializing node metadata with contact points {}", contactPoints); + LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); ImmutableMap.Builder newNodes = ImmutableMap.builder(); for (InetSocketAddress address : contactPoints) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index be9c514c67b..902624d5d4c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -48,6 +48,7 @@ public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceR private final InternalDriverContext context; private final LoadBalancingPolicy policy; + private final String logPrefix; private final ReplayingEventFilter eventFilter = new ReplayingEventFilter<>(this::processNodeStateEvent); private AtomicBoolean isInit = new AtomicBoolean(); @@ -55,11 +56,13 @@ public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceR public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPolicy policy) { this.context = context; this.policy = policy; + this.logPrefix = context.clusterName(); context.eventBus().register(NodeStateEvent.class, this::onNodeStateEvent); } public void init() { if (isInit.compareAndSet(false, true)) { + LOG.debug("[{}] Initializing policy", logPrefix); // State events can happen concurrently with init, so we must record them and replay once the // policy is initialized. eventFilter.start(); @@ -84,7 +87,7 @@ public Queue newQueryPlan() { @Override public void setDistance(Node node, NodeDistance distance) { - LOG.debug("LBP changed distance of {} to {}", node, distance); + LOG.debug("[{}] LBP changed distance of {} to {}", logPrefix, node, distance); DefaultNode defaultNode = (DefaultNode) node; defaultNode.distance = distance; context.eventBus().fire(new DistanceEvent(distance, defaultNode)); @@ -107,7 +110,7 @@ private void processNodeStateEvent(NodeStateEvent event) { } else if (event.newState == null) { policy.onDown(event.node); } else { - LOG.warn("Unsupported event: " + event); + LOG.warn("[{}] Unsupported event: {}", logPrefix, event); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index ed34d13c736..5639eac903d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -36,12 +36,14 @@ public class MetadataManager implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class); private final InternalDriverContext context; + private final String logPrefix; private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; private volatile DefaultMetadata metadata; // must be updated on adminExecutor only public MetadataManager(InternalDriverContext context) { this.context = context; + this.logPrefix = context.clusterName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(); this.metadata = DefaultMetadata.EMPTY; @@ -55,7 +57,7 @@ public CompletionStage addContactPoints(Set contactPoin if (contactPoints == null || contactPoints.isEmpty()) { return CompletableFuture.completedFuture(null); } else { - LOG.debug("Adding initial contact points {}", contactPoints); + LOG.debug("[{}] Adding initial contact points {}", logPrefix, contactPoints); CompletableFuture initNodesFuture = new CompletableFuture<>(); RunOrSchedule.on( adminExecutor, () -> singleThreaded.initNodes(contactPoints, initNodesFuture)); @@ -78,10 +80,11 @@ public CompletionStage refreshNode(Node node) { .thenApply( maybeInfo -> { if (maybeInfo.isPresent()) { - NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node); + NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node, logPrefix); } else { LOG.debug( - "Topology monitor did not return any info for the refresh of {}, skipping", + "[{}] Topology monitor did not return any info for the refresh of {}, skipping", + logPrefix, node); } return null; @@ -96,9 +99,10 @@ public void addNode(InetSocketAddress address) { (info, error) -> { if (error != null) { LOG.debug( - "Error refreshing node info for " - + address - + ", this will be retried on the next full refresh", + "[{}] Error refreshing node info for {}, " + + "this will be retried on the next full refresh", + logPrefix, + address, error); } else { singleThreaded.addNode(address, info); @@ -140,12 +144,12 @@ private class SingleThreaded { private void initNodes( Set addresses, CompletableFuture initNodesFuture) { - refresh(new InitContactPointsRefresh(metadata, addresses)); + refresh(new InitContactPointsRefresh(metadata, addresses, logPrefix)); initNodesFuture.complete(null); } private Void refreshNodes(Iterable nodeInfos) { - return refresh(new FullNodeListRefresh(metadata, nodeInfos)); + return refresh(new FullNodeListRefresh(metadata, nodeInfos, logPrefix)); } private void addNode(InetSocketAddress address, Optional maybeInfo) { @@ -155,26 +159,28 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { if (!address.equals(info.getConnectAddress())) { // This would be a bug in the TopologyMonitor, protect against it LOG.warn( - "Received a request to add a node for {}, " + "[{}] Received a request to add a node for {}, " + "but the provided info uses the connect address {}, ignoring it", + logPrefix, address, info.getConnectAddress()); } else { - refresh(new AddNodeRefresh(metadata, info)); + refresh(new AddNodeRefresh(metadata, info, logPrefix)); } } else { LOG.debug( - "Ignoring node addition for {} because the " + "[{}] Ignoring node addition for {} because the " + "topology monitor didn't return any information", + logPrefix, address); } } catch (Throwable t) { - LOG.warn("Unexpected exception while handling added node", t); + LOG.warn("[" + logPrefix + "] Unexpected exception while handling added node", logPrefix); } } private void removeNode(InetSocketAddress address) { - refresh(new RemoveNodeRefresh(metadata, address)); + refresh(new RemoveNodeRefresh(metadata, address, logPrefix)); } private void close() { @@ -182,6 +188,7 @@ private void close() { return; } closeWasCalled = true; + LOG.debug("[{}] Closing", logPrefix); closeFuture.complete(null); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java index f095c56c09c..fcfc8d955f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -36,9 +36,11 @@ abstract class MetadataRefresh { final DefaultMetadata oldMetadata; DefaultMetadata newMetadata; final List events; + protected final String logPrefix; - protected MetadataRefresh(DefaultMetadata current) { + protected MetadataRefresh(DefaultMetadata current, String logPrefix) { this.oldMetadata = current; + this.logPrefix = logPrefix; this.events = new ArrayList<>(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 7adb0b911d6..3ae0240aac5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -46,10 +46,12 @@ public class NodeStateManager implements AsyncAutoCloseable { private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; + private final String logPrefix; public NodeStateManager(InternalDriverContext context) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); + this.logPrefix = context.clusterName(); } @Override @@ -103,7 +105,7 @@ private void onChannelEvent(ChannelEvent event) { if (closeWasCalled) { return; } - LOG.debug("Processing {}", event); + LOG.debug("[{}] Processing {}", logPrefix, event); DefaultNode node = (DefaultNode) event.node; assert node != null; switch (event.type) { @@ -133,33 +135,43 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { if (closeWasCalled) { return; } - LOG.debug("Processing {}", event); + LOG.debug("[{}] Processing {}", logPrefix, event); DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); switch (event.type) { case SUGGEST_UP: if (node == null) { - LOG.debug("Received UP event for unknown node {}, adding it", event.address); + LOG.debug( + "[{}] Received UP event for unknown node {}, adding it", logPrefix, event.address); metadataManager.addNode(event.address); } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("Not setting {} UP because it is FORCED_DOWN", node); + LOG.debug("[{}] Not setting {} UP because it is FORCED_DOWN", logPrefix, node); } else { setState(node, NodeState.UP, "an UP topology event was received"); } break; case SUGGEST_DOWN: if (node == null) { - LOG.debug("Received DOWN event for unknown node {}, ignoring it", event.address); + LOG.debug( + "[{}] Received DOWN event for unknown node {}, ignoring it", + logPrefix, + event.address); } else if (node.openConnections > 0) { - LOG.debug("Not setting {} DOWN because it still has active connections", node); + LOG.debug( + "[{}] Not setting {} DOWN because it still has active connections", + logPrefix, + node); } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("Not setting {} DOWN because it is FORCED_DOWN", node); + LOG.debug("[{}] Not setting {} DOWN because it is FORCED_DOWN", logPrefix, node); } else { setState(node, NodeState.DOWN, "a DOWN topology event was received"); } break; case FORCE_UP: if (node == null) { - LOG.debug("Received FORCE_UP event for unknown node {}, adding it", event.address); + LOG.debug( + "[{}] Received FORCE_UP event for unknown node {}, adding it", + logPrefix, + event.address); metadataManager.addNode(event.address); } else { setState(node, NodeState.UP, "a FORCE_UP topology event was received"); @@ -167,7 +179,10 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { break; case FORCE_DOWN: if (node == null) { - LOG.debug("Received FORCE_DOWN event for unknown node {}, ignoring it", event.address); + LOG.debug( + "[{}] Received FORCE_DOWN event for unknown node {}, ignoring it", + logPrefix, + event.address); } else { setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); } @@ -175,7 +190,9 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { case SUGGEST_ADDED: if (node != null) { LOG.debug( - "Received ADDED event for {} but it is already in our metadata, ignoring", node); + "[{}] Received ADDED event for {} but it is already in our metadata, ignoring", + logPrefix, + node); } else { metadataManager.addNode(event.address); } @@ -183,7 +200,8 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { case SUGGEST_REMOVED: if (node == null) { LOG.debug( - "Received REMOVED event for {} but it is not in our metadata, ignoring", + "[{}] Received REMOVED event for {} but it is not in our metadata, ignoring", + logPrefix, event.address); } else { metadataManager.removeNode(event.address); @@ -216,7 +234,7 @@ private Collection coalesceTopologyEvents(List eve } result = last.values(); } - LOG.debug("Coalesced topology events: {} => {}", events, result); + LOG.debug("[{}] Coalesced topology events: {} => {}", logPrefix, events, result); return result; } @@ -241,7 +259,13 @@ private void close() { private void setState(DefaultNode node, NodeState newState, String reason) { NodeState oldState = node.state; if (oldState != newState) { - LOG.debug("Transitioning {} {}=>{} (because {})", node, oldState, newState, reason); + LOG.debug( + "[{}] Transitioning {} {}=>{} (because {})", + logPrefix, + node, + oldState, + newState, + reason); node.state = newState; if (newState != NodeState.UP) { // Fire the event immediately @@ -254,11 +278,12 @@ private void setState(DefaultNode node, NodeState newState, String reason) { (success, error) -> { try { if (error != null) { - LOG.debug("Error while refreshing info for " + node, error); + LOG.debug( + "[{}] Error while refreshing info for {}", logPrefix, node, error); } eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); } catch (Throwable t) { - LOG.warn("Unexpected exception", t); + LOG.warn("[{}] Unexpected exception", logPrefix, t); } }); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index da7f72e89a7..2c54b48c2fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -28,8 +28,8 @@ abstract class NodesRefresh extends MetadataRefresh { private static final Logger LOG = LoggerFactory.getLogger(NodesRefresh.class); - protected NodesRefresh(DefaultMetadata current) { - super(current); + protected NodesRefresh(DefaultMetadata current, String logPrefix) { + super(current, logPrefix); } /** @return null if the nodes haven't changed */ @@ -42,7 +42,7 @@ void compute() { // TODO recompute token map (even if node list hasn't changed, b/c tokens might have changed) } - protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node) { + protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node, String logPrefix) { node.broadcastAddress = nodeInfo.getBroadcastAddress(); node.listenAddress = nodeInfo.getListenAddress(); node.datacenter = nodeInfo.getDatacenter(); @@ -51,7 +51,7 @@ protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node) { try { node.cassandraVersion = CassandraVersion.parse(versionString); } catch (IllegalArgumentException e) { - LOG.warn("Error converting Cassandra version '{}'", versionString); + LOG.warn("[{}] Error converting Cassandra version '{}'", logPrefix, versionString); } node.extras = (nodeInfo.getExtras() == null) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 9056a18ffa6..84741be5467 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -29,8 +29,8 @@ public class RemoveNodeRefresh extends NodesRefresh { @VisibleForTesting final InetSocketAddress toRemove; - RemoveNodeRefresh(DefaultMetadata current, InetSocketAddress toRemove) { - super(current); + RemoveNodeRefresh(DefaultMetadata current, InetSocketAddress toRemove, String logPrefix) { + super(current, logPrefix); this.toRemove = toRemove; } @@ -43,7 +43,7 @@ protected Map computeNewNodes() { // hurt to fail gracefully just in case return null; } else { - LOG.debug("Removing node {}", node); + LOG.debug("[{}] Removing node {}", logPrefix, node); events.add(NodeStateEvent.removed((DefaultNode) node)); ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); for (Map.Entry entry : oldNodes.entrySet()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index d45590e01c5..e7502908482 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -69,8 +69,12 @@ public class ChannelPool implements AsyncAutoCloseable { * channels (i.e. {@link #next()} return {@code null}) and is reconnecting. */ public static CompletionStage init( - Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { - ChannelPool pool = new ChannelPool(node, keyspaceName, distance, context); + Node node, + CqlIdentifier keyspaceName, + NodeDistance distance, + InternalDriverContext context, + String sessionLogPrefix) { + ChannelPool pool = new ChannelPool(node, keyspaceName, distance, context, sessionLogPrefix); return pool.connect(); } @@ -80,14 +84,22 @@ public static CompletionStage init( private final Node node; private final CqlIdentifier initialKeyspaceName; private final EventExecutor adminExecutor; + private final String sessionLogPrefix; + private final String logPrefix; private final SingleThreaded singleThreaded; private volatile boolean invalidKeyspace; private ChannelPool( - Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { + Node node, + CqlIdentifier keyspaceName, + NodeDistance distance, + InternalDriverContext context, + String sessionLogPrefix) { this.node = node; this.initialKeyspaceName = keyspaceName; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.sessionLogPrefix = sessionLogPrefix; + this.logPrefix = sessionLogPrefix + "|" + node.getConnectAddress(); this.singleThreaded = new SingleThreaded(keyspaceName, distance, context); } @@ -191,6 +203,7 @@ private SingleThreaded( this.eventBus = context.eventBus(); this.reconnection = new Reconnection( + logPrefix, adminExecutor, context.reconnectionPolicy(), this::addMissingChannels, @@ -222,11 +235,12 @@ private CompletionStage addMissingChannels() { assert pendingChannels.isEmpty(); int missing = wantedCount - channels.size(); - LOG.debug("{} trying to create {} missing channels", ChannelPool.this, missing); + LOG.debug("[{}] Trying to create {} missing channels", logPrefix, missing); DriverChannelOptions options = DriverChannelOptions.builder() .withKeyspace(keyspaceName) .reportAvailableIds(wantedCount > 1) + .withOwnerLogPrefix(sessionLogPrefix) .build(); for (int i = 0; i < missing; i++) { CompletionStage channelFuture = @@ -246,7 +260,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { assert future.isDone(); if (future.isCompletedExceptionally()) { Throwable error = CompletableFutures.getFailed(future); - LOG.debug(ChannelPool.this + " error while opening new channel", error); + LOG.debug("[{}] Error while opening new channel", logPrefix, error); // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) // TODO auth exception => WARN and keep reconnecting @@ -262,12 +276,12 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { DriverChannel channel = CompletableFutures.getCompleted(future); if (isClosing) { LOG.debug( - "{} new channel added ({}) but the pool was closed, closing it", - ChannelPool.this, + "[{}] New channel added ({}) but the pool was closed, closing it", + logPrefix, channel); channel.forceClose(); } else { - LOG.debug("{} new channel added {}", ChannelPool.this, channel); + LOG.debug("[{}] New channel added {}", logPrefix, channel); channels.add(channel); eventBus.fire(ChannelEvent.channelOpened(node)); channel @@ -286,7 +300,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { pendingChannels.clear(); if (clusterNameMismatch != null) { - LOG.warn(clusterNameMismatch.getMessage()); + LOG.warn("[{}] {}", logPrefix, clusterNameMismatch.getMessage()); eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); // Don't bother continuing, the pool will get shut down soon anyway return true; @@ -296,8 +310,8 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { int currentCount = channels.size(); LOG.debug( - "{} reconnection attempt complete, {}/{} channels", - ChannelPool.this, + "[{}] Reconnection attempt complete, {}/{} channels", + logPrefix, currentCount, wantedCount); // Stop reconnecting if we have the wanted count @@ -306,11 +320,13 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { private void onChannelClosed(DriverChannel channel) { assert adminExecutor.inEventLoop(); - LOG.debug("{} lost channel {}", ChannelPool.this, channel); - channels.remove(channel); - eventBus.fire(ChannelEvent.channelClosed(node)); - if (!isClosing && !reconnection.isRunning()) { - reconnection.start(); + if (!isClosing) { + LOG.debug("[{}] Lost channel {}", logPrefix, channel); + channels.remove(channel); + eventBus.fire(ChannelEvent.channelClosed(node)); + if (!reconnection.isRunning()) { + reconnection.start(); + } } } @@ -318,14 +334,13 @@ private void resize(NodeDistance newDistance) { assert adminExecutor.inEventLoop(); int newChannelCount = computeSize(newDistance); if (newChannelCount > wantedCount) { - LOG.debug("{} growing ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); + LOG.debug("[{}] Growing ({} => {} channels)", logPrefix, wantedCount, newChannelCount); wantedCount = newChannelCount; if (!reconnection.isRunning()) { reconnection.start(); } } else if (newChannelCount < wantedCount) { - LOG.debug( - "{} shrinking ({} => {} channels)", ChannelPool.this, wantedCount, newChannelCount); + LOG.debug("[{}] Shrinking ({} => {} channels)", logPrefix, wantedCount, newChannelCount); wantedCount = newChannelCount; if (!reconnection.isRunning()) { shrinkIfTooManyChannels(); @@ -337,7 +352,7 @@ private void shrinkIfTooManyChannels() { assert adminExecutor.inEventLoop(); int extraCount = channels.size() - wantedCount; if (extraCount > 0) { - LOG.debug("{} closing {} extra channels", ChannelPool.this, extraCount); + LOG.debug("[{}] Closing {} extra channels", logPrefix, extraCount); Set toRemove = Sets.newHashSetWithExpectedSize(extraCount); for (DriverChannel channel : channels) { toRemove.add(channel); @@ -396,8 +411,7 @@ private void close() { return channel.close(); }, () -> closeFuture.complete(null), - (channel, error) -> - LOG.warn(ChannelPool.this + " error closing channel " + channel, error)); + (channel, error) -> LOG.warn("[{}] Error closing channel {}", logPrefix, channel, error)); } private void forAllChannels( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java index 3ddbba23463..2490241e088 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolFactory.java @@ -24,7 +24,11 @@ /** Just a level of indirection to make testing easier. */ public class ChannelPoolFactory { public CompletionStage init( - Node node, CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { - return ChannelPool.init(node, keyspaceName, distance, context); + Node node, + CqlIdentifier keyspaceName, + NodeDistance distance, + InternalDriverContext context, + String sessionLogPrefix) { + return ChannelPool.init(node, keyspaceName, distance, context, sessionLogPrefix); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 4a0be33af85..ea39296db44 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -69,13 +69,14 @@ public class DefaultSession implements CqlSession { private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); public static CompletionStage init( - InternalDriverContext context, CqlIdentifier keyspace) { - return new DefaultSession(context, keyspace).init(); + InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { + return new DefaultSession(context, keyspace, logPrefix).init(); } private final InternalDriverContext context; private final DriverConfig config; private final EventExecutor adminExecutor; + private final String logPrefix; private final SingleThreaded singleThreaded; private final RequestProcessorRegistry processorRegistry; @@ -89,13 +90,14 @@ public static CompletionStage init( // the map will only be updated from adminExecutor 1); - private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace) { + private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.context = context; this.config = context.config(); this.singleThreaded = new SingleThreaded(context); this.processorRegistry = context.requestProcessorRegistry(); this.keyspace = keyspace; + this.logPrefix = logPrefix; } private CompletionStage init() { @@ -120,9 +122,10 @@ public void setKeyspace(CqlIdentifier newKeyspace) { if (!Objects.equals(oldKeyspace, newKeyspace)) { if (config.defaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { LOG.warn( - "Detected a keyspace change at runtime ({} => {}). " + "[{}] Detected a keyspace change at runtime ({} => {}). " + "This is an anti-pattern that should be avoided in production " + "(see '{}' in the configuration).", + logPrefix, (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), newKeyspace.asInternal(), CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); @@ -154,7 +157,7 @@ private RequestHandler ne // TODO CASSANDRA-10145 throw new UnsupportedOperationException("Per-request keyspaces are not supported yet"); } - return processorRegistry.processorFor(request).newHandler(request, this, context); + return processorRegistry.processorFor(request).newHandler(request, this, context, logPrefix); } @Override @@ -216,7 +219,7 @@ private void init() { } initWasCalled = true; - LOG.debug("Initializing {}", DefaultSession.this); + LOG.debug("[{}] Starting initialization", logPrefix); // Make sure we don't miss any event while the pools are initializing distanceEventFilter.start(); @@ -227,11 +230,12 @@ private void init() { for (Node node : nodes) { NodeDistance distance = node.getDistance(); if (distance == NodeDistance.IGNORED) { - LOG.debug("Skipping {} because it is IGNORED", node); + LOG.debug("[{}] Skipping {} because it is IGNORED", logPrefix, node); } else if (node.getState() == NodeState.FORCED_DOWN) { - LOG.debug("Skipping {} because it is FORCED_DOWN", node); + LOG.debug("[{}] Skipping {} because it is FORCED_DOWN", logPrefix, node); } else { - poolStages.add(channelPoolFactory.init(node, keyspace, distance, context)); + LOG.debug("[{}] Creating a pool for {}", logPrefix, node); + poolStages.add(channelPoolFactory.init(node, keyspace, distance, context, logPrefix)); } } CompletableFutures.whenAllDone(poolStages, () -> this.onPoolsInit(poolStages), adminExecutor); @@ -239,14 +243,16 @@ private void init() { private void onPoolsInit(List> poolStages) { assert adminExecutor.inEventLoop(); - LOG.debug("{}: all pools have finished initializing", DefaultSession.this); + LOG.debug("[{}] All pools have finished initializing", logPrefix); // We will only propagate an invalid keyspace error if all pools get it boolean allInvalidKeyspaces = poolStages.size() > 0; for (CompletionStage poolStage : poolStages) { // Note: pool init always succeeds ChannelPool pool = CompletableFutures.getCompleted(poolStage.toCompletableFuture()); boolean invalidKeyspace = pool.isInvalidKeyspace(); - LOG.debug("Pool to {} -- invalid keyspace = {}", pool.getNode(), invalidKeyspace); + if (invalidKeyspace) { + LOG.debug("[{}] Pool to {} reports an invalid keyspace", logPrefix, pool.getNode()); + } allInvalidKeyspaces &= invalidKeyspace; pools.put(pool.getNode(), pool); } @@ -255,6 +261,7 @@ private void onPoolsInit(List> poolStages) { new InvalidKeyspaceException("Invalid keyspace " + keyspace.asPrettyCql())); forceClose(); } else { + LOG.debug("[{}] Initialization complete, ready", logPrefix); initFuture.complete(DefaultSession.this); distanceEventFilter.markReady(); stateEventFilter.markReady(); @@ -281,31 +288,33 @@ private void processDistanceEvent(DistanceEvent event) { } else if (newDistance == NodeDistance.IGNORED && pools.containsKey(node)) { ChannelPool pool = pools.remove(node); if (pool != null) { - LOG.debug("{} became IGNORED, destroying pool", node); + LOG.debug("[{}] {} became IGNORED, destroying pool", logPrefix, node); pool.closeAsync() .exceptionally( error -> { - LOG.warn("Error closing pool", error); + LOG.warn("[{}] Error closing pool", logPrefix, error); return null; }); } } else { NodeState state = node.getState(); if (state == NodeState.FORCED_DOWN) { - LOG.warn("{} became {} but it is FORCED_DOWN, ignoring", node, newDistance); + LOG.warn( + "[{}] {} became {} but it is FORCED_DOWN, ignoring", logPrefix, node, newDistance); return; } ChannelPool pool = pools.get(node); if (pool == null) { - LOG.debug("{} became {} and no pool found, initializing it", node, newDistance); + LOG.debug( + "[{}] {} became {} and no pool found, initializing it", logPrefix, node, newDistance); CompletionStage poolFuture = - channelPoolFactory.init(node, keyspace, newDistance, context); + channelPoolFactory.init(node, keyspace, newDistance, context, logPrefix); pending.put(node, poolFuture); poolFuture .thenAcceptAsync(this::onPoolAdded, adminExecutor) .exceptionally(UncaughtExceptions::log); } else { - LOG.debug("{} became {}, resizing it", node, newDistance); + LOG.debug("[{}] {} became {}, resizing it", logPrefix, node, newDistance); pool.resize(newDistance); } } @@ -321,26 +330,26 @@ private void processStateEvent(NodeStateEvent event) { } else if (newState == NodeState.FORCED_DOWN) { ChannelPool pool = pools.remove(node); if (pool != null) { - LOG.debug("{} became FORCED_DOWN, destroying pool", node); + LOG.debug("[{}] {} became FORCED_DOWN, destroying pool", logPrefix, node); pool.closeAsync() .exceptionally( error -> { - LOG.warn("Error closing pool", error); + LOG.warn("[{}] Error closing pool", logPrefix, error); return null; }); } } else if (newState == NodeState.UP) { ChannelPool pool = pools.get(node); if (pool == null) { - LOG.debug("{} came back UP and no pool found, initializing it"); + LOG.debug("[{}] {} came back UP and no pool found, initializing it", logPrefix, node); CompletionStage poolFuture = - channelPoolFactory.init(node, keyspace, node.getDistance(), context); + channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); pending.put(node, poolFuture); poolFuture .thenAcceptAsync(this::onPoolAdded, adminExecutor) .exceptionally(UncaughtExceptions::log); } else { - LOG.debug("{} came back UP, triggering pool reconnection", node); + LOG.debug("[{}] {} came back UP, triggering pool reconnection", logPrefix, node); pool.reconnectNow(); } } @@ -350,10 +359,11 @@ private void onPoolAdded(ChannelPool pool) { assert adminExecutor.inEventLoop(); Node node = pool.getNode(); if (closeWasCalled) { - LOG.debug("Session was closed while a pool to {} was initializing, closing it", node); + LOG.debug( + "[{}] Session closed while a pool to {} was initializing, closing it", logPrefix, node); pool.forceCloseAsync(); } else { - LOG.debug("New pool to {} initialized", node); + LOG.debug("[{}] New pool to {} initialized", logPrefix, node); // If the session's keyspace changed while the pool was initializing, switch it now. Don't // try too hard to wait until we expose the pool to clients, switching keyspaces is // inherently unsafe anyway. @@ -365,11 +375,16 @@ private void onPoolAdded(ChannelPool pool) { DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); NodeStateEvent stateEvent = pendingStateEvents.remove(node); if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { - LOG.debug("Received {} while the pool was initializing, processing it now", stateEvent); + LOG.debug( + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + stateEvent); processStateEvent(stateEvent); } else if (distanceEvent != null) { LOG.debug( - "Received {} while the pool was initializing, processing it now", distanceEvent); + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + distanceEvent); processDistanceEvent(distanceEvent); } } @@ -380,6 +395,7 @@ private void setKeyspace(CqlIdentifier newKeyspace) { if (closeWasCalled) { return; } + LOG.debug("[{}] Switching to keyspace {}", logPrefix, newKeyspace); for (ChannelPool pool : pools.values()) { pool.setKeyspace(newKeyspace); } @@ -391,6 +407,7 @@ private void close() { return; } closeWasCalled = true; + LOG.debug("[{}] Starting shutdown", logPrefix); // Stop listening for events context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); @@ -410,6 +427,10 @@ private void forceClose() { return; } forceCloseWasCalled = true; + LOG.debug( + "[{}] Starting forced shutdown (was {}closed before)", + logPrefix, + (closeWasCalled ? "" : "not ")); if (closeWasCalled) { for (ChannelPool pool : pools.values()) { @@ -443,6 +464,7 @@ private void onAllPoolsClosed(List> closePoolStages) { if (firstError != null) { closeFuture.completeExceptionally(firstError); } else { + LOG.debug("[{}] Shutdown complete", logPrefix); closeFuture.complete(null); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index a7bafcefe56..e78571cc26d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -68,18 +68,18 @@ protected RequestHandlerBase( : request.isIdempotent(); } - protected DriverChannel getChannel(Node node) { + protected DriverChannel getChannel(Node node, String logPrefix) { ChannelPool pool = session.getPools().get(node); if (pool == null) { - LOG.trace("No pool to {}, skipping", node); + LOG.debug("[{}] No pool to {}, skipping", logPrefix, node); return null; } else { DriverChannel channel = pool.next(); if (channel == null) { - LOG.trace("Pool returned no channel for {}, skipping", node); + LOG.trace("[{}] Pool returned no channel for {}, skipping", logPrefix, node); return null; } else if (channel.closeFuture().isDone()) { - LOG.trace("Pool returned closed connection to {}, skipping", node); + LOG.trace("[{}] Pool returned closed connection to {}, skipping", logPrefix, node); return null; } else { return channel; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index aace09845bf..98d0957eccd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -43,5 +43,6 @@ public interface RequestProcessor { RequestHandler newHandler( Request request, DefaultSession session, - InternalDriverContext context); + InternalDriverContext context, + String sessionLogPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java index acd6884362f..e5aac867772 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -25,13 +25,17 @@ public class RequestProcessorRegistry { private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); - public static final RequestProcessorRegistry DEFAULT = - new RequestProcessorRegistry(new CqlRequestProcessor(), new CqlPrepareProcessor()); + public static RequestProcessorRegistry defaultCqlProcessors(String logPrefix) { + return new RequestProcessorRegistry( + logPrefix, new CqlRequestProcessor(), new CqlPrepareProcessor()); + } + private final String logPrefix; // Effectively immutable: the contents are never modified after construction private final RequestProcessor[] processors; - public RequestProcessorRegistry(RequestProcessor... processors) { + public RequestProcessorRegistry(String logPrefix, RequestProcessor... processors) { + this.logPrefix = logPrefix; this.processors = processors; } @@ -40,14 +44,14 @@ public RequestProcessor p for (RequestProcessor processor : processors) { if (processor.canProcess(request)) { - LOG.trace("Using {} to process {}", processor, request); + LOG.trace("[{}] Using {} to process {}", logPrefix, processor, request); // The cast is safe provided that the processor implements canProcess correctly @SuppressWarnings("unchecked") RequestProcessor result = (RequestProcessor) processor; return result; } else { - LOG.trace("{} cannot process {}, trying next", processor, request); + LOG.trace("[{}] {} cannot process {}, trying next", logPrefix, processor, request); } } throw new IllegalArgumentException("No request processor found for " + request); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index 6ec567dc498..e7d50f4b010 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -61,9 +61,11 @@ public abstract class CachingCodecRegistry implements CodecRegistry { // - same for user codecs (we assume the cardinality will always be low, so a sequential array // traversal is cheap). + protected final String logPrefix; private final TypeCodec[] userCodecs; - protected CachingCodecRegistry(TypeCodec... userCodecs) { + protected CachingCodecRegistry(String logPrefix, TypeCodec... userCodecs) { + this.logPrefix = logPrefix; this.userCodecs = userCodecs; } @@ -78,15 +80,15 @@ protected CachingCodecRegistry(TypeCodec... userCodecs) { @Override public TypeCodec codecFor(DataType cqlType, GenericType javaType) { - LOG.trace("Looking up codec for {} <-> {}", cqlType, javaType); + LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); if (primitiveCodec != null && primitiveCodec.canEncode(javaType)) { - LOG.trace("Found matching primitive codec {}", primitiveCodec); + LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { - LOG.trace("Found matching user codec {}", userCodec); + LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } } @@ -95,15 +97,15 @@ public TypeCodec codecFor(DataType cqlType, GenericType javaType) { @Override public TypeCodec codecFor(DataType cqlType, Class javaType) { - LOG.trace("Looking up codec for {} <-> {}", cqlType, javaType); + LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); if (primitiveCodec != null && primitiveCodec.getJavaType().__getToken().getType() == javaType) { - LOG.trace("Found matching primitive codec {}", primitiveCodec); + LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { - LOG.trace("Found matching user codec {}", userCodec); + LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } } @@ -112,15 +114,15 @@ public TypeCodec codecFor(DataType cqlType, Class javaType) { @Override public TypeCodec codecFor(DataType cqlType) { - LOG.trace("Looking up codec for CQL type {}", cqlType); + LOG.trace("[{}] Looking up codec for CQL type {}", logPrefix, cqlType); TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); if (primitiveCodec != null) { - LOG.trace("Found matching primitive codec {}", primitiveCodec); + LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { if (userCodec.canDecode(cqlType)) { - LOG.trace("Found matching user codec {}", userCodec); + LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } } @@ -130,17 +132,17 @@ public TypeCodec codecFor(DataType cqlType) { @Override public TypeCodec codecFor(T value) { Preconditions.checkNotNull(value); - LOG.trace("Looking up codec for object {}", value); + LOG.trace("[{}] Looking up codec for object {}", logPrefix, value); for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { if (primitiveCodec.canEncode(value)) { - LOG.trace("Found matching primitive codec {}", primitiveCodec); + LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } } for (TypeCodec userCodec : userCodecs) { if (userCodec.canEncode(value)) { - LOG.trace("Found matching user codec {}", userCodec); + LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } } @@ -152,22 +154,22 @@ public TypeCodec codecFor(T value) { } GenericType javaType = inspectType(value); - LOG.trace("Continuing based on inferred type {}", javaType); + LOG.trace("[{}] Continuing based on inferred type {}", logPrefix, javaType); return safeCast(getCachedCodec(null, javaType)); } // Not exposed publicly, this is only used for the recursion from createCodec(GenericType) private TypeCodec codecFor(GenericType javaType) { - LOG.trace("Looking up codec for Java type {}", javaType); + LOG.trace("[{}] Looking up codec for Java type {}", logPrefix, javaType); for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { if (primitiveCodec.canEncode(javaType)) { - LOG.trace("Found matching primitive codec {}", primitiveCodec); + LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } } for (TypeCodec userCodec : userCodecs) { if (userCodec.canEncode(javaType)) { - LOG.trace("Found matching user codec {}", userCodec); + LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } } @@ -210,7 +212,7 @@ private GenericType inspectType(Object value) { // Try to create a codec when we haven't found it in the cache protected TypeCodec createCodec(DataType cqlType, GenericType javaType) { - LOG.trace("Cache miss, creating codec"); + LOG.trace("[{}] Cache miss, creating codec", logPrefix); // Either type can be null, but not both. if (javaType == null) { assert cqlType != null; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java index 74bcc460b6c..a06ebfc0544 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java @@ -48,13 +48,14 @@ public class DefaultCodecRegistry extends CachingCodecRegistry { * eviction is that useful anyway. */ public DefaultCodecRegistry( + String logPrefix, int initialCacheCapacity, BiFunction, Integer> cacheWeigher, int maximumCacheWeight, BiConsumer> cacheRemovalListener, TypeCodec... userCodecs) { - super(userCodecs); + super(logPrefix, userCodecs); CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); if (initialCacheCapacity > 0) { cacheBuilder.initialCapacity(initialCacheCapacity); @@ -79,13 +80,13 @@ public TypeCodec load(CacheKey key) throws Exception { }); } - public DefaultCodecRegistry(TypeCodec... userCodecs) { - this(0, null, 0, null, userCodecs); + public DefaultCodecRegistry(String logPrefix, TypeCodec... userCodecs) { + this(logPrefix, 0, null, 0, null, userCodecs); } @Override protected TypeCodec getCachedCodec(DataType cqlType, GenericType javaType) { - LOG.trace("Checking cache"); + LOG.trace("[{}] Checking cache", logPrefix); return cache.getUnchecked(new CacheKey(cqlType, javaType)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index 17df4e7e655..e6bd4d0dfc3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -38,6 +38,7 @@ public class Reconnection { private static final Logger LOG = LoggerFactory.getLogger(Reconnection.class); + private final String logPrefix; private final EventExecutor executor; private final ReconnectionPolicy reconnectionPolicy; private final Callable> reconnectionTask; @@ -53,11 +54,13 @@ public class Reconnection { * not. */ public Reconnection( + String logPrefix, EventExecutor executor, ReconnectionPolicy reconnectionPolicy, Callable> reconnectionTask, Runnable onStart, Runnable onStop) { + this.logPrefix = logPrefix; this.executor = executor; this.reconnectionPolicy = reconnectionPolicy; this.reconnectionTask = reconnectionTask; @@ -66,10 +69,11 @@ public Reconnection( } public Reconnection( + String logPrefix, EventExecutor executor, ReconnectionPolicy reconnectionPolicy, Callable> reconnectionTask) { - this(executor, reconnectionPolicy, reconnectionTask, () -> {}, () -> {}); + this(logPrefix, executor, reconnectionPolicy, reconnectionTask, () -> {}, () -> {}); } public boolean isRunning() { @@ -96,7 +100,7 @@ public void start() { public void reconnectNow(boolean forceIfStopped) { assert executor.inEventLoop(); if (isRunning || forceIfStopped) { - LOG.debug("{} forcing next attempt now", this); + LOG.debug("[{}] Forcing next attempt now", logPrefix); isRunning = true; if (nextAttempt != null) { nextAttempt.cancel(true); @@ -104,7 +108,7 @@ public void reconnectNow(boolean forceIfStopped) { try { onNextAttemptStarted(reconnectionTask.call()); } catch (Exception e) { - LOG.warn("Uncaught error while starting reconnection attempt", e); + LOG.warn("[{}] Uncaught error while starting reconnection attempt", logPrefix, e); scheduleNextAttempt(); } } @@ -114,7 +118,7 @@ public void stop() { assert executor.inEventLoop(); if (isRunning) { isRunning = false; - LOG.debug("{} stopping reconnection", this); + LOG.debug("[{}] Stopping reconnection", logPrefix); if (nextAttempt != null) { nextAttempt.cancel(true); } @@ -130,14 +134,15 @@ private void scheduleNextAttempt() { reconnectionSchedule = reconnectionPolicy.newSchedule(); } Duration nextInterval = reconnectionSchedule.nextDelay(); - LOG.debug("{} scheduling next reconnection in {}", this, nextInterval); + LOG.debug("[{}] Scheduling next reconnection in {}", logPrefix, nextInterval); nextAttempt = executor.schedule(reconnectionTask, nextInterval.toNanos(), TimeUnit.NANOSECONDS); nextAttempt.addListener( (Future> f) -> { if (f.isSuccess()) { onNextAttemptStarted(f.getNow()); } else if (!f.isCancelled()) { - LOG.warn("Uncaught error while starting reconnection attempt", f.cause()); + LOG.warn( + "[{}] Uncaught error while starting reconnection attempt", logPrefix, f.cause()); scheduleNextAttempt(); } }); @@ -155,11 +160,11 @@ private void onNextAttemptStarted(CompletionStage futureOutcome) { private void onNextAttemptCompleted(Boolean success, Throwable error) { assert executor.inEventLoop(); if (success) { - LOG.debug("{} reconnection successful", this); + LOG.debug("[{}] Reconnection successful", logPrefix); stop(); } else { if (error != null && !(error instanceof CancellationException)) { - LOG.warn("Uncaught error while starting reconnection attempt", error); + LOG.warn("[{}] Uncaught error while starting reconnection attempt", logPrefix, error); } if (isRunning) { // can be false if stop() was called scheduleNextAttempt(); diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 49662962fb0..7f4dffe962c 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -38,6 +38,15 @@ datastax-java-driver { // version = V4 } + # A name that uniquely identifies the driver instance created from this configuration. This is + # used as a prefix for log messages and metrics. + # This parameter is optional; if it is not specified, the driver will generate an identifier + # composed of the letter 'c' followed by an incrementing counter. + # If you provide a different value, try to keep it short to keep the logs readable. Also, make + # sure it is unique: reusing the same value will not break the driver, but it will mix up the logs + # and metrics. + // cluster-name = my_cluster + retry { policy-class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index f1ce6968389..12eff6a90a0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -234,7 +234,8 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), setKeyspaceTimeoutMillis, availableIdsHolder, - null); + null, + "test"); ProtocolInitHandler initHandler = new ProtocolInitHandler(context, protocolVersion, clusterName, options); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 3c900ddcbe6..51e0625c434 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -52,7 +52,12 @@ public void setup() { .pipeline() .addLast( new InFlightHandler( - CoreProtocolVersion.V3, streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, null)); + CoreProtocolVersion.V3, + streamIds, + SET_KEYSPACE_TIMEOUT_MILLIS, + null, + null, + "test")); writeCoalescer = new MockWriteCoalescer(); driverChannel = new DriverChannel(channel, writeCoalescer, null, CoreProtocolVersion.V3); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index e9763b03e30..9b503e87788 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -357,6 +357,7 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { streamIds, SET_KEYSPACE_TIMEOUT_MILLIS, null, - eventCallback)); + eventCallback, + "test")); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index f9cfdc6e2b1..60b07a0b3f1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -80,7 +80,7 @@ public void setup() { .addLast( "inflight", new InFlightHandler( - CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null)); + CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null, "test")); } @Test @@ -119,7 +119,8 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, null)); + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java index 634f25fb683..6da82b02716 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java @@ -31,7 +31,7 @@ public class EventBusTest { @BeforeMethod public void setup() { - bus = new EventBus(); + bus = new EventBus("test"); results = new HashMap<>(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 79f64099f77..efae2053ebb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -293,7 +293,6 @@ public void should_close_channel_when_closing() { // Then assertThat(closeFuture).isSuccess(); Mockito.verify(channel1).forceClose(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index f2a23801dcb..75fba5f46a6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -74,7 +74,7 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); @@ -113,7 +113,7 @@ public void should_not_reprepare_on_other_nodes_if_already_cached() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); @@ -143,7 +143,7 @@ public void should_ignore_errors_while_repreparing_on_other_nodes() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(prepareFuture).isNotDone(); @@ -183,7 +183,7 @@ public void should_retry_initial_prepare_if_recoverable_error() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); // Success on node2, reprepare on node3 @@ -218,7 +218,7 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); // Success on node2, reprepare on node3 @@ -254,7 +254,7 @@ public void should_fail_if_retry_policy_ignores_error() { CompletionStage prepareFuture = new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext()) + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") .asyncResult(); // Success on node2, reprepare on node3 diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 411a361b59b..41e52ac39f8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -63,7 +63,7 @@ public void should_always_try_next_node_if_bootstrapping( .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -102,7 +102,7 @@ public void should_always_rethrow_query_validation_error( .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -129,7 +129,7 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -160,7 +160,7 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -190,7 +190,7 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.IGNORE); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -219,7 +219,7 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETHROW); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) @@ -238,7 +238,7 @@ public void should_rethrow_error_if_not_idempotent( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); assertThat(resultSetFuture) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index f11d9523d7b..9080abaeced 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -41,7 +41,8 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()).asyncResult(); + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .asyncResult(); node1Behavior.verifyWrite(); @@ -72,7 +73,8 @@ public void should_schedule_speculative_executions( .thenReturn(secondExecutionDelay); Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(0L); - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()).asyncResult(); + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .asyncResult(); node1Behavior.verifyWrite(); @@ -115,7 +117,7 @@ public void should_not_start_execution_if_result_complete( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); @@ -159,7 +161,7 @@ public void should_retry_in_speculative_executions( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); // do not simulate a response from node1. The request will stay hanging for the rest of this test @@ -199,7 +201,7 @@ public void should_stop_retrying_other_executions_if_result_complete( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 467fca1a65c..96807f46a19 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -50,7 +50,10 @@ public void should_complete_result_if_first_node_replies_immediately() { CompletionStage resultSetFuture = new CqlRequestHandler( - UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") .asyncResult(); assertThat(resultSetFuture) @@ -82,7 +85,10 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { CompletionStage resultSetFuture = new CqlRequestHandler( - UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") .asyncResult(); // First scheduled task is the timeout, run it before node1 has responded @@ -111,7 +117,10 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { CompletionStage resultSetFuture = new CqlRequestHandler( - UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext()) + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") .asyncResult(); assertThat(resultSetFuture) @@ -140,7 +149,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext()) + new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.setWriteSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 2cfcb15c957..9a17c1ae355 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -98,7 +98,7 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.retryPolicy()).thenReturn(retryPolicy); Mockito.when(context.speculativeExecutionPolicy()).thenReturn(speculativeExecutionPolicy); - Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry()); + Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); Map pools = builder.buildMockPools(); Mockito.when(session.getPools()).thenReturn(pools); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 5a82938a466..e36ac581d47 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -39,7 +39,7 @@ public void should_add_new_node() { .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo); + AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo, "test"); // When refresh.compute(); @@ -63,7 +63,7 @@ public void should_not_add_existing_node() { .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo); + AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo, "test"); // When refresh.compute(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 2009da739c3..c00370f3366 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -41,7 +41,7 @@ public void should_add_and_remove_nodes() { ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos); + FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos, "test"); // When refresh.compute(); @@ -69,7 +69,7 @@ public void should_update_existing_nodes() { .withDatacenter("dc1") .withRack("rack2") .build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos); + FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos, "test"); // When refresh.compute(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 7fef2c55b41..451942fd934 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -30,7 +30,8 @@ public class InitContactPointsRefreshTest { public void should_create_nodes() { // Given InitContactPointsRefresh refresh = - new InitContactPointsRefresh(DefaultMetadata.EMPTY, ImmutableSet.of(ADDRESS1, ADDRESS2)); + new InitContactPointsRefresh( + DefaultMetadata.EMPTY, ImmutableSet.of(ADDRESS1, ADDRESS2), "test"); // When refresh.compute(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 6e7d01e4c88..4c777853305 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -82,7 +82,7 @@ public void setup() { policysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); Mockito.when(loadBalancingPolicy.newQueryPlan()).thenReturn(policysQueryPlan); - eventBus = Mockito.spy(new EventBus()); + eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); wrapper = new LoadBalancingPolicyWrapper(context, loadBalancingPolicy); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index fee0386fd59..6164505f5e0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -75,7 +75,7 @@ public void setup() { Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); - this.eventBus = Mockito.spy(new EventBus()); + this.eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); adminEventLoopGroup = new DefaultEventLoopGroup(1, new BlockingOperation.SafeThreadFactory()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index a84637043da..c4f2c7f5cdc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -34,7 +34,7 @@ public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2, "test"); // When refresh.compute(); @@ -48,7 +48,7 @@ public void should_remove_existing_node() { public void should_not_remove_nonexistent_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2, "test"); // When refresh.compute(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 861fec9b8d0..202b0244245 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -115,7 +115,7 @@ public void should_initialize_when_all_channels_succeed() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -139,7 +139,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -162,7 +162,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -182,7 +182,7 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro .failure(ADDRESS, error) .build(); - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -214,7 +214,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -260,7 +260,7 @@ public void should_reconnect_when_channel_dies() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -309,7 +309,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -356,7 +356,7 @@ public void should_shrink_during_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context); + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -413,7 +413,7 @@ public void should_grow_outside_of_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -468,7 +468,7 @@ public void should_grow_during_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -526,7 +526,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -568,7 +568,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -621,7 +621,7 @@ public void should_close_all_channels_when_closed() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); @@ -684,7 +684,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context); + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 4092ec4c02a..79c316a0173 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -82,7 +82,7 @@ public void setup() { Mockito.when(context.channelPoolFactory()).thenReturn(channelPoolFactory); - eventBus = Mockito.spy(new EventBus()); + eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); node1 = mockLocalNode(1); @@ -120,7 +120,7 @@ public void should_initialize_pools_with_distances() { .pending(node3, KEYSPACE, NodeDistance.REMOTE, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -154,7 +154,7 @@ public void should_not_connect_to_ignored_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -178,7 +178,7 @@ public void should_not_connect_to_forced_down_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -204,7 +204,7 @@ public void should_adjust_distance_if_changed_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -245,7 +245,7 @@ public void should_remove_pool_if_ignored_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -285,7 +285,7 @@ public void should_remove_pool_if_forced_down_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -322,7 +322,7 @@ public void should_resize_pool_if_distance_changes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -346,7 +346,7 @@ public void should_remove_pool_if_node_becomes_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -377,7 +377,7 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -405,7 +405,7 @@ public void should_remove_pool_if_node_is_forced_down() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -436,7 +436,7 @@ public void should_recreate_pool_if_node_is_forced_back_up() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -468,7 +468,7 @@ public void should_adjust_distance_if_changed_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -511,7 +511,7 @@ public void should_remove_pool_if_ignored_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -554,7 +554,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -592,7 +592,7 @@ public void should_close_all_pools_when_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -622,7 +622,7 @@ public void should_force_close_all_pools_when_force_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -657,7 +657,7 @@ public void should_close_pool_if_recreated_while_closing() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -695,7 +695,7 @@ public void should_set_keyspace_on_all_pools() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -730,7 +730,7 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index f40f0bb9738..b009f967e9b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -85,7 +85,7 @@ public void waitForCalls(Node node, CqlIdentifier keyspace, NodeDistance distanc ArgumentCaptor.forClass(InternalDriverContext.class); inOrder .verify(channelPoolFactory, timeout(100).atLeast(expected)) - .init(eq(node), eq(keyspace), eq(distance), contextCaptor.capture()); + .init(eq(node), eq(keyspace), eq(distance), contextCaptor.capture(), eq("test")); int actual = contextCaptor.getAllValues().size(); int extras = actual - expected; @@ -161,7 +161,8 @@ private void stub() { eq(params.node), eq(params.keyspace), eq(params.distance), - any(InternalDriverContext.class))) + any(InternalDriverContext.class), + eq("test"))) .thenReturn(first); for (CompletionStage result : results) { ongoingStubbing.thenReturn(result); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index ef478281c0a..bece884cb7d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -398,7 +398,7 @@ public static class TestCachingCodecRegistry extends CachingCodecRegistry { public TestCachingCodecRegistry( BiConsumer> onCacheLookup, TypeCodec... userCodecs) { - super(userCodecs); + super("test", userCodecs); this.onCacheLookup = onCacheLookup; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index 6d8271f26df..5dd1bf164dc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -56,7 +56,12 @@ public void setup() { reconnectionTask = new MockReconnectionTask(); reconnection = new Reconnection( - eventExecutor, reconnectionPolicy, reconnectionTask, onStartCallback, onStopCallback); + "test", + eventExecutor, + reconnectionPolicy, + reconnectionTask, + onStartCallback, + onStopCallback); } @Test From a810250bf3609c1f8963d57bf554e8467efebbf6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 19 Jun 2017 14:25:35 -0700 Subject: [PATCH 080/742] Inject cluster name in internal thread names --- .../internal/core/context/DefaultDriverContext.java | 2 +- .../internal/core/context/DefaultNettyOptions.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index fae2751263d..20f5dc96003 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -218,7 +218,7 @@ protected ProtocolVersionRegistry buildProtocolVersionRegistry() { } protected NettyOptions buildNettyOptions() { - return new DefaultNettyOptions(); + return new DefaultNettyOptions(clusterName); } protected Optional buildSslHandlerFactory() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 86060848c4a..a9523d7baa1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -32,15 +32,20 @@ public class DefaultNettyOptions implements NettyOptions { private final EventLoopGroup ioEventLoopGroup; private final EventLoopGroup adminEventLoopGroup; - public DefaultNettyOptions() { - // TODO inject the cluster name in thread names + public DefaultNettyOptions(String clusterName) { ThreadFactory safeFactory = new BlockingOperation.SafeThreadFactory(); ThreadFactory ioThreadFactory = - new ThreadFactoryBuilder().setThreadFactory(safeFactory).setNameFormat("io-%d").build(); + new ThreadFactoryBuilder() + .setThreadFactory(safeFactory) + .setNameFormat(clusterName + "-io-%d") + .build(); this.ioEventLoopGroup = new NioEventLoopGroup(0, ioThreadFactory); ThreadFactory adminThreadFactory = - new ThreadFactoryBuilder().setThreadFactory(safeFactory).setNameFormat("admin-%d").build(); + new ThreadFactoryBuilder() + .setThreadFactory(safeFactory) + .setNameFormat(clusterName + "-admin-%d") + .build(); int adminThreadCount = Math.min(2, Runtime.getRuntime().availableProcessors()); this.adminEventLoopGroup = new DefaultEventLoopGroup(adminThreadCount, adminThreadFactory); } From 45cecbe3d231d7dcb9deb45dc2d5102a56f7dacd Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 19 Jun 2017 14:32:36 -0700 Subject: [PATCH 081/742] Fix cluster shutdown --- .../internal/core/context/DefaultNettyOptions.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index a9523d7baa1..91e42161f80 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -24,8 +24,11 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.PromiseCombiner; import java.util.concurrent.ThreadFactory; public class DefaultNettyOptions implements NettyOptions { @@ -82,6 +85,11 @@ public void afterChannelInitialized(Channel channel) { @Override public Future onClose() { - return ioEventLoopGroup.shutdownGracefully(); + PromiseCombiner combiner = new PromiseCombiner(); + combiner.add(adminEventLoopGroup.shutdownGracefully()); + combiner.add(ioEventLoopGroup.shutdownGracefully()); + DefaultPromise closeFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + combiner.finish(closeFuture); + return closeFuture; } } From 8e2e99ec6815b10f636f550c591a793ba39803b0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 19 Jun 2017 18:59:35 -0700 Subject: [PATCH 082/742] Make policies closeable --- .../addresstranslation/AddressTranslator.java | 5 +- .../ExponentialReconnectionPolicy.java | 6 +- .../core/connection/ReconnectionPolicy.java | 8 +- .../loadbalancing/LoadBalancingPolicy.java | 8 +- .../driver/api/core/retry/RetryPolicy.java | 4 +- .../specex/SpeculativeExecutionPolicy.java | 9 +- .../driver/internal/core/DefaultCluster.java | 18 ++++ .../metadata/LoadBalancingPolicyWrapper.java | 93 ++++++++++++++----- 8 files changed, 108 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java index c73d36c9d2b..e53259c27f0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.java @@ -34,7 +34,7 @@ *

The contact point addresses provided at driver initialization are considered translated * already; in other words, they will be used as-is, without being processed by this component. */ -public interface AddressTranslator { +public interface AddressTranslator extends AutoCloseable { /** * Translates an address reported by a Cassandra node into the address that the driver will use to @@ -42,6 +42,7 @@ public interface AddressTranslator { */ InetSocketAddress translate(InetSocketAddress address); - /** Called when the associated driver instance shuts down. */ + /** Called when the cluster that this translator is associated with closes. */ + @Override void close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index fe17a0f9477..19cbc8f4688 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; import java.time.Duration; -import java.util.concurrent.TimeUnit; /** * A reconnection policy that waits exponentially longer between each reconnection attempt (but @@ -94,6 +93,11 @@ public ReconnectionSchedule newSchedule() { return new ExponentialSchedule(); } + @Override + public void close() { + // nothing to do + } + private class ExponentialSchedule implements ReconnectionSchedule { private int attempts; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java index a9465b29ec5..5cd0085c1a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java @@ -26,11 +26,15 @@ * connections, the schedule will be reset (that is, the next failure will create a fresh schedule * instance). */ -public interface ReconnectionPolicy { +public interface ReconnectionPolicy extends AutoCloseable { /** Creates a new schedule. */ ReconnectionSchedule newSchedule(); + /** Called when the cluster that this policy is associated with closes. */ + @Override + void close(); + /** * The reconnection schedule from the time a connection is lost, to the time all connections to * this node have been restored. @@ -39,6 +43,4 @@ interface ReconnectionSchedule { /** How long to wait before the next reconnection attempt. */ Duration nextDelay(); } - - // TODO lifecycle methods (cluster shutdown, etc.) } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index 45b3cc387a5..9078fe762ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -21,7 +21,7 @@ import java.util.Set; /** Decides which Cassandra nodes to contact for each query. */ -public interface LoadBalancingPolicy { +public interface LoadBalancingPolicy extends AutoCloseable { /** * Initializes this policy with the nodes discovered during driver initialization. @@ -69,10 +69,8 @@ public interface LoadBalancingPolicy { /** Called when a node is removed from the cluster. */ void onRemove(Node node); - /** - * Invoked at cluster shutdown. This gives the policy the opportunity to perform some cleanup, for - * instance stop threads that it might have started. - */ + /** Called when the cluster that this policy is associated with closes. */ + @Override void close(); /** An object that the policy uses to signal decisions it makes about node distances. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java index a86e904a348..fc5d32ac215 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -26,7 +26,7 @@ * {@link LoadBalancingPolicy}, and tries each node in sequence. This policy is invoked if the * request to that node fails. */ -public interface RetryPolicy { +public interface RetryPolicy extends AutoCloseable { RetryDecision onReadTimeout( Request request, @@ -51,5 +51,7 @@ RetryDecision onUnavailable( RetryDecision onErrorResponse(Request request, Throwable error, int retryCount); + /** Called when the cluster that this policy is associated with closes. */ + @Override void close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java index 6c14b891ea0..7324775d09b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java @@ -23,7 +23,7 @@ * The policy that decides if the driver will send speculative queries to the next nodes when the * current node takes too long to respond. */ -public interface SpeculativeExecutionPolicy { +public interface SpeculativeExecutionPolicy extends AutoCloseable { /** * @param keyspace the CQL keyspace currently associated to the session. This is set either @@ -39,10 +39,7 @@ public interface SpeculativeExecutionPolicy { */ long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions); - /** - * Gets invoked at cluster shutdown. - * - *

This gives the policy the opportunity to clean up any resource it might have acquired. - */ + /** Called when the cluster that this policy is associated with closes. */ + @Override void close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 6083b347899..7b9bd25fbc7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -201,6 +201,7 @@ private void close() { LOG.debug("[{}] Starting shutdown", logPrefix); List> childrenCloseStages = new ArrayList<>(); + closePolicies(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { childrenCloseStages.add(closeable.closeAsync()); } @@ -226,6 +227,7 @@ private void forceClose() { } } else { closeWasCalled = true; + closePolicies(); List> childrenCloseStages = new ArrayList<>(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { childrenCloseStages.add(closeable.forceCloseAsync()); @@ -263,6 +265,22 @@ private void warnIfFailed(CompletionStage stage) { } } + private void closePolicies() { + for (AutoCloseable closeable : + ImmutableList.of( + context.reconnectionPolicy(), + context.retryPolicy(), + context.loadBalancingPolicyWrapper(), + context.speculativeExecutionPolicy(), + context.addressTranslator())) { + try { + closeable.close(); + } catch (Throwable t) { + LOG.warn("[{}] Error while closing {}", logPrefix, closeable, t); + } + } + } + private List internalComponentsToClose() { return ImmutableList.builder() .addAll(sessions) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index 902624d5d4c..d9485d5208f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,15 +43,24 @@ *

  • process distance decisions from the policy and propagate them to the outside world. * */ -public class LoadBalancingPolicyWrapper implements LoadBalancingPolicy.DistanceReporter { +public class LoadBalancingPolicyWrapper + implements LoadBalancingPolicy.DistanceReporter, AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicyWrapper.class); + private enum State { + BEFORE_INIT, + DURING_INIT, + RUNNING, + CLOSING + } + private final InternalDriverContext context; private final LoadBalancingPolicy policy; private final String logPrefix; private final ReplayingEventFilter eventFilter = new ReplayingEventFilter<>(this::processNodeStateEvent); - private AtomicBoolean isInit = new AtomicBoolean(); + private AtomicReference stateRef = new AtomicReference<>(State.BEFORE_INIT); public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPolicy policy) { this.context = context; @@ -61,27 +70,35 @@ public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPo } public void init() { - if (isInit.compareAndSet(false, true)) { + if (stateRef.compareAndSet(State.BEFORE_INIT, State.DURING_INIT)) { LOG.debug("[{}] Initializing policy", logPrefix); // State events can happen concurrently with init, so we must record them and replay once the // policy is initialized. eventFilter.start(); Metadata metadata = context.metadataManager().getMetadata(); policy.init(excludeDownHosts(metadata), this); - eventFilter.markReady(); + if (stateRef.compareAndSet(State.DURING_INIT, State.RUNNING)) { + eventFilter.markReady(); + } else { // closed during init + assert stateRef.get() == State.CLOSING; + policy.close(); + } } } public Queue newQueryPlan() { - if (isInit.get()) { - return policy.newQueryPlan(); - } else { - // Still in early initialization: retrieve nodes from the metadata (at this stage it's the - // contact points). - List nodes = new ArrayList<>(); - nodes.addAll(context.metadataManager().getMetadata().getNodes().values()); - Collections.shuffle(nodes); - return new ConcurrentLinkedQueue<>(nodes); + switch (stateRef.get()) { + case BEFORE_INIT: + case DURING_INIT: + // Retrieve nodes from the metadata (at this stage it's the contact points). + List nodes = new ArrayList<>(); + nodes.addAll(context.metadataManager().getMetadata().getNodes().values()); + Collections.shuffle(nodes); + return new ConcurrentLinkedQueue<>(nodes); + case RUNNING: + return policy.newQueryPlan(); + default: + return new ConcurrentLinkedQueue<>(); } } @@ -100,17 +117,25 @@ private void onNodeStateEvent(NodeStateEvent event) { // once it has gone through the filter private void processNodeStateEvent(NodeStateEvent event) { - assert isInit.get(); - if (event.newState == NodeState.UP) { - policy.onUp(event.node); - } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { - policy.onDown(event.node); - } else if (event.newState == NodeState.UNKNOWN) { - policy.onAdd(event.node); - } else if (event.newState == null) { - policy.onDown(event.node); - } else { - LOG.warn("[{}] Unsupported event: {}", logPrefix, event); + switch (stateRef.get()) { + case BEFORE_INIT: + case DURING_INIT: + throw new AssertionError("Filter should not be marked ready until LBP init"); + case CLOSING: + return; // ignore + case RUNNING: + if (event.newState == NodeState.UP) { + policy.onUp(event.node); + } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { + policy.onDown(event.node); + } else if (event.newState == NodeState.UNKNOWN) { + policy.onAdd(event.node); + } else if (event.newState == null) { + policy.onRemove(event.node); + } else { + LOG.warn("[{}] Unsupported event: {}", logPrefix, event); + } + break; } } @@ -123,4 +148,22 @@ private static ImmutableSet excludeDownHosts(Metadata metadata) { } return nodes.build(); } + + @Override + public void close() { + State old; + while (true) { + old = stateRef.get(); + if (old == State.CLOSING) { + return; // already closed + } else if (stateRef.compareAndSet(old, State.CLOSING)) { + break; + } + } + // If BEFORE_INIT, no need to close because it was never initialized + // If DURING_INIT, this will be handled in init() + if (old == State.RUNNING) { + policy.close(); + } + } } From 7df79426eb9f3448737a064b18c338060bdd3f24 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 19 Jun 2017 19:07:25 -0700 Subject: [PATCH 083/742] Reuse text codec in ProtocolInitHandler It duplicated the code because it was initially written before the codecs. --- .../internal/core/channel/ProtocolInitHandler.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index db715126d42..c4b875969b2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -23,6 +24,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ConnectionInitException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -39,8 +41,6 @@ import com.datastax.oss.protocol.internal.response.Ready; import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; -import com.datastax.oss.protocol.internal.util.Bytes; -import com.google.common.base.Charsets; import io.netty.channel.ChannelHandlerContext; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -295,15 +295,7 @@ public String toString() { } } - // TODO we'll probably need a lightweight ResultSet implementation for internal uses, but this is good for now private String getString(List row, int i) { - ByteBuffer bytes = row.get(i); - if (bytes == null) { - return null; - } else if (bytes.remaining() == 0) { - return ""; - } else { - return new String(Bytes.getArray(bytes), Charsets.UTF_8); - } + return TypeCodecs.TEXT.decode(row.get(i), CoreProtocolVersion.DEFAULT); } } From 727455f7da5d62a26a1cebefcf044ad4e7291370 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 19 Jun 2017 19:11:23 -0700 Subject: [PATCH 084/742] Log swallowd exception in FrameDecoder --- .../oss/driver/internal/core/protocol/FrameDecoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java index c60a2ef7fdf..e9d27237f75 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -19,8 +19,11 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FrameDecoder extends LengthFieldBasedFrameDecoder { + private static final Logger LOG = LoggerFactory.getLogger(FrameDecoder.class); // Where the length of the frame is located in the payload private static final int LENGTH_FIELD_OFFSET = 5; @@ -51,8 +54,8 @@ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception } catch (Exception e1) { // Should never happen, super.decode does not return a non-null buffer until the length // field has been read, and the stream id comes before + LOG.warn("Unexpected error while reading stream id", e1); streamId = -1; - // TODO log e1? } throw new FrameDecodingException(streamId, e); } From 3c43b7973ed853805fdad7f6536c5cce7f7c23e3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 21 Jun 2017 15:50:32 -0700 Subject: [PATCH 085/742] Use id of UNPREPARED response to reprepare on the fly Motivation: When we execute a prepared statement and find out that the coordinator doesn't have it in its cache, it's not always practical to rely on the original statement to find out what to reprepare: in particular, it could be batch and we would have to traverse all children to identify the corresponding bound statement. Modifications: Use the id returned in the UNPREPARED response to look up the payload to reprepare in a cache. Because we don't want to introduce a dependency between CqlRequestHandler and CqlPrepareHandler, that cache is owned by the Session, and contains only protocol-level data, in a class that is agnostic to the PreparedStatement implementation. Result: Reprepare on the fly without inspecting the original statement. In addition, having reprepare data at the session level will prove useful if we decide to implement the "reprepare on up" feature. --- .../api/core/cql/PreparedStatement.java | 8 --- .../adminrequest/AdminRequestHandler.java | 4 +- .../internal/core/cql/CqlPrepareHandler.java | 3 + .../core/cql/CqlPrepareProcessor.java | 7 ++ .../internal/core/cql/CqlRequestHandler.java | 69 +++++++++---------- .../core/cql/DefaultPreparedStatement.java | 30 ++++---- .../internal/core/session/DefaultSession.java | 15 ++++ .../core/session/RepreparePayload.java | 43 ++++++++++++ .../core/cql/CqlRequestHandlerTest.java | 26 +++++-- .../internal/core/cql/PoolBehavior.java | 10 ++- .../core/cql/RequestHandlerTestHarness.java | 30 ++++---- 11 files changed, 155 insertions(+), 90 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 4e7ef28ddd2..2412be6acb5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core.cql; import java.nio.ByteBuffer; -import java.util.Map; /** * A query with bind variables that has been pre-parsed by the database. @@ -52,11 +51,4 @@ public interface PreparedStatement { ColumnDefinitions getResultSetDefinitions(); BoundStatement bind(); - - /** - * The custom payload that was used for the initial prepare request. - * - *

    This is mostly for internal use, if the driver needs to re-prepare the statement later. - */ - Map initialCustomPayload(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 4c53c2dc8e2..98975eb4f13 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -98,13 +98,14 @@ public CompletionStage start() { } public CompletionStage start(Map customPayload) { - LOG.debug("[{}] Executing {}", logPrefix, this); + LOG.debug("[{}] Executing {} on {}", logPrefix, this); channel.write(message, false, customPayload, this).addListener(this::onWriteComplete); return result; } private void onWriteComplete(Future future) { if (future.isSuccess()) { + LOG.debug("[{}] Successfully wrote {}, waiting for response", logPrefix, this); timeoutFuture = channel.eventLoop().schedule(this::fireTimeout, timeout.toNanos(), TimeUnit.NANOSECONDS); timeoutFuture.addListener(UncaughtExceptions::log); @@ -132,6 +133,7 @@ public void onResponse(Frame responseFrame) { timeoutFuture.cancel(true); } Message message = responseFrame.message; + LOG.debug("[{}] Got response {}", logPrefix, responseFrame.message); if (message instanceof Rows) { Rows rows = (Rows) message; ByteBuffer pagingState = rows.metadata.pagingState; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 12c8514a12e..657ec83f2b7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -184,6 +184,9 @@ private void setFinalResult(Prepared prepared) { // prepare() twice, and therefore it's already been prepared on other nodes. result.complete(cachedStatement); } else { + session + .getRepreparePayloads() + .put(cachedStatement.getId(), cachedStatement.getRepreparePayload()); prepareOnOtherNodes() .thenRun(() -> result.complete(cachedStatement)) .exceptionally( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java index 1b5c39fd080..b0360c1c32f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.session.Request; @@ -29,6 +30,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Handles the preparation of CQL queries. + * + *

    This class is stateful, you can't reuse the same instance across multiple {@link Cluster} + * instances. + */ public class CqlPrepareProcessor implements RequestProcessor> { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index cba8e6d4feb..7a970a3c82f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -20,9 +20,7 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -41,6 +39,7 @@ import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -50,10 +49,12 @@ import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.Result; +import com.datastax.oss.protocol.internal.response.error.Unprepared; import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.SchemaChange; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.response.result.Void; +import com.datastax.oss.protocol.internal.util.Bytes; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @@ -361,40 +362,38 @@ public void onResponse(Frame responseFrame) { private void processErrorResponse(Error errorMessage) { if (errorMessage.code == ProtocolConstants.ErrorCode.UNPREPARED) { - if (!(request instanceof BoundStatement)) { - // TODO can we get UNPREPARED for a BATCH message? - setFinalError( - new IllegalStateException( - "Unexpected UNPREPARED response, " - + "this should only happen for a bound statement")); - } else { - LOG.debug("[{}] Statement is not prepared on {}, repreparing", logPrefix, node); - PreparedStatement preparedStatement = - ((BoundStatement) CqlRequestHandler.this.request).getPreparedStatement(); - Prepare reprepareMessage = new Prepare(preparedStatement.getQuery()); - AdminRequestHandler reprepareHandler = - new AdminRequestHandler( - channel, - reprepareMessage, - timeout, - logPrefix, - "Reprepare " + reprepareMessage.toString()); - reprepareHandler - .start(preparedStatement.initialCustomPayload()) - .handle( - (result, error) -> { - if (error != null) { - recordError(node, error); - LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); - sendRequest(null, execution, retryCount); - } else { - LOG.debug("[{}] Reprepare sucessful, retrying", logPrefix); - sendRequest(node, execution, retryCount); - } - return null; - }); - return; + LOG.debug("[{}] Statement is not prepared on {}, repreparing", logPrefix, node); + ByteBuffer id = ByteBuffer.wrap(((Unprepared) errorMessage).id); + RepreparePayload repreparePayload = session.getRepreparePayloads().get(id); + if (repreparePayload == null) { + throw new IllegalStateException( + String.format( + "Tried to execute unprepared query %s but we don't have the data to reprepare it", + Bytes.toHexString(id))); } + Prepare reprepareMessage = new Prepare(repreparePayload.query); + AdminRequestHandler reprepareHandler = + new AdminRequestHandler( + channel, + reprepareMessage, + timeout, + logPrefix, + "Reprepare " + reprepareMessage.toString()); + reprepareHandler + .start(repreparePayload.customPayload) + .handle( + (result, error) -> { + if (error != null) { + recordError(node, error); + LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); + sendRequest(null, execution, retryCount); + } else { + LOG.debug("[{}] Reprepare sucessful, retrying", logPrefix); + sendRequest(node, execution, retryCount); + } + return null; + }); + return; } Throwable error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index d8545c95aa3..5da53091de5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -21,23 +21,22 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.session.RepreparePayload; import java.nio.ByteBuffer; import java.util.Map; public class DefaultPreparedStatement implements PreparedStatement { private final ByteBuffer id; - private final String query; + private final RepreparePayload repreparePayload; private final ColumnDefinitions variableDefinitions; private final ColumnDefinitions resultSetDefinitions; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; - private final Map initialCustomPayload; // The options to propagate to the bound statements: private final String configProfileName; private final DriverConfigProfile configProfile; - private final String keyspace; - private final Map customPayload; + private final Map customPayloadForBoundStatements; private final Boolean idempotent; public DefaultPreparedStatement( @@ -48,23 +47,23 @@ public DefaultPreparedStatement( String configProfileName, DriverConfigProfile configProfile, String keyspace, - Map customPayload, + Map customPayloadForBoundStatements, Boolean idempotent, CodecRegistry codecRegistry, ProtocolVersion protocolVersion, - Map initialCustomPayload) { + Map customPayloadForPrepare) { this.id = id; - this.query = query; + // It's important that we keep a reference to this object, so that it only gets evicted from + // the map in DefaultSession if no client reference the PreparedStatement anymore. + this.repreparePayload = new RepreparePayload(query, keyspace, customPayloadForPrepare); this.variableDefinitions = variableDefinitions; this.resultSetDefinitions = resultSetDefinitions; this.configProfileName = configProfileName; this.configProfile = configProfile; - this.keyspace = keyspace; - this.customPayload = customPayload; + this.customPayloadForBoundStatements = customPayloadForBoundStatements; this.idempotent = idempotent; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; - this.initialCustomPayload = initialCustomPayload; } @Override @@ -74,7 +73,7 @@ public ByteBuffer getId() { @Override public String getQuery() { - return query; + return repreparePayload.query; } @Override @@ -94,15 +93,14 @@ public BoundStatement bind() { variableDefinitions, configProfileName, configProfile, - keyspace, - customPayload, + repreparePayload.keyspace, + customPayloadForBoundStatements, idempotent, codecRegistry, protocolVersion); } - @Override - public Map initialCustomPayload() { - return initialCustomPayload; + public RepreparePayload getRepreparePayload() { + return this.repreparePayload; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index ea39296db44..f30354a4638 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -35,7 +35,9 @@ import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.google.common.collect.MapMaker; import io.netty.util.concurrent.EventExecutor; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -90,6 +92,15 @@ public static CompletionStage init( // the map will only be updated from adminExecutor 1); + // The raw data to reprepare requests on the fly, if we hit a node that doesn't have them in + // its cache. + // This is raw protocol-level data, as opposed to the actual instances returned to the client + // (e.g. DefaultPreparedStatement) which are handled at the protocol level (e.g. + // CqlPrepareProcessor). We keep the two separate to avoid introducing a dependency from the + // session to a particular processor implementation. + private ConcurrentMap repreparePayloads = + new MapMaker().weakValues().makeMap(); + private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.context = context; @@ -160,6 +171,10 @@ private RequestHandler ne return processorRegistry.processorFor(request).newHandler(request, this, context, logPrefix); } + public ConcurrentMap getRepreparePayloads() { + return repreparePayloads; + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java new file mode 100644 index 00000000000..e6fe2228880 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * The information that's necessary to reprepare an already prepared statement, in case we hit a + * node that doesn't have it in its cache. + * + *

    Make sure the object that's returned to the client (e.g. {@link DefaultPreparedStatement} for + * CQL statements) keeps a reference to this. + */ +public class RepreparePayload { + + public final String query; + + /** The keyspace that is set independently from the query string (see CASSANDRA-10145) */ + public final String keyspace; + + public final Map customPayload; + + public RepreparePayload(String query, String keyspace, Map customPayload) { + this.query = query; + this.keyspace = keyspace; + this.customPayload = customPayload; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 96807f46a19..2fb43318032 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -23,21 +23,29 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; +import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.response.error.Unprepared; import com.datastax.oss.protocol.internal.response.result.Prepared; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; import java.time.Duration; import java.util.Collections; import java.util.Iterator; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.mockito.Mockito; import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; public class CqlRequestHandlerTest extends CqlRequestHandlerTestBase { @@ -132,8 +140,9 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { } @Test - public void should_reprepare_on_the_fly_if_not_prepared() { - ByteBuffer mockId = ByteBuffer.wrap(new byte[0]); + public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedException { + ByteBuffer mockId = Bytes.fromHexString("0xffff"); + PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class); Mockito.when(preparedStatement.getId()).thenReturn(mockId); BoundStatement boundStatement = Mockito.mock(BoundStatement.class); @@ -148,19 +157,24 @@ public void should_reprepare_on_the_fly_if_not_prepared() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + // The handler will look for the info to reprepare in the session's cache, put it there + ConcurrentMap repreparePayloads = new ConcurrentHashMap<>(); + repreparePayloads.put( + mockId, new RepreparePayload("mock query", null, Collections.emptyMap())); + Mockito.when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); + CompletionStage resultSetFuture = new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext(), "test") .asyncResult(); - node1Behavior.setWriteSuccess(); - // Before we proceed, mock the PREPARE exchange that will occur as soon as we complete the // first response. node1Behavior.mockFollowupRequest( - Prepare.class, defaultFrameOf(new Prepared(new byte[] {}, null, null))); + Prepare.class, defaultFrameOf(new Prepared(mockId.array(), null, null))); + node1Behavior.setWriteSuccess(); node1Behavior.setResponseSuccess( - defaultFrameOf(new Unprepared("mock message", new byte[] {}))); + defaultFrameOf(new Unprepared("mock message", mockId.array()))); // Should now re-prepare, re-execute and succeed. assertThat(resultSetFuture).isSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 79f87ec8140..0add8361118 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -21,7 +21,7 @@ import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.ChannelFuture; -import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.Promise; import java.util.concurrent.CompletableFuture; import org.mockito.Mockito; @@ -41,20 +41,18 @@ public class PoolBehavior { final Node node; - private final EventExecutor executor; final DriverChannel channel; private final Promise writePromise; private final CompletableFuture callbackFuture = new CompletableFuture<>(); - public PoolBehavior(Node node, boolean createChannel, EventExecutor executor) { + public PoolBehavior(Node node, boolean createChannel) { this.node = node; - this.executor = executor; if (!createChannel) { this.channel = null; this.writePromise = null; } else { this.channel = Mockito.mock(DriverChannel.class); - this.writePromise = executor.newPromise(); + this.writePromise = GlobalEventExecutor.INSTANCE.newPromise(); Mockito.when( channel.write( any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class))) @@ -96,7 +94,7 @@ public void setResponseFailure(Throwable cause) { /** Mocks a follow-up request on the same channel. */ public void mockFollowupRequest(Class expectedMessage, Frame responseFrame) { - Promise writePromise2 = executor.newPromise(); + Promise writePromise2 = GlobalEventExecutor.INSTANCE.newPromise(); CompletableFuture callbackFuture2 = new CompletableFuture<>(); Mockito.when( channel.write( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 9a17c1ae355..673598e8ed7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -28,8 +28,8 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.Frame; import io.netty.channel.EventLoopGroup; import java.time.Duration; @@ -45,8 +45,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; -import static org.mockito.ArgumentMatchers.any; - /** * Provides the environment to test a request handler, where a query plan can be defined, and the * behavior of each successive node simulated. @@ -57,7 +55,7 @@ public static Builder builder() { return new Builder(); } - private final ScheduledTaskCapturingEventLoop schedulingEventGroup; + private final ScheduledTaskCapturingEventLoop schedulingEventLoop; @Mock private InternalDriverContext context; @Mock private DefaultSession session; @@ -72,8 +70,8 @@ public static Builder builder() { private RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); - this.schedulingEventGroup = builder.schedulingEventLoop; - Mockito.when(eventLoopGroup.next()).thenReturn(schedulingEventGroup); + this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(eventLoopGroup); + Mockito.when(eventLoopGroup.next()).thenReturn(schedulingEventLoop); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); @@ -102,6 +100,7 @@ private RequestHandlerTestHarness(Builder builder) { Map pools = builder.buildMockPools(); Mockito.when(session.getPools()).thenReturn(pools); + Mockito.when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); } public DefaultSession getSession() { @@ -117,29 +116,24 @@ public InternalDriverContext getContext() { * run it manually. */ public ScheduledTaskCapturingEventLoop.CapturedTask nextScheduledTask() { - return schedulingEventGroup.nextTask(); + return schedulingEventLoop.nextTask(); } @Override public void close() { - schedulingEventGroup.shutdownGracefully().getNow(); + schedulingEventLoop.shutdownGracefully().getNow(); } public static class Builder { - private final ScheduledTaskCapturingEventLoop schedulingEventLoop; private final List poolBehaviors = new ArrayList<>(); private boolean defaultIdempotence; - public Builder() { - this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(null); - } - /** * Sets the given node as the next one in the query plan; an empty pool will be simulated when * it gets used. */ public Builder withEmptyPool(Node node) { - poolBehaviors.add(new PoolBehavior(node, false, schedulingEventLoop)); + poolBehaviors.add(new PoolBehavior(node, false)); return this; } @@ -148,7 +142,7 @@ public Builder withEmptyPool(Node node) { * simulated when it gets used. */ public Builder withWriteFailure(Node node, Throwable cause) { - PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + PoolBehavior behavior = new PoolBehavior(node, true); behavior.setWriteFailure(cause); poolBehaviors.add(behavior); return this; @@ -159,7 +153,7 @@ public Builder withWriteFailure(Node node, Throwable cause) { * but a response failure will be simulated immediately after. */ public Builder withResponseFailure(Node node, Throwable cause) { - PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + PoolBehavior behavior = new PoolBehavior(node, true); behavior.setWriteSuccess(); behavior.setResponseFailure(cause); poolBehaviors.add(behavior); @@ -171,7 +165,7 @@ public Builder withResponseFailure(Node node, Throwable cause) { * and the given response will be simulated immediately after. */ public Builder withResponse(Node node, Frame response) { - PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + PoolBehavior behavior = new PoolBehavior(node, true); behavior.setWriteSuccess(); behavior.setResponseSuccess(response); poolBehaviors.add(behavior); @@ -188,7 +182,7 @@ public Builder withDefaultIdempotence(boolean defaultIdempotence) { * calling the methods on the returned object to complete the write and the query. */ public PoolBehavior customBehavior(Node node) { - PoolBehavior behavior = new PoolBehavior(node, true, schedulingEventLoop); + PoolBehavior behavior = new PoolBehavior(node, true); poolBehaviors.add(behavior); return behavior; } From 5887fcadd72db66db1fb6e859c2578ba4e92a715 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 21 Jun 2017 16:58:14 -0700 Subject: [PATCH 086/742] Fix log message --- .../driver/internal/core/adminrequest/AdminRequestHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 98975eb4f13..23e41b324df 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -98,7 +98,7 @@ public CompletionStage start() { } public CompletionStage start(Map customPayload) { - LOG.debug("[{}] Executing {} on {}", logPrefix, this); + LOG.debug("[{}] Executing {}", logPrefix, this); channel.write(message, false, customPayload, this).addListener(this::onWriteComplete); return result; } From acbbff9c75192089991715b97b2af02e7aa5e447 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 21 Jun 2017 18:53:08 -0700 Subject: [PATCH 087/742] JAVA-1513: Handle batch statements --- changelog/README.md | 1 + .../driver/api/core/cql/BatchStatement.java | 95 +++++++++ .../oss/driver/api/core/cql/BatchType.java | 39 ++++ .../api/core/cql/BatchableStatement.java | 19 ++ .../driver/api/core/cql/BoundStatement.java | 3 +- .../driver/api/core/cql/SimpleStatement.java | 2 +- .../driver/internal/core/cql/Conversions.java | 62 +++++- .../core/cql/DefaultBatchStatement.java | 191 ++++++++++++++++++ 8 files changed, 403 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java diff --git a/changelog/README.md b/changelog/README.md index ef760c8ebca..700cf04173f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1513: Handle batch statements - [improvement] JAVA-1496: Improve log messages - [new feature] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response - [bug] JAVA-1499: Wait for load balancing policy at cluster initialization diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java new file mode 100644 index 00000000000..ae2aad9b762 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; +import java.nio.ByteBuffer; +import java.util.Map; + +/** + * A statement that groups a number of other statements, so that they can be executed as a batch + * (i.e. sent together as a single protocol frame). + * + *

    The default implementation returned by the driver is mutable and NOT + * thread-safe: methods that return {@code BatchStatement} mutate the statement and return the + * same instance (except {@link #copy(ByteBuffer)}); all methods should be called from the thread + * that created the statement; if you use {@link CqlSession#executeAsync(Statement)} asynchronous + * execution}, do not reuse the same instance for successive calls, as you run the risk of modifying + * the statement before the driver is done processing it. + */ +public interface BatchStatement extends Statement, Iterable { + + /** Creates an instance of the default implementation for the given batch type. */ + static BatchStatement newInstance(BatchType batchType) { + return new DefaultBatchStatement(batchType); + } + + BatchType getBatchType(); + + /** + * Adds a new statement to the batch. + * + *

    Note that, due to protocol limitations, simple statements with named values are currently + * not supported. + */ + BatchStatement add(BatchableStatement statement); + + default BatchStatement addAll(Iterable statements) { + BatchStatement result = this; + for (BatchableStatement statement : statements) { + result = result.add(statement); + } + return result; + } + + default BatchStatement addAll(BatchableStatement... statements) { + BatchStatement result = this; + for (BatchableStatement statement : statements) { + result = result.add(statement); + } + return result; + } + + int size(); + + /** Clears the batch, removing all the statements added so far. */ + BatchStatement clear(); + + /** + * Sets the name of the configuration profile to use. + * + *

    Note that this will be ignored if {@link #getConfigProfile()} return a non-null value. + */ + BatchStatement setConfigProfileName(String configProfileName); + + /** Sets the configuration profile to use. */ + DefaultBatchStatement setConfigProfile(DriverConfigProfile configProfile); + + /** Sets the custom payload to send alongside the request. */ + DefaultBatchStatement setCustomPayload(Map customPayload); + + /** + * Indicates whether the statement is idempotent. + * + * @param idempotent true or false, or {@code null} to use the default defined in the + * configuration. + */ + DefaultBatchStatement setIdempotent(Boolean idempotent); + + /** Request tracing information for this statement. */ + BatchStatement setTracing(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java new file mode 100644 index 00000000000..1e92fef8370 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +/** The type of a batch. */ +public enum BatchType { + /** + * A logged batch: Cassandra will first write the batch to its distributed batch log to ensure the + * atomicity of the batch (atomicity meaning that if any statement in the batch succeeds, all will + * eventually succeed). + */ + LOGGED, + + /** + * A batch that doesn't use Cassandra's distributed batch log. Such batch are not guaranteed to be + * atomic. + */ + UNLOGGED, + + /** + * A counter batch. Note that such batch is the only type that can contain counter operations and + * it can only contain these. + */ + COUNTER, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java new file mode 100644 index 00000000000..0983560df5d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +/** A statement that can be added to a CQL batch. */ +public interface BatchableStatement extends Statement {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index 378dd193668..06e20cee573 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.data.GettableByName; import com.datastax.oss.driver.api.core.data.SettableById; import com.datastax.oss.driver.api.core.data.SettableByName; -import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; @@ -40,7 +39,7 @@ * */ public interface BoundStatement - extends Statement, + extends BatchableStatement, GettableById, GettableByName, SettableById, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 187768d520f..289d998251d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -39,7 +39,7 @@ * thread-safe. If an application is going to reuse the same statement more than once, it is * recommended to cache it (for example in a final field). */ -public interface SimpleStatement extends Statement { +public interface SimpleStatement extends BatchableStatement { /** * The CQL query to execute. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 313b6a6854f..5747c1cd06e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -21,12 +21,14 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.BatchStatement; +import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.BatchableStatement; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -52,8 +54,8 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Batch; import com.datastax.oss.protocol.internal.request.Execute; -import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.request.query.QueryOptions; import com.datastax.oss.protocol.internal.response.Error; @@ -92,6 +94,8 @@ static Message toMessage( int serialConsistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); long timestamp = Long.MIN_VALUE; // TODO timestamp generator + CodecRegistry codecRegistry = context.codecRegistry(); + ProtocolVersion protocolVersion = context.protocolVersion(); if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; @@ -100,8 +104,6 @@ static Message toMessage( throw new IllegalArgumentException( "Can't have both positional and named values in a statement."); } - CodecRegistry codecRegistry = context.codecRegistry(); - ProtocolVersion protocolVersion = context.protocolVersion(); QueryOptions queryOptions = new QueryOptions( consistency, @@ -127,9 +129,44 @@ static Message toMessage( timestamp); ByteBuffer id = boundStatement.getPreparedStatement().getId(); return new Execute(Bytes.getArray(id), queryOptions); + } else if (statement instanceof BatchStatement) { + BatchStatement batchStatement = (BatchStatement) statement; + List queriesOrIds = new ArrayList<>(batchStatement.size()); + List> values = new ArrayList<>(batchStatement.size()); + for (BatchableStatement child : batchStatement) { + if (child instanceof SimpleStatement) { + SimpleStatement simpleStatement = (SimpleStatement) child; + if (simpleStatement.getNamedValues().size() > 0) { + // We already check that in DefaultBatchStatement.add, but we could receive a custom + // implementation + throw new IllegalArgumentException( + String.format( + "Batch statements cannot contain simple statements with named values " + + "(offending statement: %s)", + simpleStatement.getQuery())); + } + queriesOrIds.add(simpleStatement.getQuery()); + values.add(encode(simpleStatement.getPositionalValues(), codecRegistry, protocolVersion)); + } else if (child instanceof BoundStatement) { + BoundStatement boundStatement = (BoundStatement) child; + queriesOrIds.add(Bytes.getArray(boundStatement.getPreparedStatement().getId())); + values.add(boundStatement.getValues()); + } else { + throw new IllegalArgumentException( + "Unsupported child statement: " + child.getClass().getName()); + } + } + return new Batch( + toProtocol(batchStatement.getBatchType()), + queriesOrIds, + values, + consistency, + serialConsistency, + timestamp); + } else { + throw new IllegalArgumentException( + "Unsupported statement type: " + statement.getClass().getName()); } - // TODO handle other types of statements - throw new UnsupportedOperationException("TODO handle other types of statements"); } private static List encode( @@ -285,4 +322,17 @@ static Throwable toThrowable(Node node, Error errorMessage) { return new ProtocolError(node, "Unknown error code: " + errorMessage.code); } } + + private static byte toProtocol(BatchType batchType) { + switch (batchType) { + case LOGGED: + return ProtocolConstants.BatchType.LOGGED; + case UNLOGGED: + return ProtocolConstants.BatchType.UNLOGGED; + case COUNTER: + return ProtocolConstants.BatchType.COUNTER; + default: + throw new IllegalArgumentException("Unsupported batch type: " + batchType); + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java new file mode 100644 index 00000000000..9e2b6ea15d8 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.BatchStatement; +import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.BatchableStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class DefaultBatchStatement implements BatchStatement { + + private final BatchType batchType; + private final List statements; + private String configProfileName; + private DriverConfigProfile configProfile; + private final String keyspace; + private Map customPayload; + private Boolean idempotent; + private boolean tracing; + private ByteBuffer pagingState; + + public DefaultBatchStatement(BatchType batchType) { + this( + batchType, new ArrayList<>(), null, null, null, Collections.emptyMap(), false, false, null); + } + + private DefaultBatchStatement( + BatchType batchType, + List statements, + String configProfileName, + DriverConfigProfile configProfile, + String keyspace, + Map customPayload, + Boolean idempotent, + boolean tracing, + ByteBuffer pagingState) { + this.batchType = batchType; + this.statements = statements; + this.configProfileName = configProfileName; + this.configProfile = configProfile; + this.keyspace = keyspace; + this.customPayload = customPayload; + this.idempotent = idempotent; + this.tracing = tracing; + this.pagingState = pagingState; + } + + @Override + public BatchType getBatchType() { + return batchType; + } + + @Override + public BatchStatement add(BatchableStatement statement) { + if (statements.size() >= 0xFFFF) { + throw new IllegalStateException( + "Batch statement cannot contain more than " + 0xFFFF + " statements."); + } else if (statement instanceof SimpleStatement + && ((SimpleStatement) statement).getNamedValues().size() > 0) { + throw new IllegalArgumentException( + String.format( + "Batch statements cannot contain simple statements with named values " + + "(offending statement: %s)", + ((SimpleStatement) statement).getQuery())); + } else { + statements.add(statement); + return this; + } + } + + @Override + public int size() { + return statements.size(); + } + + @Override + public BatchStatement clear() { + statements.clear(); + return this; + } + + @Override + public Iterator iterator() { + return statements.iterator(); + } + + @Override + public ByteBuffer getPagingState() { + return pagingState; + } + + @Override + public BatchStatement copy(ByteBuffer newPagingState) { + return new DefaultBatchStatement( + batchType, + new ArrayList<>(statements), + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + newPagingState); + } + + @Override + public String getConfigProfileName() { + return configProfileName; + } + + @Override + public BatchStatement setConfigProfileName(String configProfileName) { + this.configProfileName = configProfileName; + return this; + } + + @Override + public DriverConfigProfile getConfigProfile() { + return configProfile; + } + + @Override + public DefaultBatchStatement setConfigProfile(DriverConfigProfile configProfile) { + this.configProfile = configProfile; + return this; + } + + @Override + public String getKeyspace() { + return keyspace; + } + + public DefaultBatchStatement setKeyspace(@SuppressWarnings("unused") String keyspace) { + throw new UnsupportedOperationException( + "Per-statement keyspaces are not supported currently, " + + "this feature will be available in Cassandra 4"); + } + + @Override + public Map getCustomPayload() { + return customPayload; + } + + @Override + public DefaultBatchStatement setCustomPayload(Map customPayload) { + this.customPayload = customPayload; + return this; + } + + @Override + public Boolean isIdempotent() { + return idempotent; + } + + @Override + public DefaultBatchStatement setIdempotent(Boolean idempotent) { + this.idempotent = idempotent; + return this; + } + + @Override + public boolean isTracing() { + return tracing; + } + + @Override + public BatchStatement setTracing() { + this.tracing = true; + return this; + } +} From d9514a00b1ef3cf6966beab457ff8e04c5197832 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 09:52:45 -0700 Subject: [PATCH 088/742] Add builder method for a single contact point Mostly for nicer examples in the manual. --- .../oss/driver/api/core/ClusterBuilder.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index d17f5fbb5ef..090e3fc43cd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -28,7 +28,9 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.typesafe.config.ConfigFactory; import java.net.InetSocketAddress; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; @@ -37,7 +39,7 @@ /** Helper class to build an instance of the default {@link Cluster} implementation. */ public class ClusterBuilder { private DriverConfig config; - private Set programmaticContactPoints = Collections.emptySet(); + private Set programmaticContactPoints = new HashSet<>(); private List> typeCodecs = Collections.emptyList(); /** @@ -79,7 +81,7 @@ private static DriverConfig defaultConfig() { } /** - * Sets the contact points to use for the initial connection to the cluster. + * Adds contact points to use for the initial connection to the cluster. * *

    These are addresses of Cassandra nodes that the driver uses to discover the cluster * topology. Only one contact point is required (the driver will retrieve the address of the other @@ -94,8 +96,18 @@ private static DriverConfig defaultConfig() { * If you need that, call {@link java.net.InetAddress#getAllByName(String)} before calling this * method. */ - public ClusterBuilder withContactPoints(Set contactPoints) { - this.programmaticContactPoints = contactPoints; + public ClusterBuilder addContactPoints(Collection contactPoints) { + this.programmaticContactPoints.addAll(contactPoints); + return this; + } + + /** + * Adds a contact point to use for the initial connection to the cluster. + * + * @see #addContactPoints(Collection) + */ + public ClusterBuilder addContactPoint(InetSocketAddress contactPoint) { + this.programmaticContactPoints.add(contactPoint); return this; } From 6e099d84b4ac4b599b33abd67ca73f0d9fbb4130 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 14:40:52 -0700 Subject: [PATCH 089/742] Bootstrap documentation --- .gitignore | 1 + README.md | 66 +++++++++++++++++++++++++++++ docs.yaml | 41 ++++++++++++++++++ manual/README.md | 3 ++ manual/api_conventions/README.md | 19 +++++++++ manual/core/README.md | 48 +++++++++++++++++++++ upgrade_guide/README.md | 71 ++++++++++++++++++++++++++++++++ 7 files changed, 249 insertions(+) create mode 100644 README.md create mode 100644 docs.yaml create mode 100644 manual/README.md create mode 100644 manual/api_conventions/README.md create mode 100644 manual/core/README.md create mode 100644 upgrade_guide/README.md diff --git a/.gitignore b/.gitignore index be80b5f6688..ec7be4c3a93 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ .java-version +/docs target/ dependency-reduced-pom.xml diff --git a/README.md b/README.md new file mode 100644 index 00000000000..29fd453d15c --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Datastax Java Driver for Apache Cassandra™ + +*If you're reading this on github.com, please note that this is the readme for the development +version and that some features described here might not yet have been released. You can find the +documentation for latest version through [DataStax Docs] or via the release tags, e.g. +[4.0.0-alpha1](https://github.com/datastax/java-driver/tree/4.0.0-alpha1).* + +A modern, feature-rich and highly tunable Java client library for [Apache Cassandra™] (2.1+) and +[DataStax Enterprise] (4.7+), using exclusively Cassandra's binary protocol and Cassandra Query +Language v3. + +[DataStax Docs]: http://docs.datastax.com/en/developer/java-driver/ +[Apache Cassandra]: http://cassandra.apache.org/ +[DataStax Enterprise]: http://www.datastax.com/products/datastax-enterprise + +## Getting the driver + +The driver artifacts are published in Maven central, under the group id [com.datastax.oss]; there +are multiple modules, all prefixed with `java-driver-`. Refer to the [manual] for more details. + +[com.datastax.oss]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.datastax.oss%22 +[manual]: manual/ + +## Useful links + +* [Manual][manual] +* [API docs] +* Bug tracking: [JIRA] +* [Mailing list] +* Twitter: [@dsJavaDriver] tweets Java driver releases and important announcements (low frequency). + [@DataStaxEng] has more news, including other drivers, Cassandra, and DSE. +* [Changelog] +* [Upgrade guide] + +[API docs]: http://www.datastax.com/drivers/java/4.0 +[JIRA]: https://datastax-oss.atlassian.net/browse/JAVA +[Mailing list]: https://groups.google.com/a/lists.datastax.com/forum/#!forum/java-driver-user +[@dsJavaDriver]: https://twitter.com/dsJavaDriver +[@DataStaxEng]: https://twitter.com/datastaxeng +[Changelog]: changelog/ +[Upgrade guide]: upgrade_guide/ + +## License + +Copyright 2017, DataStax + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +---- + +DataStax is a registered trademark of DataStax, Inc. and its subsidiaries in the United States +and/or other countries. + +Apache Cassandra, Apache, Tomcat, Lucene, Solr, Hadoop, Spark, TinkerPop, and Cassandra are +trademarks of the [Apache Software Foundation](http://www.apache.org/) or its subsidiaries in +Canada, the United States and/or other countries. \ No newline at end of file diff --git a/docs.yaml b/docs.yaml new file mode 100644 index 00000000000..b7b6a03fa75 --- /dev/null +++ b/docs.yaml @@ -0,0 +1,41 @@ +title: Java Driver for Apache Cassandra +summary: High performance Java client for Apache Cassandra +homepage: http://docs.datastax.com/en/developer/java-driver +theme: datastax +sections: + - title: Manual + prefix: /manual + sources: + - type: markdown + files: 'manual/**/*.md' + - title: Changelog + prefix: /changelog + sources: + - type: markdown + files: 'changelog/**/*.md' + - title: Upgrading + prefix: /upgrade_guide + sources: + - type: markdown + files: 'upgrade_guide/**/*.md' + - title: FAQ + prefix: /faq + sources: + - type: markdown + files: 'faq/**/*.md' +links: + - title: Code + href: https://github.com/datastax/java-driver/ + - title: Docs + href: http://docs.datastax.com/en/developer/java-driver + - title: Issues + href: https://datastax-oss.atlassian.net/browse/JAVA/ + - title: Mailing List + href: https://groups.google.com/a/lists.datastax.com/forum/#!forum/java-driver-user + - title: Releases + href: https://github.com/datastax/java-driver/releases +api_docs: + 4.0: http://docs.datastax.com/en/drivers/java/4.0 +versions: + - name: '4.0' + ref: '4.x' diff --git a/manual/README.md b/manual/README.md new file mode 100644 index 00000000000..46ce849ab35 --- /dev/null +++ b/manual/README.md @@ -0,0 +1,3 @@ +## Manual + +* [Core driver](core/) \ No newline at end of file diff --git a/manual/api_conventions/README.md b/manual/api_conventions/README.md new file mode 100644 index 00000000000..f12a7a9d742 --- /dev/null +++ b/manual/api_conventions/README.md @@ -0,0 +1,19 @@ +## API conventions + +In previous versions, the driver relied solely on Java visibility rules: everything was either +private or part of the public API. This made it hard to cleanly organize the code, and things ended +up all together in one monolithic package; it also created a dilemma between providing useful hooks +for advanced users, or keeping them hidden to limit the API surface. + +Starting with 4.0, we adopt a package naming convention to address those issues: + +* Everything under `com.datastax.oss.driver.api` is part of the "official" public API of the driver, + that can be used by regular client applications to execute queries. It follows + [semantic versioning] and binary compatibility is guaranteed across minor and patch versions. +* Everything under `com.datastax.oss.driver.internal` is the "internal" API, primarily used by + driver components to communicate with each other. It also exposes hooks for expert tweaking or + framework implementors. We'll do our best to also keep that API stable, but full compatibility is + not strictly guaranteed. + + +[semantic versioning]: http://semver.org/ \ No newline at end of file diff --git a/manual/core/README.md b/manual/core/README.md new file mode 100644 index 00000000000..7638835797b --- /dev/null +++ b/manual/core/README.md @@ -0,0 +1,48 @@ +## Core driver + +The core module handles cluster connectivity and request execution. It is published under the +following coordinates: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-alpha1 + +``` + +### Quick start + +Here's a short program that connects to Cassandra and executes a query: + +```java +try (Cluster cluster = + Cluster.builder().addContactPoint(new InetSocketAddress("127.0.0.1", 9042)).build()) { // (1) + + CqlSession session = cluster.connect(); // (2) + + ResultSet rs = session.execute("select release_version from system.local"); // (3) + Row row = rs.iterator().next(); + System.out.println(row.getString("release_version")); // (4) +} +``` + +1. the [Cluster] is the main entry point of the driver. It holds the known state of the actual + Cassandra cluster. It is thread-safe, you should create a single instance (per target Cassandra + cluster), and share it throughout your application; +2. the [Session] is what you use to execute queries. Likewise, it is thread-safe and should be + reused; +3. we use `execute` to send a query to Cassandra. This returns a [ResultSet], which is an iterable + of [Row] objects. On the next line, we extract the first row (which is the only one in this case); +4. we extract the value of the first (and only) column from the row. + +Always close the `Cluster` once you're done with it, in order to free underlying resources (TCP +connections, thread pools...). Closing a `Cluster` also closes any `Session` that was created from +it. In this simple example, we can use a try-with-resources block because `Cluster` implements +`java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods +(`close`, `closeAsync`, `forceCloseAsync`) explicitly. + +[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html +[Session]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html +[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html +[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md new file mode 100644 index 00000000000..7a4dbf24d57 --- /dev/null +++ b/upgrade_guide/README.md @@ -0,0 +1,71 @@ +## Upgrade guide + +### 3.x to 4.0.0 + +Java driver 4 is **not binary compatible** with previous versions. However, most of the concepts +remain unchanged, and the new API will look very familiar to 2.x and 3.x users. + +#### Runtime requirements + +The driver now requires Java 8. It does not depend on Guava anymore (we still use it internally but +it's shaded). + +#### Packages + +The root package names have changed. There are also more sub-packages than previously. See [API +conventions] for important information about the naming conventions. + +Generally, the public types have kept the same name, so you can use the "find class" feature in your +IDE to find out the new locations. + +[API conventions]: ../manual/api_conventions + +#### Expose interfaces, not classes + +Most types in the public API are now interfaces (as opposed to 3.x: `Cluster`, statement classes, +etc). The actual implementations are part of the internal API. This provides more flexibility in +client code (e.g. to wrap them and write delegates). + +Thanks to Java 8, factory methods can now be part of these interfaces directly, e.g. +`Cluster.builder()`, `SimpleStatement.newInstance`. + +#### Generic session API + +`Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, +the driver supports the same CQL queries as 3.x, and exposes familiar signatures +(`execute(Statement)`, `prepare(String)`, etc). + +However, the request execution logic is completely pluggable, and supports arbitrary request types +as long as you write the boilerplate to convert them to protocol messages. In the future, we will +take advantage of that to provide: + +* a reactive API; +* a high-performance implementation that exposes bare Netty buffers; +* specialized requests in our DataStax Enterprise driver. + +If you're interested, take a look at `RequestProcessor`. + +#### Dual result set APIs + +In 3.x, both synchronous and asynchronous execution models shared a common result set +implementation. This made asynchronous usage [notably error-prone][3.x async paging], because of the +risk of accidentally triggering background synchronous fetches. + +There are now two separate APIs: synchronous queries return a `ResultSet` which behaves like its 3.x +counterpart; asynchronous queries return a future of `AsyncResultSet`, a simplified type that only +contains the rows of the current page. When iterating asynchronously, you no longer need to stop the +iteration manually: just consume all the rows in the iterator, and then call `fetchNextPage` to +retrieve the next page asynchronously. + +[3.x async paging]: http://docs.datastax.com/en/developer/java-driver/3.2/manual/async/#async-paging + +#### Dedicated type for CQL identifiers + +Instead of raw strings, the names of schema objects (keyspaces, tables, columns, etc.) are now +wrapped in a dedicated `CqlIdentifier` type. This avoids ambiguities with regard to case +sensitivity. + +For example, this type is used in schema metadata or when creating a session connected to a specific +keyspace. When manipulating "data containers" such as rows, UDTs and tuples, columns can also be +referenced by a `CqlIdentifier`; however, we've also kept a raw string variant for convenience, with +the same rules as in 3.x (see `GettableById` and `GettableByName` for details). \ No newline at end of file From 6c1fee6f0b0ca8674116a70ba6d2b78f22b2a5b2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 15:02:18 -0700 Subject: [PATCH 090/742] Fix link title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29fd453d15c..990179bee03 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A modern, feature-rich and highly tunable Java client library for [Apache Cassan Language v3. [DataStax Docs]: http://docs.datastax.com/en/developer/java-driver/ -[Apache Cassandra]: http://cassandra.apache.org/ +[Apache Cassandra™]: http://cassandra.apache.org/ [DataStax Enterprise]: http://www.datastax.com/products/datastax-enterprise ## Getting the driver From 97cf3856e9a35f82d514b4d3838aec6e055f5b89 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 15:03:04 -0700 Subject: [PATCH 091/742] Add trademarks in doc title and summary --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index b7b6a03fa75..70801e678bd 100644 --- a/docs.yaml +++ b/docs.yaml @@ -1,5 +1,5 @@ -title: Java Driver for Apache Cassandra -summary: High performance Java client for Apache Cassandra +title: Java Driver for Apache Cassandra™ +summary: High performance Java client for Apache Cassandra™ homepage: http://docs.datastax.com/en/developer/java-driver theme: datastax sections: From 76aae9954ff167013007d01aeabef7fa04ebaccf Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 16:08:23 -0700 Subject: [PATCH 092/742] JAVA-1531: Merge CqlSession and Session The CQL-specific overloads of `execute` used to be defined in a sub-interface, which in retrospect was more confusing than helpful. --- changelog/README.md | 1 + .../datastax/oss/driver/api/core/Cluster.java | 10 +- .../driver/api/core/cql/AsyncResultSet.java | 5 +- .../driver/api/core/cql/BatchStatement.java | 3 +- .../driver/api/core/cql/BoundStatement.java | 3 +- .../oss/driver/api/core/cql/CqlSession.java | 58 ------------ .../driver/api/core/cql/PrepareRequest.java | 5 +- .../api/core/cql/PreparedStatement.java | 7 +- .../oss/driver/api/core/cql/ResultSet.java | 5 +- .../driver/api/core/cql/SimpleStatement.java | 3 +- .../oss/driver/api/core/session/Session.java | 94 ++++++++++++++++++- .../driver/internal/core/DefaultCluster.java | 7 +- .../internal/core/session/DefaultSession.java | 10 +- .../core/session/DefaultSessionTest.java | 62 ++++++------ manual/core/README.md | 2 +- 15 files changed, 156 insertions(+), 119 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java diff --git a/changelog/README.md b/changelog/README.md index 700cf04173f..c614e8be0d6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1531: Merge CqlSession and Session - [new feature] JAVA-1513: Handle batch statements - [improvement] JAVA-1496: Improve log messages - [new feature] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index d2325d3062f..043fa50220a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -17,9 +17,9 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.concurrent.CompletionStage; @@ -60,14 +60,14 @@ static ClusterBuilder builder() { DriverContext getContext(); /** Creates a new session to execute requests against a given keyspace. */ - CompletionStage connectAsync(CqlIdentifier keyspace); + CompletionStage connectAsync(CqlIdentifier keyspace); /** * Creates a new session not tied to any keyspace. * *

    This is equivalent to {@code this.connectAsync(null)}. */ - default CompletionStage connectAsync() { + default CompletionStage connectAsync() { return connectAsync(null); } @@ -76,7 +76,7 @@ default CompletionStage connectAsync() { * *

    This must not be called on a driver thread. */ - default CqlSession connect(CqlIdentifier keyspace) { + default Session connect(CqlIdentifier keyspace) { BlockingOperation.checkNotDriverThread(); return CompletableFutures.getUninterruptibly(connectAsync(keyspace)); } @@ -86,7 +86,7 @@ default CqlSession connect(CqlIdentifier keyspace) { * *

    This must not be called on a driver thread. */ - default CqlSession connect() { + default Session connect() { return connect(null); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 99b82fa231b..b948686cf4a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; /** @@ -26,8 +27,8 @@ *

    Note that this object can only be iterated once: rows are "consumed" as they are read, * subsequent calls to {@code iterator()} will return an empty iterator. * - * @see CqlSession#executeAsync(Statement) - * @see CqlSession#executeAsync(String) + * @see Session#executeAsync(Statement) + * @see Session#executeAsync(String) */ public interface AsyncResultSet extends Iterable { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index ae2aad9b762..b5d94a428b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; import java.nio.ByteBuffer; import java.util.Map; @@ -27,7 +28,7 @@ *

    The default implementation returned by the driver is mutable and NOT * thread-safe: methods that return {@code BatchStatement} mutate the statement and return the * same instance (except {@link #copy(ByteBuffer)}); all methods should be called from the thread - * that created the statement; if you use {@link CqlSession#executeAsync(Statement)} asynchronous + * that created the statement; if you use {@link Session#executeAsync(Statement)} asynchronous * execution}, do not reuse the same instance for successive calls, as you run the risk of modifying * the statement before the driver is done processing it. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index 06e20cee573..a140a487940 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.data.GettableByName; import com.datastax.oss.driver.api.core.data.SettableById; import com.datastax.oss.driver.api.core.data.SettableByName; +import com.datastax.oss.driver.api.core.session.Session; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; @@ -31,7 +32,7 @@ * *

      *
    • not thread-safe: all methods (setting values, etc.) must be called from the thread that - * created the instance. Besides, if you use {@link CqlSession#executeAsync(Statement)} + * created the instance. Besides, if you use {@link Session#executeAsync(Statement)} * asynchronous execution}, do not reuse the same instance for multiple calls, as you run the * risk of modifying the statement while the driver internals are still processing it. *
    • mutable: all setters that return a {@code BoundStatement} modify and return the same diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java deleted file mode 100644 index 9dedb6d8eb2..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.api.core.cql; - -import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.api.core.session.Session; -import java.util.concurrent.CompletionStage; -import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; - -public interface CqlSession extends Session { - - // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion - default ResultSet execute(Statement statement) { - return execute((Request>) statement); - } - - // Not strictly needed, but it shows up as a more user-friendly signature in IDE completion - default CompletionStage executeAsync(Statement statement) { - return executeAsync((Request>) statement); - } - - default ResultSet execute(String query) { - return execute(SimpleStatement.newInstance(query)); - } - - default CompletionStage executeAsync(String query) { - return executeAsync(SimpleStatement.newInstance(query)); - } - - default PreparedStatement prepare(String query) { - return execute(new DefaultPrepareRequest(query)); - } - - default PreparedStatement prepare(SimpleStatement query) { - return execute(new DefaultPrepareRequest(query)); - } - - default CompletionStage prepareAsync(String query) { - return executeAsync(new DefaultPrepareRequest(query)); - } - - default CompletionStage prepareAsync(SimpleStatement query) { - return executeAsync(new DefaultPrepareRequest(query)); - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 5994f7b789c..5883cf696bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -26,8 +27,8 @@ * A request to prepare a CQL query. * *

      Driver clients should rarely have to deal directly with this type, it's used internally by - * {@link CqlSession}'s prepare methods. However a {@link RetryPolicy} implementation might use it - * if it needs a custom behavior for prepare requests. + * {@link Session}'s prepare methods. However a {@link RetryPolicy} implementation might use it if + * it needs a custom behavior for prepare requests. */ public interface PrepareRequest extends Request> { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 2412be6acb5..0ec1c842a8c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -15,14 +15,15 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.session.Session; import java.nio.ByteBuffer; /** * A query with bind variables that has been pre-parsed by the database. * - *

      Client applications create instances with {@link CqlSession#prepare(SimpleStatement)}. Then - * they use {@link #bind()} to obtain a {@link BoundStatement}, an executable instance that - * associates a set of values with the bind variables. + *

      Client applications create instances with {@link Session#prepare(SimpleStatement)}. Then they + * use {@link #bind()} to obtain a {@link BoundStatement}, an executable instance that associates a + * set of values with the bind variables. * *

      The default prepared statement implementation returned by the driver is thread-safe. * Client applications can -- and are expected to -- prepare each query once and store the result in diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 2f552862127..33d4b6d84ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.session.Session; import java.util.List; /** @@ -31,8 +32,8 @@ *

      Implementations of this type are not thread-safe. They can only be iterated by the * thread that invoked {@code session.execute}. * - * @see CqlSession#execute(Statement) - * @see CqlSession#execute(String) + * @see Session#execute(Statement) + * @see Session#execute(String) */ public interface ResultSet extends Iterable { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 289d998251d..c478a62b987 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import java.util.Arrays; import java.util.Collections; @@ -27,7 +28,7 @@ * *

      To create instances, client applications can use the {@code newInstance} factory methods on * this interface for common cases, or {@link #builder(String)} for more control over the - * parameters. They can then be passed to {@link CqlSession#execute(Statement)}. + * parameters. They can then be passed to {@link Session#execute(Statement)}. * *

      Simple statements should be reserved for queries that will only be executed a few times by an * application. For more frequent queries, {@link PreparedStatement} provides many advantages: it is diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 166e8e82f78..4c4e8d235c0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -18,14 +18,21 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; +import java.util.concurrent.CompletionStage; /** * A nexus to send requests to a Cassandra cluster. * *

      This is a high-level abstraction that can handle any kind of request (provided that you have - * registered a custom request processor with the driver). For regular CQL queries, see {@link - * CqlSession}. + * registered a custom request processor with the driver). However, for user-friendliness, we also + * expose overrides for the standard CQL requests that are supported out of the box. */ public interface Session extends AsyncAutoCloseable { @@ -63,4 +70,85 @@ public interface Session extends AsyncAutoCloseable { * implementation-specific). */ AsyncResultT executeAsync(Request request); + + /** + * Executes a CQL statement synchronously. + * + *

      This is a convenience method that does the exact same thing as {@link #execute(Request)}, + * but exposes a more user-friendly signature reminiscent of the 3.x API. + */ + default ResultSet execute(Statement statement) { + return execute((Request>) statement); + } + + /** + * Executes a CQL statement synchronously. + * + *

      This is a convenience method that builds a {@link SimpleStatement#newInstance(String) + * SimpleStatement} and passes it to {@link #execute(Request)}. + */ + default ResultSet execute(String query) { + return execute(SimpleStatement.newInstance(query)); + } + + /** + * Executes a CQL statement asynchronously. + * + *

      This is a convenience method that does the exact same thing as {@link + * #executeAsync(Statement)}, but exposes a more user-friendly signature reminiscent of the 3.x + * API. + */ + default CompletionStage executeAsync(Statement statement) { + return executeAsync((Request>) statement); + } + + /** + * Executes a CQL statement asynchronously. + * + *

      This is a convenience method that builds a {@link SimpleStatement#newInstance(String) + * SimpleStatement} and passes it to {@link #executeAsync(Statement)}. + */ + default CompletionStage executeAsync(String query) { + return executeAsync(SimpleStatement.newInstance(query)); + } + + /** + * Prepares a CQL statement synchronously. + * + *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link + * #execute(Request)}. + */ + default PreparedStatement prepare(SimpleStatement query) { + return execute(new DefaultPrepareRequest(query)); + } + + /** + * Prepares a CQL statement synchronously. + * + *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link + * #execute(Request)}. + */ + default PreparedStatement prepare(String query) { + return execute(new DefaultPrepareRequest(query)); + } + + /** + * Prepares a CQL statement asynchronously. + * + *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link + * #executeAsync(Request)}. + */ + default CompletionStage prepareAsync(String query) { + return executeAsync(new DefaultPrepareRequest(query)); + } + + /** + * Prepares a CQL statement asynchronously. + * + *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link + * #executeAsync(Request)}. + */ + default CompletionStage prepareAsync(SimpleStatement query) { + return executeAsync(new DefaultPrepareRequest(query)); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 7b9bd25fbc7..e91ee85693c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -86,8 +85,8 @@ public DriverContext getContext() { } @Override - public CompletionStage connectAsync(CqlIdentifier keyspace) { - CompletableFuture connectFuture = new CompletableFuture<>(); + public CompletionStage connectAsync(CqlIdentifier keyspace) { + CompletableFuture connectFuture = new CompletableFuture<>(); RunOrSchedule.on(adminExecutor, () -> singleThreaded.connect(keyspace, connectFuture)); return connectFuture; } @@ -164,7 +163,7 @@ private void init() { }); } - private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { + private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { connectFuture.completeExceptionally(new IllegalStateException("Cluster was closed")); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f30354a4638..f4447ab560e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -19,12 +19,12 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; @@ -66,11 +66,11 @@ *

    • trying to send the message on each pool, in the order of the query plan *
    */ -public class DefaultSession implements CqlSession { +public class DefaultSession implements Session { private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); - public static CompletionStage init( + public static CompletionStage init( InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { return new DefaultSession(context, keyspace, logPrefix).init(); } @@ -111,7 +111,7 @@ private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace, St this.logPrefix = logPrefix; } - private CompletionStage init() { + private CompletionStage init() { RunOrSchedule.on(adminExecutor, singleThreaded::init); return singleThreaded.initFuture; } @@ -196,7 +196,7 @@ private class SingleThreaded { private final InternalDriverContext context; private final ChannelPoolFactory channelPoolFactory; - private final CompletableFuture initFuture = new CompletableFuture<>(); + private final CompletableFuture initFuture = new CompletableFuture<>(); private boolean initWasCalled; private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 79c316a0173..a0c99c2679d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -19,11 +19,11 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -120,7 +120,7 @@ public void should_initialize_pools_with_distances() { .pending(node3, KEYSPACE, NodeDistance.REMOTE, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -154,7 +154,7 @@ public void should_not_connect_to_ignored_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -178,7 +178,7 @@ public void should_not_connect_to_forced_down_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -204,7 +204,7 @@ public void should_adjust_distance_if_changed_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -245,7 +245,7 @@ public void should_remove_pool_if_ignored_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -285,7 +285,7 @@ public void should_remove_pool_if_forced_down_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -322,7 +322,7 @@ public void should_resize_pool_if_distance_changes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -346,7 +346,7 @@ public void should_remove_pool_if_node_becomes_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -357,7 +357,7 @@ public void should_remove_pool_if_node_becomes_ignored() { eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); Mockito.verify(pool2, timeout(100)).closeAsync(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @@ -377,13 +377,13 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -405,7 +405,7 @@ public void should_remove_pool_if_node_is_forced_down() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -416,7 +416,7 @@ public void should_remove_pool_if_node_is_forced_down() { eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); Mockito.verify(pool2, timeout(100)).closeAsync(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @@ -436,13 +436,13 @@ public void should_recreate_pool_if_node_is_forced_back_up() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); @@ -468,13 +468,13 @@ public void should_adjust_distance_if_changed_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -511,13 +511,13 @@ public void should_remove_pool_if_ignored_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -554,13 +554,13 @@ public void should_remove_pool_if_forced_down_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); eventBus.fire(new DistanceEvent(NodeDistance.LOCAL, node2)); @@ -592,14 +592,14 @@ public void should_close_all_pools_when_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CompletionStage closeFuture = session.closeAsync(); waitForPendingAdminTasks(); @@ -622,14 +622,14 @@ public void should_force_close_all_pools_when_force_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CompletionStage closeFuture = session.forceCloseAsync(); waitForPendingAdminTasks(); @@ -657,13 +657,13 @@ public void should_close_pool_if_recreated_while_closing() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); // node2 comes back up, start initializing a pool for it @@ -695,14 +695,14 @@ public void should_set_keyspace_on_all_pools() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); - CqlSession session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CqlIdentifier newKeyspace = CqlIdentifier.fromInternal("newKeyspace"); ((DefaultSession) session).setKeyspace(newKeyspace); @@ -730,7 +730,7 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); diff --git a/manual/core/README.md b/manual/core/README.md index 7638835797b..9972b559bb8 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -19,7 +19,7 @@ Here's a short program that connects to Cassandra and executes a query: try (Cluster cluster = Cluster.builder().addContactPoint(new InetSocketAddress("127.0.0.1", 9042)).build()) { // (1) - CqlSession session = cluster.connect(); // (2) + Session session = cluster.connect(); // (2) ResultSet rs = session.execute("select release_version from system.local"); // (3) Row row = rs.iterator().next(); From b5f6842a6a2fe478846f0f689509f497d0f9f24b Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Jun 2017 17:26:48 -0700 Subject: [PATCH 093/742] JAVA-1530: Add ResultSet.wasApplied --- changelog/README.md | 1 + .../driver/api/core/cql/AsyncResultSet.java | 23 +++++ .../oss/driver/api/core/cql/ResultSet.java | 23 +++++ .../core/cql/DefaultAsyncResultSet.java | 23 +++++ .../internal/core/cql/MultiPageResultSet.java | 18 +++- .../core/cql/SinglePageResultSet.java | 5 + .../internal/core/util/CountingIterator.java | 7 ++ .../core/cql/DefaultAsyncResultSetTest.java | 99 +++++++++++++++++++ 8 files changed, 198 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index c614e8be0d6..70220e5d926 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1530: Add ResultSet.wasApplied - [improvement] JAVA-1531: Merge CqlSession and Session - [new feature] JAVA-1513: Handle batch statements - [improvement] JAVA-1496: Improve log messages diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index b948686cf4a..2e67cd981c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -52,4 +52,27 @@ public interface AsyncResultSet extends Iterable { * if you can call this method. */ CompletionStage fetchNextPage() throws IllegalStateException; + + /** + * If the query that produced this result was a conditional update, indicate whether it was + * successfully applied. + * + *

    This is equivalent to calling: + * + *

    +   *   this.iterator().next().getBoolean("[applied]")
    +   * 
    + * + * Except that this method peeks at the next row without consuming it. + * + *

    For consistency, this method always returns {@code true} for non-conditional queries + * (although there is no reason to call the method in that case). This is also the case for + * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF + * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. + * + *

    Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this + * method to always return {@code true} for batches containing conditional queries. + */ + boolean wasApplied(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 33d4b6d84ea..64beb3533d4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -106,4 +106,27 @@ default ExecutionInfo getExecutionInfo() { * AsyncResultSet}. */ void fetchNextPage(); + + /** + * If the query that produced this result was a conditional update, indicate whether it was + * successfully applied. + * + *

    This is equivalent to calling: + * + *

    +   *   this.iterator().next().getBoolean("[applied]")
    +   * 
    + * + * Except that this method peeks at the next row without consuming it. + * + *

    For consistency, this method always returns {@code true} for non-conditional queries + * (although there is no reason to call the method in that case). This is also the case for + * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF + * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. + * + *

    Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this + * method to always return {@code true} for batches containing conditional queries. + */ + boolean wasApplied(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 87bfa5d6ede..588b396aabc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; import java.nio.ByteBuffer; @@ -98,6 +99,23 @@ public CompletionStage fetchNextPage() throws IllegalStateExcept return session.executeAsync(nextStatement); } + @Override + public boolean wasApplied() { + if (!definitions.contains("[applied]") + || !definitions.get("[applied]").getType().equals(DataTypes.BOOLEAN)) { + return true; + } else if (iterator().hasNext()) { + // Note that [applied] has the same value for all rows, so as long as we have a row we don't + // care which one it is. + return iterator.peek().getBoolean("[applied]"); + } else { + // If the server provided [applied], it means there was at least one row. So if we get here it + // means the client consumed all the rows before, we can't handle that case because we have + // nowhere left to read the boolean from. + throw new IllegalStateException("This method must be called before consuming all the rows"); + } + } + static AsyncResultSet empty(final ExecutionInfo executionInfo) { return new AsyncResultSet() { @Override @@ -130,6 +148,11 @@ public CompletionStage fetchNextPage() throws IllegalStateExcept public Iterator iterator() { return Collections.emptyList().iterator(); } + + @Override + public boolean wasApplied() { + return true; + } }; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index f37c1a8706e..4359081594c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -20,10 +20,10 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.util.CountingIterator; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.google.common.collect.AbstractIterator; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; @@ -75,6 +75,11 @@ public Iterator iterator() { return iterator; } + @Override + public boolean wasApplied() { + return iterator.wasApplied(); + } + private class RowIterator extends CountingIterator { // The pages fetched so far. The first is the one we're currently iterating. private LinkedList pages = new LinkedList<>(); @@ -120,5 +125,16 @@ private void fetchNextPage() { } } } + + private boolean wasApplied() { + if (!columnDefinitions.contains("[applied]") + || !columnDefinitions.get("[applied]").getType().equals(DataTypes.BOOLEAN)) { + return true; + } else if (pages.isEmpty()) { + throw new IllegalStateException("This method must be called before consuming all the rows"); + } else { + return pages.getFirst().wasApplied(); + } + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java index 5663af060be..4ac2a26c2fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java @@ -67,4 +67,9 @@ public void fetchNextPage() { public Iterator iterator() { return onlyPage.iterator(); } + + @Override + public boolean wasApplied() { + return onlyPage.wasApplied(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java index 47deb3b8472..087729585fc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java @@ -106,4 +106,11 @@ public final T next() { remaining -= 1; return result; } + + public final T peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return next; + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 1491d09ded7..a88b362dbee 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -15,14 +15,23 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.Lists; import java.nio.ByteBuffer; import java.util.LinkedList; +import java.util.List; +import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.mockito.Mock; @@ -46,6 +55,8 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(executionInfo.getStatement()).thenReturn(statement); + Mockito.when(context.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); + Mockito.when(context.protocolVersion()).thenReturn(CoreProtocolVersion.DEFAULT); } @Test(expectedExceptions = IllegalStateException.class) @@ -87,4 +98,92 @@ public void should_invoke_session_to_fetch_next_page() { Mockito.verify(session).executeAsync(mockNextStatement); assertThat(nextPageFuture).isEqualTo(mockResultFuture); } + + @Test + public void should_report_applied_if_column_not_present_and_empty() { + // Given + Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(false); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet( + columnDefinitions, executionInfo, new LinkedList<>(), session, context); + + // Then + assertThat(resultSet.wasApplied()).isTrue(); + } + + @Test + public void should_report_applied_if_column_not_present_and_not_empty() { + // Given + Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(false); + Queue> data = new LinkedList<>(); + data.add(Lists.newArrayList(Bytes.fromHexString("0xffff"))); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet(columnDefinitions, executionInfo, data, session, context); + + // Then + assertThat(resultSet.wasApplied()).isTrue(); + } + + @Test + public void should_report_not_applied_if_column_present_and_false() { + // Given + Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); + Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); + Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); + + Queue> data = new LinkedList<>(); + data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(false, CoreProtocolVersion.DEFAULT))); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet(columnDefinitions, executionInfo, data, session, context); + + // Then + assertThat(resultSet.wasApplied()).isFalse(); + } + + @Test + public void should_report_not_applied_if_column_present_and_true() { + // Given + Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); + Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); + Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); + + Queue> data = new LinkedList<>(); + data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(true, CoreProtocolVersion.DEFAULT))); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet(columnDefinitions, executionInfo, data, session, context); + + // Then + assertThat(resultSet.wasApplied()).isTrue(); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void should_fail_to_report_if_applied_if_column_present_but_empty() { + // Given + Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); + Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + + // When + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet( + columnDefinitions, executionInfo, new LinkedList<>(), session, context); + + // Then + resultSet.wasApplied(); + } } From 8d32f4b67448362bca736fdb94bf6477b930ff00 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 23 Jun 2017 10:56:25 -0700 Subject: [PATCH 094/742] Document logging conventions --- CONTRIBUTING.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a386d5d9e0..95d44b72098 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,61 @@ in the documented item's signature. Builder withLimit(int limit) { ``` +### Logs + +We use SLF4J; loggers are declared like this: + +```java +private static final Logger LOG = LoggerFactory.getLogger(TheEnclosingClass.class); +``` + +Logs are intended for two personae: + +* Ops who manage the application in production. +* Developers (maybe you) who debug a particular issue. + +The first 3 log levels are for ops: + +* `ERROR`: something that renders the driver -- or a part of it -- completely unusable. An action is + required to fix it: bouncing the client, applying a patch, etc. +* `WARN`: something that the driver can recover from automatically, but indicates a configuration or + programming error that should be addressed. For example: the driver connected successfully, but + one of the contact points in the configuration was malformed; the same prepared statement is being + prepared multiple time by the application code. +* `INFO`: something that is part of the normal operation of the driver, but might be useful to know + for an operator. For example: the driver has initialized successfully and is ready to process + queries; an optional dependency was detected in the classpath and activated an enhanced feature. + +Do not log errors that are rethrown to the client (such as the error that you're going to complete a +request with). This is annoying for ops because they see a lot of stack traces that require no +actual action on their part, because they're already handled by application code. + +The last 2 levels are for developers: + +* `DEBUG`: anything that would be useful to understand what the driver is doing from a "black box" + perspective, i.e. if all you have are the logs. +* `TRACE`: same thing, but for events that happen very often, produce a lot of output, or should be + irrelevant most of the time (this is a bit more subjective and left to your interpretation). + +Logs statements start with a prefix that identifies its origin, for example: + +* for components that are unique to the cluster instance, just the cluster name: `[c0]`. +* for sessions, the cluster name + a generated unique identifier: `[c0|s0]`. +* for channel pools, the session identifier + the address of the node: `[c0|s0|/127.0.0.2:9042]`. +* for channels, the identifier of the owner (session or control connection) + the Netty identifier, + which indicates the local and remote ports: + `[c0|s0|id: 0xf9ef0b15, L:/127.0.0.1:51482 - R:/127.0.0.1:9042]`. +* for request handlers, the session identifier, a unique identifier, and the index of the + speculative execution: `[c0|s0|1077199500|0]`. + +Tests run with the configuration defined in `src/test/resources/logback-test.xml`. The default level +for driver classes is `WARN`, but you can override it with a system property: `-DdriverLevel=DEBUG`. +A nice setup is to use `DEBUG` when you run from your IDE, and keep the default for the command +line. + +When you add or review new code, take a moment to run the tests in `DEBUG` mode and check if the +output looks good. + ## Coding style -- test code Static imports are permitted in a couple of places: From ca1bd98233144ee2ffa4245f9d9ade3d4911cb33 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 23 Jun 2017 15:01:37 -0700 Subject: [PATCH 095/742] Properly initialize pool when a node gets added The LBP must change the distance to LOCAL (it starts as IGNORED) in order to create the initialization of the pool in DefaultSession. Additionally, improve NodeStateManager to avoid a duplicate refresh for added nodes. --- .../RoundRobinLoadBalancingPolicy.java | 14 ++++++++++---- .../oss/driver/api/core/metadata/NodeState.java | 5 +++-- .../internal/core/metadata/NodeStateManager.java | 9 ++++++--- .../internal/core/session/DefaultSession.java | 2 +- .../core/metadata/NodeStateManagerTest.java | 8 ++++++-- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index 40606911592..b55d81b0ce8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -41,6 +41,7 @@ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { private final String logPrefix; private final AtomicInteger startIndex = new AtomicInteger(); private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); + private volatile DistanceReporter distanceReporter; public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { this.logPrefix = context.clusterName(); @@ -49,6 +50,7 @@ public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext c @Override public void init(Set nodes, DistanceReporter distanceReporter) { LOG.debug("[{}] Initializing with {}", logPrefix, nodes); + this.distanceReporter = distanceReporter; for (Node node : nodes) { distanceReporter.setDistance(node, NodeDistance.LOCAL); if (node.getState() == NodeState.UNKNOWN || node.getState() == NodeState.UP) { @@ -71,24 +73,28 @@ public Queue newQueryPlan() { @Override public void onAdd(Node node) { - onUp(node); + LOG.debug("[{}] {} was added, setting distance to LOCAL", logPrefix, node); + // Setting to a non-ignored distance triggers the session to open a pool, which will in turn set + // the node UP when the first channel gets opened. + distanceReporter.setDistance(node, NodeDistance.LOCAL); } @Override public void onUp(Node node) { - LOG.debug("[{}] Adding {}", logPrefix, node); + LOG.debug("[{}] {} came back UP, adding to live set", logPrefix, node); liveNodes.add(node); } @Override public void onDown(Node node) { - LOG.debug("[{}] Removing {}", logPrefix, node); + LOG.debug("[{}] {} went DOWN, removing from live set", logPrefix, node); liveNodes.remove(node); } @Override public void onRemove(Node node) { - onDown(node); + LOG.debug("[{}] {} was removed, removing from live set", logPrefix, node); + liveNodes.remove(node); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java index e8b033e9f69..217a4fbc542 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java @@ -20,8 +20,9 @@ public enum NodeState { /** * The driver has never tried to connect to the node, nor received any topology events about it. * - *

    This happens if your load balancing policy ignores some of the nodes. Since the driver does - * not connect to them, the only way it can assess their states is from topology events. + *

    This happens when nodes are first added to the cluster, and will persist if your load + * balancing policy decides to ignore them. Since the driver does not connect to them, the only + * way it can assess their states is from topology events. */ UNKNOWN, /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 3ae0240aac5..b149b0ee65a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -267,11 +267,13 @@ private void setState(DefaultNode node, NodeState newState, String reason) { newState, reason); node.state = newState; - if (newState != NodeState.UP) { - // Fire the event immediately + // Fire the state change event, either immediately, or after a refresh if the node just came + // back up. + // If oldState == UNKNOWN, the node was just added, we already refreshed while processing + // the addition. + if (oldState == NodeState.UNKNOWN || newState != NodeState.UP) { eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); } else { - // Refresh the node first (but still fire event if that fails) metadataManager .refreshNode(node) .whenComplete( @@ -281,6 +283,7 @@ private void setState(DefaultNode node, NodeState newState, String reason) { LOG.debug( "[{}] Error while refreshing info for {}", logPrefix, node, error); } + // Fire the event whether the refresh succeeded or not eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); } catch (Throwable t) { LOG.warn("[{}] Unexpected exception", logPrefix, t); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f4447ab560e..4bd60c96c7e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -382,7 +382,7 @@ private void onPoolAdded(ChannelPool pool) { // If the session's keyspace changed while the pool was initializing, switch it now. Don't // try too hard to wait until we expose the pool to clients, switching keyspaces is // inherently unsafe anyway. - if (!keyspace.equals(pool.getInitialKeyspaceName())) { + if (!Objects.equals(keyspace, pool.getInitialKeyspaceName())) { pool.setKeyspace(keyspace); } pending.remove(node); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 6164505f5e0..b6bc8001a8a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -134,7 +134,9 @@ public void should_apply_up_event_if_node_is_unknown_or_down() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + if (oldState != NodeState.UNKNOWN) { + Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + } Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -300,7 +302,9 @@ public void should_apply_force_up_event_if_node_is_not_up() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); - Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + if (oldState != NodeState.UNKNOWN) { + Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + } } } From 4784cfb79c955e3fb2aaefe63c3f267d737c85ad Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 23 Jun 2017 15:07:52 -0700 Subject: [PATCH 096/742] Link to LBP in NodeState javadocs --- .../driver/api/core/metadata/NodeState.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java index 217a4fbc542..9c1a9dab69b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java @@ -15,14 +15,16 @@ */ package com.datastax.oss.driver.api.core.metadata; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; + /** The state of a node, as viewed from the driver. */ public enum NodeState { /** * The driver has never tried to connect to the node, nor received any topology events about it. * - *

    This happens when nodes are first added to the cluster, and will persist if your load - * balancing policy decides to ignore them. Since the driver does not connect to them, the only - * way it can assess their states is from topology events. + *

    This happens when nodes are first added to the cluster, and will persist if your {@link + * LoadBalancingPolicy} decides to ignore them. Since the driver does not connect to them, the + * only way it can assess their states is from topology events. */ UNKNOWN, /** @@ -31,8 +33,8 @@ public enum NodeState { *

      *
    • the driver has at least one active connection to the node. *
    • the driver is not actively trying to connect to the node (because it's ignored by the - * load balancing policy), but it has received a topology event indicating that the node is - * up. + * {@link LoadBalancingPolicy}), but it has received a topology event indicating that the + * node is up. *
    */ UP, @@ -42,14 +44,14 @@ public enum NodeState { *
      *
    • the driver has lost all connections to the node (and is currently trying to reconnect). *
    • the driver is not actively trying to connect to the node (because it's ignored by the - * load balancing policy), but it has received a topology event indicating that the node is - * down. + * {@link LoadBalancingPolicy}), but it has received a topology event indicating that the + * node is down. *
    */ DOWN, /** * The node was forced down externally, the driver will never try to reconnect to it, whatever the - * load balancing policy says. + * {@link LoadBalancingPolicy} says. * *

    This is used for edge error cases, for example when the driver detects that it's trying to * connect to a node that does not belong to the Cassandra cluster (e.g. a wrong address was From 8b675bfc5e5e47be4be0a61febea51954da71ac2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 25 Jun 2017 09:58:55 -0700 Subject: [PATCH 097/742] Make "prepare on all nodes" configurable --- .../api/core/config/CoreDriverOption.java | 2 ++ .../internal/core/cql/CqlPrepareHandler.java | 26 +++++++++++---- core/src/main/resources/reference.conf | 21 ++++++++++++ .../core/cql/CqlPrepareHandlerTest.java | 32 +++++++++++++++++++ .../core/cql/RequestHandlerTestHarness.java | 2 ++ 5 files changed, 76 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 4b2c750e168..73aa202ded6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -52,6 +52,8 @@ public enum CoreDriverOption implements DriverOption { RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection.config.base-delay", true), RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), + PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), + POOLING_LOCAL_CONNECTIONS("pooling.local.connections", true), POOLING_REMOTE_CONNECTIONS("pooling.remote.connections", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 657ec83f2b7..a40509d5d1b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -71,6 +71,7 @@ public class CqlPrepareHandler private final Duration timeout; private final ScheduledFuture timeoutFuture; private final RetryPolicy retryPolicy; + private final Boolean prepareOnAllNodes; private final CqlPrepareProcessor processor; // The errors on the nodes that were already tried (lazily initialized on the first error). @@ -105,6 +106,7 @@ public class CqlPrepareHandler this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); + this.prepareOnAllNodes = configProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES); sendRequest(null, 0); } @@ -187,13 +189,23 @@ private void setFinalResult(Prepared prepared) { session .getRepreparePayloads() .put(cachedStatement.getId(), cachedStatement.getRepreparePayload()); - prepareOnOtherNodes() - .thenRun(() -> result.complete(cachedStatement)) - .exceptionally( - error -> { - result.completeExceptionally(error); - return null; - }); + if (prepareOnAllNodes) { + prepareOnOtherNodes() + .thenRun( + () -> { + LOG.debug( + "[{}] Done repreparing on other nodes, completing the request", logPrefix); + result.complete(cachedStatement); + }) + .exceptionally( + error -> { + result.completeExceptionally(error); + return null; + }); + } else { + LOG.debug("[{}] Prepare on all nodes is disabled, completing the request", logPrefix); + result.complete(cachedStatement); + } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 7f4dffe962c..62ec75e60fa 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -144,6 +144,27 @@ datastax-java-driver { default-idempotence = false } + prepared-statements { + # Whether `Session.prepare` calls should be sent to all nodes in the cluster. + # + # A request to prepare is handled in two steps: + # 1) send to a single node first (to rule out simple errors like malformed queries). + # 2) if step 1 succeeds, re-send to all other active nodes (i.e. not ignored by the load + # balancing policy). + # This option controls whether step 2 is executed. + # + # The reason why you might want to disable it is to optimize network usage if you have a large + # number of clients preparing the same set of statements at startup. If your load balancing + # policy distributes queries randomly, each client will pick a different host to prepare its + # statements, and on the whole each host has a good chance of having been hit by at least one + # client for each statement. + # On the other hand, if that assumption turns out to be wrong and one host hasn't prepared a + # given statement, it needs to be re-prepared on the fly the first time it gets executed; this + # causes a performance penalty (one extra roundtrip to resend the query to prepare, and another + # to retry the execution). + prepare-on-all-nodes = true + } + # The driver maintains a connection pool to each node, according to the distance assigned to it # by the load balancing policy. If the distance is IGNORED, no connections are maintained. pooling { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 75fba5f46a6..a988d6363a3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -97,6 +99,36 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { } } + @Test + public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + DriverConfigProfile config = harness.getContext().config().defaultProfile(); + Mockito.when(config.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); + + CompletionStage prepareFuture = + new CqlPrepareHandler( + PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") + .asyncResult(); + + node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); + node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + + // The future should complete immediately: + assertThat(prepareFuture).isSuccess(); + + // And the other nodes should not be contacted: + node2Behavior.verifyNoWrite(); + node3Behavior.verifyNoWrite(); + } + } + @Test public void should_not_reprepare_on_other_nodes_if_already_cached() { // Simulate an existing entry in the driver's cache: diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 673598e8ed7..3dec75dc236 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -86,6 +86,8 @@ private RequestHandlerTestHarness(Builder builder) { .thenReturn(ConsistencyLevel.SERIAL); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)) + .thenReturn(true); Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); From ca5b91f41947ccf2e58509653c6ae068aeb1fde0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 25 Jun 2017 18:49:06 -0700 Subject: [PATCH 098/742] Update to latest native-protocol snapshot --- .../com/datastax/oss/driver/internal/core/TestResponses.java | 2 +- .../oss/driver/internal/core/cql/CqlPrepareHandlerTest.java | 2 -- .../oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java index e4257795161..f7bf9cf0b4c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -37,7 +37,7 @@ public static Rows clusterNameResponse(String actualClusterName) { "cluster_name", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR)); - RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), 1, null, null); + RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); Queue> data = Lists.newLinkedList(); data.add(Lists.newArrayList(ByteBuffer.wrap(actualClusterName.getBytes(Charsets.UTF_8)))); return new Rows(metadata, data); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index a988d6363a3..9e09d42cd61 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -324,7 +324,6 @@ private static Message simplePrepared() { "key", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), - 1, null, new int[] {0}); RowsMetadata resultMetadata = @@ -336,7 +335,6 @@ private static Message simplePrepared() { "message", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), - 1, null, new int[] {}); return new Prepared(Bytes.fromHexString("0xffff").array(), variablesMetadata, resultMetadata); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 8f8c634e272..6e2c113bbd3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -77,7 +77,6 @@ protected static Message singleRow() { "message", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), - 1, null, new int[] {}); Queue> data = new LinkedList<>(); From ab64cba7f2c6b0fc41d0591bef23742b2f3fb6e7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 25 Jun 2017 09:59:22 -0700 Subject: [PATCH 099/742] JAVA-1502: Reprepare statements on newly added/up nodes --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 4 + .../core/cql/DefaultPreparedStatement.java | 2 +- .../internal/core/session/DefaultSession.java | 80 +++-- .../internal/core/session/ReprepareOnUp.java | 207 ++++++++++++ .../core/session/RepreparePayload.java | 6 +- core/src/main/resources/reference.conf | 34 +- .../core/cql/CqlRequestHandlerTest.java | 6 +- .../core/session/DefaultSessionTest.java | 6 + .../core/session/ReprepareOnUpTest.java | 310 ++++++++++++++++++ 10 files changed, 621 insertions(+), 35 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java diff --git a/changelog/README.md b/changelog/README.md index 70220e5d926..ccf7ae11e6f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1502: Reprepare statements on newly added/up nodes - [new feature] JAVA-1530: Add ResultSet.wasApplied - [improvement] JAVA-1531: Merge CqlSession and Session - [new feature] JAVA-1513: Handle batch statements diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 73aa202ded6..09e84cf443d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -53,6 +53,10 @@ public enum CoreDriverOption implements DriverOption { RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), + REPREPARE_ENABLED("prepared-statements.reprepare-on-up.enabled", true), + REPREPARE_MAX_STATEMENTS("prepared-statements.reprepare-on-up.max-statements", false), + REPREPARE_MAX_PARALLELISM("prepared-statements.reprepare-on-up.max-parallelism", false), + REPREPARE_TIMEOUT("prepared-statements.reprepare-on-up.timeout", false), POOLING_LOCAL_CONNECTIONS("pooling.local.connections", true), POOLING_REMOTE_CONNECTIONS("pooling.remote.connections", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 5da53091de5..1007087b93f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -55,7 +55,7 @@ public DefaultPreparedStatement( this.id = id; // It's important that we keep a reference to this object, so that it only gets evicted from // the map in DefaultSession if no client reference the PreparedStatement anymore. - this.repreparePayload = new RepreparePayload(query, keyspace, customPayloadForPrepare); + this.repreparePayload = new RepreparePayload(id, query, keyspace, customPayloadForPrepare); this.variableDefinitions = variableDefinitions; this.resultSetDefinitions = resultSetDefinitions; this.configProfileName = configProfileName; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 4bd60c96c7e..3eab0ece95c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -326,7 +326,7 @@ private void processDistanceEvent(DistanceEvent event) { channelPoolFactory.init(node, keyspace, newDistance, context, logPrefix); pending.put(node, poolFuture); poolFuture - .thenAcceptAsync(this::onPoolAdded, adminExecutor) + .thenAcceptAsync(this::onPoolInitialized, adminExecutor) .exceptionally(UncaughtExceptions::log); } else { LOG.debug("[{}] {} became {}, resizing it", logPrefix, node, newDistance); @@ -361,7 +361,7 @@ private void processStateEvent(NodeStateEvent event) { channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); pending.put(node, poolFuture); poolFuture - .thenAcceptAsync(this::onPoolAdded, adminExecutor) + .thenAcceptAsync(this::onPoolInitialized, adminExecutor) .exceptionally(UncaughtExceptions::log); } else { LOG.debug("[{}] {} came back UP, triggering pool reconnection", logPrefix, node); @@ -370,7 +370,7 @@ private void processStateEvent(NodeStateEvent event) { } } - private void onPoolAdded(ChannelPool pool) { + private void onPoolInitialized(ChannelPool pool) { assert adminExecutor.inEventLoop(); Node node = pool.getNode(); if (closeWasCalled) { @@ -379,32 +379,62 @@ private void onPoolAdded(ChannelPool pool) { pool.forceCloseAsync(); } else { LOG.debug("[{}] New pool to {} initialized", logPrefix, node); - // If the session's keyspace changed while the pool was initializing, switch it now. Don't - // try too hard to wait until we expose the pool to clients, switching keyspaces is - // inherently unsafe anyway. - if (!Objects.equals(keyspace, pool.getInitialKeyspaceName())) { - pool.setKeyspace(keyspace); - } - pending.remove(node); - pools.put(node, pool); - DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); - NodeStateEvent stateEvent = pendingStateEvents.remove(node); - if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { - LOG.debug( - "[{}] Received {} while the pool was initializing, processing it now", - logPrefix, - stateEvent); - processStateEvent(stateEvent); - } else if (distanceEvent != null) { - LOG.debug( - "[{}] Received {} while the pool was initializing, processing it now", - logPrefix, - distanceEvent); - processDistanceEvent(distanceEvent); + if (Objects.equals(keyspace, pool.getInitialKeyspaceName())) { + reprepareStatements(pool); + } else { + // The keyspace changed while the pool was being initialized, switch it now. + pool.setKeyspace(keyspace) + .handleAsync( + (result, error) -> { + if (error != null) { + LOG.warn("Error while switching keyspace to " + keyspace, error); + } + reprepareStatements(pool); + return null; + }, + adminExecutor); } } } + private void reprepareStatements(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + if (config.defaultProfile().getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { + new ReprepareOnUp( + logPrefix + "|" + pool.getNode().getConnectAddress(), + pool, + repreparePayloads, + config, + () -> RunOrSchedule.on(adminExecutor, () -> onPoolReady(pool))) + .start(); + } else { + LOG.debug("[{}] Reprepare on up is disabled, skipping", logPrefix); + onPoolReady(pool); + } + } + + private void onPoolReady(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + Node node = pool.getNode(); + pending.remove(node); + pools.put(node, pool); + DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); + NodeStateEvent stateEvent = pendingStateEvents.remove(node); + if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { + LOG.debug( + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + stateEvent); + processStateEvent(stateEvent); + } else if (distanceEvent != null) { + LOG.debug( + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + distanceEvent); + processDistanceEvent(distanceEvent); + } + } + private void setKeyspace(CqlIdentifier newKeyspace) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java new file mode 100644 index 00000000000..6443d133981 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.cql.CqlRequestHandler; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.request.Prepare; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.annotations.VisibleForTesting; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Ensures that a newly added or restarted node knows all the prepared statements created from this + * driver instance. + * + *

    See the comments in {@code reference.conf} for more explanations about this process. If any + * prepare request fail, we ignore the error because it will be retried on the fly (see {@link + * CqlRequestHandler}). + * + *

    Logically this code belongs to {@link DefaultSession}, but it was extracted for modularity and + * testability. + */ +class ReprepareOnUp { + + private static final Logger LOG = LoggerFactory.getLogger(ReprepareOnUp.class); + private static final Query QUERY_SERVER_IDS = + new Query("SELECT prepared_id FROM system.prepared_statements"); + + private final String logPrefix; + private final DriverChannel channel; + private final Map repreparePayloads; + private final Runnable whenPrepared; + private final int maxStatements; + private final int maxParallelism; + private final Duration timeout; + + // After the constructor, everything happens on the channel's event loop, so these fields do not + // need any synchronization. + private Set serverKnownIds; + private Queue toReprepare; + private int runningWorkers; + + ReprepareOnUp( + String logPrefix, + ChannelPool pool, + Map repreparePayloads, + DriverConfig config, + Runnable whenPrepared) { + + this.logPrefix = logPrefix; + this.channel = pool.next(); + this.repreparePayloads = repreparePayloads; + this.whenPrepared = whenPrepared; + + this.timeout = config.defaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); + this.maxStatements = config.defaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS); + this.maxParallelism = + config.defaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM); + } + + void start() { + LOG.debug("[{}] Repreparing statements on newly added/up node", logPrefix); + + if (repreparePayloads.isEmpty()) { + LOG.debug("[{}] No statements to reprepare, done", logPrefix); + whenPrepared.run(); + } else if (this.channel == null) { + // Should not happen, but handle cleanly + LOG.debug("[{}] No channel available to reprepare, done", logPrefix); + whenPrepared.run(); + } else { + queryAsync(QUERY_SERVER_IDS, Collections.emptyMap(), "QUERY system.prepared_statements") + .whenComplete(this::gatherServerIds); + } + } + + private void gatherServerIds(AdminResult rows, Throwable error) { + assert channel.eventLoop().inEventLoop(); + if (serverKnownIds == null) { + serverKnownIds = new HashSet<>(); + } + if (error != null) { + LOG.debug( + "[{}] Error querying system.prepared_statements ({}), proceeding without server ids", + logPrefix, + error.toString()); + gatherPayloadsToReprepare(); + } else { + for (AdminResult.Row row : rows) { + serverKnownIds.add(row.getByteBuffer("prepared_id")); + } + if (rows.hasNextPage()) { + LOG.debug("[{}] system.prepared_statements has more pages", logPrefix); + rows.nextPage().whenComplete(this::gatherServerIds); + } else { + LOG.debug("[{}] Gathered {} server ids, proceeding", logPrefix, serverKnownIds.size()); + gatherPayloadsToReprepare(); + } + } + } + + private void gatherPayloadsToReprepare() { + assert channel.eventLoop().inEventLoop(); + toReprepare = new LinkedList<>(); + for (RepreparePayload payload : repreparePayloads.values()) { + if (serverKnownIds.contains(payload.id)) { + LOG.trace( + "[{}] Skipping statement {} because it is already known to the server", + logPrefix, + Bytes.toHexString(payload.id)); + } else { + if (maxStatements > 0 && toReprepare.size() == maxStatements) { + LOG.debug( + "[{}] Limiting number of statements to reprepare to {} as configured, " + + "but there are more", + logPrefix, + maxStatements); + break; + } else { + toReprepare.add(payload); + } + } + } + if (toReprepare.isEmpty()) { + LOG.debug( + "[{}] No statements to reprepare that are not known by the server already, done", + logPrefix); + whenPrepared.run(); + } else { + startWorkers(); + } + } + + private void startWorkers() { + assert channel.eventLoop().inEventLoop(); + runningWorkers = Math.min(maxParallelism, toReprepare.size()); + LOG.debug( + "[{}] Repreparing {} statements with {} parallel workers", + logPrefix, + toReprepare.size(), + runningWorkers); + for (int i = 0; i < runningWorkers; i++) { + startWorker(); + } + } + + private void startWorker() { + assert channel.eventLoop().inEventLoop(); + if (toReprepare.isEmpty()) { + runningWorkers -= 1; + if (runningWorkers == 0) { + LOG.debug("[{}] All workers finished, done", logPrefix); + whenPrepared.run(); + } + } else { + RepreparePayload payload = toReprepare.poll(); + queryAsync( + new Prepare(payload.query), + payload.customPayload, + String.format("Reprepare '%s'", payload.query)) + .handle( + (result, error) -> { + // Don't log, AdminRequestHandler does already + startWorker(); + return null; + }); + } + } + + @VisibleForTesting + protected CompletionStage queryAsync( + Message message, Map customPayload, String debugString) { + AdminRequestHandler reprepareHandler = + new AdminRequestHandler(channel, message, timeout, logPrefix, debugString); + return reprepareHandler.start(customPayload); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java index e6fe2228880..e0e5fd36694 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java @@ -27,7 +27,7 @@ * CQL statements) keeps a reference to this. */ public class RepreparePayload { - + public final ByteBuffer id; public final String query; /** The keyspace that is set independently from the query string (see CASSANDRA-10145) */ @@ -35,7 +35,9 @@ public class RepreparePayload { public final Map customPayload; - public RepreparePayload(String query, String keyspace, Map customPayload) { + public RepreparePayload( + ByteBuffer id, String query, String keyspace, Map customPayload) { + this.id = id; this.query = query; this.keyspace = keyspace; this.customPayload = customPayload; diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 62ec75e60fa..7afd85daa5b 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -64,7 +64,7 @@ datastax-java-driver { # after we open a connection. If this timeout fires, the initialization of the connection will # fail. If this is the first connection ever, the driver will fail to initialize as well, # otherwise it will retry the connection later. - init-query-timeout = 5 seconds + init-query-timeout = 500 milliseconds # The timeout to use when the driver changes the keyspace on a connection at runtime (this # happens when the client issues a `USE ...` query, and all connections belonging to the @@ -99,7 +99,7 @@ datastax-java-driver { control-connection { # How long the driver waits for responses to control queries (e.g. fetching the list of # nodes, refreshing the schema). - timeout = 5 seconds + timeout = 500 milliseconds # The page size used for control queries. If a query returns more than this number of # results, it will be fetched in multiple requests. page-size = 5000 @@ -163,6 +163,36 @@ datastax-java-driver { # causes a performance penalty (one extra roundtrip to resend the query to prepare, and another # to retry the execution). prepare-on-all-nodes = true + + # How the driver replicates prepared statements on a node that just came back up or joined the + # cluster. + # Note that, since CASSANDRA-8831 (merged in 3.10), nodes keep track of their prepared + # statements in a system table, and reprepare them when they restart. The driver checks if that + # table exists and doesn't try to reprepare statements that are already known. + # This feature is still useful for added nodes. + reprepare-on-up { + # Whether the driver tries to prepare on new nodes at all. + # + # The reason why you might want to disable it is to optimize reconnection time when you + # believe nodes often get marked down because of temporary network issues, rather than the + # node really crashing. In that case, the node still has prepared statements in its cache when + # the driver reconnects, so re-preparing is redundant. + # + # On the other hand, if that assumption turns out to be wrong and the node had really + # restarted, its prepared statement cache is empty (before CASSANDRA-8831), and statements + # need to be re-prepared on the fly the first time they get executed; this causes a + # performance penalty (one extra roundtrip to resend the query to prepare, and another to + # retry the execution). + enabled = true + # The maximum number of statements that should be reprepared. 0 or a negative value means no + # limit. + max-statements = 0 + # The maximum number of concurrent requests when repreparing. + max-parallelism = 100 + # The request timeout. This applies both to querying the system.prepared_statements table (if + # relevant), and the prepare requests themselves. + timeout = ${datastax-java-driver.connection.init-query-timeout} + } } # The driver maintains a connection pool to each node, according to the distance assigned to it diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 2fb43318032..c304185672c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Row; -import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.request.Prepare; @@ -43,9 +42,6 @@ import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyMap; public class CqlRequestHandlerTest extends CqlRequestHandlerTestBase { @@ -160,7 +156,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce // The handler will look for the info to reprepare in the session's cache, put it there ConcurrentMap repreparePayloads = new ConcurrentHashMap<>(); repreparePayloads.put( - mockId, new RepreparePayload("mock query", null, Collections.emptyMap())); + mockId, new RepreparePayload(mockId, "mock query", null, Collections.emptyMap())); Mockito.when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); CompletionStage resultSetFuture = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index a0c99c2679d..0c9e8bcc5b9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -52,6 +52,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.timeout; public class DefaultSessionTest { @@ -99,6 +100,8 @@ public void setup() { Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) .thenReturn(true); + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) + .thenReturn(false); Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); } @@ -762,6 +765,9 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() private ChannelPool mockPool(Node node) { ChannelPool pool = Mockito.mock(ChannelPool.class); Mockito.when(pool.getNode()).thenReturn(node); + Mockito.when(pool.getInitialKeyspaceName()).thenReturn(KEYSPACE); + Mockito.when(pool.setKeyspace(any(CqlIdentifier.class))) + .thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture closeFuture = new CompletableFuture<>(); Mockito.when(pool.closeFuture()).thenReturn(closeFuture); Mockito.when(pool.closeAsync()) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java new file mode 100644 index 00000000000..d2f965a40a8 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Prepare; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.RawType; +import com.datastax.oss.protocol.internal.response.result.Rows; +import com.datastax.oss.protocol.internal.response.result.RowsMetadata; +import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.channel.EventLoop; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ReprepareOnUpTest { + @Mock private ChannelPool pool; + @Mock private DriverChannel channel; + @Mock private EventLoop eventLoop; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfigProfile; + private Runnable whenPrepared; + private CompletionStage done; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(pool.next()).thenReturn(channel); + Mockito.when(channel.eventLoop()).thenReturn(eventLoop); + Mockito.when(eventLoop.inEventLoop()).thenReturn(true); + + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REPREPARE_TIMEOUT)) + .thenReturn(Duration.ofMillis(500)); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) + .thenReturn(0); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM)) + .thenReturn(100); + + done = new CompletableFuture<>(); + whenPrepared = () -> ((CompletableFuture) done).complete(null); + } + + @Test + public void should_complete_immediately_if_no_prepared_statements() { + // Given + Map repreparePayloads = Collections.emptyMap(); + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, repreparePayloads, config, whenPrepared); + + // When + reprepareOnUp.start(); + + // Then + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_complete_immediately_if_pool_empty() { + // Given + Mockito.when(pool.next()).thenReturn(null); + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + // When + reprepareOnUp.start(); + + // Then + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_reprepare_all_if_system_table_query_fails() { + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Query.class); + assertThat(((Query) adminQuery.request).query) + .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); + adminQuery.resultFuture.completeExceptionally(new RuntimeException("mock error")); + + for (char c = 'a'; c <= 'f'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_reprepare_all_if_system_table_empty() { + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Query.class); + assertThat(((Query) adminQuery.request).query) + .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); + // server knows no ids: + adminQuery.resultFuture.complete( + new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + + for (char c = 'a'; c <= 'f'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_not_reprepare_already_known_statements() { + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Query.class); + assertThat(((Query) adminQuery.request).query) + .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); + // server knows d, e and f already: + adminQuery.resultFuture.complete( + new AdminResult(preparedIdRows('d', 'e', 'f'), null, CoreProtocolVersion.DEFAULT)); + + for (char c = 'a'; c <= 'c'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_limit_number_of_statements_to_reprepare() { + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) + .thenReturn(3); + + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Query.class); + assertThat(((Query) adminQuery.request).query) + .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); + // server knows no ids: + adminQuery.resultFuture.complete( + new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + + for (char c = 'a'; c <= 'c'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + @Test + public void should_limit_number_of_statements_reprepared_in_parallel() { + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM)) + .thenReturn(3); + + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Query.class); + assertThat(((Query) adminQuery.request).query) + .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); + // server knows no ids => will reprepare all 6: + adminQuery.resultFuture.complete( + new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + + // 3 statements have enqueued, we've not completed the queries yet so no more should be sent: + assertThat(reprepareOnUp.queries.size()).isEqualTo(3); + + // As we complete each statement, another one should enqueue: + for (char c = 'a'; c <= 'c'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + assertThat(reprepareOnUp.queries.size()).isEqualTo(3); + } + + // Complete the last 3: + for (char c = 'd'; c <= 'f'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + + private Map getMockPayloads() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (char c = 'a'; c <= 'f'; c++) { + ByteBuffer id = Bytes.fromHexString("0x0" + c); + builder.put(id, new RepreparePayload(id, "mock query " + c, null, Collections.emptyMap())); + } + return builder.build(); + } + + /** Bypasses the channel to make testing easier. */ + private static class MockReprepareOnUp extends ReprepareOnUp { + + private Queue queries = new LinkedList<>(); + + MockReprepareOnUp( + String logPrefix, + ChannelPool pool, + Map repreparePayloads, + DriverConfig config, + Runnable whenPrepared) { + super(logPrefix, pool, repreparePayloads, config, whenPrepared); + } + + @Override + protected CompletionStage queryAsync( + Message message, Map customPayload, String debugString) { + CompletableFuture resultFuture = new CompletableFuture<>(); + queries.add(new MockAdminQuery(message, resultFuture)); + return resultFuture; + } + } + + private static class MockAdminQuery { + private final Message request; + private final CompletableFuture resultFuture; + + public MockAdminQuery(Message request, CompletableFuture resultFuture) { + this.request = request; + this.resultFuture = resultFuture; + } + } + + private Rows preparedIdRows(char... values) { + ColumnSpec preparedIdSpec = + new ColumnSpec( + "system", + "prepared_statements", + "prepared_id", + 0, + RawType.PRIMITIVES.get(ProtocolConstants.DataType.BLOB)); + RowsMetadata rowsMetadata = new RowsMetadata(ImmutableList.of(preparedIdSpec), null, null); + Queue> data = new LinkedList<>(); + for (char value : values) { + data.add(ImmutableList.of(Bytes.fromHexString("0x0" + value))); + } + return new Rows(rowsMetadata, data); + } +} From 6e4d7df781f7c44a4011a2bed661de012da5b61c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 26 Jun 2017 09:38:02 -0700 Subject: [PATCH 100/742] Add option to disable query to `system.prepared_statements` --- .../api/core/config/CoreDriverOption.java | 1 + .../internal/core/session/ReprepareOnUp.java | 25 +++++++-- core/src/main/resources/reference.conf | 12 +++-- .../core/session/ReprepareOnUpTest.java | 53 ++++++++++++++----- 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 09e84cf443d..6404f296f11 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -54,6 +54,7 @@ public enum CoreDriverOption implements DriverOption { PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), REPREPARE_ENABLED("prepared-statements.reprepare-on-up.enabled", true), + REPREPARE_CHECK_SYSTEM_TABLE("prepared-statements.reprepare-on-up.check-system-table", false), REPREPARE_MAX_STATEMENTS("prepared-statements.reprepare-on-up.max-statements", false), REPREPARE_MAX_PARALLELISM("prepared-statements.reprepare-on-up.max-parallelism", false), REPREPARE_TIMEOUT("prepared-statements.reprepare-on-up.timeout", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 6443d133981..dd2232b3c0b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -60,6 +60,7 @@ class ReprepareOnUp { private final DriverChannel channel; private final Map repreparePayloads; private final Runnable whenPrepared; + private final boolean checkSystemTable; private final int maxStatements; private final int maxParallelism; private final Duration timeout; @@ -82,6 +83,8 @@ class ReprepareOnUp { this.repreparePayloads = repreparePayloads; this.whenPrepared = whenPrepared; + this.checkSystemTable = + config.defaultProfile().getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); this.timeout = config.defaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); this.maxStatements = config.defaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS); this.maxParallelism = @@ -89,8 +92,6 @@ class ReprepareOnUp { } void start() { - LOG.debug("[{}] Repreparing statements on newly added/up node", logPrefix); - if (repreparePayloads.isEmpty()) { LOG.debug("[{}] No statements to reprepare, done", logPrefix); whenPrepared.run(); @@ -99,8 +100,24 @@ void start() { LOG.debug("[{}] No channel available to reprepare, done", logPrefix); whenPrepared.run(); } else { - queryAsync(QUERY_SERVER_IDS, Collections.emptyMap(), "QUERY system.prepared_statements") - .whenComplete(this::gatherServerIds); + if (LOG.isDebugEnabled()) { // check because ConcurrentMap.size is not a constant operation + LOG.debug( + "[{}] {} statements to reprepare on newly added/up node", + logPrefix, + repreparePayloads.size()); + } + if (checkSystemTable) { + LOG.debug("[{}] Checking which statements the server knows about", logPrefix); + queryAsync(QUERY_SERVER_IDS, Collections.emptyMap(), "QUERY system.prepared_statements") + .whenComplete(this::gatherServerIds); + } else { + LOG.debug( + "[{}] {} is disabled, repreparing directly", + logPrefix, + CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); + serverKnownIds = Collections.emptySet(); + gatherPayloadsToReprepare(); + } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 7afd85daa5b..c05a33343d7 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -166,10 +166,6 @@ datastax-java-driver { # How the driver replicates prepared statements on a node that just came back up or joined the # cluster. - # Note that, since CASSANDRA-8831 (merged in 3.10), nodes keep track of their prepared - # statements in a system table, and reprepare them when they restart. The driver checks if that - # table exists and doesn't try to reprepare statements that are already known. - # This feature is still useful for added nodes. reprepare-on-up { # Whether the driver tries to prepare on new nodes at all. # @@ -184,6 +180,14 @@ datastax-java-driver { # performance penalty (one extra roundtrip to resend the query to prepare, and another to # retry the execution). enabled = true + # Whether to check `system.prepared_statements` on the target node before repreparing. + # + # This table exists since CASSANDRA-8831 (merged in 3.10). It stores the statements already + # prepared on the node, and preserves them across restarts. + # + # Checking the table first avoids repreparing unnecessarily, but the cost of the query is not + # always worth the improvement, especially if the number of statements is low. + check-system-table = false # The maximum number of statements that should be reprepared. 0 or a negative value means no # limit. max-statements = 0 diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index d2f965a40a8..bf72326aae2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -69,6 +69,8 @@ public void setup() { Mockito.when(eventLoop.inEventLoop()).thenReturn(true); Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + .thenReturn(true); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REPREPARE_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) @@ -83,9 +85,8 @@ public void setup() { @Test public void should_complete_immediately_if_no_prepared_statements() { // Given - Map repreparePayloads = Collections.emptyMap(); MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, repreparePayloads, config, whenPrepared); + new MockReprepareOnUp("test", pool, getMockPayloads(/*none*/ ), config, whenPrepared); // When reprepareOnUp.start(); @@ -99,7 +100,7 @@ public void should_complete_immediately_if_pool_empty() { // Given Mockito.when(pool.next()).thenReturn(null); MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp("test", pool, getMockPayloads('a'), config, whenPrepared); // When reprepareOnUp.start(); @@ -111,7 +112,8 @@ public void should_complete_immediately_if_pool_empty() { @Test public void should_reprepare_all_if_system_table_query_fails() { MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); reprepareOnUp.start(); @@ -134,7 +136,8 @@ public void should_reprepare_all_if_system_table_query_fails() { @Test public void should_reprepare_all_if_system_table_empty() { MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); reprepareOnUp.start(); @@ -156,10 +159,33 @@ public void should_reprepare_all_if_system_table_empty() { assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } + @Test + public void should_reprepare_all_if_system_query_disabled() { + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + .thenReturn(false); + + MockReprepareOnUp reprepareOnUp = + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + + reprepareOnUp.start(); + + MockAdminQuery adminQuery; + for (char c = 'a'; c <= 'f'; c++) { + adminQuery = reprepareOnUp.queries.poll(); + assertThat(adminQuery.request).isInstanceOf(Prepare.class); + assertThat(((Prepare) adminQuery.request).cqlQuery).isEqualTo("mock query " + c); + adminQuery.resultFuture.complete(null); + } + + assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + } + @Test public void should_not_reprepare_already_known_statements() { MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); reprepareOnUp.start(); @@ -187,7 +213,8 @@ public void should_limit_number_of_statements_to_reprepare() { .thenReturn(3); MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); reprepareOnUp.start(); @@ -215,7 +242,8 @@ public void should_limit_number_of_statements_reprepared_in_parallel() { .thenReturn(3); MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(), config, whenPrepared); + new MockReprepareOnUp( + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); reprepareOnUp.start(); @@ -250,11 +278,12 @@ public void should_limit_number_of_statements_reprepared_in_parallel() { assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } - private Map getMockPayloads() { + private Map getMockPayloads(char... values) { ImmutableMap.Builder builder = ImmutableMap.builder(); - for (char c = 'a'; c <= 'f'; c++) { - ByteBuffer id = Bytes.fromHexString("0x0" + c); - builder.put(id, new RepreparePayload(id, "mock query " + c, null, Collections.emptyMap())); + for (char value : values) { + ByteBuffer id = Bytes.fromHexString("0x0" + value); + builder.put( + id, new RepreparePayload(id, "mock query " + value, null, Collections.emptyMap())); } return builder.build(); } From 3a14f38affbddf9ca34bdd9f7ed2dcd8b49a236f Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 27 Jun 2017 11:44:41 -0700 Subject: [PATCH 101/742] Update Scala test script to reflect recent API change --- core/console.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/console.scala b/core/console.scala index 8f2c8305997..c48a0034eaa 100644 --- a/core/console.scala +++ b/core/console.scala @@ -23,7 +23,7 @@ val address4 = new InetSocketAddress("127.0.0.4", 9042) val address5 = new InetSocketAddress("127.0.0.5", 9042) val address6 = new InetSocketAddress("127.0.0.6", 9042) -val builder = Cluster.builder().withContactPoints(Set(address1)) +val builder = Cluster.builder().addContactPoint(address1) println("********************************************") println("* To start a driver instance, run: *") From 23c336d235ca0a73988df472861d43599410ab3b Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 27 Jun 2017 13:46:03 -0700 Subject: [PATCH 102/742] JAVA-1529: Make configuration reloadable --- changelog/README.md | 1 + .../oss/driver/api/core/ClusterBuilder.java | 28 +-- .../api/core/config/CoreDriverOption.java | 1 + .../api/core/config/DriverConfigLoader.java | 39 +++++ .../driver/internal/core/DefaultCluster.java | 4 +- .../core/config/ConfigChangeEvent.java | 21 +++ .../core/config/ForceReloadConfigEvent.java | 21 +++ .../PeriodicTypeSafeDriverConfigLoader.java | 165 ++++++++++++++++++ .../config/typesafe/TypeSafeDriverConfig.java | 47 +++-- .../core/context/DefaultDriverContext.java | 12 +- .../core/context/InternalDriverContext.java | 3 + core/src/main/resources/reference.conf | 4 + ...eriodicTypeSafeDriverConfigLoaderTest.java | 161 +++++++++++++++++ ...equestHandlerSpeculativeExecutionTest.java | 14 +- .../core/cql/CqlRequestHandlerTest.java | 2 +- .../ScheduledTaskCapturingEventLoop.java | 84 +++++++-- .../ScheduledTaskCapturingEventLoopTest.java | 3 +- 17 files changed, 558 insertions(+), 52 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java diff --git a/changelog/README.md b/changelog/README.md index ccf7ae11e6f..546d67e9fdf 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1529: Make configuration reloadable - [new feature] JAVA-1502: Reprepare statements on newly added/up nodes - [new feature] JAVA-1530: Add ResultSet.wasApplied - [improvement] JAVA-1531: Merge CqlSession and Session diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 090e3fc43cd..7c55b8b0850 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -17,16 +17,16 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; -import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig; +import com.datastax.oss.driver.internal.core.config.typesafe.PeriodicTypeSafeDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.typesafe.config.ConfigFactory; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; @@ -38,15 +38,15 @@ /** Helper class to build an instance of the default {@link Cluster} implementation. */ public class ClusterBuilder { - private DriverConfig config; + private DriverConfigLoader configLoader; private Set programmaticContactPoints = new HashSet<>(); private List> typeCodecs = Collections.emptyList(); /** - * Sets the configuration to use. + * Sets the configuration loader to use. * - *

    If you don't call this method, the builder will try to build an instance of the default - * implementation, based on the Typesafe config library. More precisely: + *

    If you don't call this method, the builder will use the default implementation, based on the + * Typesafe config library. More precisely: * *

      *
    • configuration properties are loaded and merged from the following (first-listed are @@ -70,14 +70,13 @@ public class ClusterBuilder { * @see Typesafe config's * standard loading behavior */ - public ClusterBuilder withConfig(DriverConfig config) { - this.config = config; + public ClusterBuilder withConfigLoader(DriverConfigLoader configLoader) { + this.configLoader = configLoader; return this; } - private static DriverConfig defaultConfig() { - return new TypeSafeDriverConfig( - ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + private static DriverConfigLoader defaultConfigLoader() { + return new PeriodicTypeSafeDriverConfigLoader(); } /** @@ -123,9 +122,10 @@ public ClusterBuilder withTypeCodecs(List> typeCodecs) { * @return a completion stage that completes with the cluster when it is fully initialized. */ public CompletionStage buildAsync() { - DriverConfig config = buildIfNull(this.config, ClusterBuilder::defaultConfig); + DriverConfigLoader configLoader = + buildIfNull(this.configLoader, ClusterBuilder::defaultConfigLoader); - DriverConfigProfile defaultConfig = config.defaultProfile(); + DriverConfigProfile defaultConfig = configLoader.getInitialConfig().defaultProfile(); List configContactPoints = defaultConfig.isDefined(CoreDriverOption.CONTACT_POINTS) ? defaultConfig.getStringList(CoreDriverOption.CONTACT_POINTS) @@ -134,7 +134,7 @@ public CompletionStage buildAsync() { Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); - InternalDriverContext context = new DefaultDriverContext(config, typeCodecs); + InternalDriverContext context = new DefaultDriverContext(configLoader, typeCodecs); return DefaultCluster.init(context, contactPoints); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 6404f296f11..53673ec1267 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -24,6 +24,7 @@ public enum CoreDriverOption implements DriverOption { CONTACT_POINTS("contact-points", false), PROTOCOL_VERSION("protocol.version", false), CLUSTER_NAME("cluster-name", false), + CONFIG_RELOAD_INTERVAL("config-reload-interval", true), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), CONNECTION_SET_KEYSPACE_TIMEOUT("connection.set-keyspace-timeout", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java new file mode 100644 index 00000000000..5d05765354b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +import com.datastax.oss.driver.api.core.context.DriverContext; + +/** + * Manages the initialization, and optionally the periodic reloading, of the driver configuration. + */ +public interface DriverConfigLoader extends AutoCloseable { + + /** Loads the first configuration that will be used to initialize the driver. */ + DriverConfig getInitialConfig(); + + /** + * Called when the driver initializes. For loaders that periodically check for configuration + * updates, this is a good time to grab an internal executor and schedule a recurring task. + */ + void onDriverInit(DriverContext context); + + /** + * Called when the cluster closes. This is a good time to release any external resource, for + * example cancel a scheduled reloading task. + */ + void close(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index e91ee85693c..2bd6ea2808b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -149,6 +149,7 @@ private void init() { v -> { try { context.loadBalancingPolicyWrapper().init(); + context.configLoader().onDriverInit(context); LOG.debug("[{}] Initialization complete, ready", logPrefix); initFuture.complete(DefaultCluster.this); // TODO schedule full schema refresh asynchronously (does not block init) @@ -271,7 +272,8 @@ private void closePolicies() { context.retryPolicy(), context.loadBalancingPolicyWrapper(), context.speculativeExecutionPolicy(), - context.addressTranslator())) { + context.addressTranslator(), + context.configLoader())) { try { closeable.close(); } catch (Throwable t) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java new file mode 100644 index 00000000000..8a2e99fe059 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config; + +/** An event triggered when the configuration was changed. */ +public enum ConfigChangeEvent { + INSTANCE +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java new file mode 100644 index 00000000000..3461dfb4b13 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config; + +/** An event that forces an immediate reload of the configuration. */ +public enum ForceReloadConfigEvent { + INSTANCE +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java new file mode 100644 index 00000000000..c3e89714371 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.ClusterBuilder; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; +import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.ScheduledFuture; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A loader that reloads the configuration at a configurable interval. */ +public class PeriodicTypeSafeDriverConfigLoader implements DriverConfigLoader { + + private static final Logger LOG = + LoggerFactory.getLogger(PeriodicTypeSafeDriverConfigLoader.class); + + public static final Supplier DEFAULT_CONFIG_SUPPLIER = + () -> { + ConfigFactory.invalidateCaches(); + return ConfigFactory.load().getConfig("datastax-java-driver"); + }; + + private final Supplier configSupplier; + private final TypeSafeDriverConfig driverConfig; + + private volatile SingleThreaded singleThreaded; + + /** + * Builds a new instance with the default TypeSafe config loading rules (documented in {@link + * ClusterBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. + */ + public PeriodicTypeSafeDriverConfigLoader() { + this(DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values()); + } + + /** + * Builds an instance with custom arguments, if you want to load the configuration from somewhere + * else or have custom options. + */ + public PeriodicTypeSafeDriverConfigLoader( + Supplier configSupplier, DriverOption[]... options) { + this.configSupplier = configSupplier; + this.driverConfig = new TypeSafeDriverConfig(configSupplier.get(), options); + } + + @Override + public DriverConfig getInitialConfig() { + return driverConfig; + } + + @Override + public void onDriverInit(DriverContext driverContext) { + this.singleThreaded = new SingleThreaded((InternalDriverContext) driverContext); + } + + @Override + public void close() { + RunOrSchedule.on(singleThreaded.adminExecutor, singleThreaded::close); + } + + private class SingleThreaded { + private final String logPrefix; + private final EventExecutor adminExecutor; + private final EventBus eventBus; + private final DriverConfigProfile config; + private final Object forceLoadListenerKey; + + private Duration reloadInterval; + private ScheduledFuture reloadFuture; + private boolean closeWasCalled; + + private SingleThreaded(InternalDriverContext context) { + this.logPrefix = context.clusterName(); + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.eventBus = context.eventBus(); + this.config = context.config().defaultProfile(); + this.reloadInterval = + context.config().defaultProfile().getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); + + forceLoadListenerKey = + this.eventBus.register( + ForceReloadConfigEvent.class, e -> RunOrSchedule.on(adminExecutor, this::reload)); + + RunOrSchedule.on(adminExecutor, this::scheduleReloadTask); + } + + private void scheduleReloadTask() { + assert adminExecutor.inEventLoop(); + // Cancel any previously running task + if (reloadFuture != null) { + reloadFuture.cancel(false); + } + if (reloadInterval.isZero()) { + LOG.debug("[{}] Reload interval is 0, disabling periodic reloading", logPrefix); + } else { + LOG.debug("[{}] Scheduling periodic reloading with interval {}", logPrefix, reloadInterval); + reloadFuture = + adminExecutor.scheduleAtFixedRate( + this::reload, + reloadInterval.toNanos(), + reloadInterval.toNanos(), + TimeUnit.NANOSECONDS); + } + } + + private void reload() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + if (driverConfig.reload(configSupplier.get())) { + LOG.info("[{}] Detected a configuration change", logPrefix); + eventBus.fire(ConfigChangeEvent.INSTANCE); + Duration newReloadInterval = config.getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); + if (!newReloadInterval.equals(reloadInterval)) { + reloadInterval = newReloadInterval; + scheduleReloadTask(); + } + } else { + LOG.debug("[{}] Reloaded configuration but it hasn't changed", logPrefix); + } + } + + private void close() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + eventBus.unregister(forceLoadListenerKey, ForceReloadConfigEvent.class); + if (reloadFuture != null) { + reloadFuture.cancel(false); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 6a9ce613e53..3c4e19c231c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -40,8 +40,11 @@ public class TypeSafeDriverConfig implements DriverConfig { private final Collection options; private final TypesafeDriverConfigProfile.Base defaultProfile; private final Map profiles; + // Only used to detect if reload saw any change + private volatile Config lastLoadedConfig; public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) { + this.lastLoadedConfig = config; this.options = merge(optionArrays); Map profileConfigs = extractProfiles(config); @@ -63,24 +66,36 @@ public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) { } } - public void reload(Config config) { - Map profileConfigs = extractProfiles(config); - this.defaultProfile.refresh(profileConfigs.get(DEFAULT_PROFILE_KEY)); - if (profileConfigs.size() > 1) { - for (Map.Entry entry : profileConfigs.entrySet()) { - String profileName = entry.getKey(); - if (!profileName.equals(DEFAULT_PROFILE_KEY)) { - TypesafeDriverConfigProfile.Base profile = this.profiles.get(profileName); - if (profile == null) { - LOG.warn( - String.format( - "Unknown profile '%s' while reloading configuration. " - + "Adding profiles at runtime is not supported.", - profileName)); - } else { - profile.refresh(entry.getValue()); + /** @return whether the configuration changed */ + public boolean reload(Config config) { + if (config.equals(lastLoadedConfig)) { + return false; + } else { + lastLoadedConfig = config; + try { + Map profileConfigs = extractProfiles(config); + this.defaultProfile.refresh(profileConfigs.get(DEFAULT_PROFILE_KEY)); + if (profileConfigs.size() > 1) { + for (Map.Entry entry : profileConfigs.entrySet()) { + String profileName = entry.getKey(); + if (!profileName.equals(DEFAULT_PROFILE_KEY)) { + TypesafeDriverConfigProfile.Base profile = this.profiles.get(profileName); + if (profile == null) { + LOG.warn( + String.format( + "Unknown profile '%s' while reloading configuration. " + + "Adding profiles at runtime is not supported.", + profileName)); + } else { + profile.refresh(entry.getValue()); + } + } } } + return true; + } catch (Throwable t) { + LOG.warn("Error reloading configuration, keeping previous one", t); + return false; } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 20f5dc96003..d8713bd5b9b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -121,12 +122,14 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); private final DriverConfig config; + private final DriverConfigLoader configLoader; private final ChannelPoolFactory channelPoolFactory = new ChannelPoolFactory(); private final CodecRegistry codecRegistry; private final String clusterName; - public DefaultDriverContext(DriverConfig config, List> typeCodecs) { - this.config = config; + public DefaultDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { + this.config = configLoader.getInitialConfig(); + this.configLoader = configLoader; DriverConfigProfile defaultProfile = config.defaultProfile(); if (defaultProfile.isDefined(CoreDriverOption.CLUSTER_NAME)) { this.clusterName = defaultProfile.getString(CoreDriverOption.CLUSTER_NAME); @@ -268,6 +271,11 @@ public DriverConfig config() { return config; } + @Override + public DriverConfigLoader configLoader() { + return configLoader; + } + @Override public LoadBalancingPolicy loadBalancingPolicy() { return loadBalancingPolicyRef.get(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index cc264f59be7..6255b3d58ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -61,4 +62,6 @@ public interface InternalDriverContext extends DriverContext { ControlConnection controlConnection(); RequestProcessorRegistry requestProcessorRegistry(); + + DriverConfigLoader configLoader(); } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index c05a33343d7..8be43b9df0b 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -47,6 +47,10 @@ datastax-java-driver { # and metrics. // cluster-name = my_cluster + # How often the driver tries to reload the configuration. + # To disable periodic reloading, set this to 0. + config-reload-interval = 5 minutes + retry { policy-class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java new file mode 100644 index 00000000000..545ddd52482 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; +import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop.CapturedTask; +import com.typesafe.config.ConfigFactory; +import io.netty.channel.EventLoopGroup; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + +public class PeriodicTypeSafeDriverConfigLoaderTest { + + @Mock private InternalDriverContext context; + @Mock private NettyOptions nettyOptions; + @Mock private EventLoopGroup adminEventExecutorGroup; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfigProfile; + private ScheduledTaskCapturingEventLoop adminExecutor; + private EventBus eventBus; + private AtomicReference configSource; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(context.clusterName()).thenReturn("test"); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); + + adminExecutor = new ScheduledTaskCapturingEventLoop(adminEventExecutorGroup); + Mockito.when(adminEventExecutorGroup.next()).thenReturn(adminExecutor); + + eventBus = Mockito.spy(new EventBus("test")); + Mockito.when(context.eventBus()).thenReturn(eventBus); + + // The already loaded config in the context. + // In real life, it's the object managed by the loader, but in this test it's simpler to mock + // it. + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL)) + .thenReturn(Duration.ofSeconds(12)); + + configSource = new AtomicReference<>("required_int = 42"); + } + + @Test + public void should_build_initial_config() { + PeriodicTypeSafeDriverConfigLoader loader = + new PeriodicTypeSafeDriverConfigLoader( + () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); + DriverConfig initialConfig = loader.getInitialConfig(); + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); + } + + @Test + public void should_schedule_reloading_task() { + PeriodicTypeSafeDriverConfigLoader loader = + new PeriodicTypeSafeDriverConfigLoader( + () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); + + loader.onDriverInit(context); + adminExecutor.waitForNonScheduledTasks(); + + CapturedTask task = adminExecutor.nextTask(); + assertThat(task.getInitialDelay(TimeUnit.SECONDS)).isEqualTo(12); + assertThat(task.getPeriod(TimeUnit.SECONDS)).isEqualTo(12); + } + + @Test + public void should_reload_if_config_has_changed() { + PeriodicTypeSafeDriverConfigLoader loader = + new PeriodicTypeSafeDriverConfigLoader( + () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); + DriverConfig initialConfig = loader.getInitialConfig(); + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); + + loader.onDriverInit(context); + adminExecutor.waitForNonScheduledTasks(); + + CapturedTask task = adminExecutor.nextTask(); + + configSource.set("required_int = 43"); + + task.run(); + + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 43); + Mockito.verify(eventBus).fire(ConfigChangeEvent.INSTANCE); + } + + @Test + public void should_reload_if_forced() { + PeriodicTypeSafeDriverConfigLoader loader = + new PeriodicTypeSafeDriverConfigLoader( + () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); + DriverConfig initialConfig = loader.getInitialConfig(); + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); + + loader.onDriverInit(context); + adminExecutor.waitForNonScheduledTasks(); + + configSource.set("required_int = 43"); + + eventBus.fire(ForceReloadConfigEvent.INSTANCE); + adminExecutor.waitForNonScheduledTasks(); + + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 43); + Mockito.verify(eventBus).fire(ConfigChangeEvent.INSTANCE); + } + + @Test + public void should_not_notify_if_config_has_not_changed() { + PeriodicTypeSafeDriverConfigLoader loader = + new PeriodicTypeSafeDriverConfigLoader( + () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); + DriverConfig initialConfig = loader.getInitialConfig(); + assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); + + loader.onDriverInit(context); + adminExecutor.waitForNonScheduledTasks(); + + CapturedTask task = adminExecutor.nextTask(); + + // no change to the config source + + task.run(); + + Mockito.verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 9080abaeced..4bec3f3c467 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -82,13 +82,14 @@ public void should_schedule_speculative_executions( ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = harness.nextScheduledTask(); - assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); firstExecutionTask.run(); node2Behavior.verifyWrite(); ScheduledTaskCapturingEventLoop.CapturedTask secondExecutionTask = harness.nextScheduledTask(); - assertThat(secondExecutionTask.getDelay(TimeUnit.MILLISECONDS)) + assertThat(secondExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) .isEqualTo(secondExecutionDelay); secondExecutionTask.run(); node3Behavior.verifyWrite(); @@ -126,7 +127,8 @@ public void should_not_start_execution_if_result_complete( // Check that the first execution was scheduled but don't run it yet ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = harness.nextScheduledTask(); - assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); // Complete the request from the initial execution node1Behavior.setWriteSuccess(); @@ -170,7 +172,8 @@ public void should_retry_in_speculative_executions( ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = harness.nextScheduledTask(); - assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); firstExecutionTask.run(); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); @@ -209,7 +212,8 @@ public void should_stop_retrying_other_executions_if_result_complete( ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = harness.nextScheduledTask(); - assertThat(firstExecutionTask.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(firstExecutionDelay); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); firstExecutionTask.run(); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index c304185672c..56861c79f57 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -103,7 +103,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { .config() .defaultProfile() .getDuration(CoreDriverOption.REQUEST_TIMEOUT); - assertThat(scheduledTask.getDelay(TimeUnit.NANOSECONDS)) + assertThat(scheduledTask.getInitialDelay(TimeUnit.NANOSECONDS)) .isEqualTo(configuredTimeout.toNanos()); scheduledTask.run(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index c4bf0830710..c7b288d12d6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -15,28 +15,35 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultEventLoop; import io.netty.channel.EventLoopGroup; import io.netty.util.concurrent.ScheduledFuture; -import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyBoolean; /** * Extend Netty's default event loop to capture scheduled tasks instead of running them. The tasks * can be checked later, and run manually. * + *

      Tasks submitted with {@link #execute(Runnable)} or {@link #submit(Callable)} are still + * executed normally. + * *

      This is used to make unit tests independent of time. */ public class ScheduledTaskCapturingEventLoop extends DefaultEventLoop { - private final Queue capturedTasks = new ConcurrentLinkedQueue<>(); + private final BlockingQueue capturedTasks = new ArrayBlockingQueue<>(100); public ScheduledTaskCapturingEventLoop(EventLoopGroup parent) { super(parent); @@ -61,21 +68,70 @@ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) unit); } + @Override + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + CapturedTask task = + new CapturedTask<>( + () -> { + command.run(); + return null; + }, + initialDelay, + period, + unit); + boolean added = capturedTasks.offer(task); + assertThat(added).isTrue(); + return task.scheduledFuture; + } + + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + throw new UnsupportedOperationException("Not supported yet"); + } + public CapturedTask nextTask() { - return capturedTasks.poll(); + try { + return capturedTasks.poll(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Unexpected interruption", e); + throw new AssertionError(); + } + } + + /** + * Wait for any pending non-scheduled task (submitted with {@code submit}, {@code execute}, etc.) + * to complete. + */ + public void waitForNonScheduledTasks() { + ScheduledFuture f = super.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } } - public static class CapturedTask { + public class CapturedTask { private final FutureTask futureTask; - private final long delay; + private final long initialDelay; + private final long period; private final TimeUnit unit; @SuppressWarnings("unchecked") private final ScheduledFuture scheduledFuture = Mockito.mock(ScheduledFuture.class); - CapturedTask(Callable task, long delay, TimeUnit unit) { + CapturedTask(Callable task, long initialDelay, TimeUnit unit) { + this(task, initialDelay, -1, unit); + } + + CapturedTask(Callable task, long initialDelay, long period, TimeUnit unit) { this.futureTask = new FutureTask<>(task); - this.delay = delay; + this.initialDelay = initialDelay; + this.period = period; this.unit = unit; // If the code under test cancels the scheduled future, cancel our task @@ -90,15 +146,21 @@ public static class CapturedTask { } public void run() { - futureTask.run(); + submit(futureTask); + waitForNonScheduledTasks(); } public boolean isCancelled() { return futureTask.isCancelled(); } - public long getDelay(TimeUnit targetUnit) { - return targetUnit.convert(delay, unit); + public long getInitialDelay(TimeUnit targetUnit) { + return targetUnit.convert(initialDelay, unit); + } + + /** By convention, non-recurring tasks have a negative period */ + public long getPeriod(TimeUnit targetUnit) { + return targetUnit.convert(period, unit); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java index 9d3ba18d7c3..7dda6b4e068 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop.CapturedTask; import io.netty.util.concurrent.ScheduledFuture; -import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.testng.annotations.Test; @@ -38,7 +37,7 @@ public void should_capture_task_and_let_test_complete_it_manually() { assertThat(ran.get()).isFalse(); CapturedTask task = eventLoop.nextTask(); - assertThat(task.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(1); + assertThat(task.getInitialDelay(TimeUnit.NANOSECONDS)).isEqualTo(1); task.run(); From 011ecd2be1933334942e70d25836df0ab904fd6b Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 27 Jun 2017 16:59:21 -0700 Subject: [PATCH 103/742] Add more contributing guidelines --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95d44b72098..d7780f8fc7f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,45 @@ line. When you add or review new code, take a moment to run the tests in `DEBUG` mode and check if the output looks good. +### No stream API + +Please don't use `java.util.stream` in the driver codebase. Streams were designed for *data +processing*, not to make your collection traversals "functional". + +Here's an example from the driver codebase (`ChannelSet`): + +```java +DriverChannel[] snapshot = this.channels; +DriverChannel best = null; +int bestScore = 0; +for (DriverChannel channel : snapshot) { + int score = channel.availableIds(); + if (score > bestScore) { + bestScore = score; + best = channel; + } +} +return best; +``` + +And here's a terrible way to rewrite it using streams: + +```java +// Don't do this: +DriverChannel best = + Stream.of(snapshot) + .reduce((a, b) -> a.availableIds() > b.availableIds() ? a : b) + .get(); +``` + +The stream version is not easier to read, and will probably be slower (creating intermediary objects +vs. an array iteration, compounded by the fact that this particular array typically has a low +cardinality). + +The driver never does the kind of processing that the stream API is intended for; the only large +collections we manipulate are result sets, and these get passed on to the client directly. + + ## Coding style -- test code Static imports are permitted in a couple of places: From 4f69c82cb6f162a3c057bcea1f96c5183ec2365a Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 28 Jun 2017 10:49:24 -0700 Subject: [PATCH 104/742] Resize pools when the configuration changes --- .../internal/core/pool/ChannelPool.java | 22 ++- .../internal/core/pool/ChannelPoolTest.java | 165 +++++++++++++++++- 2 files changed, 183 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index e7502908482..a333834a456 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; @@ -184,7 +185,9 @@ private class SingleThreaded { // The channels that are currently connecting private final List> pendingChannels = new ArrayList<>(); private final Reconnection reconnection; + private final Object configListenerKey; + private NodeDistance distance; private int wantedCount; private CompletableFuture connectFuture = new CompletableFuture<>(); private boolean isConnecting; @@ -198,7 +201,8 @@ private SingleThreaded( CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { this.keyspaceName = keyspaceName; this.config = context.config(); - this.wantedCount = computeSize(distance); + this.distance = distance; + this.wantedCount = getConfiguredSize(distance); this.channelFactory = context.channelFactory(); this.eventBus = context.eventBus(); this.reconnection = @@ -209,6 +213,9 @@ private SingleThreaded( this::addMissingChannels, () -> eventBus.fire(ChannelEvent.reconnectionStarted(node)), () -> eventBus.fire(ChannelEvent.reconnectionStopped(node))); + this.configListenerKey = + eventBus.register( + ConfigChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onConfigChanged)); } private void connect() { @@ -332,7 +339,8 @@ private void onChannelClosed(DriverChannel channel) { private void resize(NodeDistance newDistance) { assert adminExecutor.inEventLoop(); - int newChannelCount = computeSize(newDistance); + distance = newDistance; + int newChannelCount = getConfiguredSize(newDistance); if (newChannelCount > wantedCount) { LOG.debug("[{}] Growing ({} => {} channels)", logPrefix, wantedCount, newChannelCount); wantedCount = newChannelCount; @@ -368,6 +376,13 @@ private void shrinkIfTooManyChannels() { } } + private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event) { + assert adminExecutor.inEventLoop(); + // resize re-reads the pool size from the configuration and does nothing if it hasn't changed, + // which is exactly what we want. + resize(distance); + } + private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { assert adminExecutor.inEventLoop(); if (setKeyspaceFuture != null && !setKeyspaceFuture.isDone()) { @@ -404,6 +419,7 @@ private void close() { isClosing = true; reconnection.stop(); + eventBus.unregister(configListenerKey, ConfigChangeEvent.class); forAllChannels( channel -> { @@ -449,7 +465,7 @@ private void forceClose() { } } - private int computeSize(NodeDistance distance) { + private int getConfiguredSize(NodeDistance distance) { return config .defaultProfile() .getInt( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 202b0244245..1aacd7a7177 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -71,8 +72,8 @@ public class ChannelPoolTest { @Mock private ReconnectionPolicy reconnectionPolicy; @Mock private ReconnectionSchedule reconnectionSchedule; @Mock private NettyOptions nettyOptions; - @Mock private EventBus eventBus; @Mock private ChannelFactory channelFactory; + private EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; @BeforeMethod @@ -85,6 +86,7 @@ public void setup() { Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); Mockito.when(context.config()).thenReturn(config); Mockito.when(config.defaultProfile()).thenReturn(defaultProfile); + this.eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.channelFactory()).thenReturn(channelFactory); @@ -513,6 +515,167 @@ public void should_grow_during_reconnection() throws Exception { factoryHelper.verifyNoMoreCalls(); } + @Test + public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // growth attempt + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Simulate a configuration change + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // It should have triggered a reconnection + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_resize_during_reconnection_if_config_changes() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .failure(ADDRESS, "mock channel init failure") + // first reconnection attempt + .pending(ADDRESS, channel2Future) + // extra reconnection attempt after we realize the pool must grow + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + + // A reconnection should have been scheduled to add the missing channel, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + // Simulate a configuration change + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // Complete the channel for the first reconnection, bringing the count to 2 + channel2Future.complete(channel2); + factoryHelper.waitForCall(ADDRESS); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2); + + // A second attempt should have been scheduled since we're now still under the target size + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + // Same reconnection is still running, no additional events + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); + + // Two more channels get opened, bringing us to the target count + factoryHelper.waitForCalls(ADDRESS, 2); + channel3Future.complete(channel3); + channel4Future.complete(channel4); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_ignore_config_change_if_not_relevant() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Config changes, but not for our distance + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(1); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // It should not have triggered a reconnection + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + + factoryHelper.verifyNoMoreCalls(); + } + @Test public void should_switch_keyspace_on_existing_channels() throws Exception { Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); From a6112afaf44021359c92b399f6b1ebf70454e123 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 28 Jun 2017 13:23:07 -0700 Subject: [PATCH 105/742] Allow config options with relative paths This will be useful to nest policies. --- .../PassThroughAddressTranslator.java | 5 +- .../api/core/auth/PlainTextAuthProvider.java | 23 +++--- .../api/core/config/CoreDriverOption.java | 27 ++++--- .../driver/api/core/config/DriverOption.java | 25 ++++++ .../ExponentialReconnectionPolicy.java | 24 +++--- .../RoundRobinLoadBalancingPolicy.java | 5 +- .../api/core/retry/DefaultRetryPolicy.java | 5 +- .../specex/NoSpeculativeExecutionPolicy.java | 5 +- .../api/core/ssl/DefaultSslEngineFactory.java | 21 ++--- .../core/context/DefaultDriverContext.java | 36 ++++----- .../driver/internal/core/util/Reflection.java | 35 ++++++--- core/src/main/resources/reference.conf | 77 ++++++++----------- .../RoundRobinLoadBalancingPolicyTest.java | 4 +- .../core/channel/ChannelFactoryTestBase.java | 4 +- .../control/ControlConnectionTestBase.java | 5 +- .../metadata/DefaultTopologyMonitorTest.java | 4 +- 16 files changed, 182 insertions(+), 123 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java index 35363144321..c0476bd1384 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java @@ -15,13 +15,16 @@ */ package com.datastax.oss.driver.api.core.addresstranslation; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; /** An address translator that always returns the same address unchanged. */ public class PassThroughAddressTranslator implements AddressTranslator { - public PassThroughAddressTranslator(@SuppressWarnings("unused") DriverContext context) { + public PassThroughAddressTranslator( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index aed2ea58a8c..b96fd7873fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Charsets; import java.net.SocketAddress; @@ -26,17 +27,15 @@ * A simple authentication provider that supports SASL authentication using the PLAIN mechanism for * version 3 (or above) of the CQL native protocol. * - *

      To activate this provider, an {@code authentication} section must be included in the driver + *

      To activate this provider, an {@code auth-provider} section must be included in the driver * configuration, for example: * *

        * datastax-java-driver {
      - *   authentication {
      - *     provider-class = com.datastax.driver.api.core.auth.PlainTextAuthProvider
      - *     config {
      - *       username = cassandra
      - *       password = cassandra
      - *     }
      + *   auth-provider {
      + *     class = com.datastax.driver.api.core.auth.PlainTextAuthProvider
      + *     username = cassandra
      + *     password = cassandra
        *   }
        * }
        * 
      @@ -46,16 +45,20 @@ public class PlainTextAuthProvider implements AuthProvider { private final DriverConfigProfile config; + private final DriverOption configRoot; /** Builds a new instance. */ - public PlainTextAuthProvider(DriverContext context) { + public PlainTextAuthProvider(DriverContext context, DriverOption configRoot) { this.config = context.config().defaultProfile(); + this.configRoot = configRoot; } @Override public Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) { - String username = config.getString(CoreDriverOption.AUTHENTICATION_CONFIG_USERNAME); - String password = config.getString(CoreDriverOption.AUTHENTICATION_CONFIG_PASSWORD); + String username = + config.getString(configRoot.concat(CoreDriverOption.RELATIVE_PLAIN_TEXT_AUTH_USERNAME)); + String password = + config.getString(configRoot.concat(CoreDriverOption.RELATIVE_PLAIN_TEXT_AUTH_PASSWORD)); return new PlainTextAuthenticator(username, password); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 53673ec1267..9b0beeb8fbc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -43,15 +43,18 @@ public enum CoreDriverOption implements DriverOption { CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), - RETRY_POLICY_CLASS("retry.policy-class", true), + // "Sub-option" for all the policies, etc. + RELATIVE_POLICY_CLASS("class", false), - LOAD_BALANCING_POLICY_CLASS("load-balancing.policy-class", true), + RETRY_POLICY_ROOT("retry-policy", true), - SPECULATIVE_EXECUTION_POLICY_CLASS("speculative-execution.policy-class", true), + LOAD_BALANCING_POLICY_ROOT("load-balancing-policy", true), - RECONNECTION_POLICY_CLASS("connection.reconnection.policy-class", true), - RECONNECTION_CONFIG_BASE_DELAY("connection.reconnection.config.base-delay", true), - RECONNECTION_CONFIG_MAX_DELAY("connection.reconnection.config.max-delay", true), + SPECULATIVE_EXECUTION_POLICY_ROOT("speculative-execution-policy", true), + + RECONNECTION_POLICY_ROOT("connection.reconnection-policy", true), + RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY("connection.reconnection.config.base-delay", false), + RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY("connection.reconnection.config.max-delay", false), PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), REPREPARE_ENABLED("prepared-statements.reprepare-on-up.enabled", true), @@ -63,14 +66,14 @@ public enum CoreDriverOption implements DriverOption { POOLING_LOCAL_CONNECTIONS("pooling.local.connections", true), POOLING_REMOTE_CONNECTIONS("pooling.remote.connections", true), - ADDRESS_TRANSLATOR_CLASS("address-translation.translator-class", true), + ADDRESS_TRANSLATOR_ROOT("address-translator", true), - AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false), - AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false), - AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false), + AUTH_PROVIDER_ROOT("auth-provider", false), + RELATIVE_PLAIN_TEXT_AUTH_USERNAME("username", false), + RELATIVE_PLAIN_TEXT_AUTH_PASSWORD("password", false), - SSL_FACTORY_CLASS("ssl.factory-class", false), - SSL_CONFIG_CIPHER_SUITES("ssl.config.cipher-suites", false), + SSL_ENGINE_FACTORY_ROOT("ssl-engine-factory", false), + RELATIVE_DEFAULT_SSL_CIPHER_SUITES("ssl.config.cipher-suites", false), METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java index 22ebe27006f..19b3110fb24 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java @@ -20,4 +20,29 @@ public interface DriverOption { String getPath(); boolean required(); + + /** + * Concatenates two options to build a longer path. + * + *

      This is intended for options that can appear at different levels, for example arguments of + * policies that can be nested. + */ + default DriverOption concat(DriverOption child) { + DriverOption parent = this; + // Not particularly efficient, but this will mainly be used for policies, which initialize at + // startup, so it should be good enough. + return new DriverOption() { + @Override + public String getPath() { + return parent.getPath() + "." + child.getPath(); + } + + @Override + public boolean required() { + // This property is only for initial validation of the configuration, and we can't validate + // nested fields because they are by definition not known in advance. + return false; + } + }; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index 19cbc8f4688..ea263f7d36b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; import java.time.Duration; @@ -32,27 +33,28 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { private final long maxAttempts; /** Builds a new instance. */ - public ExponentialReconnectionPolicy(DriverContext context) { + public ExponentialReconnectionPolicy(DriverContext context, DriverOption configRoot) { DriverConfigProfile config = context.config().defaultProfile(); - this.baseDelayMs = - config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY).toMillis(); - this.maxDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY).toMillis(); + DriverOption baseDelayOption = + configRoot.concat(CoreDriverOption.RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY); + DriverOption maxDelayOption = + configRoot.concat(CoreDriverOption.RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY); + + this.baseDelayMs = config.getDuration(baseDelayOption).toMillis(); + this.maxDelayMs = config.getDuration(maxDelayOption).toMillis(); Preconditions.checkArgument( baseDelayMs > 0, "%s must be strictly positive (got %s)", - CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY.getPath(), + baseDelayOption.getPath(), baseDelayMs); Preconditions.checkArgument( - maxDelayMs >= 0, - "%s must be positive (got %s)", - CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY.getPath(), - maxDelayMs); + maxDelayMs >= 0, "%s must be positive (got %s)", maxDelayOption.getPath(), maxDelayMs); Preconditions.checkArgument( maxDelayMs >= baseDelayMs, "%s must be bigger than %s (got %s, %s)", - CoreDriverOption.RECONNECTION_CONFIG_MAX_DELAY.getPath(), - CoreDriverOption.RECONNECTION_CONFIG_BASE_DELAY.getPath(), + maxDelayOption.getPath(), + baseDelayOption.getPath(), maxDelayMs, baseDelayMs); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index b55d81b0ce8..5a9b0d03409 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -43,7 +44,9 @@ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); private volatile DistanceReporter distanceReporter; - public RoundRobinLoadBalancingPolicy(@SuppressWarnings("unused") DriverContext context) { + public RoundRobinLoadBalancingPolicy( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { this.logPrefix = context.clusterName(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index 134596b92be..f8c48cca0dd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.retry; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; @@ -26,7 +27,9 @@ */ public class DefaultRetryPolicy implements RetryPolicy { - public DefaultRetryPolicy(@SuppressWarnings("unused") DriverContext context) { + public DefaultRetryPolicy( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java index 333a56fe805..14f696571ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -16,13 +16,16 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; /** A policy that never triggers speculative executions. */ public class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { - public NoSpeculativeExecutionPolicy(@SuppressWarnings("unused") DriverContext context) { + public NoSpeculativeExecutionPolicy( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java index 74244cf87b4..ae6c744fae7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -28,16 +29,14 @@ /** * Default SSL implementation. * - *

      To activate this class, an {@code ssl} section must be included in the driver configuration, - * for example: + *

      To activate this class, an {@code ssl-engine-factory} section must be included in the driver + * configuration, for example: * *

        * datastax-java-driver {
      - *   ssl {
      - *     factory-class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory
      - *     config {
      - *       cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
      - *     }
      + *   ssl-engine-factory {
      + *     class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory
      + *     cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
        *   }
        * }
        * 
      @@ -50,15 +49,17 @@ public class DefaultSslEngineFactory implements SslEngineFactory { private final String[] cipherSuites; /** Builds a new instance from the driver configuration. */ - public DefaultSslEngineFactory(DriverContext driverContext) { + public DefaultSslEngineFactory(DriverContext driverContext, DriverOption configRoot) { try { this.sslContext = SSLContext.getDefault(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Cannot initialize SSL Context", e); } DriverConfigProfile config = driverContext.config().defaultProfile(); - if (config.isDefined(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES)) { - List list = config.getStringList(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES); + DriverOption cipherSuiteOption = + configRoot.concat(CoreDriverOption.RELATIVE_DEFAULT_SSL_CIPHER_SUITES); + if (config.isDefined(cipherSuiteOption)) { + List list = config.getStringList(cipherSuiteOption); String tmp[] = new String[list.size()]; this.cipherSuites = list.toArray(tmp); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index d8713bd5b9b..ab57477101f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.retry.RetryPolicy; @@ -140,66 +141,65 @@ public DefaultDriverContext(DriverConfigLoader configLoader, List> } protected LoadBalancingPolicy buildLoadBalancingPolicy() { - CoreDriverOption classOption = CoreDriverOption.LOAD_BALANCING_POLICY_CLASS; - return Reflection.buildFromConfig(this, classOption, LoadBalancingPolicy.class) + DriverOption rootOption = CoreDriverOption.LOAD_BALANCING_POLICY_ROOT; + return Reflection.buildFromConfig(this, rootOption, LoadBalancingPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing load balancing policy, check your configuration (%s)", - classOption))); + rootOption))); } protected ReconnectionPolicy buildReconnectionPolicy() { - CoreDriverOption classOption = CoreDriverOption.RECONNECTION_POLICY_CLASS; - return Reflection.buildFromConfig(this, classOption, ReconnectionPolicy.class) + CoreDriverOption rootOption = CoreDriverOption.RECONNECTION_POLICY_ROOT; + return Reflection.buildFromConfig(this, rootOption, ReconnectionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing reconnection policy, check your configuration (%s)", - classOption))); + "Missing reconnection policy, check your configuration (%s)", rootOption))); } protected RetryPolicy buildRetryPolicy() { - CoreDriverOption classOption = CoreDriverOption.RETRY_POLICY_CLASS; - return Reflection.buildFromConfig(this, classOption, RetryPolicy.class) + CoreDriverOption rootOption = CoreDriverOption.RETRY_POLICY_ROOT; + return Reflection.buildFromConfig(this, rootOption, RetryPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing retry policy, check your configuration (%s)", classOption))); + "Missing retry policy, check your configuration (%s)", rootOption))); } protected SpeculativeExecutionPolicy buildSpeculativeExecutionPolicy() { - CoreDriverOption classOption = CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS; - return Reflection.buildFromConfig(this, classOption, SpeculativeExecutionPolicy.class) + CoreDriverOption rootOption = CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT; + return Reflection.buildFromConfig(this, rootOption, SpeculativeExecutionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing speculative execution policy, check your configuration (%s)", - classOption))); + rootOption))); } protected AddressTranslator buildAddressTranslator() { - CoreDriverOption classOption = CoreDriverOption.ADDRESS_TRANSLATOR_CLASS; - return Reflection.buildFromConfig(this, classOption, AddressTranslator.class) + CoreDriverOption rootOption = CoreDriverOption.ADDRESS_TRANSLATOR_ROOT; + return Reflection.buildFromConfig(this, rootOption, AddressTranslator.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing address translator, check your configuration (%s)", classOption))); + "Missing address translator, check your configuration (%s)", rootOption))); } protected Optional buildAuthProvider() { return Reflection.buildFromConfig( - this, CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS, AuthProvider.class); + this, CoreDriverOption.AUTH_PROVIDER_ROOT, AuthProvider.class); } protected Optional buildSslEngineFactory() { return Reflection.buildFromConfig( - this, CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class); + this, CoreDriverOption.SSL_ENGINE_FACTORY_ROOT, SslEngineFactory.class); } protected EventBus buildEventBus() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 89b2d56eb0e..7d2613a9a3d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -46,24 +47,36 @@ public static Class loadClass(String className, String source) { } /** - * Tries to create an instance of a class, given its name defined in the driver configuration. + * Tries to create an instance of a class, given a set of options defined in the driver + * configuration. * - *

      By convention, a class instantiated through this method must have a constructor that takes a - * {@link DriverContext} as its single argument. + *

      For example: + * + *

      +   * my-policy {
      +   *   class = my.package.MyPolicyImpl
      +   *   arg1 = some custom option
      +   * }
      +   * 
      + * + * The {@code class} option is mandatory and will be used to construct the instance via + * reflection. It must have a constructor that takes two arguments: the {@link DriverContext}, and + * a {@link DriverOption} that represents the configuration root ({@code my-policy} in the example + * above). * * @param context the driver context. - * @param classNameOption the configuration option that contains the fully-qualified name of the - * class to instantiate. It will be looked up in the default profile of the configuration - * stored in the context. + * @param rootOption the root of the set of options that configures the class. It will be looked + * up in the default profile of the configuration stored in the context. * @param expectedSuperType a super-type that the class is expected to implement/extend. - * @return the new instance, or empty if {@code classNameOption} is not defined in the - * configuration. + * @return the new instance, or empty if {@code rootOption} or the class sub-option is not defined + * in the configuration. */ public static Optional buildFromConfig( - DriverContext context, DriverOption classNameOption, Class expectedSuperType) { + DriverContext context, DriverOption rootOption, Class expectedSuperType) { DriverConfigProfile config = context.config().defaultProfile(); + DriverOption classNameOption = rootOption.concat(CoreDriverOption.RELATIVE_POLICY_CLASS); if (!config.isDefined(classNameOption)) { return Optional.empty(); } @@ -80,7 +93,7 @@ public static Optional buildFromConfig( Constructor constructor; try { - constructor = clazz.getConstructor(DriverContext.class); + constructor = clazz.getConstructor(DriverContext.class, DriverOption.class); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format( @@ -89,7 +102,7 @@ public static Optional buildFromConfig( className, configPath, DriverConfigProfile.class.getSimpleName())); } try { - Object instance = constructor.newInstance(context); + Object instance = constructor.newInstance(context, rootOption); return Optional.of(expectedSuperType.cast(instance)); } catch (Exception e) { throw new IllegalArgumentException( diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 8be43b9df0b..fdc5990645e 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -51,16 +51,16 @@ datastax-java-driver { # To disable periodic reloading, set this to 0. config-reload-interval = 5 minutes - retry { - policy-class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy + retry-policy { + class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy } - load-balancing { - policy-class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy + load-balancing-policy { + class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy } - speculative-execution { - policy-class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy + speculative-execution-policy { + class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy } connection { @@ -92,12 +92,10 @@ datastax-java-driver { # fail with an exception max-frame-length = 256 MB - reconnection { - policy-class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy - config { - base-delay = 1 second - max-delay = 60 seconds - } + reconnection-policy { + class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy + base-delay = 1 second + max-delay = 60 seconds } control-connection { @@ -232,39 +230,32 @@ datastax-java-driver { max-events = 20 } } - address-translation { - # The address translator to use to convert the addresses sent by Cassandra nodes into ones that - # the driver uses to connect. - # This is only needed if the nodes are not directly reachable from the driver (for example, the - # driver is in a different network region and needs to use a public IP, or it connects through - # a proxy). - # The default implementation always returns the same address unchanged. - translator-class = com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator + # The address translator to use to convert the addresses sent by Cassandra nodes into ones that + # the driver uses to connect. + # This is only needed if the nodes are not directly reachable from the driver (for example, the + # driver is in a different network region and needs to use a public IP, or it connects through + # a proxy). + address-translator { + # This default implementation always returns the same address unchanged. + class = com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator } - authentication { - # The auth provider class to use. The driver expects this class to have a public constructor - # that takes a DriverConfigProfile argument. It will invoke it with the default configuration - # profile. - // provider-class = com.datastax.driver.api.core.auth.PlainTextAuthProvider - # The configuration of the auth provider. This is specific to each implementation, and will - # therefore depend on the provider-class configured above. - // config { - // username = cassandra - // password = cassandra - // } + # The auth provider that will handle authentication for each new connection to a server. + auth-provider { + # This property is optional; if it is not present, no authentication will occur. + // class = com.datastax.driver.api.core.auth.PlainTextAuthProvider + # Sample configuration for the plain-text provider: + // username = cassandra + // password = cassandra } - ssl { - # The SSL engine factory to use. The driver expects this class to implement SslEngineFactory, - # and have a public constructor that takes a DriverConfigProfile argument. It will invoke it - # with the default configuration profile. + # The SSL engine factory that will initialize an SSL engine for each new connection to a server. + ssl-engine-factory { # This property is optional; if it is not present, SSL won't be activated. - // factory-class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory - config { - # The cipher suites to enable when creating an SSLEngine for a connection. - # This property is optional. If it is not present, the driver won't explicitly enable cipher - # suites on the engine, which according to the JDK documentations results in "a minimum - # quality of service". - // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] - } + // class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory + # Sample configuration for the default SSL factory: + # The cipher suites to enable when creating an SSLEngine for a connection. + # This property is optional. If it is not present, the driver won't explicitly enable cipher + # suites on the engine, which according to the JDK documentations results in "a minimum + # quality of service". + // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] } } \ No newline at end of file diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java index 6429cb05cf8..8ca603c769e 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; import com.datastax.oss.driver.api.core.metadata.Node; @@ -43,7 +44,8 @@ public void setup() { Mockito.when(node2.getState()).thenReturn(NodeState.UP); Mockito.when(node3.getState()).thenReturn(NodeState.UP); - policy = new RoundRobinLoadBalancingPolicy(context); + policy = + new RoundRobinLoadBalancingPolicy(context, CoreDriverOption.LOAD_BALANCING_POLICY_ROOT); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 12eff6a90a0..f7538f0c8db 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -104,7 +104,9 @@ public void setup() throws InterruptedException { Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS)) + Mockito.when( + defaultConfigProfile.isDefined( + CoreDriverOption.AUTH_PROVIDER_ROOT.concat(CoreDriverOption.RELATIVE_POLICY_CLASS))) .thenReturn(false); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(100)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index f68af5a6f62..006aac32a59 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -113,7 +114,9 @@ public void setup() { .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(context.metadataManager()).thenReturn(metadataManager); - addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); + addressTranslator = + Mockito.spy( + new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); controlConnection = new ControlConnection(context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index e1c4dc9443c..dd1035ca358 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -74,7 +74,9 @@ public void setup() { Mockito.when(config.defaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); - addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); + addressTranslator = + Mockito.spy( + new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); Mockito.when(controlConnection.channel()).thenReturn(channel); From abbae455ab2af7e51154aa517d7fd35d37f49cdc Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 28 Jun 2017 15:53:16 -0700 Subject: [PATCH 106/742] Change cluster builder method to add type codecs For consistency with contact points, and upcoming node state listeners. --- .../com/datastax/oss/driver/api/core/ClusterBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 7c55b8b0850..0bb3134137e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -28,6 +27,7 @@ import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -40,7 +40,7 @@ public class ClusterBuilder { private DriverConfigLoader configLoader; private Set programmaticContactPoints = new HashSet<>(); - private List> typeCodecs = Collections.emptyList(); + private List> typeCodecs = new ArrayList<>(); /** * Sets the configuration loader to use. @@ -111,8 +111,8 @@ public ClusterBuilder addContactPoint(InetSocketAddress contactPoint) { } /** Registers additional codecs for custom type mappings. */ - public ClusterBuilder withTypeCodecs(List> typeCodecs) { - this.typeCodecs = typeCodecs; + public ClusterBuilder addTypeCodecs(TypeCodec... typeCodecs) { + Collections.addAll(this.typeCodecs, typeCodecs); return this; } From 8eef5992623f6f5bd971084aeacd8706a6fab633 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 30 Jun 2017 14:51:14 -0700 Subject: [PATCH 107/742] =?UTF-8?q?Use=20=C2=AE=20instead=20of=20=E2=84=A2?= =?UTF-8?q?=20in=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 990179bee03..5d1404bad0b 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# Datastax Java Driver for Apache Cassandra™ +# Datastax Java Driver for Apache Cassandra® *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. [4.0.0-alpha1](https://github.com/datastax/java-driver/tree/4.0.0-alpha1).* -A modern, feature-rich and highly tunable Java client library for [Apache Cassandra™] (2.1+) and +A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] (2.1+) and [DataStax Enterprise] (4.7+), using exclusively Cassandra's binary protocol and Cassandra Query Language v3. [DataStax Docs]: http://docs.datastax.com/en/developer/java-driver/ -[Apache Cassandra™]: http://cassandra.apache.org/ +[Apache Cassandra®]: http://cassandra.apache.org/ [DataStax Enterprise]: http://www.datastax.com/products/datastax-enterprise ## Getting the driver From 4e680fbde94366b9a213f12253cfabf7c4aea239 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 30 Jun 2017 10:15:06 -0700 Subject: [PATCH 108/742] JAVA-1519: Close channel if number of "orphan" stream ids exceeds a configurable threshold Motivation: Sometimes the driver might stop waiting for a response on a particular channel (for example if the request timed out, or was completed by another speculative execution). Before this change we just kept the response callback in our inflight map, which is a problem if we never get the response: it creates a memory leak, and if this keeps happening we'll eventually run out of stream ids on the channel. Modifications: Add a channel method to cancel a response callback, indicating that the caller is not interested in the response anymore. Modify existing clients to cancel their callbacks. Track the number of "orphan" stream ids (cancelled and have not yet received a response from the server). Initiate a graceful shutdown if this number exceeds a threshold. Improve the way the channel pool manages channel shutdowns: start the reconnection as soon as an orderly shutdown has *started*, not when it finishes. Result: Cancelled callbacks are not leaked anymore. When the number of orphan ids exceed the threshold, the channel is closed gracefully and the pool starts replacing it immediately. --- changelog/README.md | 2 + .../api/core/config/CoreDriverOption.java | 1 + .../adminrequest/AdminRequestHandler.java | 3 + .../internal/core/channel/ChannelFactory.java | 7 + .../core/channel/ChannelHandlerRequest.java | 4 + .../internal/core/channel/DriverChannel.java | 37 +++- .../core/channel/InFlightHandler.java | 98 ++++++++--- .../core/channel/ResponseCallback.java | 4 + .../internal/core/cql/CqlPrepareHandler.java | 33 +++- .../internal/core/cql/CqlRequestHandler.java | 24 +++ .../internal/core/pool/ChannelPool.java | 109 ++++++++---- core/src/main/resources/reference.conf | 12 ++ .../core/channel/ChannelFactoryTestBase.java | 2 + .../core/channel/DriverChannelTest.java | 2 + .../core/channel/InFlightHandlerTest.java | 166 ++++++++++++++++-- .../core/channel/ProtocolInitHandlerTest.java | 9 +- ...equestHandlerSpeculativeExecutionTest.java | 4 + .../internal/core/cql/PoolBehavior.java | 4 + .../internal/core/pool/ChannelPoolTest.java | 114 +++++++++--- 19 files changed, 529 insertions(+), 106 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 546d67e9fdf..29c48d23fc5 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1519: Close channel if number of orphan stream ids exceeds a configurable + threshold - [new feature] JAVA-1529: Make configuration reloadable - [new feature] JAVA-1502: Reprepare statements on newly added/up nodes - [new feature] JAVA-1530: Add ResultSet.wasApplied diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 9b0beeb8fbc..940bdd15078 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -32,6 +32,7 @@ public enum CoreDriverOption implements DriverOption { CONNECTION_MAX_REQUESTS("connection.max-requests-per-connection", true), CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), + CONNECTION_MAX_ORPHAN_REQUESTS("connection.max-orphan-requests", true), REQUEST_TIMEOUT("request.timeout", true), REQUEST_CONSISTENCY("request.consistency", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 23e41b324df..916eea48f5a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -117,6 +117,9 @@ private void onWriteComplete(Future future) { private void fireTimeout() { result.completeExceptionally( new DriverTimeoutException(String.format("%s timed out after %s", debugString, timeout))); + if (!channel.closeFuture().isDone()) { + channel.cancel(this); + } } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index f2a5a9e9509..bb1c64eb61e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -31,6 +31,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -185,13 +186,19 @@ protected void initChannel(Channel channel) throws Exception { (int) defaultConfigProfile.getBytes(CoreDriverOption.CONNECTION_MAX_FRAME_LENGTH); int maxRequestsPerConnection = defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + int maxOrphanRequests = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); + + ChannelPromise closeStartedFuture = channel.newPromise(); InFlightHandler inFlightHandler = new InFlightHandler( protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), + maxOrphanRequests, setKeyspaceTimeoutMillis, availableIdsHolder, + closeStartedFuture, options.eventCallback, options.ownerLogPrefix); ProtocolInitHandler initHandler = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java index 8d399250809..d75b745b719 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java @@ -86,6 +86,10 @@ public final void onFailure(Throwable error) { private void onTimeout() { fail(new DriverTimeoutException(describe() + ": timed out after " + timeoutMillis + " ms")); + if (!channel.closeFuture().isDone()) { + // Cancel the response callback + channel.writeAndFlush(this); + } } void failOnUnexpected(Message response) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 59f156a42e8..8a427077771 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -43,6 +43,7 @@ public class DriverChannel { static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); private final Channel channel; + private final ChannelFuture closeStartedFuture; private final WriteCoalescer writeCoalescer; private final AvailableIdsHolder availableIdsHolder; private final ProtocolVersion protocolVersion; @@ -55,6 +56,7 @@ public class DriverChannel { AvailableIdsHolder availableIdsHolder, ProtocolVersion protocolVersion) { this.channel = channel; + this.closeStartedFuture = channel.pipeline().get(InFlightHandler.class).closeStartedFuture; this.writeCoalescer = writeCoalescer; this.availableIdsHolder = availableIdsHolder; this.protocolVersion = protocolVersion; @@ -76,6 +78,22 @@ public Future write( return writeCoalescer.writeAndFlush(channel, message); } + /** + * Cancels a callback, indicating that the client that wrote it is no longer interested in the + * answer. + * + *

      Note that this does not cancel the request server-side (but might in the future if Cassandra + * supports it). + */ + public void cancel(ResponseCallback responseCallback) { + if (closing.get()) { + throw new IllegalStateException("Driver channel is closing"); + } + // To avoid creating an extra message, we adopt the convention that writing the callback + // directly means cancellation + writeCoalescer.writeAndFlush(channel, responseCallback); + } + /** * Releases a stream id if the client was holding onto it, and has now determined that it can be * safely reused. @@ -156,7 +174,24 @@ public Future forceClose() { return channel.closeFuture(); } - /** Does not close the channel, but returns a future that will complete when it does. */ + /** + * Returns a future that will complete when a graceful close has started, but not yet completed. + * + *

      In other words, the channel has stopped accepting new requests, but is still waiting for + * pending requests to finish. Once the last response has been received, the channel will really + * close and {@link #closeFuture()} will be completed. + * + *

      If there were no pending requests when the graceful shutdown was initiated, or if {@link + * #forceClose()} is called first, this future will never complete. + */ + public ChannelFuture closeStartedFuture() { + return this.closeStartedFuture; + } + + /** + * Does not close the channel, but returns a future that will complete when it is completely + * closed. + */ public ChannelFuture closeFuture() { return channel.closeFuture(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 8866c3bfa8f..83494cb24f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -28,13 +28,13 @@ import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; -import com.google.common.collect.Maps; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Promise; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,28 +44,35 @@ public class InFlightHandler extends ChannelDuplexHandler { private final ProtocolVersion protocolVersion; private final StreamIdGenerator streamIds; + final ChannelPromise closeStartedFuture; private final String ownerLogPrefix; - private final Map inFlight; + private final BiMap inFlight; private final long setKeyspaceTimeoutMillis; private final AvailableIdsHolder availableIdsHolder; private final EventCallback eventCallback; + private final int maxOrphanStreamIds; private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; private String logPrefix; + private int orphanStreamIds; InFlightHandler( ProtocolVersion protocolVersion, StreamIdGenerator streamIds, + int maxOrphanStreamIds, long setKeyspaceTimeoutMillis, AvailableIdsHolder availableIdsHolder, + ChannelPromise closeStartedFuture, EventCallback eventCallback, String ownerLogPrefix) { this.protocolVersion = protocolVersion; this.streamIds = streamIds; + this.maxOrphanStreamIds = maxOrphanStreamIds; + this.closeStartedFuture = closeStartedFuture; this.ownerLogPrefix = ownerLogPrefix; this.logPrefix = ownerLogPrefix + "|connecting..."; reportAvailableIds(); - this.inFlight = Maps.newHashMapWithExpectedSize(streamIds.getMaxAvailableIds()); + this.inFlight = HashBiMap.create(streamIds.getMaxAvailableIds()); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; this.availableIdsHolder = availableIdsHolder; this.eventCallback = eventCallback; @@ -81,29 +88,27 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { @Override public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) throws Exception { if (in == DriverChannel.GRACEFUL_CLOSE_MESSAGE) { - if (inFlight.isEmpty()) { - LOG.debug( - "[{}] Received graceful close request and no pending queries, closing now", logPrefix); - ctx.channel().close(); - } else { - LOG.debug( - "[{}] Received graceful close request, waiting for pending queries to complete", - logPrefix); - closingGracefully = true; - } - return; + LOG.debug("[{}] Received graceful close request", logPrefix); + startGracefulShutdown(ctx); } else if (in == DriverChannel.FORCEFUL_CLOSE_MESSAGE) { LOG.debug("[{}] Received forceful close request, aborting pending queries", logPrefix); abortAllInFlight(new ClosedConnectionException("Channel was force-closed")); ctx.channel().close(); - return; } else if (in instanceof HeartbeatException) { abortAllInFlight( new ClosedConnectionException("Heartbeat query failed", ((HeartbeatException) in))); ctx.close(); + } else if (in instanceof RequestMessage) { + write(ctx, (RequestMessage) in, promise); + } else if (in instanceof ResponseCallback) { + cancel(ctx, (ResponseCallback) in, promise); + } else { + promise.setFailure( + new IllegalArgumentException("Unsupported message type " + in.getClass().getName())); } + } - assert in instanceof RequestMessage; + private void write(ChannelHandlerContext ctx, RequestMessage message, ChannelPromise promise) { if (closingGracefully) { promise.setFailure(new IllegalStateException("Channel is closing")); return; @@ -122,7 +127,6 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) reportAvailableIds(); - RequestMessage message = (RequestMessage) in; LOG.debug("[{}] Writing {} on stream id {}", logPrefix, message.responseCallback, streamId); Frame frame = Frame.forRequest( @@ -144,6 +148,48 @@ public void write(ChannelHandlerContext ctx, Object in, ChannelPromise promise) } } + private void cancel( + ChannelHandlerContext ctx, ResponseCallback responseCallback, ChannelPromise promise) { + Integer streamId = inFlight.inverse().remove(responseCallback); + if (streamId == null) { + LOG.debug( + "[{}] Received cancellation request for unknown callback {}, skipping", + logPrefix, + responseCallback); + } else { + LOG.debug( + "[{}] Cancelled callback {} for stream id {}", logPrefix, responseCallback, streamId); + if (closingGracefully && inFlight.isEmpty()) { + LOG.debug("[{}] Last pending query was cancelled, closing channel", logPrefix); + ctx.channel().close(); + } else { + // We can't release the stream id, because a response might still come back from the server. + // Keep track of how many of those ids are held, because we want to replace the channel if + // it becomes too high. + orphanStreamIds += 1; + if (orphanStreamIds > maxOrphanStreamIds) { + LOG.debug( + "[{}] Orphan stream ids exceeded the configured threshold ({}), closing gracefully", + logPrefix, + maxOrphanStreamIds); + startGracefulShutdown(ctx); + } + } + } + promise.setSuccess(); + } + + private void startGracefulShutdown(ChannelHandlerContext ctx) { + if (inFlight.isEmpty()) { + LOG.debug("[{}] No pending queries, completing graceful shutdown now", logPrefix); + ctx.channel().close(); + } else { + LOG.debug("[{}] There are pending queries, delaying graceful shutdown", logPrefix); + closingGracefully = true; + closeStartedFuture.setSuccess(); + } + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Frame responseFrame = (Frame) msg; @@ -163,12 +209,16 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } } else { ResponseCallback responseCallback = inFlight.get(streamId); - LOG.debug( - "[{}] Got response on stream id {}, completing {}", - logPrefix, - streamId, - responseCallback); - if (responseCallback != null) { + if (responseCallback == null) { + LOG.debug("[{}] Got response on orphan stream id {}, releasing", logPrefix, streamId); + release(streamId, ctx); + orphanStreamIds -= 1; + } else { + LOG.debug( + "[{}] Got response on stream id {}, completing {}", + logPrefix, + streamId, + responseCallback); if (!responseCallback.holdStreamId()) { release(streamId, ctx); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java index 8dbf5c0e0c9..b20ae02ee49 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -21,6 +21,10 @@ * The outcome of a request sent to a Cassandra node. * *

      This comes into play after the request has been successfully written to the channel. + * + *

      Due to internal implementation constraints, different instances of this type must not be equal + * to each other (they are stored in a {@code BiMap} in {@link InFlightHandler}); reference equality + * should be appropriate in all cases. */ public interface ResponseCallback { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index a40509d5d1b..971ce2d071f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -73,6 +73,7 @@ public class CqlPrepareHandler private final RetryPolicy retryPolicy; private final Boolean prepareOnAllNodes; private final CqlPrepareProcessor processor; + private volatile InitialPrepareCallback initialCallback; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -124,7 +125,12 @@ public PreparedStatement syncResult() { private ScheduledFuture scheduleTimeout(Duration timeout) { if (timeout.toNanos() > 0) { return scheduler.schedule( - () -> setFinalError(new DriverTimeoutException("Query timed out after " + timeout)), + () -> { + setFinalError(new DriverTimeoutException("Query timed out after " + timeout)); + if (initialCallback != null) { + initialCallback.cancel(); + } + }, timeout.toNanos(), TimeUnit.NANOSECONDS); } else { @@ -154,7 +160,8 @@ private void sendRequest(Node node, int retryCount) { if (channel == null) { setFinalError(AllNodesFailedException.fromErrors(this.errors)); } else { - InitialPrepareCallback initialPrepareCallback = new InitialPrepareCallback(node, retryCount); + InitialPrepareCallback initialPrepareCallback = + new InitialPrepareCallback(node, channel, retryCount); channel .write(message, false, Frame.NO_PAYLOAD, initialPrepareCallback) .addListener(initialPrepareCallback); @@ -254,12 +261,14 @@ private void setFinalError(Throwable error) { private class InitialPrepareCallback implements ResponseCallback, GenericFutureListener> { private final Node node; + private final DriverChannel channel; // How many times we've invoked the retry policy and it has returned a "retry" decision (0 for // the first attempt of each execution). private final int retryCount; - private InitialPrepareCallback(Node node, int retryCount) { + private InitialPrepareCallback(Node node, DriverChannel channel, int retryCount) { this.node = node; + this.channel = channel; this.retryCount = retryCount; } @@ -275,7 +284,13 @@ public void operationComplete(Future future) throws Exception { recordError(node, future.cause()); sendRequest(null, retryCount); // try next host } else { - LOG.debug("[{}] Request sent to {}", logPrefix, node); + if (result.isDone()) { + // Might happen if the timeout just fired + cancel(); + } else { + LOG.debug("[{}] Request sent to {}", logPrefix, node); + initialCallback = this; + } } } @@ -365,6 +380,16 @@ public void onFailure(Throwable error) { processRetryDecision(decision, error); } + public void cancel() { + try { + if (!channel.closeFuture().isDone()) { + this.channel.cancel(this); + } + } catch (Throwable t) { + LOG.warn("[{}] Error cancelling", logPrefix, t); + } + } + @Override public String toString() { return logPrefix; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 7a970a3c82f..39b7809f0a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -90,6 +90,7 @@ public class CqlRequestHandler private final Duration timeout; private final ScheduledFuture timeoutFuture; private final List> pendingExecutions; + private final List inFlightCallbacks; private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; @@ -146,6 +147,7 @@ public class CqlRequestHandler LOG.debug("[{}] Request is not idempotent, no speculative executions", logPrefix); this.pendingExecutions = null; } + this.inFlightCallbacks = new CopyOnWriteArrayList<>(); sendRequest(null, 0, 0); } @@ -252,6 +254,9 @@ private void cancelScheduledTasks() { future.cancel(false); } } + for (NodeResponseCallback callback : inFlightCallbacks) { + callback.cancel(); + } } private void setFinalResult( @@ -333,11 +338,19 @@ public void operationComplete(Future future) throws Exception { sendRequest(null, execution, retryCount); // try next node } else { LOG.debug("[{}] Request sent on {}", logPrefix, channel); + if (result.isDone()) { + // If the handler completed since the last time we checked, cancel directly because we + // don't know if cancelScheduledTasks() has run yet + cancel(); + } else { + inFlightCallbacks.add(this); + } } } @Override public void onResponse(Frame responseFrame) { + inFlightCallbacks.remove(this); if (result.isDone()) { return; } @@ -474,6 +487,7 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { @Override public void onFailure(Throwable error) { + inFlightCallbacks.remove(this); if (result.isDone()) { return; } @@ -485,6 +499,16 @@ public void onFailure(Throwable error) { processRetryDecision(decision, error); } + public void cancel() { + try { + if (!channel.closeFuture().isDone()) { + this.channel.cancel(this); + } + } catch (Throwable t) { + LOG.warn("[{}] Error cancelling", logPrefix, t); + } + } + @Override public String toString() { return logPrefix; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index a333834a456..ffc17158835 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -39,14 +39,14 @@ import com.google.common.collect.Sets; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -184,6 +184,7 @@ private class SingleThreaded { private final EventBus eventBus; // The channels that are currently connecting private final List> pendingChannels = new ArrayList<>(); + private final Set closingChannels = new HashSet<>(); private final Reconnection reconnection; private final Object configListenerKey; @@ -291,6 +292,13 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { LOG.debug("[{}] New channel added {}", logPrefix, channel); channels.add(channel); eventBus.fire(ChannelEvent.channelOpened(node)); + channel + .closeStartedFuture() + .addListener( + f -> + adminExecutor + .submit(() -> onChannelCloseStarted(channel)) + .addListener(UncaughtExceptions::log)); channel .closeFuture() .addListener( @@ -325,11 +333,12 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { return currentCount >= wantedCount; } - private void onChannelClosed(DriverChannel channel) { + private void onChannelCloseStarted(DriverChannel channel) { assert adminExecutor.inEventLoop(); if (!isClosing) { - LOG.debug("[{}] Lost channel {}", logPrefix, channel); + LOG.debug("[{}] Channel {} started graceful shutdown", logPrefix, channel); channels.remove(channel); + closingChannels.add(channel); eventBus.fire(ChannelEvent.channelClosed(node)); if (!reconnection.isRunning()) { reconnection.start(); @@ -337,6 +346,24 @@ private void onChannelClosed(DriverChannel channel) { } } + private void onChannelClosed(DriverChannel channel) { + assert adminExecutor.inEventLoop(); + if (!isClosing) { + // Either it was closed abruptly and was still in the live set, or it was an orderly + // shutdown and it had moved to the closing set. + if (channels.remove(channel)) { + LOG.debug("[{}] Lost channel {}", logPrefix, channel); + eventBus.fire(ChannelEvent.channelClosed(node)); + if (!reconnection.isRunning()) { + reconnection.start(); + } + } else { + LOG.debug("[{}] Channel {} completed graceful shutdown", logPrefix, channel); + closingChannels.remove(channel); + } + } + } + private void resize(NodeDistance newDistance) { assert adminExecutor.inEventLoop(); distance = newDistance; @@ -392,9 +419,26 @@ private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { } keyspaceName = newKeyspaceName; setKeyspaceFuture = new CompletableFuture<>(); - // Note that we don't handle errors; if the keyspace switch fails, the channel closes - forAllChannels( - ch -> ch.setKeyspace(newKeyspaceName), () -> setKeyspaceFuture.complete(null), null); + + // Switch the keyspace on all live channels. + // We can read the size before iterating because mutations are confined to this thread: + int toSwitch = channels.size(); + if (toSwitch == 0) { + setKeyspaceFuture.complete(null); + } else { + AtomicInteger remaining = new AtomicInteger(toSwitch); + for (DriverChannel channel : channels) { + channel + .setKeyspace(newKeyspaceName) + .addListener( + f -> { + // Don't handle errors: if a channel fails to switch the keyspace, it closes + if (remaining.decrementAndGet() == 0) { + setKeyspaceFuture.complete(null); + } + }); + } + } // pending channels were scheduled with the old keyspace name, ensure they eventually switch for (CompletionStage channelFuture : pendingChannels) { @@ -421,36 +465,28 @@ private void close() { reconnection.stop(); eventBus.unregister(configListenerKey, ConfigChangeEvent.class); - forAllChannels( - channel -> { - eventBus.fire(ChannelEvent.channelClosed(node)); - return channel.close(); - }, - () -> closeFuture.complete(null), - (channel, error) -> LOG.warn("[{}] Error closing channel {}", logPrefix, channel, error)); - } - - private void forAllChannels( - Function> task, - Runnable whenAllDone, - BiConsumer onError) { - assert adminExecutor.inEventLoop(); - // we can read the size before iterating because it's only mutated from this thread - int todo = channels.size(); - if (todo == 0) { - whenAllDone.run(); + // Close all channels, the pool future completes when all the channels futures have completed + int toClose = closingChannels.size() + channels.size(); + if (toClose == 0) { + closeFuture.complete(null); } else { - AtomicInteger done = new AtomicInteger(); + AtomicInteger remaining = new AtomicInteger(toClose); + GenericFutureListener> channelCloseListener = + f -> { + if (!f.isSuccess()) { + LOG.warn("[{}] Error closing channel", logPrefix, f.cause()); + } + if (remaining.decrementAndGet() == 0) { + closeFuture.complete(null); + } + }; for (DriverChannel channel : channels) { - task.apply(channel) - .addListener( - f -> { - if (!f.isSuccess() && onError != null) { - onError.accept(channel, f.cause()); - } else if (done.incrementAndGet() == todo) { - whenAllDone.run(); - } - }); + eventBus.fire(ChannelEvent.channelClosed(node)); + channel.close().addListener(channelCloseListener); + } + for (DriverChannel channel : closingChannels) { + // don't fire the close event, onChannelCloseStarted() already did it + channel.closeFuture().addListener(channelCloseListener); } } } @@ -463,6 +499,9 @@ private void forceClose() { for (DriverChannel channel : channels) { channel.forceClose(); } + for (DriverChannel channel : closingChannels) { + channel.forceClose(); + } } private int getConfiguredSize(NodeDistance distance) { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index fdc5990645e..ff9ed03b286 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -79,6 +79,18 @@ datastax-java-driver { # This must be between 1 and 32768. max-requests-per-connection = 32768 + # The maximum number of "orphaned" requests before a connection gets closed automatically. + # + # Sometimes the driver writes to a node but stops listening for a response (for example if the + # request timed out, or was completed by another node). But we can't safely reuse the stream id + # on this connection until we know for sure that the server is done with it. Therefore the id is + # marked as "orphaned" until we get a response from the node. + # + # If the response never comes (or is lost because of a network issue), orphaned ids can + # accumulate over time, eventually affecting the connection's throughput. So we monitor them and + # close the connection above a given threshold (the pool will replace it). + max-orphan-requests = 24576 + heartbeat { # The heartbeat interval. If a connection stays idle for that duration (no reads), the driver # sends a dummy message on it to make sure it's still alive. If not, the connection is diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index f7538f0c8db..22c8b03f128 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -234,8 +234,10 @@ protected void initChannel(Channel channel) throws Exception { new InFlightHandler( protocolVersion, new StreamIdGenerator(maxRequestsPerConnection), + Integer.MAX_VALUE, setKeyspaceTimeoutMillis, availableIdsHolder, + channel.newPromise(), null, "test"); ProtocolInitHandler initHandler = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 51e0625c434..f1b6449672c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -54,8 +54,10 @@ public void setup() { new InFlightHandler( CoreProtocolVersion.V3, streamIds, + Integer.MAX_VALUE, SET_KEYSPACE_TIMEOUT_MILLIS, null, + channel.newPromise(), null, "test")); writeCoalescer = new MockWriteCoalescer(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 9b503e87788..1713ab0b697 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -45,6 +45,7 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { private static final Query QUERY = new Query("select * from foo"); private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; + private static final int MAX_ORPHAN_IDS = 10; @Mock private StreamIdGenerator streamIds; @@ -118,8 +119,10 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); // When RuntimeException mockCause = new RuntimeException("test"); @@ -131,13 +134,15 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl } @Test - public void should_delay_graceful_close_until_all_pending_complete() { + public void should_delay_graceful_close_and_complete_when_last_pending_completes() { // Given addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); // When channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); @@ -155,6 +160,32 @@ public void should_delay_graceful_close_until_all_pending_complete() { assertThat(channel.closeFuture()).isSuccess(); } + @Test + public void should_delay_graceful_close_and_complete_when_last_pending_cancelled() { + // Given + addToPipeline(); + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + + // When + channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); + + // Then + // not closed yet because there is one pending request + assertThat(channel.closeFuture()).isNotDone(); + + // When + // cancelling pending request + channel.write(responseCallback); + + // Then + assertThat(channel.closeFuture()).isSuccess(); + } + @Test public void should_graceful_close_immediately_if_no_pending() { // Given @@ -173,8 +204,10 @@ public void should_refuse_new_writes_during_graceful_close() { addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); // When channel.write(DriverChannel.GRACEFUL_CLOSE_MESSAGE); @@ -194,6 +227,92 @@ public void should_refuse_new_writes_during_graceful_close() { .hasMessage("Channel is closing")); } + @Test + public void should_close_gracefully_if_orphan_ids_above_max_and_pending_requests() { + // Given + addToPipeline(); + // Generate n orphan ids by writing and cancelling the requests: + for (int i = 0; i < MAX_ORPHAN_IDS; i++) { + Mockito.when(streamIds.acquire()).thenReturn(i); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + channel.writeAndFlush(responseCallback).awaitUninterruptibly(); + } + // Generate another request that is pending and not cancelled: + Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); + MockResponseCallback pendingResponseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage( + QUERY, false, Frame.NO_PAYLOAD, pendingResponseCallback)) + .awaitUninterruptibly(); + + // When + // Generate the n+1th orphan id that makes us go above the threshold + Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS + 1); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + channel.writeAndFlush(responseCallback).awaitUninterruptibly(); + + // Then + // Channel should be closing gracefully. There's no way to observe that from the outside, so + // write another request and check that it's rejected: + assertThat(channel.closeFuture()).isNotDone(); + ChannelFuture otherWriteFuture = + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + assertThat(otherWriteFuture) + .isFailed( + e -> + assertThat(e) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Channel is closing")); + + // When + // Cancel the last pending request + channel.writeAndFlush(pendingResponseCallback).awaitUninterruptibly(); + + // Then + // The graceful shutdown completes + assertThat(channel.closeFuture()).isSuccess(); + } + + @Test + public void should_close_immediately_if_orphan_ids_above_max_and_no_pending_requests() { + // Given + addToPipeline(); + // Generate n orphan ids by writing and cancelling the requests: + for (int i = 0; i < MAX_ORPHAN_IDS; i++) { + Mockito.when(streamIds.acquire()).thenReturn(i); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + channel.writeAndFlush(responseCallback).awaitUninterruptibly(); + } + + // When + // Generate the n+1th orphan id that makes us go above the threshold + Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + channel.writeAndFlush(responseCallback).awaitUninterruptibly(); + + // Then + // Channel should close immediately since no active pending requests. + assertThat(channel.closeFuture()).isSuccess(); + } + @Test public void should_fail_all_pending_when_force_closed() throws Throwable { // Given @@ -201,10 +320,14 @@ public void should_fail_all_pending_when_force_closed() throws Throwable { Mockito.when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)) + .awaitUninterruptibly(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)) + .awaitUninterruptibly(); // When channel.write(DriverChannel.FORCEFUL_CLOSE_MESSAGE); @@ -225,10 +348,14 @@ public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() Mockito.when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)); - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)) + .awaitUninterruptibly(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)) + .awaitUninterruptibly(); // When RuntimeException mockException = new RuntimeException("test"); @@ -251,9 +378,10 @@ public void should_hold_stream_id_if_required() { MockResponseCallback responseCallback = new MockResponseCallback(true); // When - channel.writeAndFlush( - new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); - channel.runPendingTasks(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); // Then // notify callback of stream id @@ -355,8 +483,10 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { new InFlightHandler( CoreProtocolVersion.V3, streamIds, + MAX_ORPHAN_IDS, SET_KEYSPACE_TIMEOUT_MILLIS, null, + channel.newPromise(), eventCallback, "test")); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 60b07a0b3f1..c10c8f42034 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -80,7 +80,14 @@ public void setup() { .addLast( "inflight", new InFlightHandler( - CoreProtocolVersion.V4, new StreamIdGenerator(100), 100, null, null, "test")); + CoreProtocolVersion.V4, + new StreamIdGenerator(100), + Integer.MAX_VALUE, + 100, + null, + channel.newPromise(), + null, + "test")); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 4bec3f3c467..a9294effa8d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -166,6 +166,7 @@ public void should_retry_in_speculative_executions( new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); // do not simulate a response from node1. The request will stay hanging for the rest of this test harness.nextScheduledTask(); // Discard the timeout task @@ -184,6 +185,9 @@ public void should_retry_in_speculative_executions( // The second execution should move to node3 and complete the request assertThat(resultSetFuture).isSuccess(); + + // The request to node1 was still in flight, it should have been cancelled + node1Behavior.verifyCancellation(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 0add8361118..70fdd7dd855 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -107,4 +107,8 @@ public void mockFollowupRequest(Class expectedMessage, Frame writePromise2.setSuccess(null); callbackFuture2.thenAccept(callback -> callback.onResponse(responseFrame)); } + + public void verifyCancellation() { + Mockito.verify(channel).cancel(any(ResponseCallback.class)); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java index 1aacd7a7177..5d79dda556b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java @@ -242,7 +242,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { } @Test - public void should_reconnect_when_channel_dies() throws Exception { + public void should_reconnect_when_channel_closes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); @@ -292,6 +292,56 @@ public void should_reconnect_when_channel_dies() throws Exception { factoryHelper.verifyNoMoreCalls(); } + @Test + public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + // Simulate graceful shutdown on channel2 + ((ChannelPromise) channel2.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + factoryHelper.waitForCall(ADDRESS); + + channel3Future.complete(channel3); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel3); + + factoryHelper.verifyNoMoreCalls(); + } + @Test public void should_shrink_outside_of_reconnection() throws Exception { Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); @@ -770,16 +820,17 @@ public void should_close_all_channels_when_closed() throws Exception { DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(2); - CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init .success(ADDRESS, channel1) .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") + .success(ADDRESS, channel3) // reconnection - .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); @@ -788,12 +839,17 @@ public void should_close_all_channels_when_closed() throws Exception { factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); - // Reconnection should have kicked in and started to open a channel, do not complete it yet + // Simulate graceful shutdown on channel3 + ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); + + // Reconnection should have kicked in and started to open channel4, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(ADDRESS, 1); @@ -804,21 +860,23 @@ public void should_close_all_channels_when_closed() throws Exception { Mockito.verify(channel1).close(); Mockito.verify(channel2).close(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + // The closing channel was not closed again + Mockito.verify(channel3, never()).close(); // Complete the reconnecting channel - channel3Future.complete(channel3); + channel4Future.complete(channel4); waitForPendingAdminTasks(); // It should be force-closed once we find out the pool was closed - Mockito.verify(channel3).forceClose(); + Mockito.verify(channel4).forceClose(); // No events because the channel was never really associated to the pool inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); - // Note that we don't wait for reconnected channels to close, so the pool only depends on - // channel 1 and 2 + // We don't wait for reconnected channels to close, so the pool only depends on channel 1 to 3 ((ChannelPromise) channel1.closeFuture()).setSuccess(); ((ChannelPromise) channel2.closeFuture()).setSuccess(); + ((ChannelPromise) channel3.closeFuture()).setSuccess(); assertThat(closeFuture).isSuccess(); @@ -833,16 +891,17 @@ public void should_force_close_all_channels_when_force_closed() throws Exception DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(2); - CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) // init .success(ADDRESS, channel1) .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") + .success(ADDRESS, channel3) // reconnection - .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) .build(); InOrder inOrder = Mockito.inOrder(eventBus); @@ -854,7 +913,12 @@ public void should_force_close_all_channels_when_force_closed() throws Exception assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + + // Simulate graceful shutdown on channel3 + ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); @@ -863,25 +927,27 @@ public void should_force_close_all_channels_when_force_closed() throws Exception CompletionStage closeFuture = pool.forceCloseAsync(); waitForPendingAdminTasks(); - // The two original channels were force-closed - Mockito.verify(channel1).close(); - Mockito.verify(channel2).close(); + // The three original channels were force-closed + Mockito.verify(channel1).forceClose(); + Mockito.verify(channel2).forceClose(); + Mockito.verify(channel3).forceClose(); + // Only two events because the one for channel3 was sent earlier inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); // Complete the reconnecting channel - channel3Future.complete(channel3); + channel4Future.complete(channel4); waitForPendingAdminTasks(); // It should be force-closed once we find out the pool was closed - Mockito.verify(channel3).forceClose(); + Mockito.verify(channel4).forceClose(); // No events because the channel was never really associated to the pool inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); - // Note that we don't wait for reconnected channels to close, so the pool only depends on - // channel 1 and 2 + // We don't wait for reconnected channels to close, so the pool only depends on channel 1-3 ((ChannelPromise) channel1.closeFuture()).setSuccess(); ((ChannelPromise) channel2.closeFuture()).setSuccess(); + ((ChannelPromise) channel3.closeFuture()).setSuccess(); assertThat(closeFuture).isSuccess(); @@ -892,9 +958,11 @@ private DriverChannel newMockDriverChannel(int id) { DriverChannel channel = Mockito.mock(DriverChannel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); + DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(null, adminExecutor); Mockito.when(channel.close()).thenReturn(closeFuture); Mockito.when(channel.forceClose()).thenReturn(closeFuture); Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + Mockito.when(channel.closeStartedFuture()).thenReturn(closeStartedFuture); Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) .thenReturn(adminExecutor.newSucceededFuture(null)); Mockito.when(channel.toString()).thenReturn("channel" + id); From f75366b18c2eb7ceab5f21df8c93765cf2228b2d Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 30 Jun 2017 17:12:34 -0700 Subject: [PATCH 109/742] Fix bug in relative policy options --- .../oss/driver/api/core/config/CoreDriverOption.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 940bdd15078..69da6ca1102 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -54,8 +54,8 @@ public enum CoreDriverOption implements DriverOption { SPECULATIVE_EXECUTION_POLICY_ROOT("speculative-execution-policy", true), RECONNECTION_POLICY_ROOT("connection.reconnection-policy", true), - RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY("connection.reconnection.config.base-delay", false), - RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY("connection.reconnection.config.max-delay", false), + RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY("base-delay", false), + RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY("max-delay", false), PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), REPREPARE_ENABLED("prepared-statements.reprepare-on-up.enabled", true), @@ -74,7 +74,7 @@ public enum CoreDriverOption implements DriverOption { RELATIVE_PLAIN_TEXT_AUTH_PASSWORD("password", false), SSL_ENGINE_FACTORY_ROOT("ssl-engine-factory", false), - RELATIVE_DEFAULT_SSL_CIPHER_SUITES("ssl.config.cipher-suites", false), + RELATIVE_DEFAULT_SSL_CIPHER_SUITES("cipher-suites", false), METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), From 2665b39c34c0229ed0a1bf0e02c4b0241fd1b740 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 2 Jul 2017 17:59:30 -0700 Subject: [PATCH 110/742] JAVA-1539: Configure for deployment to Maven central --- changelog/README.md | 1 + pom.xml | 112 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 29c48d23fc5..730f47d34cf 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1539: Configure for deployment to Maven central - [new feature] JAVA-1519: Close channel if number of orphan stream ids exceeds a configurable threshold - [new feature] JAVA-1529: Make configuration reloadable diff --git a/pom.xml b/pom.xml index 1353f192340..aaae9168186 100644 --- a/pom.xml +++ b/pom.xml @@ -113,10 +113,6 @@ maven-surefire-plugin 2.19.1 - - maven-javadoc-plugin - 2.10.4 - maven-shade-plugin 3.0.0 @@ -134,6 +130,27 @@ + + maven-source-plugin + 3.0.1 + + + maven-javadoc-plugin + 2.10.4 + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + + + maven-gpg-plugin + 1.5 + + + maven-release-plugin + 2.5.3 + @@ -218,12 +235,99 @@ limitations under the License.]]> + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://oss.sonatype.org/ + false + true + + + + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + maven-javadoc-plugin -Xdoclint:none + + + attach-javadocs + + jar + + + + + + maven-release-plugin + + @{project.version} + true + false + release + deploy + + + + release + + + + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + Apache License Version 2.0 + + + + scm:git:git@github.com:datastax/java-driver.git + scm:git:git@github.com:datastax/java-driver.git + https://github.com/datastax/java-driver + HEAD + + + + Various + DataStax + + From 632e358fe783b50e039caff43fdd18d0cb721b7b Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Jul 2017 09:42:04 -0700 Subject: [PATCH 111/742] Split ChannelPool tests into multiple classes --- .../core/pool/ChannelPoolInitTest.java | 177 ++++ .../core/pool/ChannelPoolKeyspaceTest.java | 119 +++ .../core/pool/ChannelPoolReconnectTest.java | 136 +++ .../core/pool/ChannelPoolResizeTest.java | 420 ++++++++ .../core/pool/ChannelPoolShutdownTest.java | 178 ++++ .../internal/core/pool/ChannelPoolTest.java | 984 ------------------ .../core/pool/ChannelPoolTestBase.java | 116 +++ 7 files changed, 1146 insertions(+), 984 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java delete mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java new file mode 100644 index 00000000000..2ab657688eb --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class ChannelPoolInitTest extends ChannelPoolTestBase { + + @Test + public void should_initialize_when_all_channels_succeed() throws Exception { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + .build(); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + + assertThat(poolFuture) + .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); + Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_initialize_when_all_channels_fail() throws Exception { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + .build(); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_indicate_when_keyspace_failed_on_all_channels() { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) + .build(); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); + } + + @Test + public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + ClusterNameMismatchException error = + new ClusterNameMismatchException(ADDRESS, "actual", "expected"); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .failure(ADDRESS, error) + .failure(ADDRESS, error) + .failure(ADDRESS, error) + .build(); + + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + + Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_reconnect_when_init_incomplete() throws Exception { + // Short delay so we don't have to wait in the test + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // Init: 1 channel fails, the other succeeds + .failure(ADDRESS, "mock channel init failure") + .success(ADDRESS, channel1) + // 1st reconnection + .pending(ADDRESS, channel2Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + // A reconnection should have been scheduled + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + channel2Future.complete(channel2); + factoryHelper.waitForCalls(ADDRESS, 1); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2); + + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java new file mode 100644 index 00000000000..c6ace1f3b8e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ChannelPoolKeyspaceTest extends ChannelPoolTestBase { + + @Test + public void should_switch_keyspace_on_existing_channels() throws Exception { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .build(); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); + CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + + Mockito.verify(channel1).setKeyspace(newKeyspace); + Mockito.verify(channel2).setKeyspace(newKeyspace); + + assertThat(setKeyspaceFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_switch_keyspace_on_pending_channels() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + CompletableFuture channel1Future = new CompletableFuture<>(); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel1Future) + .pending(ADDRESS, channel2Future) + .build(); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + + // Check that reconnection has kicked in, but do not complete it yet + Mockito.verify(reconnectionSchedule).nextDelay(); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + factoryHelper.waitForCalls(ADDRESS, 2); + + // Switch keyspace, it succeeds immediately since there is no active channel + CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); + CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); + waitForPendingAdminTasks(); + assertThat(setKeyspaceFuture).isSuccess(); + + // Now let the two channels succeed to complete the reconnection + channel1Future.complete(channel1); + channel2Future.complete(channel2); + waitForPendingAdminTasks(); + + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + Mockito.verify(channel1).setKeyspace(newKeyspace); + Mockito.verify(channel2).setKeyspace(newKeyspace); + + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java new file mode 100644 index 00000000000..7fffee347ba --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import io.netty.channel.ChannelPromise; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.times; + +public class ChannelPoolReconnectTest extends ChannelPoolTestBase { + + @Test + public void should_reconnect_when_channel_closes() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + // Simulate fatal error on channel2 + ((ChannelPromise) channel2.closeFuture()) + .setFailure(new Exception("mock channel init failure")); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + factoryHelper.waitForCall(ADDRESS); + + channel3Future.complete(channel3); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel3); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // reconnection + .pending(ADDRESS, channel3Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + // Simulate graceful shutdown on channel2 + ((ChannelPromise) channel2.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + factoryHelper.waitForCall(ADDRESS); + + channel3Future.complete(channel3); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel3); + + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java new file mode 100644 index 00000000000..ff881684c73 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class ChannelPoolResizeTest extends ChannelPoolTestBase { + + @Test + public void should_shrink_outside_of_reconnection() throws Exception { + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 4); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(NODE)); + + pool.resize(NodeDistance.LOCAL); + + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + + assertThat(pool.channels).containsOnly(channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_shrink_during_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .failure(ADDRESS, "mock channel init failure") + .failure(ADDRESS, "mock channel init failure") + // reconnection + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 4); + waitForPendingAdminTasks(); + + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // A reconnection should have been scheduled to add the missing channels, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + pool.resize(NodeDistance.LOCAL); + + waitForPendingAdminTasks(); + + // Now allow the reconnected channels to complete initialization + channel3Future.complete(channel3); + channel4Future.complete(channel4); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + + // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + assertThat(pool.channels).containsOnly(channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_grow_outside_of_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // growth attempt + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + pool.resize(NodeDistance.REMOTE); + waitForPendingAdminTasks(); + + // The resizing should have triggered a reconnection + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_grow_during_reconnection() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .failure(ADDRESS, "mock channel init failure") + // first reconnection attempt + .pending(ADDRESS, channel2Future) + // extra reconnection attempt after we realize the pool must grow + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + + // A reconnection should have been scheduled to add the missing channel, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + pool.resize(NodeDistance.REMOTE); + + waitForPendingAdminTasks(); + + // Complete the channel for the first reconnection, bringing the count to 2 + channel2Future.complete(channel2); + factoryHelper.waitForCall(ADDRESS); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2); + + // A second attempt should have been scheduled since we're now still under the target size + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + // Same reconnection is still running, no additional events + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); + + // Two more channels get opened, bringing us to the target count + factoryHelper.waitForCalls(ADDRESS, 2); + channel3Future.complete(channel3); + channel4Future.complete(channel4); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + // growth attempt + .success(ADDRESS, channel3) + .success(ADDRESS, channel4) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Simulate a configuration change + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // It should have triggered a reconnection + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_resize_during_reconnection_if_config_changes() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + CompletableFuture channel3Future = new CompletableFuture<>(); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .failure(ADDRESS, "mock channel init failure") + // first reconnection attempt + .pending(ADDRESS, channel2Future) + // extra reconnection attempt after we realize the pool must grow + .pending(ADDRESS, channel3Future) + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1); + + // A reconnection should have been scheduled to add the missing channel, don't complete yet + Mockito.verify(reconnectionSchedule).nextDelay(); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + + // Simulate a configuration change + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // Complete the channel for the first reconnection, bringing the count to 2 + channel2Future.complete(channel2); + factoryHelper.waitForCall(ADDRESS); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2); + + // A second attempt should have been scheduled since we're now still under the target size + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + // Same reconnection is still running, no additional events + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); + + // Two more channels get opened, bringing us to the target count + factoryHelper.waitForCalls(ADDRESS, 2); + channel3Future.complete(channel3); + channel4Future.complete(channel4); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_ignore_config_change_if_not_relevant() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + assertThat(pool.channels).containsOnly(channel1, channel2); + + // Config changes, but not for our distance + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(1); + eventBus.fire(ConfigChangeEvent.INSTANCE); + waitForPendingAdminTasks(); + + // It should not have triggered a reconnection + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java new file mode 100644 index 00000000000..02d175ec7e6 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.channel.ChannelEvent; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import io.netty.channel.ChannelPromise; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class ChannelPoolShutdownTest extends ChannelPoolTestBase { + + @Test + public void should_close_all_channels_when_closed() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + // reconnection + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + + // Simulate graceful shutdown on channel3 + ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); + + // Reconnection should have kicked in and started to open channel4, do not complete it yet + Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCalls(ADDRESS, 1); + + CompletionStage closeFuture = pool.closeAsync(); + waitForPendingAdminTasks(); + + // The two original channels were closed normally + Mockito.verify(channel1).close(); + Mockito.verify(channel2).close(); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + // The closing channel was not closed again + Mockito.verify(channel3, never()).close(); + + // Complete the reconnecting channel + channel4Future.complete(channel4); + waitForPendingAdminTasks(); + + // It should be force-closed once we find out the pool was closed + Mockito.verify(channel4).forceClose(); + // No events because the channel was never really associated to the pool + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); + + // We don't wait for reconnected channels to close, so the pool only depends on channel 1 to 3 + ((ChannelPromise) channel1.closeFuture()).setSuccess(); + ((ChannelPromise) channel2.closeFuture()).setSuccess(); + ((ChannelPromise) channel3.closeFuture()).setSuccess(); + + assertThat(closeFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_force_close_all_channels_when_force_closed() throws Exception { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + DriverChannel channel3 = newMockDriverChannel(3); + DriverChannel channel4 = newMockDriverChannel(4); + CompletableFuture channel4Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + .success(ADDRESS, channel2) + .success(ADDRESS, channel3) + // reconnection + .pending(ADDRESS, channel4Future) + .build(); + InOrder inOrder = Mockito.inOrder(eventBus); + + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + + factoryHelper.waitForCalls(ADDRESS, 3); + waitForPendingAdminTasks(); + + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + + // Simulate graceful shutdown on channel3 + ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); + + // Reconnection should have kicked in and started to open a channel, do not complete it yet + Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCalls(ADDRESS, 1); + + CompletionStage closeFuture = pool.forceCloseAsync(); + waitForPendingAdminTasks(); + + // The three original channels were force-closed + Mockito.verify(channel1).forceClose(); + Mockito.verify(channel2).forceClose(); + Mockito.verify(channel3).forceClose(); + // Only two events because the one for channel3 was sent earlier + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + + // Complete the reconnecting channel + channel4Future.complete(channel4); + waitForPendingAdminTasks(); + + // It should be force-closed once we find out the pool was closed + Mockito.verify(channel4).forceClose(); + // No events because the channel was never really associated to the pool + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); + + // We don't wait for reconnected channels to close, so the pool only depends on channel 1-3 + ((ChannelPromise) channel1.closeFuture()).setSuccess(); + ((ChannelPromise) channel2.closeFuture()).setSuccess(); + ((ChannelPromise) channel3.closeFuture()).setSuccess(); + + assertThat(closeFuture).isSuccess(); + + factoryHelper.verifyNoMoreCalls(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java deleted file mode 100644 index 5d79dda556b..00000000000 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTest.java +++ /dev/null @@ -1,984 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.pool; - -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.InvalidKeyspaceException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; -import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; -import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.internal.core.channel.ChannelEvent; -import com.datastax.oss.driver.internal.core.channel.ChannelFactory; -import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; -import com.datastax.oss.driver.internal.core.channel.DriverChannel; -import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; -import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; -import com.datastax.oss.driver.internal.core.context.EventBus; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.context.NettyOptions; -import com.datastax.oss.driver.internal.core.metadata.DefaultNode; -import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; -import com.google.common.util.concurrent.Uninterruptibles; -import io.netty.channel.ChannelPromise; -import io.netty.channel.DefaultChannelPromise; -import io.netty.channel.DefaultEventLoopGroup; -import io.netty.channel.EventLoop; -import io.netty.util.concurrent.Future; -import java.net.InetSocketAddress; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - -public class ChannelPoolTest { - private static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); - private static final Node NODE = new DefaultNode(ADDRESS); - - @Mock private InternalDriverContext context; - @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultProfile; - @Mock private ReconnectionPolicy reconnectionPolicy; - @Mock private ReconnectionSchedule reconnectionSchedule; - @Mock private NettyOptions nettyOptions; - @Mock private ChannelFactory channelFactory; - private EventBus eventBus; - private DefaultEventLoopGroup adminEventLoopGroup; - - @BeforeMethod - public void setup() { - MockitoAnnotations.initMocks(this); - - adminEventLoopGroup = new DefaultEventLoopGroup(1); - - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.config()).thenReturn(config); - Mockito.when(config.defaultProfile()).thenReturn(defaultProfile); - this.eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.channelFactory()).thenReturn(channelFactory); - - Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); - // By default, set a large reconnection delay. Tests that care about reconnection will override - // it. - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - } - - @AfterMethod - public void teardown() { - adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); - } - - @Test - public void should_initialize_when_all_channels_succeed() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) - .build(); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - - assertThat(poolFuture) - .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); - Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_initialize_when_all_channels_fail() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") - .build(); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_indicate_when_keyspace_failed_on_all_channels() { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) - .failure(ADDRESS, new InvalidKeyspaceException("invalid keyspace")) - .build(); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); - } - - @Test - public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - ClusterNameMismatchException error = - new ClusterNameMismatchException(ADDRESS, "actual", "expected"); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .failure(ADDRESS, error) - .failure(ADDRESS, error) - .failure(ADDRESS, error) - .build(); - - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - - Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_reconnect_when_init_incomplete() throws Exception { - // Short delay so we don't have to wait in the test - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - CompletableFuture channel2Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // Init: 1 channel fails, the other succeeds - .failure(ADDRESS, "mock channel init failure") - .success(ADDRESS, channel1) - // 1st reconnection - .pending(ADDRESS, channel2Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - - // A reconnection should have been scheduled - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - channel2Future.complete(channel2); - factoryHelper.waitForCalls(ADDRESS, 1); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_reconnect_when_channel_closes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - CompletableFuture channel3Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - // reconnection - .pending(ADDRESS, channel3Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - - // Simulate fatal error on channel2 - ((ChannelPromise) channel2.closeFuture()) - .setFailure(new Exception("mock channel init failure")); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); - - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCall(ADDRESS); - - channel3Future.complete(channel3); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel3); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - CompletableFuture channel3Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - // reconnection - .pending(ADDRESS, channel3Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - - // Simulate graceful shutdown on channel2 - ((ChannelPromise) channel2.closeStartedFuture()).setSuccess(); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); - - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCall(ADDRESS); - - channel3Future.complete(channel3); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel3); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_shrink_outside_of_reconnection() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - DriverChannel channel4 = newMockDriverChannel(4); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) - .success(ADDRESS, channel4) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 4); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(NODE)); - - pool.resize(NodeDistance.LOCAL); - - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); - - assertThat(pool.channels).containsOnly(channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_shrink_during_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - CompletableFuture channel3Future = new CompletableFuture<>(); - DriverChannel channel4 = newMockDriverChannel(4); - CompletableFuture channel4Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") - // reconnection - .pending(ADDRESS, channel3Future) - .pending(ADDRESS, channel4Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 4); - waitForPendingAdminTasks(); - - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - - // A reconnection should have been scheduled to add the missing channels, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - pool.resize(NodeDistance.LOCAL); - - waitForPendingAdminTasks(); - - // Now allow the reconnected channels to complete initialization - channel3Future.complete(channel3); - channel4Future.complete(channel4); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - assertThat(pool.channels).containsOnly(channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_grow_outside_of_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - DriverChannel channel4 = newMockDriverChannel(4); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - // growth attempt - .success(ADDRESS, channel3) - .success(ADDRESS, channel4) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - - pool.resize(NodeDistance.REMOTE); - waitForPendingAdminTasks(); - - // The resizing should have triggered a reconnection - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_grow_during_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - CompletableFuture channel2Future = new CompletableFuture<>(); - DriverChannel channel3 = newMockDriverChannel(3); - CompletableFuture channel3Future = new CompletableFuture<>(); - DriverChannel channel4 = newMockDriverChannel(4); - CompletableFuture channel4Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .failure(ADDRESS, "mock channel init failure") - // first reconnection attempt - .pending(ADDRESS, channel2Future) - // extra reconnection attempt after we realize the pool must grow - .pending(ADDRESS, channel3Future) - .pending(ADDRESS, channel4Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1); - - // A reconnection should have been scheduled to add the missing channel, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - pool.resize(NodeDistance.REMOTE); - - waitForPendingAdminTasks(); - - // Complete the channel for the first reconnection, bringing the count to 2 - channel2Future.complete(channel2); - factoryHelper.waitForCall(ADDRESS); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2); - - // A second attempt should have been scheduled since we're now still under the target size - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); - // Same reconnection is still running, no additional events - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); - - // Two more channels get opened, bringing us to the target count - factoryHelper.waitForCalls(ADDRESS, 2); - channel3Future.complete(channel3); - channel4Future.complete(channel4); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - DriverChannel channel4 = newMockDriverChannel(4); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - // growth attempt - .success(ADDRESS, channel3) - .success(ADDRESS, channel4) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - - // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); - eventBus.fire(ConfigChangeEvent.INSTANCE); - waitForPendingAdminTasks(); - - // It should have triggered a reconnection - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_resize_during_reconnection_if_config_changes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - CompletableFuture channel2Future = new CompletableFuture<>(); - DriverChannel channel3 = newMockDriverChannel(3); - CompletableFuture channel3Future = new CompletableFuture<>(); - DriverChannel channel4 = newMockDriverChannel(4); - CompletableFuture channel4Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .failure(ADDRESS, "mock channel init failure") - // first reconnection attempt - .pending(ADDRESS, channel2Future) - // extra reconnection attempt after we realize the pool must grow - .pending(ADDRESS, channel3Future) - .pending(ADDRESS, channel4Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1); - - // A reconnection should have been scheduled to add the missing channel, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - - // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); - eventBus.fire(ConfigChangeEvent.INSTANCE); - waitForPendingAdminTasks(); - - // Complete the channel for the first reconnection, bringing the count to 2 - channel2Future.complete(channel2); - factoryHelper.waitForCall(ADDRESS); - waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2); - - // A second attempt should have been scheduled since we're now still under the target size - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); - // Same reconnection is still running, no additional events - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); - - // Two more channels get opened, bringing us to the target count - factoryHelper.waitForCalls(ADDRESS, 2); - channel3Future.complete(channel3); - channel4Future.complete(channel4); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - - assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_ignore_config_change_if_not_relevant() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - - // Config changes, but not for our distance - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(1); - eventBus.fire(ConfigChangeEvent.INSTANCE); - waitForPendingAdminTasks(); - - // It should not have triggered a reconnection - Mockito.verify(reconnectionSchedule, never()).nextDelay(); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_switch_keyspace_on_existing_channels() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .build(); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - assertThat(pool.channels).containsOnly(channel1, channel2); - - CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); - CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); - waitForPendingAdminTasks(); - - Mockito.verify(channel1).setKeyspace(newKeyspace); - Mockito.verify(channel2).setKeyspace(newKeyspace); - - assertThat(setKeyspaceFuture).isSuccess(); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_switch_keyspace_on_pending_channels() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - - DriverChannel channel1 = newMockDriverChannel(1); - CompletableFuture channel1Future = new CompletableFuture<>(); - DriverChannel channel2 = newMockDriverChannel(2); - CompletableFuture channel2Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .failure(ADDRESS, "mock channel init failure") - .failure(ADDRESS, "mock channel init failure") - // reconnection - .pending(ADDRESS, channel1Future) - .pending(ADDRESS, channel2Future) - .build(); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 2); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - - // Check that reconnection has kicked in, but do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); - factoryHelper.waitForCalls(ADDRESS, 2); - - // Switch keyspace, it succeeds immediately since there is no active channel - CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); - CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); - waitForPendingAdminTasks(); - assertThat(setKeyspaceFuture).isSuccess(); - - // Now let the two channels succeed to complete the reconnection - channel1Future.complete(channel1); - channel2Future.complete(channel2); - waitForPendingAdminTasks(); - - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); - Mockito.verify(channel1).setKeyspace(newKeyspace); - Mockito.verify(channel2).setKeyspace(newKeyspace); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_close_all_channels_when_closed() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - DriverChannel channel4 = newMockDriverChannel(4); - CompletableFuture channel4Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) - // reconnection - .pending(ADDRESS, channel4Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - - // Simulate graceful shutdown on channel3 - ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); - - // Reconnection should have kicked in and started to open channel4, do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(ADDRESS, 1); - - CompletionStage closeFuture = pool.closeAsync(); - waitForPendingAdminTasks(); - - // The two original channels were closed normally - Mockito.verify(channel1).close(); - Mockito.verify(channel2).close(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); - // The closing channel was not closed again - Mockito.verify(channel3, never()).close(); - - // Complete the reconnecting channel - channel4Future.complete(channel4); - waitForPendingAdminTasks(); - - // It should be force-closed once we find out the pool was closed - Mockito.verify(channel4).forceClose(); - // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); - - // We don't wait for reconnected channels to close, so the pool only depends on channel 1 to 3 - ((ChannelPromise) channel1.closeFuture()).setSuccess(); - ((ChannelPromise) channel2.closeFuture()).setSuccess(); - ((ChannelPromise) channel3.closeFuture()).setSuccess(); - - assertThat(closeFuture).isSuccess(); - - factoryHelper.verifyNoMoreCalls(); - } - - @Test - public void should_force_close_all_channels_when_force_closed() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); - - DriverChannel channel1 = newMockDriverChannel(1); - DriverChannel channel2 = newMockDriverChannel(2); - DriverChannel channel3 = newMockDriverChannel(3); - DriverChannel channel4 = newMockDriverChannel(4); - CompletableFuture channel4Future = new CompletableFuture<>(); - MockChannelFactoryHelper factoryHelper = - MockChannelFactoryHelper.builder(channelFactory) - // init - .success(ADDRESS, channel1) - .success(ADDRESS, channel2) - .success(ADDRESS, channel3) - // reconnection - .pending(ADDRESS, channel4Future) - .build(); - InOrder inOrder = Mockito.inOrder(eventBus); - - CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); - - factoryHelper.waitForCalls(ADDRESS, 3); - waitForPendingAdminTasks(); - - assertThat(poolFuture).isSuccess(); - ChannelPool pool = poolFuture.toCompletableFuture().get(); - inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); - - // Simulate graceful shutdown on channel3 - ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); - waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); - - // Reconnection should have kicked in and started to open a channel, do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); - factoryHelper.waitForCalls(ADDRESS, 1); - - CompletionStage closeFuture = pool.forceCloseAsync(); - waitForPendingAdminTasks(); - - // The three original channels were force-closed - Mockito.verify(channel1).forceClose(); - Mockito.verify(channel2).forceClose(); - Mockito.verify(channel3).forceClose(); - // Only two events because the one for channel3 was sent earlier - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); - - // Complete the reconnecting channel - channel4Future.complete(channel4); - waitForPendingAdminTasks(); - - // It should be force-closed once we find out the pool was closed - Mockito.verify(channel4).forceClose(); - // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); - - // We don't wait for reconnected channels to close, so the pool only depends on channel 1-3 - ((ChannelPromise) channel1.closeFuture()).setSuccess(); - ((ChannelPromise) channel2.closeFuture()).setSuccess(); - ((ChannelPromise) channel3.closeFuture()).setSuccess(); - - assertThat(closeFuture).isSuccess(); - - factoryHelper.verifyNoMoreCalls(); - } - - private DriverChannel newMockDriverChannel(int id) { - DriverChannel channel = Mockito.mock(DriverChannel.class); - EventLoop adminExecutor = adminEventLoopGroup.next(); - DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); - DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(null, adminExecutor); - Mockito.when(channel.close()).thenReturn(closeFuture); - Mockito.when(channel.forceClose()).thenReturn(closeFuture); - Mockito.when(channel.closeFuture()).thenReturn(closeFuture); - Mockito.when(channel.closeStartedFuture()).thenReturn(closeStartedFuture); - Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) - .thenReturn(adminExecutor.newSucceededFuture(null)); - Mockito.when(channel.toString()).thenReturn("channel" + id); - return channel; - } - - // Wait for all the tasks on the pool's admin executor to complete. - private void waitForPendingAdminTasks() { - // This works because the event loop group is single-threaded - Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); - try { - Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); - } catch (ExecutionException e) { - fail("unexpected error", e.getCause()); - } catch (TimeoutException e) { - fail("timed out while waiting for admin tasks to complete", e); - } - } -} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java new file mode 100644 index 00000000000..77a33ff7695 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.pool; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.channel.ChannelFactory; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.google.common.util.concurrent.Uninterruptibles; +import io.netty.channel.DefaultChannelPromise; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.Future; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; + +abstract class ChannelPoolTestBase { + + static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); + static final Node NODE = new DefaultNode(ADDRESS); + + @Mock InternalDriverContext context; + @Mock private DriverConfig config; + @Mock DriverConfigProfile defaultProfile; + @Mock private ReconnectionPolicy reconnectionPolicy; + @Mock ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; + @Mock private NettyOptions nettyOptions; + @Mock ChannelFactory channelFactory; + EventBus eventBus; + private DefaultEventLoopGroup adminEventLoopGroup; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + adminEventLoopGroup = new DefaultEventLoopGroup(1); + + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.defaultProfile()).thenReturn(defaultProfile); + this.eventBus = Mockito.spy(new EventBus("test")); + Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.channelFactory()).thenReturn(channelFactory); + + Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + // By default, set a large reconnection delay. Tests that care about reconnection will override + // it. + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + } + + @AfterMethod + public void teardown() { + adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); + } + + DriverChannel newMockDriverChannel(int id) { + DriverChannel channel = Mockito.mock(DriverChannel.class); + EventLoop adminExecutor = adminEventLoopGroup.next(); + DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); + DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(null, adminExecutor); + Mockito.when(channel.close()).thenReturn(closeFuture); + Mockito.when(channel.forceClose()).thenReturn(closeFuture); + Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + Mockito.when(channel.closeStartedFuture()).thenReturn(closeStartedFuture); + Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) + .thenReturn(adminExecutor.newSucceededFuture(null)); + Mockito.when(channel.toString()).thenReturn("channel" + id); + return channel; + } + + // Wait for all the tasks on the pool's admin executor to complete. + void waitForPendingAdminTasks() { + // This works because the event loop group is single-threaded + Future f = adminEventLoopGroup.schedule(() -> null, 5, TimeUnit.NANOSECONDS); + try { + Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + fail("unexpected error", e.getCause()); + } catch (TimeoutException e) { + fail("timed out while waiting for admin tasks to complete", e); + } + } +} From fdc7e29a6214c7341bc3dde451b5a8cca4da4fe6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Jul 2017 16:00:55 -0700 Subject: [PATCH 112/742] Fix error message in Reflection.buildFromConfig --- .../datastax/oss/driver/internal/core/util/Reflection.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 7d2613a9a3d..000c33e2d9b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -98,8 +98,11 @@ public static Optional buildFromConfig( throw new IllegalArgumentException( String.format( "Expected class %s (specified by %s) " - + "to have an accessible constructor with a single %s argument", - className, configPath, DriverConfigProfile.class.getSimpleName())); + + "to have an accessible constructor with arguments (%s, %s)", + className, + configPath, + DriverConfigProfile.class.getSimpleName(), + DriverOption.class.getSimpleName())); } try { Object instance = constructor.newInstance(context, rootOption); From 1f65734fb52173b5b6159f69d9f08bb542940165 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Jul 2017 16:09:31 -0700 Subject: [PATCH 113/742] Raise heartbeat interval in the Scala console --- core/console.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/console.scala b/core/console.scala index c48a0034eaa..a156b319dfc 100644 --- a/core/console.scala +++ b/core/console.scala @@ -16,6 +16,9 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext import java.net.InetSocketAddress import scala.collection.JavaConversions._ +// Heartbeat logs every 30 seconds are annoying in the console, raise the interval +System.setProperty("datastax-java-driver.connection.heartbeat.interval", "1 hour") + val address1 = new InetSocketAddress("127.0.0.1", 9042) val address2 = new InetSocketAddress("127.0.0.2", 9042) val address3 = new InetSocketAddress("127.0.0.3", 9042) From 53c97e3bf15eb8b2c6e404906c6399c8e44f4a00 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 5 Jul 2017 16:12:20 -0700 Subject: [PATCH 114/742] JAVA-1497: Port timestamp generators from 3.x --- changelog/README.md | 1 + core/pom.xml | 11 ++ .../api/core/config/CoreDriverOption.java | 5 + .../driver/api/core/config/DriverOption.java | 19 +++ .../api/core/context/DriverContext.java | 3 + .../driver/api/core/cql/BatchStatement.java | 7 + .../driver/api/core/cql/BoundStatement.java | 7 + .../driver/api/core/cql/SimpleStatement.java | 3 + .../api/core/cql/SimpleStatementBuilder.java | 16 ++ .../oss/driver/api/core/cql/Statement.java | 9 ++ .../core/time/AtomicTimestampGenerator.java | 52 ++++++ .../time/MonotonicTimestampGenerator.java | 108 +++++++++++++ .../time/ServerSideTimestampGenerator.java | 37 +++++ .../time/ThreadLocalTimestampGenerator.java | 51 ++++++ .../api/core/time/TimestampGenerator.java | 38 +++++ .../core/context/DefaultDriverContext.java | 18 +++ .../driver/internal/core/cql/Conversions.java | 5 +- .../core/cql/DefaultBatchStatement.java | 26 ++- .../core/cql/DefaultBoundStatement.java | 12 ++ .../core/cql/DefaultSimpleStatement.java | 11 ++ .../oss/driver/internal/core/os/Native.java | 93 +++++++++++ .../oss/driver/internal/core/time/Clock.java | 50 ++++++ .../driver/internal/core/time/JavaClock.java | 23 +++ .../internal/core/time/NativeClock.java | 83 ++++++++++ core/src/main/resources/reference.conf | 30 ++++ .../time/AtomicTimestampGeneratorTest.java | 70 ++++++++ .../MonotonicTimestampGeneratorTestBase.java | 149 ++++++++++++++++++ .../ThreadLocalTimestampGeneratorTest.java | 79 ++++++++++ .../core/cql/RequestHandlerTestHarness.java | 5 + pom.xml | 5 + 30 files changed, 1024 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/time/TimestampGenerator.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/time/JavaClock.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java diff --git a/changelog/README.md b/changelog/README.md index 730f47d34cf..9aeef4cc334 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [new feature] JAVA-1497: Port timestamp generators from 3.x - [improvement] JAVA-1539: Configure for deployment to Maven central - [new feature] JAVA-1519: Close channel if number of orphan stream ids exceeds a configurable threshold diff --git a/core/pom.xml b/core/pom.xml index 4963cdebf9e..f3b421235a2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -53,6 +53,17 @@ com.typesafe config + + + com.github.jnr + jnr-ffi + org.slf4j slf4j-api diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 69da6ca1102..322b20f5d89 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -78,6 +78,11 @@ public enum CoreDriverOption implements DriverOption { METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), + + TIMESTAMP_GENERATOR_ROOT("timestamp-generator", true), + RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), + RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD("drift-warning.threshold", false), + RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL("drift-warning.interval", false), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java index 19b3110fb24..3a3ddec78bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java @@ -43,6 +43,25 @@ public boolean required() { // nested fields because they are by definition not known in advance. return false; } + + // Only needed for unit tests: comparing options has no meaningful purpose, but it is needed + // to mock them + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof DriverOption) { + DriverOption that = (DriverOption) other; + return this.getPath().equals(that.getPath()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return getPath().hashCode(); + } }; } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 8904f716b6d..c78e9ecd1ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.util.Optional; /** Holds common components that are shared throughout a driver instance. */ @@ -53,4 +54,6 @@ public interface DriverContext extends AttachmentPoint { /** The SSL engine factory, if SSL was configured. */ Optional sslEngineFactory(); + + TimestampGenerator timestampGenerator(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index b5d94a428b4..43dd085cf39 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; import java.nio.ByteBuffer; import java.util.Map; @@ -93,4 +94,10 @@ default BatchStatement addAll(BatchableStatement... statements) { /** Request tracing information for this statement. */ BatchStatement setTracing(); + + /** + * Sets the timestamp for this statement. If left unset, the {@link TimestampGenerator} will + * assign it when the statement gets executed. + */ + BatchStatement setTimestamp(long timestamp); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index a140a487940..0ffcaf19551 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.data.SettableById; import com.datastax.oss.driver.api.core.data.SettableByName; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; @@ -75,4 +76,10 @@ public interface BoundStatement /** Request tracing information for this statement. */ BoundStatement setTracing(); + + /** + * Sets the timestamp for this statement. If left unset, the {@link TimestampGenerator} will + * assign it when the statement gets executed. + */ + BoundStatement setTimestamp(long timestamp); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index c478a62b987..7b1e515854a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -100,6 +100,7 @@ static SimpleStatement newInstance(String cqlQuery) { Collections.emptyMap(), null, false, + Long.MIN_VALUE, null); } @@ -117,6 +118,7 @@ static SimpleStatement newInstance(String cqlQuery, Object... positionalValues) Collections.emptyMap(), null, false, + Long.MIN_VALUE, null); } @@ -134,6 +136,7 @@ static SimpleStatement newInstance(String cqlQuery, Map namedVal Collections.emptyMap(), null, false, + Long.MIN_VALUE, null); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index a8d99192ee2..0185bcab5d4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; @@ -33,6 +34,7 @@ public class SimpleStatementBuilder { private ImmutableMap.Builder customPayload; private Boolean idempotent; private boolean tracing; + private long timestamp = Long.MIN_VALUE; private ByteBuffer pagingState; public SimpleStatementBuilder(String query) { @@ -154,6 +156,19 @@ public SimpleStatementBuilder withTracing() { return this; } + /** + * Sets the timestamp to use for this statement. + * + *

      If this method is not called, the {@link TimestampGenerator} will assign it when the + * statement gets executed. + * + * @see Statement#getTimestamp() + */ + public SimpleStatementBuilder withTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + /** * Sets the paging state to use for this statement. * @@ -177,6 +192,7 @@ public SimpleStatement build() { (customPayload == null) ? Collections.emptyMap() : customPayload.build(), idempotent, tracing, + timestamp, pagingState); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 7323f541ba1..590ae1437f7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.nio.ByteBuffer; import java.util.concurrent.CompletionStage; @@ -24,6 +25,14 @@ public interface Statement extends RequestIf this is equal to {@link Long#MIN_VALUE}, the {@link TimestampGenerator} configured for + * this driver instance will be used to generate a timestamp. + */ + long getTimestamp(); + ByteBuffer getPagingState(); /** Creates a new statement with a different paging state. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java new file mode 100644 index 00000000000..e506e8d067a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.time.Clock; +import com.google.common.annotations.VisibleForTesting; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A timestamp generator that guarantees monotonically increasing timestamps across all client + * threads, and logs warnings when timestamps drift in the future. + */ +public class AtomicTimestampGenerator extends MonotonicTimestampGenerator { + + private AtomicLong lastRef = new AtomicLong(0); + + public AtomicTimestampGenerator(DriverContext context, DriverOption configRoot) { + super(context, configRoot); + } + + @VisibleForTesting + AtomicTimestampGenerator(Clock clock, InternalDriverContext context, DriverOption configRoot) { + super(clock, context, configRoot); + } + + @Override + public long next() { + while (true) { + long last = lastRef.get(); + long next = computeNext(last); + if (lastRef.compareAndSet(last, next)) { + return next; + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java new file mode 100644 index 00000000000..f9b3212c1c0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.time.Clock; +import com.google.common.annotations.VisibleForTesting; +import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A timestamp generator that guarantees monotonicity, and logs warnings when timestamps drift in + * the future. + */ +abstract class MonotonicTimestampGenerator implements TimestampGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(MonotonicTimestampGenerator.class); + + private final Clock clock; + private final long warningThresholdMicros; + private final long warningIntervalMillis; + private final AtomicLong lastDriftWarning = new AtomicLong(Long.MIN_VALUE); + + protected MonotonicTimestampGenerator(DriverContext context, DriverOption configRoot) { + this(buildClock(context, configRoot), context, configRoot); + } + + @VisibleForTesting + protected MonotonicTimestampGenerator( + Clock clock, DriverContext context, DriverOption configRoot) { + this.clock = clock; + + DriverOption warningThresholdOption = + configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD); + DriverConfigProfile config = context.config().defaultProfile(); + this.warningThresholdMicros = + (config.isDefined(warningThresholdOption)) + ? config.getDuration(warningThresholdOption).toNanos() / 1000 + : 0; + + if (this.warningThresholdMicros == 0) { + this.warningIntervalMillis = 0; + } else { + DriverOption warningIntervalOption = + configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL); + this.warningIntervalMillis = config.getDuration(warningIntervalOption).toMillis(); + } + } + + /** + * Compute the next timestamp, given the current clock tick and the last timestamp returned. + * + *

      If timestamps have to drift ahead of the current clock tick to guarantee monotonicity, a + * warning will be logged according to the rules defined in the configuration. + */ + protected long computeNext(long last) { + long currentTick = clock.currentTimeMicros(); + if (last >= currentTick) { + maybeLog(currentTick, last); + return last + 1; + } + return currentTick; + } + + private void maybeLog(long currentTick, long last) { + if (warningThresholdMicros != 0 + && LOG.isWarnEnabled() + && last > currentTick + warningThresholdMicros) { + long now = System.currentTimeMillis(); + long lastWarning = lastDriftWarning.get(); + if (now > lastWarning + warningIntervalMillis + && lastDriftWarning.compareAndSet(lastWarning, now)) { + LOG.warn( + "Clock skew detected: current tick ({}) was {} microseconds behind the last generated timestamp ({}), " + + "returned timestamps will be artificially incremented to guarantee monotonicity.", + currentTick, + last - currentTick, + last); + } + } + } + + private static Clock buildClock(DriverContext context, DriverOption configRoot) { + DriverOption forceJavaClockOption = + configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); + DriverConfigProfile config = context.config().defaultProfile(); + boolean forceJavaClock = + config.isDefined(forceJavaClockOption) && config.getBoolean(forceJavaClockOption); + return Clock.getInstance(forceJavaClock); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java new file mode 100644 index 00000000000..6330a4c5028 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; + +/** + * A timestamp generator that never sends a timestamp with any query, therefore letting Cassandra + * assign a server-side timestamp. + */ +public class ServerSideTimestampGenerator implements TimestampGenerator { + + public ServerSideTimestampGenerator( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { + // nothing to do + } + + @Override + public long next() { + return Long.MIN_VALUE; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java new file mode 100644 index 00000000000..83e7d9a2066 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.time.Clock; +import com.google.common.annotations.VisibleForTesting; + +/** + * A timestamp generator that guarantees monotonically increasing timestamps within each thread, and + * logs warnings when timestamps drift in the future. + * + *

      Beware that there is a risk of timestamp collision with this generator when accessed by more + * than one thread at a time; only use it when threads are not in direct competition for timestamp + * ties (i.e., they are executing independent statements). + */ +public class ThreadLocalTimestampGenerator extends MonotonicTimestampGenerator { + + private final ThreadLocal lastRef = ThreadLocal.withInitial(() -> 0L); + + public ThreadLocalTimestampGenerator(DriverContext context, DriverOption configRoot) { + super(context, configRoot); + } + + @VisibleForTesting + ThreadLocalTimestampGenerator(Clock clock, DriverContext context, DriverOption configRoot) { + super(clock, context, configRoot); + } + + @Override + public long next() { + Long last = this.lastRef.get(); + long next = computeNext(last); + this.lastRef.set(next); + return next; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/TimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/TimestampGenerator.java new file mode 100644 index 00000000000..98a0f672a8a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/TimestampGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +/** + * Generates client-side, microsecond-precision query timestamps. + * + *

      These timestamps are used to order queries server-side, and resolve potential conflicts. + */ +public interface TimestampGenerator { + + /** + * Returns the next timestamp, in microseconds. + * + *

      The timestamps returned by this method should be monotonic; that is, successive invocations + * should return strictly increasing results. Note that this might not be possible using the clock + * alone, if it is not precise enough; alternative strategies might include incrementing the last + * returned value if the clock tick hasn't changed, and possibly drifting in the future. See the + * built-in driver implementations for more details. + * + * @return the next timestamp, or {@link Long#MIN_VALUE} to indicate that the driver should not + * send one with the query (and let Cassandra generate a server-side timestamp). + */ + long next(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index ab57477101f..c92d6fe76a8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -28,6 +28,7 @@ import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; @@ -121,6 +122,8 @@ public class DefaultDriverContext implements InternalDriverContext { "loadBalancingPolicyWrapper", this::buildLoadBalancingPolicyWrapper, cycleDetector); private final LazyReference controlConnectionRef = new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); + private final LazyReference timestampGeneratorRef = + new LazyReference<>("timestampGenerator", this::buildTimestampGenerator, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -261,6 +264,16 @@ protected CodecRegistry buildCodecRegistry(String logPrefix, List> return new DefaultCodecRegistry(logPrefix, codecs.toArray(array)); } + protected TimestampGenerator buildTimestampGenerator() { + CoreDriverOption rootOption = CoreDriverOption.TIMESTAMP_GENERATOR_ROOT; + return Reflection.buildFromConfig(this, rootOption, TimestampGenerator.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing timestamp generator, check your configuration (%s)", rootOption))); + } + @Override public String clusterName() { return clusterName; @@ -381,6 +394,11 @@ public RequestProcessorRegistry requestProcessorRegistry() { return RequestProcessorRegistry.defaultCqlProcessors(clusterName()); } + @Override + public TimestampGenerator timestampGenerator() { + return timestampGeneratorRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 5747c1cd06e..00fe7c4cd48 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -93,7 +93,10 @@ static Message toMessage( int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); int serialConsistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); - long timestamp = Long.MIN_VALUE; // TODO timestamp generator + long timestamp = statement.getTimestamp(); + if (timestamp == Long.MIN_VALUE) { + timestamp = context.timestampGenerator().next(); + } CodecRegistry codecRegistry = context.codecRegistry(); ProtocolVersion protocolVersion = context.protocolVersion(); if (statement instanceof SimpleStatement) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 9e2b6ea15d8..737e3ee8aa7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -37,11 +37,21 @@ public class DefaultBatchStatement implements BatchStatement { private Map customPayload; private Boolean idempotent; private boolean tracing; + private long timestamp; private ByteBuffer pagingState; public DefaultBatchStatement(BatchType batchType) { this( - batchType, new ArrayList<>(), null, null, null, Collections.emptyMap(), false, false, null); + batchType, + new ArrayList<>(), + null, + null, + null, + Collections.emptyMap(), + false, + false, + Long.MIN_VALUE, + null); } private DefaultBatchStatement( @@ -53,6 +63,7 @@ private DefaultBatchStatement( Map customPayload, Boolean idempotent, boolean tracing, + long timestamp, ByteBuffer pagingState) { this.batchType = batchType; this.statements = statements; @@ -62,6 +73,7 @@ private DefaultBatchStatement( this.customPayload = customPayload; this.idempotent = idempotent; this.tracing = tracing; + this.timestamp = timestamp; this.pagingState = pagingState; } @@ -120,6 +132,7 @@ public BatchStatement copy(ByteBuffer newPagingState) { customPayload, idempotent, tracing, + timestamp, newPagingState); } @@ -188,4 +201,15 @@ public BatchStatement setTracing() { this.tracing = true; return this; } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public BatchStatement setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 2f25c48c952..23edd0a5f14 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -41,6 +41,7 @@ public class DefaultBoundStatement implements BoundStatement { private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; private boolean tracing; + private long timestamp = Long.MIN_VALUE; private ByteBuffer pagingState; public DefaultBoundStatement( @@ -179,6 +180,17 @@ public BoundStatement setTracing() { return this; } + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public BoundStatement setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + @Override public ByteBuffer getPagingState() { return pagingState; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 475f95b1c82..8af166ef3da 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -32,6 +32,7 @@ public class DefaultSimpleStatement implements SimpleStatement { private final Map customPayload; private final Boolean idempotent; private final boolean tracing; + private final long timestamp; private final ByteBuffer pagingState; public DefaultSimpleStatement(String query, List positionalValues) { @@ -44,6 +45,7 @@ public DefaultSimpleStatement(String query, List positionalValues) { Collections.emptyMap(), null, false, + Long.MIN_VALUE, null); } @@ -57,6 +59,7 @@ public DefaultSimpleStatement(String query, Map namedValues) { Collections.emptyMap(), null, false, + Long.MIN_VALUE, null); } @@ -70,6 +73,7 @@ public DefaultSimpleStatement( Map customPayload, Boolean idempotent, boolean tracing, + long timestamp, ByteBuffer pagingState) { this.query = query; this.positionalValues = positionalValues; @@ -79,6 +83,7 @@ public DefaultSimpleStatement( this.customPayload = customPayload; this.idempotent = idempotent; this.tracing = tracing; + this.timestamp = timestamp; this.pagingState = pagingState; } @@ -128,6 +133,11 @@ public boolean isTracing() { return tracing; } + @Override + public long getTimestamp() { + return timestamp; + } + @Override public ByteBuffer getPagingState() { return pagingState; @@ -144,6 +154,7 @@ public DefaultSimpleStatement copy(ByteBuffer newPagingState) { customPayload, idempotent, tracing, + timestamp, newPagingState); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java new file mode 100644 index 00000000000..531b9483873 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.os; + +import jnr.ffi.LibraryLoader; +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.Struct; +import jnr.ffi.annotations.Out; +import jnr.ffi.annotations.Transient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A gateway to perform system calls. */ +public class Native { + + private static final Logger LOG = LoggerFactory.getLogger(Native.class); + + /** Handles libc calls through JNR (must be public). */ + public interface LibC { + int gettimeofday(@Out @Transient Timeval tv, Pointer unused); + } + + // See http://man7.org/linux/man-pages/man2/settimeofday.2.html + private static class Timeval extends Struct { + private final time_t tv_sec = new time_t(); + private final Unsigned32 tv_usec = new Unsigned32(); + + private Timeval(Runtime runtime) { + super(runtime); + } + } + + private static final LibC LIB_C; + private static final Runtime LIB_C_RUNTIME; + + /** Whether {@link Native#currentTimeMicros()} is available on this system. */ + public static final boolean CURRENT_TIME_MICROS_AVAILABLE; + + static { + LibC libc; + Runtime runtime = null; + try { + libc = LibraryLoader.create(LibC.class).load("c"); + runtime = Runtime.getRuntime(libc); + } catch (Throwable t) { + libc = null; + LOG.debug("Error loading libc", t); + } + LIB_C = libc; + LIB_C_RUNTIME = runtime; + boolean gettimeofday = false; + if (LIB_C_RUNTIME != null) { + try { + gettimeofday = LIB_C.gettimeofday(new Timeval(LIB_C_RUNTIME), null) == 0; + } catch (Throwable t) { + LOG.debug("Error accessing libc.gettimeofday()", t); + } + } + CURRENT_TIME_MICROS_AVAILABLE = gettimeofday; + } + + /** + * The current time in microseconds, as returned by libc.gettimeofday(); can only be used if + * {@link #CURRENT_TIME_MICROS_AVAILABLE} is true. + */ + public static long currentTimeMicros() { + if (!CURRENT_TIME_MICROS_AVAILABLE) { + throw new IllegalStateException( + "Native call not available. " + + "Check CURRENT_TIME_MICROS_AVAILABLE before calling this method."); + } + Timeval tv = new Timeval(LIB_C_RUNTIME); + int res = LIB_C.gettimeofday(tv, null); + if (res != 0) { + throw new IllegalStateException("Call to libc.gettimeofday() failed with result " + res); + } + return tv.tv_sec.get() * 1000000 + tv.tv_usec.get(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java new file mode 100644 index 00000000000..ed6c9cd8cd0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.time; + +import com.datastax.oss.driver.internal.core.os.Native; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A small abstraction around system clock that aims to provide microsecond precision with the best + * accuracy possible. + */ +public interface Clock { + Logger LOG = LoggerFactory.getLogger(Clock.class); + + /** Returns the best implementation for the current platform. */ + static Clock getInstance(boolean forceJavaClock) { + if (forceJavaClock) { + LOG.info("Using Java system clock because this was explicitly required in the configuration"); + return new JavaClock(); + } else if (!Native.CURRENT_TIME_MICROS_AVAILABLE) { + LOG.info( + "Could not access native clock (see debug logs for details), " + + "falling back to Java system clock"); + return new JavaClock(); + } else { + LOG.info("Using native clock for microsecond precision"); + return new NativeClock(); + } + } + + /** + * Returns the difference, measured in microseconds, between the current time and and the + * Epoch (that is, midnight, January 1, 1970 UTC). + */ + long currentTimeMicros(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/JavaClock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/JavaClock.java new file mode 100644 index 00000000000..3538d5d091d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/JavaClock.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.time; + +public class JavaClock implements Clock { + @Override + public long currentTimeMicros() { + return System.currentTimeMillis() * 1000; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java new file mode 100644 index 00000000000..bf99afba3fe --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.time; + +import com.datastax.oss.driver.internal.core.os.Native; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +/** + * Provides the current time with microseconds precision with some reasonable accuracy through the + * use of {@link Native#currentTimeMicros()}. + * + *

      Because calling JNR methods is slightly expensive, we only call it once per second and add the + * number of nanoseconds since the last call to get the current time, which is good enough an + * accuracy for our purpose (see CASSANDRA-6106). + * + *

      This reduces the cost of the call to {@link NativeClock#currentTimeMicros()} to levels + * comparable to those of a call to {@link System#nanoTime()}. + */ +public class NativeClock implements Clock { + + private static final long ONE_SECOND_NS = NANOSECONDS.convert(1, SECONDS); + private static final long ONE_MILLISECOND_NS = NANOSECONDS.convert(1, MILLISECONDS); + + // Records a time in micros along with the System.nanoTime() value at the time the time is + // fetched. + private static class FetchedTime { + + private final long timeInMicros; + private final long nanoTimeAtCheck; + + private FetchedTime(long timeInMicros, long nanoTimeAtCheck) { + this.timeInMicros = timeInMicros; + this.nanoTimeAtCheck = nanoTimeAtCheck; + } + } + + private final AtomicReference lastFetchedTime = + new AtomicReference<>(fetchTimeMicros()); + + @Override + public long currentTimeMicros() { + FetchedTime spec = lastFetchedTime.get(); + long curNano = System.nanoTime(); + if (curNano > spec.nanoTimeAtCheck + ONE_SECOND_NS) { + lastFetchedTime.compareAndSet(spec, spec = fetchTimeMicros()); + } + return spec.timeInMicros + ((curNano - spec.nanoTimeAtCheck) / 1000); + } + + private static FetchedTime fetchTimeMicros() { + // To compensate for the fact that the Native.currentTimeMicros call could take some time, + // instead of picking the nano time before the call or after the call, we take the average of + // both. + long start = System.nanoTime(); + long micros = Native.currentTimeMicros(); + long end = System.nanoTime(); + // If it turns out the call took us more than 1 millisecond (can happen while the JVM warms up, + // unlikely otherwise, but no reasons to take risks), fall back to System.currentTimeMillis() + // temporarily. + if ((end - start) > ONE_MILLISECOND_NS) { + return new FetchedTime(System.currentTimeMillis() * 1000, System.nanoTime()); + } else { + return new FetchedTime(micros, (end + start) / 2); + } + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index ff9ed03b286..dd6636c4a81 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -158,6 +158,36 @@ datastax-java-driver { default-idempotence = false } + # The generator that assigns a microsecond timestamp to each query sent by the driver. + timestamp-generator { + # The implementation to use. Built-in options are (all from the package + # com.datastax.oss.driver.api.core.time): + # - AtomicTimestampGenerator: timestamps are guaranteed to be unique across all client threads. + # - ThreadLocalTimestampGenerator: timestamps that are guaranteed to be unique within each + # thread only. + # - ServerSideTimestampGenerator: do not generate timestamps, let the server assign them. + class = com.datastax.oss.driver.api.core.time.AtomicTimestampGenerator + + # To guarantee that queries are applied on the server in the same order as the client issued + # them, timestamps must be strictly increasing. But this means that, if the driver sends more + # than one query per microsecond, timestamps will drift in the future. While this could happen + # occasionally under high load, it should not be a regular occurrence. Therefore the built-in + # implementations log a warning to detect potential issues. + drift-warning { + # How far in the future timestamps are allowed to drift before the warning is logged. + # If it is undefined or set to 0, warnings are disabled. + threshold = 1 second + # How often the warning will be logged if timestamps keep drifting above the threshold. + interval = 10 seconds + } + + # Whether to force the driver to use Java's millisecond-precision system clock. + # If this is false, the driver will try to access the microsecond-precision OS clock via native + # calls (and fallback to the Java one if the native calls fail). + # Unless you explicitly want to avoid native calls, there's no reason to change this. + force-java-clock = false + } + prepared-statements { # Whether `Session.prepare` calls should be sent to all nodes in the cluster. # diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java new file mode 100644 index 00000000000..e2dee9c206f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.time.Clock; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.mockito.Mockito; +import org.mockito.stubbing.OngoingStubbing; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.fail; + +public class AtomicTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { + @Override + protected MonotonicTimestampGenerator newInstance(Clock clock) { + return new AtomicTimestampGenerator(clock, context, CoreDriverOption.TIMESTAMP_GENERATOR_ROOT); + } + + @Test + public void should_share_timestamps_across_all_threads() throws Exception { + // Prepare to generate 1000 timestamps with the clock frozen at 1 + OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + for (int i = 0; i < 1000; i++) { + stub = stub.thenReturn(1L); + } + + MonotonicTimestampGenerator generator = newInstance(clock); + + final int testThreadsCount = 2; + assertThat(1000 % testThreadsCount).isZero(); + + final SortedSet allTimestamps = new ConcurrentSkipListSet(); + ExecutorService executor = Executors.newFixedThreadPool(testThreadsCount); + for (int i = 0; i < testThreadsCount; i++) { + executor.submit( + () -> { + for (int j = 0; j < 1000 / testThreadsCount; j++) { + allTimestamps.add(generator.next()); + } + }); + } + executor.shutdown(); + if (!executor.awaitTermination(1, TimeUnit.SECONDS)) { + fail("Expected executor to shut down cleanly"); + } + + assertThat(allTimestamps).hasSize(1000); + assertThat(allTimestamps.first()).isEqualTo(1); + assertThat(allTimestamps.last()).isEqualTo(1000); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java new file mode 100644 index 00000000000..513e96a8fe9 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.time.Clock; +import java.time.Duration; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.OngoingStubbing; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.timeout; + +abstract class MonotonicTimestampGeneratorTestBase { + + protected static final DriverOption WARNING_THRESHOLD_OPTION = + CoreDriverOption.TIMESTAMP_GENERATOR_ROOT.concat( + CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD); + protected static final DriverOption WARNING_INTERVAL_OPTION = + CoreDriverOption.TIMESTAMP_GENERATOR_ROOT.concat( + CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL); + + @Mock protected Clock clock; + @Mock protected InternalDriverContext context; + @Mock private DriverConfig config; + @Mock protected DriverConfigProfile defaultConfigProfile; + + @Mock private Appender appender; + @Captor private ArgumentCaptor loggingEventCaptor; + + private Logger logger; + + @BeforeMethod + public void setup() { + MockitoAnnotations.initMocks(this); + + Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(context.config()).thenReturn(config); + + // Disable warnings by default + Mockito.when(defaultConfigProfile.isDefined(WARNING_THRESHOLD_OPTION)).thenReturn(true); + Mockito.when(defaultConfigProfile.getDuration(WARNING_THRESHOLD_OPTION)) + .thenReturn(Duration.ofNanos(0)); + // Actual value doesn't really matter since we only test the first warning + Mockito.when(defaultConfigProfile.getDuration(WARNING_INTERVAL_OPTION)) + .thenReturn(Duration.ofSeconds(10)); + + logger = (Logger) LoggerFactory.getLogger(MonotonicTimestampGenerator.class); + logger.addAppender(appender); + } + + @AfterMethod + public void teardown() { + logger.detachAppender(appender); + } + + protected abstract MonotonicTimestampGenerator newInstance(Clock clock); + + @Test + public void should_use_clock_if_it_keeps_increasing() { + OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + for (long l = 1; l < 5; l++) { + stub = stub.thenReturn(l); + } + + MonotonicTimestampGenerator generator = newInstance(clock); + + for (long l = 1; l < 5; l++) { + assertThat(generator.next()).isEqualTo(l); + } + } + + @Test + public void should_increment_if_clock_does_not_increase() { + Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 5L); + + MonotonicTimestampGenerator generator = newInstance(clock); + + assertThat(generator.next()).isEqualTo(1); + assertThat(generator.next()).isEqualTo(2); + assertThat(generator.next()).isEqualTo(3); + assertThat(generator.next()).isEqualTo(5); + } + + @Test + public void should_warn_if_timestamps_drift() { + Mockito.when(defaultConfigProfile.getDuration(WARNING_THRESHOLD_OPTION)) + .thenReturn(Duration.ofNanos(2 * 1000)); + Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); + + MonotonicTimestampGenerator generator = newInstance(clock); + + assertThat(generator.next()).isEqualTo(1); + assertThat(generator.next()).isEqualTo(2); + assertThat(generator.next()).isEqualTo(3); + assertThat(generator.next()).isEqualTo(4); + // Clock still at 1, last returned timestamp is 4 (> 1 + 2), should warn + assertThat(generator.next()).isEqualTo(5); + + Mockito.verify(appender).doAppend(loggingEventCaptor.capture()); + ILoggingEvent log = loggingEventCaptor.getValue(); + assertThat(log.getLevel()).isEqualTo(Level.WARN); + assertThat(log.getMessage()).contains("Clock skew detected"); + } + + @Test + public void should_go_back_to_clock_if_new_tick_high_enough() { + Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L, 10L); + + MonotonicTimestampGenerator generator = newInstance(clock); + + for (long l = 1; l <= 5; l++) { + // Clock at 1, keep incrementing + assertThat(generator.next()).isEqualTo(l); + } + + // Last returned is 5, but clock has ticked to 10, should use that. + assertThat(generator.next()).isEqualTo(10); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java new file mode 100644 index 00000000000..de370a8ffca --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.time; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.time.Clock; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.mockito.Mockito; +import org.mockito.stubbing.OngoingStubbing; +import org.testng.annotations.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.fail; + +public class ThreadLocalTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { + @Override + protected MonotonicTimestampGenerator newInstance(Clock clock) { + return new ThreadLocalTimestampGenerator( + clock, context, CoreDriverOption.TIMESTAMP_GENERATOR_ROOT); + } + + @Test + public void should_confine_timestamps_to_thread() throws Exception { + final int testThreadsCount = 2; + + // Prepare to generate 1000 timestamps for each thread, with the clock frozen at 1 + OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + for (int i = 0; i < testThreadsCount * 1000; i++) { + stub = stub.thenReturn(1L); + } + + MonotonicTimestampGenerator generator = newInstance(clock); + + List> futures = new CopyOnWriteArrayList<>(); + ExecutorService executor = Executors.newFixedThreadPool(testThreadsCount); + for (int i = 0; i < testThreadsCount; i++) { + executor.submit( + () -> { + try { + for (long l = 1; l <= 1000; l++) { + assertThat(generator.next()).isEqualTo(l); + } + futures.add(CompletableFuture.completedFuture(null)); + } catch (Throwable t) { + futures.add(CompletableFutures.failedFuture(t)); + } + }); + } + executor.shutdown(); + if (!executor.awaitTermination(1, TimeUnit.SECONDS)) { + fail("Expected executor to shut down cleanly"); + } + + assertThat(futures).hasSize(testThreadsCount); + for (CompletionStage future : futures) { + assertThat(future).isSuccess(); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 3dec75dc236..c44315b51f9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -66,6 +67,7 @@ public static Builder builder() { @Mock private LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @Mock private RetryPolicy retryPolicy; @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; + @Mock private TimestampGenerator timestampGenerator; private RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); @@ -100,6 +102,9 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + Mockito.when(timestampGenerator.next()).thenReturn(Long.MIN_VALUE); + Mockito.when(context.timestampGenerator()).thenReturn(timestampGenerator); + Map pools = builder.buildMockPools(); Mockito.when(session.getPools()).thenReturn(pools); Mockito.when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); diff --git a/pom.xml b/pom.xml index aaae9168186..c7d6f174657 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,11 @@ logback-classic 1.2.3 + + com.github.jnr + jnr-ffi + 2.1.6 + org.testng testng From 1b6c73d766e79915de457ca312562684ecdfd108 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Jul 2017 10:04:42 -0700 Subject: [PATCH 115/742] Mention request timeout in upgrade guide --- upgrade_guide/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 7a4dbf24d57..5f3fba96900 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -59,6 +59,19 @@ retrieve the next page asynchronously. [3.x async paging]: http://docs.datastax.com/en/developer/java-driver/3.2/manual/async/#async-paging +#### Simplified request timeout + +The driver-side request timeout -- defined by the `request.timeout` configuration option -- now +spans the entire request, including all retries, speculative executions, etc. In other +words, it's the maximum amount of time that the driver will spend processing the request. If it +fires, all pending tasks are cancelled, and a `DriverTimeoutException` is returned to the client. +(Note that the "cancellation" is only driver-side, currently the protocol does not provide a way to +tell the server to stop processing a request; if a message was "on the wire" when the timeout fired, +then the driver will simply ignore the response when it eventually comes back.) + +This is in contrast to 3.x, where the timeout defined in the configuration was per retry, and a +global timeout required specific user code. + #### Dedicated type for CQL identifiers Instead of raw strings, the names of schema objects (keyspaces, tables, columns, etc.) are now From 5357a45df46e56c5a9431c4e5a6c03341e1cb6a0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Jul 2017 17:25:20 -0700 Subject: [PATCH 116/742] Fix obsolete dependency to deprecated module --- core/pom.xml | 5 ----- .../internal/core/config/typesafe/TypeSafeDriverConfig.java | 2 +- .../oss/driver/internal/core/session/ReprepareOnUpTest.java | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index f3b421235a2..addd051cf26 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -32,11 +32,6 @@ DataStax Java driver for Apache Cassandra® - core - - ${project.groupId} - java-driver-types - ${project.version} - com.datastax.oss native-protocol diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 3c4e19c231c..19d43ce16fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.typesafe.config.Config; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index bf72326aae2..fca53cf54ea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -22,8 +22,6 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Prepare; @@ -33,6 +31,8 @@ import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.RowsMetadata; import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.netty.channel.EventLoop; import java.nio.ByteBuffer; import java.time.Duration; From 938576cadc746bd91054024c729070aead447ef4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 7 Jul 2017 13:27:44 -0700 Subject: [PATCH 117/742] Fix buffer leak in frame decoder --- .../oss/driver/internal/core/protocol/FrameDecoder.java | 5 +++++ .../oss/driver/internal/core/protocol/FrameDecoderTest.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java index e9d27237f75..19d7564ec09 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -60,4 +60,9 @@ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception throw new FrameDecodingException(streamId, e); } } + + @Override + protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) { + return buffer.slice(index, length); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java index 332f4e9808e..09e6e55de2e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java @@ -65,6 +65,8 @@ public void should_decode_valid_payload() { channel.pipeline().addLast(decoder); // When + // The decoder releases the buffer, so make sure we retain it for the other tests + VALID_PAYLOAD.retain(); channel.writeInbound(VALID_PAYLOAD.duplicate()); Frame frame = readInboundFrame(); @@ -83,6 +85,7 @@ public void should_fail_to_decode_if_payload_is_valid_but_too_long() { channel.pipeline().addLast(decoder); // When + VALID_PAYLOAD.retain(); try { channel.writeInbound(VALID_PAYLOAD.duplicate()); fail("expected an exception"); @@ -101,6 +104,7 @@ public void should_fail_to_decode_if_payload_cannot_be_decoded() { channel.pipeline().addLast(decoder); // When + INVALID_PAYLOAD.retain(); try { channel.writeInbound(INVALID_PAYLOAD.duplicate()); fail("expected an exception"); From 8195ac7c06ec30edb1e2a76ec29a69e545480006 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 8 Jul 2017 16:12:51 -0700 Subject: [PATCH 118/742] Fix bug when last execution reaches the end of the query plan --- .../internal/core/cql/CqlRequestHandler.java | 2 +- ...equestHandlerSpeculativeExecutionTest.java | 109 ++++++++++++++++++ .../core/cql/CqlRequestHandlerTest.java | 21 ++++ 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 39b7809f0a7..39a4570006e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -217,7 +217,7 @@ private void sendRequest(Node node, int execution, int retryCount) { } if (channel == null) { // We've reached the end of the query plan without finding any node to write to - if (!result.isDone() && executions.decrementAndGet() == 0) { + if (!result.isDone() && executions.decrementAndGet() == -1) { // We're the last execution so fail the result setFinalError(AllNodesFailedException.fromErrors(this.errors)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index a9294effa8d..f8d85137b42 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -15,12 +15,16 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.Error; +import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.mockito.Mockito; @@ -146,6 +150,111 @@ public void should_not_start_execution_if_result_complete( } } + @Test(dataProvider = "idempotentConfig") + public void should_fail_if_no_more_nodes_and_initial_execution_is_last( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + harnessBuilder.withResponse( + node2, + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .asyncResult(); + node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); + // do not simulate a response from node1 yet + + harness.nextScheduledTask(); // Discard the timeout task + + // Run the next scheduled task to start the speculative execution. node2 will reply with a + // BOOTSTRAPPING error, causing a RETRY_NEXT; but the query plan is now empty so the + // speculative execution stops. + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); + firstExecutionTask.run(); + + // node1 now replies with the same response, that triggers a RETRY_NEXT + node1Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + // But again the query plan is empty so that should fail the request + assertThat(resultSetFuture) + .isFailed( + error -> { + assertThat(error).isInstanceOf(AllNodesFailedException.class); + Map nodeErrors = ((AllNodesFailedException) error).getErrors(); + assertThat(nodeErrors).containsOnlyKeys(node1, node2); + assertThat(nodeErrors.get(node1)).isInstanceOf(BootstrappingException.class); + assertThat(nodeErrors.get(node2)).isInstanceOf(BootstrappingException.class); + }); + } + } + + @Test(dataProvider = "idempotentConfig") + public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( + boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .asyncResult(); + node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); + // do not simulate a response from node1 yet + + harness.nextScheduledTask(); // Discard the timeout task + + // Run the next scheduled task to start the speculative execution. + ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = + harness.nextScheduledTask(); + assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + .isEqualTo(firstExecutionDelay); + firstExecutionTask.run(); + + // node1 now replies with a BOOTSTRAPPING error that triggers a RETRY_NEXT + // but the query plan is empty so the initial execution stops + node1Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + // Same thing with node2, so the speculative execution should reach the end of the query plan + // and fail the request + node2Behavior.setResponseSuccess( + defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); + + assertThat(resultSetFuture) + .isFailed( + error -> { + assertThat(error).isInstanceOf(AllNodesFailedException.class); + Map nodeErrors = ((AllNodesFailedException) error).getErrors(); + assertThat(nodeErrors).containsOnlyKeys(node1, node2); + assertThat(nodeErrors.get(node1)).isInstanceOf(BootstrappingException.class); + assertThat(nodeErrors.get(node2)).isInstanceOf(BootstrappingException.class); + }); + } + } + @Test(dataProvider = "idempotentConfig") public void should_retry_in_speculative_executions( boolean defaultIdempotence, SimpleStatement statement) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 56861c79f57..d13ca205a90 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BoundStatement; @@ -79,6 +80,26 @@ public void should_complete_result_if_first_node_replies_immediately() { } } + @Test + public void should_fail_if_no_node_available() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + // Mock no responses => this will produce an empty query plan + .build()) { + + CompletionStage resultSetFuture = + new CqlRequestHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") + .asyncResult(); + + assertThat(resultSetFuture) + .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); + } + } + @Test public void should_time_out_if_first_node_takes_too_long_to_respond() { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); From f4c777a86a609c068f6d5f5a7cad66107b2886a8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 8 Jul 2017 20:11:00 -0700 Subject: [PATCH 119/742] Fail node refresh if rpc_address is missing --- .../driver/internal/core/metadata/DefaultTopologyMonitor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index f4b13d1c829..510da7415bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -167,6 +167,9 @@ private CompletionStage query(DriverChannel channel, String querySt private NodeInfo buildNodeInfo(AdminResult.Row row) { InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); + if (broadcastRpcAddress == null) { + throw new IllegalArgumentException("Missing rpc_address in system row, can't refresh node"); + } InetSocketAddress connectAddress = addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port)); return buildNodeInfo(row, connectAddress); From 33e9952d71622bf34d43303fcbac94236d6b8e10 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 11 Jul 2017 19:04:20 -0500 Subject: [PATCH 120/742] Fix error message (DriverConfigProfile -> DriverContext) --- .../com/datastax/oss/driver/internal/core/util/Reflection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 000c33e2d9b..952f4ba414c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -101,7 +101,7 @@ public static Optional buildFromConfig( + "to have an accessible constructor with arguments (%s, %s)", className, configPath, - DriverConfigProfile.class.getSimpleName(), + DriverContext.class.getSimpleName(), DriverOption.class.getSimpleName())); } try { From bd5ca14aa9dc608b67818e65c6e71f69f3aa18d1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 13 Jul 2017 13:27:17 -0700 Subject: [PATCH 121/742] Add comment to clarify usage of LoadBalancingPolicyWrapper.newQueryPlan --- .../internal/core/metadata/LoadBalancingPolicyWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index d9485d5208f..08aa7a3ba96 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -90,7 +90,8 @@ public Queue newQueryPlan() { switch (stateRef.get()) { case BEFORE_INIT: case DURING_INIT: - // Retrieve nodes from the metadata (at this stage it's the contact points). + // Retrieve nodes from the metadata (at this stage it's the contact points). The only time + // when this can happen is during control connection initialization. List nodes = new ArrayList<>(); nodes.addAll(context.metadataManager().getMetadata().getNodes().values()); Collections.shuffle(nodes); From 0cfc799aef366e4e27849d87436f3032e1530a18 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 13 Jul 2017 14:32:27 -0700 Subject: [PATCH 122/742] JAVA-1547: Abort pending requests when connection dropped --- changelog/README.md | 1 + .../core/channel/InFlightHandler.java | 9 +++++++ .../core/channel/InFlightHandlerTest.java | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 9aeef4cc334..ff8ec4ed80c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1547: Abort pending requests when connection dropped - [new feature] JAVA-1497: Port timestamp generators from 3.x - [improvement] JAVA-1539: Configure for deployment to Maven central - [new feature] JAVA-1519: Close channel if number of orphan stream ids exceeds a configurable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 83494cb24f6..c351e13d729 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -281,6 +281,15 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws E } } + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + // If the channel was closed normally (normal or forced shutdown), inFlight is already empty by + // the time we get here. So if it's not, it means the channel closed unexpectedly (e.g. the + // connection was dropped). + abortAllInFlight(new ClosedConnectionException("Lost connection to remote peer")); + super.channelInactive(ctx); + } + private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); ResponseCallback responseCallback = inFlight.remove(streamId); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 1713ab0b697..68ec39fea77 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -370,6 +370,33 @@ public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() } } + @Test + public void should_fail_all_pending_if_connection_lost() { + // Given + addToPipeline(); + Mockito.when(streamIds.acquire()).thenReturn(42, 43); + MockResponseCallback responseCallback1 = new MockResponseCallback(); + MockResponseCallback responseCallback2 = new MockResponseCallback(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback1)) + .awaitUninterruptibly(); + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback2)) + .awaitUninterruptibly(); + + // When + channel.pipeline().fireChannelInactive(); + + // Then + for (MockResponseCallback callback : ImmutableList.of(responseCallback1, responseCallback2)) { + assertThat(callback.getFailure()) + .isInstanceOf(ClosedConnectionException.class) + .hasMessageContaining("Lost connection to remote peer"); + } + } + @Test public void should_hold_stream_id_if_required() { // Given From 7d962af9291f69f0da6115375efd5bd5224a2353 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 13 Jul 2017 16:58:23 -0700 Subject: [PATCH 123/742] Migrate unit tests to JUnit --- core/pom.xml | 20 ++++++++-- ...nterceptor.java => DriverRunListener.java} | 27 +++++-------- .../driver/api/core/CassandraVersionTest.java | 2 +- .../driver/api/core/CqlIdentifierTest.java | 6 +-- .../driver/api/core/data/CqlDurationTest.java | 2 +- .../RoundRobinLoadBalancingPolicyTest.java | 6 +-- .../time/AtomicTimestampGeneratorTest.java | 2 +- .../MonotonicTimestampGeneratorTestBase.java | 11 +++-- .../ThreadLocalTimestampGeneratorTest.java | 2 +- .../core/type/reflect/GenericTypeTest.java | 2 +- .../core/ProtocolVersionRegistryTest.java | 13 +++--- .../ChannelFactoryAvailableIdsTest.java | 6 +-- .../ChannelFactoryClusterNameTest.java | 2 +- ...ChannelFactoryProtocolNegotiationTest.java | 16 +++++--- .../core/channel/ChannelFactoryTestBase.java | 13 +++--- .../core/channel/ChannelHandlerTestBase.java | 4 +- .../core/channel/ConnectInitHandlerTest.java | 6 +-- .../core/channel/DriverChannelTest.java | 6 +-- .../core/channel/InFlightHandlerTest.java | 6 +-- .../core/channel/ProtocolInitHandlerTest.java | 6 +-- .../core/channel/StreamIdGeneratorTest.java | 2 +- ...eriodicTypeSafeDriverConfigLoaderTest.java | 6 +-- .../typesafe/TypeSafeDriverConfigTest.java | 13 +++--- .../core/context/bus/EventBusTest.java | 6 +-- .../control/ControlConnectionEventsTest.java | 2 +- .../core/control/ControlConnectionTest.java | 2 +- .../control/ControlConnectionTestBase.java | 8 ++-- .../core/cql/CqlPrepareHandlerTest.java | 6 +-- .../core/cql/CqlRequestHandlerRetryTest.java | 26 +++++++----- ...equestHandlerSpeculativeExecutionTest.java | 24 +++++++---- .../core/cql/CqlRequestHandlerTest.java | 2 +- .../core/cql/CqlRequestHandlerTestBase.java | 11 +++-- .../core/cql/DefaultAsyncResultSetTest.java | 10 ++--- .../internal/core/cql/ResultSetsTest.java | 2 +- .../core/data/AccessibleByIdTestBase.java | 2 +- .../core/data/AccessibleByIndexTestBase.java | 6 +-- .../core/data/DefaultTupleValueTest.java | 2 +- .../core/data/DefaultUdtValueTest.java | 2 +- .../core/data/IdentifierIndexTest.java | 2 +- .../core/metadata/AddNodeRefreshTest.java | 2 +- .../metadata/DefaultTopologyMonitorTest.java | 6 +-- .../metadata/FullNodeListRefreshTest.java | 2 +- .../InitContactPointsRefreshTest.java | 2 +- .../LoadBalancingPolicyWrapperTest.java | 6 +-- .../core/metadata/MetadataManagerTest.java | 10 ++--- .../core/metadata/NodeStateManagerTest.java | 10 ++--- .../core/metadata/RemoveNodeRefreshTest.java | 2 +- .../core/pool/ChannelPoolInitTest.java | 2 +- .../core/pool/ChannelPoolKeyspaceTest.java | 2 +- .../core/pool/ChannelPoolReconnectTest.java | 2 +- .../core/pool/ChannelPoolResizeTest.java | 2 +- .../core/pool/ChannelPoolShutdownTest.java | 2 +- .../core/pool/ChannelPoolTestBase.java | 8 ++-- .../internal/core/pool/ChannelSetTest.java | 6 +-- .../protocol/ByteBufPrimitiveCodecTest.java | 40 ++++++++++--------- .../core/protocol/FrameDecoderTest.java | 6 +-- .../core/session/DefaultSessionTest.java | 6 +-- .../core/session/ReprepareOnUpTest.java | 6 +-- .../core/type/DataTypeDetachableTest.java | 6 +-- .../core/type/DataTypeSerializationTest.java | 2 +- .../core/type/codec/BigIntCodecTest.java | 8 ++-- .../core/type/codec/BlobCodecTest.java | 4 +- .../core/type/codec/BooleanCodecTest.java | 6 +-- .../core/type/codec/CounterCodecTest.java | 8 ++-- .../core/type/codec/CqlDurationCodecTest.java | 6 +-- .../core/type/codec/CustomCodecTest.java | 4 +- .../core/type/codec/DateCodecTest.java | 6 +-- .../core/type/codec/DecimalCodecTest.java | 6 +-- .../core/type/codec/DoubleCodecTest.java | 6 +-- .../core/type/codec/FloatCodecTest.java | 6 +-- .../core/type/codec/InetCodecTest.java | 10 ++--- .../core/type/codec/IntCodecTest.java | 8 ++-- .../core/type/codec/ListCodecTest.java | 8 ++-- .../core/type/codec/MapCodecTest.java | 8 ++-- .../core/type/codec/SetCodecTest.java | 8 ++-- .../core/type/codec/SmallIntCodecTest.java | 8 ++-- .../core/type/codec/StringCodecTest.java | 4 +- .../core/type/codec/TimeCodecTest.java | 8 ++-- .../core/type/codec/TimeUuidCodecTest.java | 6 +-- .../core/type/codec/TimestampCodecTest.java | 8 ++-- .../core/type/codec/TinyIntCodecTest.java | 6 +-- .../core/type/codec/TupleCodecTest.java | 8 ++-- .../core/type/codec/UdtCodecTest.java | 8 ++-- .../core/type/codec/UuidCodecTest.java | 8 ++-- .../core/type/codec/VarintCodecTest.java | 4 +- .../registry/CachingCodecRegistryTest.java | 8 ++-- .../util/concurrent/CycleDetectorTest.java | 2 +- .../core/util/concurrent/DebouncerTest.java | 6 +-- .../util/concurrent/ReconnectionTest.java | 8 ++-- .../concurrent/ReplayingEventFilterTest.java | 6 +-- .../ScheduledTaskCapturingEventLoopTest.java | 3 +- .../services/org.testng.ITestNGListener | 1 - pom.xml | 11 +++-- 93 files changed, 341 insertions(+), 296 deletions(-) rename core/src/test/java/com/datastax/oss/driver/{TestInterceptor.java => DriverRunListener.java} (51%) delete mode 100644 core/src/test/resources/META-INF/services/org.testng.ITestNGListener diff --git a/core/pom.xml b/core/pom.xml index addd051cf26..bcd62eb9108 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -69,9 +69,12 @@ test - org.testng - testng - test + junit + junit + + + com.tngtech.java + junit-dataprovider org.assertj @@ -110,6 +113,17 @@ + + maven-surefire-plugin + + + + listener + com.datastax.oss.driver.DriverRunListener + + + + diff --git a/core/src/test/java/com/datastax/oss/driver/TestInterceptor.java b/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java similarity index 51% rename from core/src/test/java/com/datastax/oss/driver/TestInterceptor.java rename to core/src/test/java/com/datastax/oss/driver/DriverRunListener.java index 392e5ce35ff..8ff33662209 100644 --- a/core/src/test/java/com/datastax/oss/driver/TestInterceptor.java +++ b/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java @@ -15,30 +15,25 @@ */ package com.datastax.oss.driver; -import org.testng.IHookCallBack; -import org.testng.IHookable; -import org.testng.ITestResult; +import org.junit.runner.Description; +import org.junit.runner.notification.RunListener; + +import static org.assertj.core.api.Assertions.fail; /** - * Intercepts the execution of each test, in order to perform additional tests. + * Common parent of all driver tests, to store common configuration and perform sanity checks. * - * @see "src/test/resources/META-INF/services/org.testng.ITestNGListener" + * @see "maven-surefire-plugin configuration in pom.xml" */ -public class TestInterceptor implements IHookable { +public class DriverRunListener extends RunListener { @Override - public void run(IHookCallBack callback, ITestResult result) { - - // If a test interrupts the main thread silently, this can make later tests fail. Instead, we + public void testFinished(Description description) throws Exception { + // If a test interrupted the main thread silently, this can make later tests fail. Instead, we // fail the test and clear the interrupt status. - boolean wasInterrupted = Thread.currentThread().isInterrupted(); - - callback.runTestMethod(result); - // Note: Thread.interrupted() also clears the flag, which is what we want. - if (!wasInterrupted && Thread.interrupted()) { - result.setStatus(ITestResult.FAILURE); - result.setThrowable(new IllegalStateException("The test interrupted the main thread")); + if (Thread.interrupted()) { + fail(description.getMethodName() + " interrupted the main thread"); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java index 97d490bd53b..788a857de35 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java index ab166692ce6..b52797159e5 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -39,12 +39,12 @@ public void should_build_from_valid_cql() { assertThat(CqlIdentifier.fromCql("\"create\"").asInternal()).isEqualTo("create"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_build_from_valid_cql_if_special_characters() { CqlIdentifier.fromCql("foo bar"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_build_from_valid_cql_if_reserved_keyword() { CqlIdentifier.fromCql("Create"); } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java index 312db90a0d6..fb732530304 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java index 8ca603c769e..d758c3a7c15 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java @@ -21,11 +21,11 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +36,7 @@ public class RoundRobinLoadBalancingPolicyTest { private RoundRobinLoadBalancingPolicy policy; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java index e2dee9c206f..47e866d5022 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java @@ -22,9 +22,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.junit.Test; import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.fail; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java index 513e96a8fe9..874442b33d6 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -26,6 +26,9 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.time.Clock; import java.time.Duration; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -33,12 +36,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; import org.slf4j.LoggerFactory; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.timeout; abstract class MonotonicTimestampGeneratorTestBase { @@ -59,7 +58,7 @@ abstract class MonotonicTimestampGeneratorTestBase { private Logger logger; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -78,7 +77,7 @@ public void setup() { logger.addAppender(appender); } - @AfterMethod + @After public void teardown() { logger.detachAppender(appender); } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java index de370a8ffca..60fb548c4ec 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java @@ -25,9 +25,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.junit.Test; import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.fail; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java index ce7d9f66898..65d8b08b8b8 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java @@ -18,7 +18,7 @@ import com.google.common.reflect.TypeToken; import java.util.List; import java.util.Map; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java index 825711216f5..bf915821c0c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java @@ -17,7 +17,9 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import java.util.Optional; -import org.testng.annotations.Test; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; import static com.datastax.oss.driver.Assertions.assertThat; @@ -30,11 +32,12 @@ public class ProtocolVersionRegistryTest { private static ProtocolVersion V10 = new MockProtocolVersion(10, false); private static ProtocolVersion V11 = new MockProtocolVersion(11, false); - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Duplicate version code: 5 in V5 and V5_BETA" - ) + @Rule public ExpectedException expectedException = ExpectedException.none(); + + @Test public void should_fail_if_duplicate_version_code() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Duplicate version code: 5 in V5 and V5_BETA"); new ProtocolVersionRegistry(new ProtocolVersion[] {V5, V5_BETA}); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 83b9d0202f7..8b762f9e193 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -22,10 +22,10 @@ import com.datastax.oss.protocol.internal.response.result.Void; import io.netty.util.concurrent.Future; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -35,7 +35,7 @@ public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Mock private ResponseCallback responseCallback; - @BeforeMethod + @Before public void setup() throws InterruptedException { super.setup(); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 0754095dd1b..e4463ee2f16 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.protocol.internal.response.Ready; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index dc72bea11bc..88b3c5d8a76 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -23,11 +23,12 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.Ready; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Optional; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -55,7 +56,8 @@ public void should_succeed_if_version_specified_and_supported_by_server() { assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); } - @Test(dataProvider = "unsupportedProtocolCodes") + @Test + @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_version_specified_and_not_supported_by_server(int errorCode) { // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) @@ -112,7 +114,8 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); } - @Test(dataProvider = "unsupportedProtocolCodes") + @Test + @UseDataProvider("unsupportedProtocolCodes") public void should_negotiate_if_version_not_specified_and_server_supports_legacy(int errorCode) { // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) @@ -145,7 +148,8 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V3); } - @Test(dataProvider = "unsupportedProtocolCodes") + @Test + @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) { // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) @@ -192,7 +196,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) * Depending on the Cassandra version, an "unsupported protocol" response can use different error * codes, so we test all of them. */ - @DataProvider(name = "unsupportedProtocolCodes") + @DataProvider public static Object[][] unsupportedProtocolCodes() { return new Object[][] { new Object[] {ProtocolConstants.ErrorCode.PROTOCOL_ERROR}, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 22c8b03f128..7019adc3d72 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -30,6 +30,7 @@ import com.datastax.oss.protocol.internal.FrameCodec; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.response.Ready; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; @@ -48,12 +49,13 @@ import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.fail; @@ -71,7 +73,8 @@ * writeInboundFrame (for instance the position of the connection in creation order) to specify * which instance to use. */ -abstract class ChannelFactoryTestBase { +@RunWith(DataProviderRunner.class) +public abstract class ChannelFactoryTestBase { static final LocalAddress SERVER_ADDRESS = new LocalAddress(ChannelFactoryTestBase.class.getSimpleName() + "-server"); @@ -95,7 +98,7 @@ abstract class ChannelFactoryTestBase { // The channel to send responses to the last open connection private volatile LocalChannel serverResponseChannel; - @BeforeMethod + @Before public void setup() throws InterruptedException { MockitoAnnotations.initMocks(this); @@ -248,7 +251,7 @@ protected void initChannel(Channel channel) throws Exception { } } - @AfterMethod + @After public void tearDown() throws InterruptedException { serverAcceptChannel.close(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java index 1dbc1f7c636..6b837af780f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java @@ -19,7 +19,7 @@ import com.datastax.oss.protocol.internal.Message; import io.netty.channel.embedded.EmbeddedChannel; import java.util.Collections; -import org.testng.annotations.BeforeMethod; +import org.junit.Before; import static org.assertj.core.api.Assertions.assertThat; @@ -33,7 +33,7 @@ public class ChannelHandlerTestBase { protected EmbeddedChannel channel; - @BeforeMethod + @Before public void setup() { channel = new EmbeddedChannel(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java index 7ffb307af15..7ac53f1eef0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java @@ -18,8 +18,8 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -27,7 +27,7 @@ public class ConnectInitHandlerTest extends ChannelHandlerTestBase { private TestHandler handler; - @BeforeMethod + @Before @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index f1b6449672c..68d0766528a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -28,10 +28,10 @@ import java.util.LinkedList; import java.util.Map; import java.util.Queue; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -43,7 +43,7 @@ public class DriverChannelTest extends ChannelHandlerTestBase { @Mock private StreamIdGenerator streamIds; - @BeforeMethod + @Before @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 68ec39fea77..adf6fdcadae 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -32,12 +32,12 @@ import java.net.InetSocketAddress; import java.util.Collections; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; @@ -49,7 +49,7 @@ public class InFlightHandlerTest extends ChannelHandlerTestBase { @Mock private StreamIdGenerator streamIds; - @BeforeMethod + @Before @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index c10c8f42034..c6f8a3f3960 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -46,11 +46,11 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -63,7 +63,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private DriverConfigProfile defaultConfigProfile; private ProtocolVersionRegistry protocolVersionRegistry = new ProtocolVersionRegistry(); - @BeforeMethod + @Before @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java index 547334df61d..68b765195a5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java index 545ddd52482..b41c6d8d6dd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java @@ -30,11 +30,11 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; @@ -50,7 +50,7 @@ public class PeriodicTypeSafeDriverConfigLoaderTest { private EventBus eventBus; private AtomicReference configSource; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 5945c0b2b4f..54fb4c562a4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -19,12 +19,16 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import org.testng.annotations.Test; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; import static com.datastax.oss.driver.Assertions.assertThat; public class TypeSafeDriverConfigTest { + @Rule public ExpectedException expectedException = ExpectedException.none(); + @Test public void should_load_minimal_config_with_required_options_and_no_profiles() { TypeSafeDriverConfig config = parse("required_int = 42"); @@ -38,11 +42,10 @@ public void should_load_config_with_no_profiles_and_optional_values() { assertThat(config).hasIntOption(MockOptions.OPTIONAL_INT, 43); } - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Missing option required_int. Check your configuration file." - ) + @Test public void should_fail_if_required_option_is_missing() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Missing option required_int. Check your configuration file."); parse(""); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java index 6da82b02716..2dd5f00c800 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import java.util.HashMap; import java.util.Map; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -29,7 +29,7 @@ public class EventBusTest { private Map results; private ChildEvent event = new ChildEvent(); - @BeforeMethod + @Before public void setup() { bus = new EventBus("test"); results = new HashMap<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 92434360c0c..2444737d0eb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -26,9 +26,9 @@ import com.datastax.oss.protocol.internal.response.event.TopologyChangeEvent; import com.google.common.collect.ImmutableList; import java.util.concurrent.CompletableFuture; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index efae2053ebb..b1039406ecf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -21,8 +21,8 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 006aac32a59..b0c49a89cdf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -43,12 +43,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.internal.util.MockUtil; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -74,7 +74,7 @@ abstract class ControlConnectionTestBase { protected ControlConnection controlConnection; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -122,7 +122,7 @@ public void setup() { controlConnection = new ControlConnection(context); } - @AfterMethod + @After public void teardown() { adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 9e09d42cd61..999323a191e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -35,11 +35,11 @@ import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -56,7 +56,7 @@ public class CqlPrepareHandlerTest { @Mock private CqlPrepareProcessor processor; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 41e52ac39f8..445ee47dbd8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -37,11 +37,12 @@ import com.datastax.oss.protocol.internal.response.error.ReadTimeout; import com.datastax.oss.protocol.internal.response.error.Unavailable; import com.datastax.oss.protocol.internal.response.error.WriteTimeout; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Iterator; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -49,7 +50,8 @@ public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { - @Test(dataProvider = "allIdempotenceConfigs") + @Test + @UseDataProvider("allIdempotenceConfigs") public void should_always_try_next_node_if_bootstrapping( boolean defaultIdempotence, SimpleStatement statement) { try (RequestHandlerTestHarness harness = @@ -90,7 +92,8 @@ public void should_always_try_next_node_if_bootstrapping( } } - @Test(dataProvider = "allIdempotenceConfigs") + @Test + @UseDataProvider("allIdempotenceConfigs") public void should_always_rethrow_query_validation_error( boolean defaultIdempotence, SimpleStatement statement) { try (RequestHandlerTestHarness harness = @@ -116,7 +119,8 @@ public void should_always_rethrow_query_validation_error( } } - @Test(dataProvider = "failureAndIdempotent") + @Test + @UseDataProvider("failureAndIdempotent") public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -147,7 +151,8 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( } } - @Test(dataProvider = "failureAndIdempotent") + @Test + @UseDataProvider("failureAndIdempotent") public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -178,7 +183,8 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( } } - @Test(dataProvider = "failureAndIdempotent") + @Test + @UseDataProvider("failureAndIdempotent") public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -206,7 +212,8 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( } } - @Test(dataProvider = "failureAndIdempotent") + @Test + @UseDataProvider("failureAndIdempotent") public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -228,7 +235,8 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( } } - @Test(dataProvider = "failureAndNotIdempotent") + @Test + @UseDataProvider("failureAndNotIdempotent") public void should_rethrow_error_if_not_idempotent( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index f8d85137b42..693d11e5665 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -24,17 +24,19 @@ import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.Error; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; public class CqlRequestHandlerSpeculativeExecutionTest extends CqlRequestHandlerTestBase { - @Test(dataProvider = "nonIdempotentConfig") + @Test + @UseDataProvider("nonIdempotentConfig") public void should_not_schedule_speculative_executions_if_not_idempotent( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -57,7 +59,8 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_schedule_speculative_executions( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -106,7 +109,8 @@ public void should_schedule_speculative_executions( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_not_start_execution_if_result_complete( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -150,7 +154,8 @@ public void should_not_start_execution_if_result_complete( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_fail_if_no_more_nodes_and_initial_execution_is_last( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -202,7 +207,8 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -255,7 +261,8 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_retry_in_speculative_executions( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = @@ -300,7 +307,8 @@ public void should_retry_in_speculative_executions( } } - @Test(dataProvider = "idempotentConfig") + @Test + @UseDataProvider("idempotentConfig") public void should_stop_retrying_other_executions_if_result_complete( boolean defaultIdempotence, SimpleStatement statement) { RequestHandlerTestHarness.Builder harnessBuilder = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index d13ca205a90..3e0b986c8a6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -39,8 +39,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 6e2c113bbd3..f459db4c879 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -28,17 +28,20 @@ import com.datastax.oss.protocol.internal.response.result.RowsMetadata; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import org.junit.Before; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -abstract class CqlRequestHandlerTestBase { +@RunWith(DataProviderRunner.class) +public abstract class CqlRequestHandlerTestBase { protected static final SimpleStatement UNDEFINED_IDEMPOTENCE_STATEMENT = SimpleStatement.newInstance("mock query"); @@ -51,7 +54,7 @@ abstract class CqlRequestHandlerTestBase { @Mock protected Node node2; @Mock protected Node node3; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index a88b362dbee..bd08c8afd01 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -34,11 +34,11 @@ import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -50,7 +50,7 @@ public class DefaultAsyncResultSetTest { @Mock private Session session; @Mock private InternalDriverContext context; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -59,7 +59,7 @@ public void setup() { Mockito.when(context.protocolVersion()).thenReturn(CoreProtocolVersion.DEFAULT); } - @Test(expectedExceptions = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void should_fail_to_fetch_next_page_if_last() { // Given Mockito.when(executionInfo.getPagingState()).thenReturn(null); @@ -170,7 +170,7 @@ public void should_report_not_applied_if_column_present_and_true() { assertThat(resultSet.wasApplied()).isTrue(); } - @Test(expectedExceptions = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void should_fail_to_report_if_applied_if_column_present_but_empty() { // Given Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index 28e19d77ef6..a1d3e08090d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -26,8 +26,8 @@ import java.util.Iterator; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index f610af68d18..c77bba6427a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -27,8 +27,8 @@ import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index f7ba7fc55c8..4e4bb552fd2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -31,11 +31,11 @@ import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.util.List; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -51,7 +51,7 @@ public abstract class AccessibleByIndexTestBase doubleCodec; protected TypeCodec textCodec; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index d8d1ed7bf0f..e86d2a8114e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.util.List; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index 0ea7081e5c1..e51be20b31d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -25,7 +25,7 @@ import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.datastax.oss.protocol.internal.util.Bytes; import java.util.List; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java index d325f1290c1..6af1fd62e36 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index e36ac581d47..05bf1aa335f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -19,7 +19,7 @@ import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index dd1035ca358..5425056fc90 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -40,11 +40,11 @@ import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -65,7 +65,7 @@ public class DefaultTopologyMonitorTest { private TestTopologyMonitor topologyMonitor; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index c00370f3366..c383acc98dd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 451942fd934..e5184ddfda7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableSet; import java.net.InetSocketAddress; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 4c777853305..1d071eca1e6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -32,13 +32,13 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -62,7 +62,7 @@ public class LoadBalancingPolicyWrapperTest { private LoadBalancingPolicyWrapper wrapper; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 1e11b66caee..01c80796d94 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -32,12 +32,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -56,7 +56,7 @@ public class MetadataManagerTest { private TestMetadataManager metadataManager; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -69,7 +69,7 @@ public void setup() { metadataManager = new TestMetadataManager(context); } - @AfterMethod + @After public void teardown() { adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index b6bc8001a8a..ec6d37601e8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -37,12 +37,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -63,7 +63,7 @@ public class NodeStateManagerTest { private EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -96,7 +96,7 @@ public void setup() { Mockito.when(context.metadataManager()).thenReturn(metadataManager); } - @AfterMethod + @After public void teardown() { adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index c4f2c7f5cdc..eea8da8ded7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 2ab657688eb..5572183aa2d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -26,9 +26,9 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index c6ace1f3b8e..5a76faf50e9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -24,8 +24,8 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 7fffee347ba..1b03d7a7860 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -24,9 +24,9 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.times; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index ff881684c73..5f741958e5e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -24,9 +24,9 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 02d175ec7e6..6a2c627bfa4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -24,9 +24,9 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 77a33ff7695..daa16638df3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -36,11 +36,11 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -60,7 +60,7 @@ abstract class ChannelPoolTestBase { EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -81,7 +81,7 @@ public void setup() { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); } - @AfterMethod + @After public void teardown() { adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java index e8a3a84d12f..31e6ccd5771 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java @@ -16,11 +16,11 @@ package com.datastax.oss.driver.internal.core.pool; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; @@ -29,7 +29,7 @@ public class ChannelSetTest { @Mock private DriverChannel channel1, channel2, channel3; private ChannelSet set; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); set = new ChannelSet(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java index aeb07582b4d..5b2cb171783 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java @@ -22,7 +22,9 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import org.testng.annotations.Test; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; import static com.datastax.oss.driver.Assertions.assertThat; @@ -33,6 +35,8 @@ public class ByteBufPrimitiveCodecTest { private ByteBufPrimitiveCodec codec = new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT); + @Rule public ExpectedException expectedException = ExpectedException.none(); + @Test public void should_concatenate() { ByteBuf left = ByteBufs.wrap(0xca, 0xfe); @@ -85,11 +89,10 @@ public void should_read_inet_v6() { assertThat(inet.getPort()).isEqualTo(9042); } - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Invalid address length: 3 \\(\\[127, 0, 1\\]\\)" - ) + @Test public void should_fail_to_read_inet_if_length_invalid() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address length: 3 ([127, 0, 1])"); ByteBuf source = ByteBufs.wrap( // length (as a byte) @@ -131,11 +134,11 @@ public void should_read_inetaddr_v6() { assertThat(inetAddr.getHostAddress()).isEqualTo("0:0:0:0:0:0:0:1"); } - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Invalid address length: 3 \\(\\[127, 0, 1\\]\\)" - ) + @Test public void should_fail_to_read_inetaddr_if_length_invalid() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address length: 3 ([127, 0, 1])"); + ByteBuf source = ByteBufs.wrap( // length (as a byte) @@ -202,12 +205,12 @@ public void should_read_string() { assertThat(codec.readString(source)).isEqualTo("hello"); } - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = - "Not enough bytes to read an UTF-8 serialized string of size 4" - ) + @Test public void should_fail_to_read_string_if_not_enough_characters() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage( + "Not enough bytes to read an UTF-8 serialized string of size 4"); + ByteBuf source = codec.allocate(2); source.writeShort(4); @@ -232,12 +235,11 @@ public void should_read_long_string() { assertThat(codec.readLongString(source)).isEqualTo("hello"); } - @Test( - expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = - "Not enough bytes to read an UTF-8 serialized string of size 4" - ) + @Test public void should_fail_to_read_long_string_if_not_enough_characters() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage( + "Not enough bytes to read an UTF-8 serialized string of size 4"); ByteBuf source = codec.allocate(2); source.writeInt(4); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java index 09e6e55de2e..e780bafc458 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java @@ -24,8 +24,8 @@ import io.netty.buffer.ByteBuf; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.TooLongFrameException; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -50,7 +50,7 @@ public class FrameDecoderTest extends ChannelHandlerTestBase { private FrameCodec frameCodec; - @BeforeMethod + @Before @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 0c9e8bcc5b9..c84bcaaacc1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -44,11 +44,11 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -73,7 +73,7 @@ public class DefaultSessionTest { private DefaultEventLoopGroup adminEventLoopGroup; private EventBus eventBus; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index fca53cf54ea..99d7aa9def1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -43,11 +43,11 @@ import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; @@ -60,7 +60,7 @@ public class ReprepareOnUpTest { private Runnable whenPrepared; private CompletionStage done; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java index d3ff735c0ac..d8068a2b863 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java @@ -26,10 +26,10 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.SerializationHelper; import com.google.common.collect.ImmutableList; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +37,7 @@ public class DataTypeDetachableTest { @Mock private AttachmentPoint attachmentPoint; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java index 8c8bf398155..67287c7106e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.SerializationHelper; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java index 4f8c8f54329..78796e558d5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -39,7 +39,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000000000000000" + "0000"); } @@ -59,12 +59,12 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a number"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_if_out_of_range() { parse(Long.toString(Long.MAX_VALUE) + "0"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java index bb78d110080..6afe44cdaff 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -78,7 +78,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a blob"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java index 59211226066..9daa851deea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +41,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000"); } @@ -63,7 +63,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("maybe"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java index 6938afd8973..bbd28d881b7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -39,7 +39,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000000000000000" + "0000"); } @@ -59,12 +59,12 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a number"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_if_out_of_range() { parse(Long.toString(Long.MAX_VALUE) + "0"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java index 7f6ba160a7b..463da1cfd1d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -48,7 +48,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } @@ -68,7 +68,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a duration"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java index 3598e0e5bcf..bfc2f165eb5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -79,7 +79,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a blob"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java index 463f17bd0d7..1dc1bb073ed 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalDate; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +52,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x00000000" + "0000"); } @@ -82,7 +82,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a date"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java index 7a37b6aeca3..6b9f642507b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigDecimal; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +52,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } @@ -74,7 +74,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a decimal"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java index 3afcddaa789..d2f34732f8f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +41,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000"); } @@ -61,7 +61,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a double"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java index adcf6af4563..fa36e3add58 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +41,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000"); } @@ -61,7 +61,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a float"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java index 8cfa5f4555b..e3794be944c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import java.net.InetAddress; import java.net.UnknownHostException; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -58,17 +58,17 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_incorrect_byte_count() { decode("0x" + Strings.repeat("00", 7)); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x" + Strings.repeat("00", 17)); } @@ -90,7 +90,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not an address"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java index fa34dd54123..d7128543915 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,12 +41,12 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000000000000000"); } @@ -66,7 +66,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not an int"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java index 24f74019625..4754bea189c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java @@ -24,11 +24,11 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +36,7 @@ public class ListCodecTest extends CodecTestBase> { @Mock private TypeCodec elementCodec; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -136,7 +136,7 @@ public void should_parse_non_empty_list() { assertThat(parse("[a,b,c]")).containsExactly(1, 2, 3); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_malformed_list() { parse("not a list"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java index 2d2758465f0..3df9ff0e056 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java @@ -24,11 +24,11 @@ import com.google.common.collect.ImmutableMap; import java.util.LinkedHashMap; import java.util.Map; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +37,7 @@ public class MapCodecTest extends CodecTestBase> { @Mock private TypeCodec keyCodec; @Mock private TypeCodec valueCodec; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -182,7 +182,7 @@ public void should_parse_non_empty_map() { .containsEntry("c", 3); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_malformed_map() { parse("not a map"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java index 6686151ecb7..4ebd8b628a3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java @@ -24,11 +24,11 @@ import com.google.common.collect.ImmutableSet; import java.util.LinkedHashSet; import java.util.Set; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +36,7 @@ public class SetCodecTest extends CodecTestBase> { @Mock private TypeCodec elementCodec; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -136,7 +136,7 @@ public void should_parse_non_empty_set() { assertThat(parse("{a,b,c}")).containsExactly(1, 2, 3); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_malformed_set() { parse("not a set"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java index 099441bbfdb..3686e415844 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,12 +41,12 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x00"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x000000"); } @@ -66,7 +66,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a smallint"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java index e2e0a62866f..26748f8ebd1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -55,7 +55,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a string"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java index 3098662b58b..aae03d65f41 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalTime; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -39,12 +39,12 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000000000000000" + "0000"); } @@ -71,7 +71,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a time"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java index 1de96f682c8..6c323cd6374 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -38,7 +38,7 @@ public void should_encode_time_uuid() { assertThat(encode(TIME_BASED)).isEqualTo("0x58046580293811e7b0631332a5f033c2"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_not_encode_non_time_uuid() { assertThat(codec.canEncode(NOT_TIME_BASED)).isFalse(); encode(NOT_TIME_BASED); @@ -49,7 +49,7 @@ public void should_format_time_uuid() { assertThat(format(TIME_BASED)).isEqualTo("58046580-2938-11e7-b063-1332a5f033c2"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_not_format_non_time_uuid() { format(NOT_TIME_BASED); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java index 17c276d9a7d..f219d76c2d8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.Instant; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,12 +41,12 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000000000000000" + "0000"); } @@ -73,7 +73,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a timestamp"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java index 838bd50475c..8fcf1ea3119 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +41,7 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x0000"); } @@ -61,7 +61,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a tinyint"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index bc8c112cbcf..4e64c3c3cea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -27,11 +27,11 @@ import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ public class TupleCodecTest extends CodecTestBase { private TupleType tupleType; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -158,7 +158,7 @@ public void should_parse_tuple() { Mockito.verify(textCodec).parse("'a'"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a tuple"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 9e340135767..76daef03d9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -28,11 +28,11 @@ import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ public class UdtCodecTest extends CodecTestBase { private UserDefinedType userType; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -166,7 +166,7 @@ public void should_parse_udt() { Mockito.verify(textCodec).parse("'a'"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a udt"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java index 9a4e51d84d7..8e5d83996f4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -43,12 +43,12 @@ public void should_decode() { assertThat(decode(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_not_enough_bytes() { decode("0x0000"); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_decode_if_too_many_bytes() { decode("0x00000000000000020000000000000001" + "0000"); } @@ -68,7 +68,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a uuid"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java index b57ce0ff3b8..84aaeb577d9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigInteger; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -57,7 +57,7 @@ public void should_parse() { assertThat(parse(null)).isNull(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void should_fail_to_parse_invalid_input() { parse("not a varint"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index bece884cb7d..49dc09c529b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -50,21 +50,21 @@ import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; +import org.junit.Before; +import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.fail; public class CachingCodecRegistryTest { @Mock private BiConsumer> onCacheLookup; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java index a0f9dbddd6a..704205918d0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java @@ -23,7 +23,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import org.testng.annotations.Test; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java index 73582c8e4aa..4f90a5b3e95 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java @@ -22,13 +22,13 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; @@ -43,7 +43,7 @@ public class DebouncerTest { @Mock private ScheduledFuture scheduledFuture; private List results; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(adminExecutor.inEventLoop()).thenReturn(true); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index 5dd1bf164dc..850ea268927 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -23,11 +23,11 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; @@ -43,7 +43,7 @@ public class ReconnectionTest { private MockReconnectionTask reconnectionTask; private Reconnection reconnection; - @BeforeMethod + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -83,7 +83,7 @@ public void should_schedule_first_attempt_on_start() { Mockito.verify(onStartCallback).run(); } - @Test(expectedExceptions = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void should_fail_if_started_twice() { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); reconnection.start(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java index 5cc121c09f0..7e556132c97 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java @@ -17,8 +17,8 @@ import java.util.ArrayList; import java.util.List; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -26,7 +26,7 @@ public class ReplayingEventFilterTest { private ReplayingEventFilter filter; private List filteredEvents; - @BeforeMethod + @Before public void setup() { filteredEvents = new ArrayList<>(); filter = new ReplayingEventFilter<>(filteredEvents::add); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java index 7dda6b4e068..80fc1820eea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java @@ -19,10 +19,9 @@ import io.netty.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.testng.annotations.Test; +import org.junit.Test; import static com.datastax.oss.driver.Assertions.assertThat; -import static com.datastax.oss.driver.Assertions.fail; public class ScheduledTaskCapturingEventLoopTest { diff --git a/core/src/test/resources/META-INF/services/org.testng.ITestNGListener b/core/src/test/resources/META-INF/services/org.testng.ITestNGListener deleted file mode 100644 index d5149d1ba9d..00000000000 --- a/core/src/test/resources/META-INF/services/org.testng.ITestNGListener +++ /dev/null @@ -1 +0,0 @@ -com.datastax.oss.driver.TestInterceptor \ No newline at end of file diff --git a/pom.xml b/pom.xml index c7d6f174657..4aea88355fb 100644 --- a/pom.xml +++ b/pom.xml @@ -80,9 +80,14 @@ 2.1.6 - org.testng - testng - 6.11 + junit + junit + 4.12 + + + com.tngtech.java + junit-dataprovider + 1.10.0 org.assertj From 4a066b2bf4c80bd85f0897410cf9ff20e4f90da0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 14 Jul 2017 12:07:42 -0700 Subject: [PATCH 124/742] Adjust configuration API - rename methods - provide a way to get all the named profiles --- .../oss/driver/api/core/ClusterBuilder.java | 2 +- .../api/core/auth/PlainTextAuthProvider.java | 2 +- .../oss/driver/api/core/config/DriverConfig.java | 16 ++++++++++++++-- .../ExponentialReconnectionPolicy.java | 2 +- .../api/core/ssl/DefaultSslEngineFactory.java | 2 +- .../core/time/MonotonicTimestampGenerator.java | 4 ++-- .../internal/core/channel/ChannelFactory.java | 4 ++-- .../core/channel/ProtocolInitHandler.java | 2 +- .../PeriodicTypeSafeDriverConfigLoader.java | 4 ++-- .../config/typesafe/TypeSafeDriverConfig.java | 9 +++++++-- .../core/context/DefaultDriverContext.java | 2 +- .../core/metadata/DefaultTopologyMonitor.java | 2 +- .../internal/core/metadata/NodeStateManager.java | 2 +- .../driver/internal/core/pool/ChannelPool.java | 2 +- .../internal/core/session/DefaultSession.java | 4 ++-- .../internal/core/session/ReprepareOnUp.java | 9 +++++---- .../core/session/RequestHandlerBase.java | 4 ++-- .../driver/internal/core/util/Reflection.java | 2 +- .../MonotonicTimestampGeneratorTestBase.java | 2 +- .../driver/internal/core/DriverConfigAssert.java | 4 ++-- .../core/channel/ChannelFactoryTestBase.java | 4 ++-- .../core/channel/ProtocolInitHandlerTest.java | 2 +- .../PeriodicTypeSafeDriverConfigLoaderTest.java | 2 +- .../typesafe/TypeSafeDriverConfigTest.java | 8 ++++---- .../internal/core/cql/CqlPrepareHandlerTest.java | 2 +- .../internal/core/cql/CqlRequestHandlerTest.java | 2 +- .../core/cql/RequestHandlerTestHarness.java | 2 +- .../metadata/DefaultTopologyMonitorTest.java | 2 +- .../core/metadata/NodeStateManagerTest.java | 2 +- .../internal/core/pool/ChannelPoolTestBase.java | 2 +- .../core/session/DefaultSessionTest.java | 2 +- .../internal/core/session/ReprepareOnUpTest.java | 2 +- 32 files changed, 65 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 0bb3134137e..23951564219 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -125,7 +125,7 @@ public CompletionStage buildAsync() { DriverConfigLoader configLoader = buildIfNull(this.configLoader, ClusterBuilder::defaultConfigLoader); - DriverConfigProfile defaultConfig = configLoader.getInitialConfig().defaultProfile(); + DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = defaultConfig.isDefined(CoreDriverOption.CONTACT_POINTS) ? defaultConfig.getStringList(CoreDriverOption.CONTACT_POINTS) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index b96fd7873fb..6c12fa32630 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -49,7 +49,7 @@ public class PlainTextAuthProvider implements AuthProvider { /** Builds a new instance. */ public PlainTextAuthProvider(DriverContext context, DriverOption configRoot) { - this.config = context.config().defaultProfile(); + this.config = context.config().getDefaultProfile(); this.configRoot = configRoot; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java index f250138cb5d..70ca6cb3989 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.config; +import java.util.Map; + /** * The configuration of the driver. * @@ -24,7 +26,17 @@ * "analytics" profile vs. a "transactional" profile). */ public interface DriverConfig { - DriverConfigProfile defaultProfile(); + DriverConfigProfile getDefaultProfile(); + + /** @throws IllegalArgumentException if there is no profile with this name. */ + DriverConfigProfile getNamedProfile(String profileName); - DriverConfigProfile getProfile(String profileName); + /** + * Returns an immutable view of all the named profiles. + * + *

      Implementations typically return a defensive copy of their internal state; therefore this + * should not be used in performance-sensitive parts of the code, see {@link + * #getNamedProfile(String)} instead. + */ + Map getNamedProfiles(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index ea263f7d36b..1a4b74bde25 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -34,7 +34,7 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { /** Builds a new instance. */ public ExponentialReconnectionPolicy(DriverContext context, DriverOption configRoot) { - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); DriverOption baseDelayOption = configRoot.concat(CoreDriverOption.RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY); DriverOption maxDelayOption = diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java index ae6c744fae7..6d8b5508085 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -55,7 +55,7 @@ public DefaultSslEngineFactory(DriverContext driverContext, DriverOption configR } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Cannot initialize SSL Context", e); } - DriverConfigProfile config = driverContext.config().defaultProfile(); + DriverConfigProfile config = driverContext.config().getDefaultProfile(); DriverOption cipherSuiteOption = configRoot.concat(CoreDriverOption.RELATIVE_DEFAULT_SSL_CIPHER_SUITES); if (config.isDefined(cipherSuiteOption)) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java index f9b3212c1c0..e717adde6d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java @@ -49,7 +49,7 @@ protected MonotonicTimestampGenerator( DriverOption warningThresholdOption = configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD); - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); this.warningThresholdMicros = (config.isDefined(warningThresholdOption)) ? config.getDuration(warningThresholdOption).toNanos() / 1000 @@ -100,7 +100,7 @@ private void maybeLog(long currentTick, long last) { private static Clock buildClock(DriverContext context, DriverOption configRoot) { DriverOption forceJavaClockOption = configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); boolean forceJavaClock = config.isDefined(forceJavaClockOption) && config.getBoolean(forceJavaClockOption); return Clock.getInstance(forceJavaClock); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index bb1c64eb61e..4c2be72d663 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -56,7 +56,7 @@ public class ChannelFactory { public ChannelFactory(InternalDriverContext context) { this.context = context; - DriverConfigProfile defaultConfig = context.config().defaultProfile(); + DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); if (defaultConfig.isDefined(CoreDriverOption.PROTOCOL_VERSION)) { String versionName = defaultConfig.getString(CoreDriverOption.PROTOCOL_VERSION); this.protocolVersion = context.protocolVersionRegistry().fromName(versionName); @@ -176,7 +176,7 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index c4b875969b2..5d737171221 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -73,7 +73,7 @@ class ProtocolInitHandler extends ConnectInitHandler { this.internalDriverContext = internalDriverContext; - DriverConfigProfile defaultConfig = internalDriverContext.config().defaultProfile(); + DriverConfigProfile defaultConfig = internalDriverContext.config().getDefaultProfile(); this.timeoutMillis = defaultConfig.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java index c3e89714371..13f0a0a9c8a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java @@ -102,9 +102,9 @@ private SingleThreaded(InternalDriverContext context) { this.logPrefix = context.clusterName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.eventBus = context.eventBus(); - this.config = context.config().defaultProfile(); + this.config = context.config().getDefaultProfile(); this.reloadInterval = - context.config().defaultProfile().getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); + context.config().getDefaultProfile().getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); forceLoadListenerKey = this.eventBus.register( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 19d43ce16fb..4df9d226a06 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -145,12 +145,12 @@ private Map extractProfiles(Config sourceConfig) { } @Override - public DriverConfigProfile defaultProfile() { + public DriverConfigProfile getDefaultProfile() { return defaultProfile; } @Override - public DriverConfigProfile getProfile(String profileName) { + public DriverConfigProfile getNamedProfile(String profileName) { Preconditions.checkArgument( profiles.containsKey(profileName), "Unknown profile '%s'. Check your configuration.", @@ -158,6 +158,11 @@ public DriverConfigProfile getProfile(String profileName) { return profiles.get(profileName); } + @Override + public Map getNamedProfiles() { + return ImmutableMap.copyOf(profiles); + } + private static void validateRequired(Config config, Collection options) { for (DriverOption option : options) { Preconditions.checkArgument( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index c92d6fe76a8..18c7484dd20 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -134,7 +134,7 @@ public class DefaultDriverContext implements InternalDriverContext { public DefaultDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { this.config = configLoader.getInitialConfig(); this.configLoader = configLoader; - DriverConfigProfile defaultProfile = config.defaultProfile(); + DriverConfigProfile defaultProfile = config.getDefaultProfile(); if (defaultProfile.isDefined(CoreDriverOption.CLUSTER_NAME)) { this.clusterName = defaultProfile.getString(CoreDriverOption.CLUSTER_NAME); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 510da7415bf..5fbf2e61db0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -64,7 +64,7 @@ public DefaultTopologyMonitor(InternalDriverContext context) { this.logPrefix = context.clusterName(); this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); this.timeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); this.closeFuture = new CompletableFuture<>(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index b149b0ee65a..3239988280c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -81,7 +81,7 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.metadataManager = context.metadataManager(); - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); this.topologyEventDebouncer = new Debouncer<>( adminExecutor, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index ffc17158835..f326a0aca39 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -506,7 +506,7 @@ private void forceClose() { private int getConfiguredSize(NodeDistance distance) { return config - .defaultProfile() + .getDefaultProfile() .getInt( (distance == NodeDistance.LOCAL) ? CoreDriverOption.POOLING_LOCAL_CONNECTIONS diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 3eab0ece95c..545396f270a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -131,7 +131,7 @@ public CqlIdentifier getKeyspace() { public void setKeyspace(CqlIdentifier newKeyspace) { CqlIdentifier oldKeyspace = this.keyspace; if (!Objects.equals(oldKeyspace, newKeyspace)) { - if (config.defaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { + if (config.getDefaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { LOG.warn( "[{}] Detected a keyspace change at runtime ({} => {}). " + "This is an anti-pattern that should be avoided in production " @@ -399,7 +399,7 @@ private void onPoolInitialized(ChannelPool pool) { private void reprepareStatements(ChannelPool pool) { assert adminExecutor.inEventLoop(); - if (config.defaultProfile().getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { + if (config.getDefaultProfile().getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { new ReprepareOnUp( logPrefix + "|" + pool.getNode().getConnectAddress(), pool, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index dd2232b3c0b..d8bf0e6e6ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -84,11 +84,12 @@ class ReprepareOnUp { this.whenPrepared = whenPrepared; this.checkSystemTable = - config.defaultProfile().getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); - this.timeout = config.defaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); - this.maxStatements = config.defaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS); + config.getDefaultProfile().getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); + this.timeout = config.getDefaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); + this.maxStatements = + config.getDefaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS); this.maxParallelism = - config.defaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM); + config.getDefaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM); } void start() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index e78571cc26d..fa73140bdd7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -59,8 +59,8 @@ protected RequestHandlerBase( String profileName = request.getConfigProfileName(); this.configProfile = (profileName == null || profileName.isEmpty()) - ? config.defaultProfile() - : config.getProfile(profileName); + ? config.getDefaultProfile() + : config.getNamedProfile(profileName); } this.isIdempotent = (request.isIdempotent() == null) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 952f4ba414c..08959bf2108 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -74,7 +74,7 @@ public static Class loadClass(String className, String source) { public static Optional buildFromConfig( DriverContext context, DriverOption rootOption, Class expectedSuperType) { - DriverConfigProfile config = context.config().defaultProfile(); + DriverConfigProfile config = context.config().getDefaultProfile(); DriverOption classNameOption = rootOption.concat(CoreDriverOption.RELATIVE_POLICY_CLASS); if (!config.isDefined(classNameOption)) { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java index 874442b33d6..223e1368325 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -62,7 +62,7 @@ abstract class MonotonicTimestampGeneratorTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); // Disable warnings by default diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java index 67626310dab..8794bf22646 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java @@ -27,12 +27,12 @@ public DriverConfigAssert(DriverConfig actual) { } public DriverConfigAssert hasIntOption(DriverOption option, int expected) { - assertThat(actual.defaultProfile().getInt(option)).isEqualTo(expected); + assertThat(actual.getDefaultProfile().getInt(option)).isEqualTo(expected); return this; } public DriverConfigAssert hasIntOption(String profileName, DriverOption option, int expected) { - assertThat(actual.getProfile(profileName).getInt(option)).isEqualTo(expected); + assertThat(actual.getNamedProfile(profileName).getInt(option)).isEqualTo(expected); return this; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 7019adc3d72..c70ab6fc01a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -106,7 +106,7 @@ public void setup() throws InterruptedException { clientGroup = new DefaultEventLoopGroup(1); Mockito.when(context.config()).thenReturn(driverConfig); - Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when( defaultConfigProfile.isDefined( CoreDriverOption.AUTH_PROVIDER_ROOT.concat(CoreDriverOption.RELATIVE_POLICY_CLASS))) @@ -224,7 +224,7 @@ ChannelInitializer initializer( return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = context.config().defaultProfile(); + DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); long setKeyspaceTimeoutMillis = defaultConfigProfile diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index c6f8a3f3960..ff0ec9538c7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -69,7 +69,7 @@ public void setup() { super.setup(); MockitoAnnotations.initMocks(this); Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); - Mockito.when(driverConfig.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); Mockito.when(internalDriverContext.protocolVersionRegistry()) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java index b41c6d8d6dd..fe66039d212 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java @@ -68,7 +68,7 @@ public void setup() { // In real life, it's the object managed by the loader, but in this test it's simpler to mock // it. Mockito.when(context.config()).thenReturn(config); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL)) .thenReturn(Duration.ofSeconds(12)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 54fb4c562a4..28df0151cbc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -76,7 +76,7 @@ public void should_load_default_driver_config() { @Test public void should_create_derived_profile_with_new_option() { TypeSafeDriverConfig config = parse("required_int = 42"); - DriverConfigProfile base = config.defaultProfile(); + DriverConfigProfile base = config.getDefaultProfile(); DriverConfigProfile derived = base.withInt(MockOptions.OPTIONAL_INT, 43); assertThat(base.isDefined(MockOptions.OPTIONAL_INT)).isFalse(); @@ -87,7 +87,7 @@ public void should_create_derived_profile_with_new_option() { @Test public void should_create_derived_profile_overriding_option() { TypeSafeDriverConfig config = parse("required_int = 42"); - DriverConfigProfile base = config.defaultProfile(); + DriverConfigProfile base = config.getDefaultProfile(); DriverConfigProfile derived = base.withInt(MockOptions.REQUIRED_INT, 43); assertThat(base.getInt(MockOptions.REQUIRED_INT)).isEqualTo(42); @@ -113,9 +113,9 @@ public void should_update_derived_profiles_after_reloading() { parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); DriverConfigProfile derivedFromDefault = - config.defaultProfile().withInt(MockOptions.OPTIONAL_INT, 50); + config.getDefaultProfile().withInt(MockOptions.OPTIONAL_INT, 50); DriverConfigProfile derivedFromProfile1 = - config.getProfile("profile1").withInt(MockOptions.OPTIONAL_INT, 51); + config.getNamedProfile("profile1").withInt(MockOptions.OPTIONAL_INT, 51); config.reload( ConfigFactory.parseString( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 999323a191e..21549030a77 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -108,7 +108,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverConfigProfile config = harness.getContext().config().defaultProfile(); + DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); Mockito.when(config.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 3e0b986c8a6..7a52b42fc9d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -122,7 +122,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { harness .getContext() .config() - .defaultProfile() + .getDefaultProfile() .getDuration(CoreDriverOption.REQUEST_TIMEOUT); assertThat(scheduledTask.getInitialDelay(TimeUnit.NANOSECONDS)) .isEqualTo(configuredTimeout.toNanos()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index c44315b51f9..9feb9501841 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -91,7 +91,7 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)) .thenReturn(true); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()).thenReturn(builder.buildQueryPlan()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 5425056fc90..4ce35a8b2b5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -71,7 +71,7 @@ public void setup() { Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfig); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); addressTranslator = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index ec6d37601e8..f0db03686c5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -72,7 +72,7 @@ public void setup() { .thenReturn(Duration.ofSeconds(0)); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); this.eventBus = Mockito.spy(new EventBus("test")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index daa16638df3..18e9a3679a8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -69,7 +69,7 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); Mockito.when(context.config()).thenReturn(config); - Mockito.when(config.defaultProfile()).thenReturn(defaultProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); this.eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.channelFactory()).thenReturn(channelFactory); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index c84bcaaacc1..c3ea4790c9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -102,7 +102,7 @@ public void setup() { .thenReturn(true); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) .thenReturn(false); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index 99d7aa9def1..ea584bcb8d4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -68,7 +68,7 @@ public void setup() { Mockito.when(channel.eventLoop()).thenReturn(eventLoop); Mockito.when(eventLoop.inEventLoop()).thenReturn(true); - Mockito.when(config.defaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(true); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REPREPARE_TIMEOUT)) From befdd353af71f2c7e87f800ec50c4d97b261a447 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 17 Jul 2017 10:56:57 -0700 Subject: [PATCH 125/742] Rename default config loader --- .../oss/driver/api/core/ClusterBuilder.java | 4 ++-- ...er.java => DefaultDriverConfigLoader.java} | 12 +++++----- ...ava => DefaultDriverConfigLoaderTest.java} | 22 +++++++++---------- 3 files changed, 18 insertions(+), 20 deletions(-) rename core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/{PeriodicTypeSafeDriverConfigLoader.java => DefaultDriverConfigLoader.java} (93%) rename core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/{PeriodicTypeSafeDriverConfigLoaderTest.java => DefaultDriverConfigLoaderTest.java} (91%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 23951564219..c37f0c6a693 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; -import com.datastax.oss.driver.internal.core.config.typesafe.PeriodicTypeSafeDriverConfigLoader; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; @@ -76,7 +76,7 @@ public ClusterBuilder withConfigLoader(DriverConfigLoader configLoader) { } private static DriverConfigLoader defaultConfigLoader() { - return new PeriodicTypeSafeDriverConfigLoader(); + return new DefaultDriverConfigLoader(); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java similarity index 93% rename from core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index 13f0a0a9c8a..02de200ebf0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -37,11 +37,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** A loader that reloads the configuration at a configurable interval. */ -public class PeriodicTypeSafeDriverConfigLoader implements DriverConfigLoader { +/** The default loader; it is based on TypeSafe Config and reloads at a configurable interval. */ +public class DefaultDriverConfigLoader implements DriverConfigLoader { - private static final Logger LOG = - LoggerFactory.getLogger(PeriodicTypeSafeDriverConfigLoader.class); + private static final Logger LOG = LoggerFactory.getLogger(DefaultDriverConfigLoader.class); public static final Supplier DEFAULT_CONFIG_SUPPLIER = () -> { @@ -58,7 +57,7 @@ public class PeriodicTypeSafeDriverConfigLoader implements DriverConfigLoader { * Builds a new instance with the default TypeSafe config loading rules (documented in {@link * ClusterBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. */ - public PeriodicTypeSafeDriverConfigLoader() { + public DefaultDriverConfigLoader() { this(DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values()); } @@ -66,8 +65,7 @@ public PeriodicTypeSafeDriverConfigLoader() { * Builds an instance with custom arguments, if you want to load the configuration from somewhere * else or have custom options. */ - public PeriodicTypeSafeDriverConfigLoader( - Supplier configSupplier, DriverOption[]... options) { + public DefaultDriverConfigLoader(Supplier configSupplier, DriverOption[]... options) { this.configSupplier = configSupplier; this.driverConfig = new TypeSafeDriverConfig(configSupplier.get(), options); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java similarity index 91% rename from core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index fe66039d212..c522f067b05 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/PeriodicTypeSafeDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -39,7 +39,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; -public class PeriodicTypeSafeDriverConfigLoaderTest { +public class DefaultDriverConfigLoaderTest { @Mock private InternalDriverContext context; @Mock private NettyOptions nettyOptions; @@ -77,8 +77,8 @@ public void setup() { @Test public void should_build_initial_config() { - PeriodicTypeSafeDriverConfigLoader loader = - new PeriodicTypeSafeDriverConfigLoader( + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); DriverConfig initialConfig = loader.getInitialConfig(); assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); @@ -86,8 +86,8 @@ public void should_build_initial_config() { @Test public void should_schedule_reloading_task() { - PeriodicTypeSafeDriverConfigLoader loader = - new PeriodicTypeSafeDriverConfigLoader( + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); loader.onDriverInit(context); @@ -100,8 +100,8 @@ public void should_schedule_reloading_task() { @Test public void should_reload_if_config_has_changed() { - PeriodicTypeSafeDriverConfigLoader loader = - new PeriodicTypeSafeDriverConfigLoader( + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); DriverConfig initialConfig = loader.getInitialConfig(); assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); @@ -121,8 +121,8 @@ public void should_reload_if_config_has_changed() { @Test public void should_reload_if_forced() { - PeriodicTypeSafeDriverConfigLoader loader = - new PeriodicTypeSafeDriverConfigLoader( + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); DriverConfig initialConfig = loader.getInitialConfig(); assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); @@ -141,8 +141,8 @@ public void should_reload_if_forced() { @Test public void should_not_notify_if_config_has_not_changed() { - PeriodicTypeSafeDriverConfigLoader loader = - new PeriodicTypeSafeDriverConfigLoader( + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( () -> ConfigFactory.parseString(configSource.get()), MockOptions.values()); DriverConfig initialConfig = loader.getInitialConfig(); assertThat(initialConfig).hasIntOption(MockOptions.REQUIRED_INT, 42); From 36a096d77d0559310d30447ef32a62e911207809 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 14 Jul 2017 12:01:37 -0700 Subject: [PATCH 126/742] Document configuration in the manual --- .../api/core/config/CoreDriverOption.java | 2 +- core/src/main/resources/reference.conf | 73 ++- manual/configuration/README.md | 455 ++++++++++++++++++ upgrade_guide/README.md | 12 + 4 files changed, 535 insertions(+), 7 deletions(-) create mode 100644 manual/configuration/README.md diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 322b20f5d89..c3d03a78960 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -24,7 +24,7 @@ public enum CoreDriverOption implements DriverOption { CONTACT_POINTS("contact-points", false), PROTOCOL_VERSION("protocol.version", false), CLUSTER_NAME("cluster-name", false), - CONFIG_RELOAD_INTERVAL("config-reload-interval", true), + CONFIG_RELOAD_INTERVAL("config-reload-interval", false), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), CONNECTION_SET_KEYSPACE_TIMEOUT("connection.set-keyspace-timeout", true), diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index dd6636c4a81..806c9e8d0f2 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -3,6 +3,11 @@ # Unless you use a custom mechanism to load your configuration (see DriverContext), all the values # declared here will be used as defaults if you don't override them in your own `application.conf`. # +# Unless explicitly stated otherwise: +# - options are required; +# - they can not be overridden in a profile; +# - runtime changes are ignored. +# # This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md. datastax-java-driver { # The contact points to use for the initial connection to the cluster. @@ -13,15 +18,15 @@ datastax-java-driver { # if that single contact point is unavailable, the driver cannot initialize itself correctly. # # This must be a list of strings with each contact point specified as "host:port". If the host is - # a DNS name that resolves to multiple A-records, all the corresponding addressess will be used. + # a DNS name that resolves to multiple A-records, all the corresponding addresses will be used. # Do not use "localhost" as the host name (since it resolves to both IPv4 and IPv6 addresses on # some platforms). # # Note that the current version of Cassandra (3.11) requires all nodes in a cluster to share the # same port. # - # Contact points can also be provided programmatically when you build a cluster instance. If both - # are specified, they will be merged. + # This option is not required. Contact points can also be provided programmatically when you build + # a cluster instance. If both are specified, they will be merged. // contact-points = [ "127.0.0.1:9042", "127.0.0.2:9042" ] protocol { @@ -40,7 +45,7 @@ datastax-java-driver { # A name that uniquely identifies the driver instance created from this configuration. This is # used as a prefix for log messages and metrics. - # This parameter is optional; if it is not specified, the driver will generate an identifier + # This option is not required; if it is not specified, the driver will generate an identifier # composed of the letter 'c' followed by an incrementing counter. # If you provide a different value, try to keep it short to keep the logs readable. Also, make # sure it is unique: reusing the same value will not break the driver, but it will mix up the logs @@ -48,6 +53,8 @@ datastax-java-driver { // cluster-name = my_cluster # How often the driver tries to reload the configuration. + # This option is required, except if you use a different config loader than the driver's built-in + # one. Runtime changes are taken into account. # To disable periodic reloading, set this to 0. config-reload-interval = 5 minutes @@ -68,15 +75,24 @@ datastax-java-driver { # after we open a connection. If this timeout fires, the initialization of the connection will # fail. If this is the first connection ever, the driver will fail to initialize as well, # otherwise it will retry the connection later. + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. init-query-timeout = 500 milliseconds # The timeout to use when the driver changes the keyspace on a connection at runtime (this # happens when the client issues a `USE ...` query, and all connections belonging to the # current session need to be updated). + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. set-keyspace-timeout = ${datastax-java-driver.connection.init-query-timeout} - # The maximum number of requests that can be executed concurrently on a connection. - # This must be between 1 and 32768. + # The maximum number of requests that can be executed concurrently on a connection. This must be + # between 1 and 32768. + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. max-requests-per-connection = 32768 # The maximum number of "orphaned" requests before a connection gets closed automatically. @@ -89,19 +105,31 @@ datastax-java-driver { # If the response never comes (or is lost because of a network issue), orphaned ids can # accumulate over time, eventually affecting the connection's throughput. So we monitor them and # close the connection above a given threshold (the pool will replace it). + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. max-orphan-requests = 24576 heartbeat { # The heartbeat interval. If a connection stays idle for that duration (no reads), the driver # sends a dummy message on it to make sure it's still alive. If not, the connection is # trashed and replaced. + # + # This option can be changed at runtime, the new value will be used for new connections + # created after the change. interval = 30 seconds # How long the driver waits for the response to a heartbeat. If this timeout fires, the # heartbeat is considered failed. + # + # This option can be changed at runtime, the new value will be used for heartbeat queries + # issued after the change. timeout = ${datastax-java-driver.connection.init-query-timeout} } # The maximum length of the frames supported by the driver. Beyond that limit, requests will # fail with an exception + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. max-frame-length = 256 MB reconnection-policy { @@ -123,9 +151,15 @@ datastax-java-driver { request { # How long the driver waits for a request to complete. This is a global limit on the duration # of a session.execute() call, including any internal retries the driver might do. + # + # This option can be changed at runtime, the new value will be used for requests issued after + # the change. It can be overridden in a profile. timeout = 500 milliseconds # The consistency level. + # + # This option can be changed at runtime, the new value will be used for requests issued after + # the change. It can be overridden in a profile. consistency = ONE # The page size. This controls how many rows will be retrieved simultaneously in a single @@ -134,10 +168,16 @@ datastax-java-driver { # automatically if you iterate with the sync API, or explicitly with the async API's # fetchNextPage method). # If the value is 0 or negative, it will be ignored and the request will not be paged. + # + # This option can be changed at runtime, the new value will be used for requests issued after + # the change. It can be overridden in a profile. page-size = 5000 # The serial consistency level. # The allowed values are SERIAL and LOCAL_SERIAL. + # + # This option can be changed at runtime, the new value will be used for requests issued after + # the change. It can be overridden in a profile. serial-consistency = SERIAL # Whether a warning is logged when a request (such as a CQL `USE ...`) changes the active @@ -151,10 +191,16 @@ datastax-java-driver { # # Note that CASSANDRA-10145 (scheduled for C* 4.0) will introduce a per-request keyspace # option as a workaround to this issue. + # + # This option can be changed at runtime, it will apply to keyspace switches occurring after the + # change. warn-if-set-keyspace = true # The default idempotence of a request, that will be used for all `Request` instances where # `isIdempotent()` returns null. + # + # This option can be changed at runtime, the new value will be used for requests issued after + # the change. It can be overridden in a profile. default-idempotence = false } @@ -206,10 +252,15 @@ datastax-java-driver { # given statement, it needs to be re-prepared on the fly the first time it gets executed; this # causes a performance penalty (one extra roundtrip to resend the query to prepare, and another # to retry the execution). + # + # This option can be changed at runtime, the new value will be used for prepares issued after + # the change. It can be overridden in a profile. prepare-on-all-nodes = true # How the driver replicates prepared statements on a node that just came back up or joined the # cluster. + # These options can be changed at runtime, the new values will be used for prepares issued after + # the change. However they can not be overridden in a profile. reprepare-on-up { # Whether the driver tries to prepare on new nodes at all. # @@ -248,6 +299,9 @@ datastax-java-driver { pooling { local { # The number of connections in the pool. + # + # This option can be changed at runtime; when the change is detected, all active pools will + # adjust their size. connections = 1 } remote { @@ -300,4 +354,11 @@ datastax-java-driver { # quality of service". // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] } + + profiles { + # This is where your custom profiles go, for example: + # olap { + # request.timeout = 5 seconds + # } + } } \ No newline at end of file diff --git a/manual/configuration/README.md b/manual/configuration/README.md new file mode 100644 index 00000000000..fe03e2dc454 --- /dev/null +++ b/manual/configuration/README.md @@ -0,0 +1,455 @@ +## Configuration + +The driver's configuration is composed of options, organized in a hierarchical manner. Optionally, +it can define *profiles* that customize a set of options for a particular kind of request. + +The default implementation is based on the TypeSafe Config framework. It can be completely +overridden if needed. + +For a complete list of built-in options, see [reference.conf] in the driver sources. + + +### Concepts + +#### Options + +Essentially, an option is a path in the configuration with an expected type, for example +`connection.heartbeat.interval`, representing a duration. + +#### Profiles + +Imagine an application that does both transactional and analytical requests. Transactional requests +are simpler and must return quickly, so they will typically use a short timeout, let's say 100 +milliseconds; analytical requests are more complex and less frequent so a higher SLA is acceptable, +for example 5 seconds. In addition, maybe you want to use a different consistency level. + +Instead of manually adjusting the options on every request, you can create configuration profiles: + +``` +datastax-java-driver { + profiles { + oltp { + request.timeout = 100 milliseconds + request.consistency = ONE + } + olap { + request.timeout = 5 seconds + request.consistency = QUORUM + } +} +``` + +Now each request only needs a profile name: + +```java +SimpleStatement s = + SimpleStatement.builder("SELECT name FROM user WHERE id = 1") + .withConfigProfileName("oltp") + .build(); +session.execute(s); +``` + +The configuration has an anonymous *default profile* that is always present. It can define an +arbitrary number of named profiles. They inherit from the default profile, so you only need to +override the options that have a different value. + + +### Default implementation: TypeSafe Config + +Out of the box, the driver uses [TypeSafe Config]. + +It looks at the following locations, according to the [standard behavior][config standard behavior] +of that library: + +* system properties +* `application.conf` (all resources on the classpath with this name) +* `application.json` (all resources on the classpath with this name) +* `application.properties` (all resources on the classpath with this name) +* `reference.conf` (all resources on the classpath with this name) + +The driver ships with a [reference.conf] that defines sensible defaults for all the options. That +file is heavily documented, so refer to it for details about each option. It is included in the core +driver JAR, so it is in your application's classpath. If you to customize something, add an +`application.conf` in your source tree and also place it in the classpath; since it inherits from +`reference.conf`, you only need to redeclare what you override: + +``` +# Sample application.conf: overrides one option and adds a profile +datastax-java-driver { + protocol.version = V4 + profiles { + slow { + request.timeout = 10 seconds + } + } +} +``` + +`.conf` files are in the *HOCON* format, an improved superset of JSON; refer to the +[HOCON spec][HOCON] for details. + +By default, configuration files are reloaded regularly, and the driver will adjust to the new values +(on a "best effort" basis: some options, like protocol version and policy configurations, cannot be +changed at runtime and will be ignored). The reload interval is defined in the configuration: + +``` +# To disable periodic reloading, set this to 0. +datastax-java-driver.config-reload-interval = 5 minutes +``` + +As mentioned previously, system properties can also be used to override individual options. This is +great for temporary changes, for example in your development environment: + +``` +# Increase heartbeat interval to limit the amount of debug logs: +java -Ddatastax-java-driver.connection.heartbeat.interval="5 minutes" ... +``` + +We recommend reserving system properties for the early phases of the project; in production, having +all the configuration in one place will make it easier to manage and review. + +As shown so far, all options live under a `datastax-java-driver` prefix. This can be changed, for +example if you need multiple driver instances in the same VM with different configurations. See the +[Advanced topics](changing-the-config-prefix) section. + + +### The configuration API + +You don't need the configuration API for everyday usage of the driver, but it can be useful if: + +* you're writing custom policies or a custom config implementation; +* use dynamic profiles (see below); +* or simply want to read configuration options at runtime. + +#### Basics + +The driver's context exposes a [DriverConfig] instance: + +```java +DriverConfig config = cluster.getContext().config(); +DriverConfigProfile defaultProfile = config.getDefaultProfile(); +DriverConfigProfile olapProfile = config.getNamedProfile("olap"); + +// This method creates a defensive copy of the map, do not use in performance-sensitive code: +config.getNamedProfiles().forEach((name, profile) -> ...); +``` + +[DriverConfigProfile] has typed option getters: + +```java +Duration requestTimeout = defaultProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); +int maxRequestsPerConnection = defaultProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); +``` + +Note that we use the [CoreDriverOption] enum to access built-in options, but the method takes a more +generic [DriverOption] interface. This is intended to allow custom options, see the +[Advanced topics](#custom-options) section. + +#### Derived profiles + +Configuration profiles are hard-coded in the configuration, and can't be changed at runtime (except +by modifying and reloading the files). What if you want to adjust an option for a single request, +without having a dedicated profile for it? + +To allow this, you start from an existing profile in the configuration and build a *derived profile* +that overrides a subset of options: + +```java +DriverConfigProfile defaultProfile = cluster.getContext().config().getDefaultProfile(); +DriverConfigProfile dynamicProfile = + defaultProfile.withConsistencyLevel( + CoreDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.EACH_QUORUM); +SimpleStatement s = + SimpleStatement.builder("SELECT name FROM user WHERE id = 1") + .withConfigProfile(dynamicProfile) + .build(); +session.execute(s); +``` + +A derived profile keeps a reference to its base profile, and reflects the change if the +configuration gets reloaded. + +Do not overuse derived profiles, as they can have an impact on performance: each `withXxx` method +creates a new copy, and propagating the changes from the base profile also has an overhead. We +strongly suggest defining all your profiles ahead of time in the configuration file; at the very +least, try to cache derived profiles if you reuse them multiple times. + + +### Advanced topics + +*Note: all the features described in this section use the driver's internal API, which is subject to +the restrictions explained in [API conventions]*. + +#### Changing the config prefix + +As mentioned earlier, all configuration options are looked up under the `datastax-java-driver` +prefix. This might be a problem if you have multiple instances of the driver executing in the same +VM, but with different configurations. What you want instead is separate option trees, like this: + +``` +# application.conf +cluster1 { + cluster-name = "cluster1" + protocol-version = V4 + // etc. +} +cluster2 { + cluster-name = "cluster2" + protocol-version = V3 + // etc. +} +``` + +To achieve that, first write a method that loads the configuration under your prefix, and uses the +driver's `reference.conf` as a fallback: + +```java +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +private static Config loadConfig(String prefix) { + // Make sure we see the changes when reloading: + ConfigFactory.invalidateCaches(); + + // Every config file in the classpath, without stripping the prefixes + Config root = ConfigFactory.load(); + + // The driver's built-in defaults, under the default prefix in reference.conf: + Config reference = root.getConfig("datastax-java-driver"); + + // Everything under your custom prefix in application.conf: + Config application = root.getConfig(prefix); + + return application.withFallback(reference); +} +``` + +Next, create a `DriverConfigLoader`. This is the component that abstracts the configuration +implementation to the rest of the driver. Here we use the built-in class, but tell it to load the +TypeSafe Config object with the previous method: + +```java +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; + +DriverConfigLoader cluster1ConfigLoader = + new DefaultDriverConfigLoader( + () -> loadConfig("cluster1"), CoreDriverOption.values()); +``` + +Finally, pass the config loader when building the driver: + +```java +Cluster cluster1 = + Cluster.builder() + .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) + .withConfigLoader(cluster1ConfigLoader) + .build(); +``` + +#### Loading from a different source + +If you don't want to use a config file, you can write custom code to create the TypeSafe `Config` +object (refer to the [documentation][TypeSafe Config] for more details). + +Then reuse the examples from the previous section to merge it with the driver's reference file, and +pass it to the driver. Here's a contrived example that loads the configuration from a string: + +```java +String configSource = "protocol.version = V3"; +DriverConfigLoader loader = + new DefaultDriverConfigLoader( + () -> { + ConfigFactory.invalidateCaches(); + Config reference = ConfigFactory.load().getConfig("datastax-java-driver"); + Config application = ConfigFactory.parseString(configSource); + return application.withFallback(reference); + }, + CoreDriverOption.values()); + +Cluster cluster = + Cluster.builder() + .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) + .withConfigLoader(loader) + .build(); +``` + +#### Bypassing TypeSafe Config + +If TypeSafe Config doesn't work for you, it is possible to get rid of it entirely. + +You will need to provide your own implementations of [DriverConfig] and [DriverConfigProfile]. Then +write a [DriverConfigLoader] and pass it to the cluster at initialization, as shown in the previous +sections. Study the built-in implementation (package +`com.datastax.oss.driver.internal.core.config.typesafe`) for reference. + +Reloading is not mandatory: you can choose not to implement it, and the driver will simply keep +using the initial configuration. + +Note that the option getters (`DriverConfigProfile.getInt` and similar) are invoked very frequently +on the hot code path; if your implementation is slow, consider caching the results between reloads. + +#### Configuration change events + +You can force an immediate reload instead of waiting for the next interval: + +```java +import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; + +// DANGER ZONE: this gives you access to the driver internals, which allow very nasty things. +// Use responsibly. +InternalDriverContext context = (InternalDriverContext) cluster.getContext(); + +EventBus eventBus = context.eventBus(); +eventBus.fire(ForceReloadConfigEvent.INSTANCE); +``` + +An event is also fired when we detect that the configuration changed after a reload. You can +register a callback to listen to it: + +```java +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; + +Object key = + eventBus.register( + ConfigChangeEvent.class, (e) -> System.out.println("The configuration changed")); + +// If your component has a shorter lifecycle than the driver, make sure to unregister when it closes +eventBus.unregister(key, ConfigChangeEvent.class); +``` + +Both events are managed by the config loader. If you write a custom loader, study the source of +`DefaultDriverConfigLoader` to reproduce the behavior. + +#### Policies + +The preferred way to instantiate policies (load balancing policy, retry policy, etc.) is via the +configuration: + +``` +reconnection-policy { + class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy + base-delay = 1 second + max-delay = 60 seconds +} +``` + +When the driver encounters such a declaration, it will load the class and look for a constructor +with the following signature: + +```java +ExponentialReconnectionPolicy(DriverContext context, DriverOption configRoot) +``` + +* `context` argument allows the policy to access other driver components (for example the + configuration); +* `configRoot` represents the root of the configuration element (`reconnection-policy` in the + example above), and can be used to build the path of the other options, in particular for nestable + policies that can appear at arbitrary paths in the configuration tree. + +If you write custom policy implementations, you should follow that same pattern; it provides an +elegant way to switch policies without having to recompile the application (if your policy needs +custom options, see the next section). Study the built-in implementations for reference. + +If for some reason you really can't use reflection, there is a way out; subclass +`DefaultDriverContext` and override the corresponding method: + +```java +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; + +public class MyDriverContext extends DefaultDriverContext { + + public MyDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { + super(configLoader, typeCodecs); + } + + @Override + protected ReconnectionPolicy buildReconnectionPolicy() { + return myReconnectionPolicy; + } +} +``` + +Then you'll need to pass an instance of this context to `DefaultCluster.init`. You can either do so +directly, or subclass `ClusterBuilder` and override the `buildAsync` method. + +#### Custom options + +You can add your own options to the configuration. This is useful for custom components, or even as +a way to associate arbitrary key/value pairs with the cluster instance. + +First, write an enum that implements [DriverOption]: + +```java +public enum MyCustomOption implements DriverOption { + + ADMIN_NAME("admin.name", true), + ADMIN_EMAIL("admin.email", true), + AWESOMENESS_FACTOR("awesomeness-factor", true), + ; + + private final String path; + private final boolean required; + + MyCustomOption(String path, boolean required) { + this.path = path; + this.required = required; + } + + @Override + public String getPath() { + return path; + } + + @Override + public boolean required() { + return required; + } +} +``` + +Pass the options to the config loader: + +```java +Cluster cluster = Cluster.builder() + .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) + .withConfigLoader(new DefaultDriverConfigLoader( + DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER, + CoreDriverOption.values(), // don't forget to keep the core options + MyCustomOption.values())) + .build(); +``` + +You can now add the options to your configuration: + +``` +datastax-java-driver { + admin { + name = "Bob" + email = "bob@example.com" + } + awesomeness-factor = 11 +} +``` + +And access them from the code: + +```java +DriverConfig config = cluster.getContext().config(); +config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL); +config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); +``` + +[DriverConfig]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html +[DriverConfigProfile]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigProfile.html +[DriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html +[CoreDriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/CoreDriverOption.html +[DriverConfigLoader]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html + +[TypeSafe Config]: https://github.com/typesafehub/config +[config standard behavior]: https://github.com/typesafehub/config#standard-behavior +[reference.conf]: https://github.com/datastax/java-driver/blob/4.x/core/src/main/resources/reference.conf +[HOCON]: https://github.com/typesafehub/config/blob/master/HOCON.md +[API conventions]: ../api_conventions/ diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 5f3fba96900..068911b7fe3 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -20,6 +20,18 @@ IDE to find out the new locations. [API conventions]: ../manual/api_conventions +#### New configuration API + +The configuration has been completely revamped. Instead of ad-hoc configuration classes, the default +configuration mechanism is now file-based, using the [TypeSafe Config] library. This is a better +choice for most deployments, since it allows configuration changes without recompiling the client +application. This is fully customizable, including loading from different sources, or completely +overriding the default implementation. + +For more details, refer to the [manual](../manual/configuration). + +[TypeSafe Config]: https://github.com/typesafehub/config + #### Expose interfaces, not classes Most types in the public API are now interfaces (as opposed to 3.x: `Cluster`, statement classes, From 501d09ff5fec87ef65462bfeaa53cf1d04c9cbc0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 18 Jul 2017 18:03:30 -0700 Subject: [PATCH 127/742] Expose basic options for Netty event loop groups --- .../api/core/config/CoreDriverOption.java | 9 ++++ .../core/context/DefaultDriverContext.java | 2 +- .../core/context/DefaultNettyOptions.java | 41 +++++++++++++++---- core/src/main/resources/reference.conf | 20 +++++++++ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index c3d03a78960..afb656f399e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -83,6 +83,15 @@ public enum CoreDriverOption implements DriverOption { RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD("drift-warning.threshold", false), RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL("drift-warning.interval", false), + + NETTY_IO_SIZE("netty.io-group.size", false), + NETTY_IO_SHUTDOWN_QUIET_PERIOD("netty.io-group.shutdown.quiet-period", false), + NETTY_IO_SHUTDOWN_TIMEOUT("netty.io-group.shutdown.timeout", false), + NETTY_IO_SHUTDOWN_UNIT("netty.io-group.shutdown.unit", false), + NETTY_ADMIN_SIZE("netty.admin-group.size", false), + NETTY_ADMIN_SHUTDOWN_QUIET_PERIOD("netty.admin-group.shutdown.quiet-period", false), + NETTY_ADMIN_SHUTDOWN_TIMEOUT("netty.admin-group.shutdown.timeout", false), + NETTY_ADMIN_SHUTDOWN_UNIT("netty.admin-group.shutdown.unit", false), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 18c7484dd20..d7df5a82942 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -224,7 +224,7 @@ protected ProtocolVersionRegistry buildProtocolVersionRegistry() { } protected NettyOptions buildNettyOptions() { - return new DefaultNettyOptions(clusterName); + return new DefaultNettyOptions(this); } protected Optional buildSslHandlerFactory() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 91e42161f80..ef19df8b042 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.Bootstrap; @@ -30,27 +32,46 @@ import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.PromiseCombiner; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; public class DefaultNettyOptions implements NettyOptions { private final EventLoopGroup ioEventLoopGroup; private final EventLoopGroup adminEventLoopGroup; + private final int ioShutdownQuietPeriod; + private final int ioShutdownTimeout; + private final TimeUnit ioShutdownUnit; + private final int adminShutdownQuietPeriod; + private final int adminShutdownTimeout; + private final TimeUnit adminShutdownUnit; + + public DefaultNettyOptions(InternalDriverContext context) { + DriverConfigProfile config = context.config().getDefaultProfile(); + int ioGroupSize = config.getInt(CoreDriverOption.NETTY_IO_SIZE); + this.ioShutdownQuietPeriod = config.getInt(CoreDriverOption.NETTY_IO_SHUTDOWN_QUIET_PERIOD); + this.ioShutdownTimeout = config.getInt(CoreDriverOption.NETTY_IO_SHUTDOWN_TIMEOUT); + this.ioShutdownUnit = + TimeUnit.valueOf(config.getString(CoreDriverOption.NETTY_IO_SHUTDOWN_UNIT)); + int adminGroupSize = config.getInt(CoreDriverOption.NETTY_ADMIN_SIZE); + this.adminShutdownQuietPeriod = + config.getInt(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_QUIET_PERIOD); + this.adminShutdownTimeout = config.getInt(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_TIMEOUT); + this.adminShutdownUnit = + TimeUnit.valueOf(config.getString(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_UNIT)); - public DefaultNettyOptions(String clusterName) { ThreadFactory safeFactory = new BlockingOperation.SafeThreadFactory(); ThreadFactory ioThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(clusterName + "-io-%d") + .setNameFormat(context.clusterName() + "-io-%d") .build(); - this.ioEventLoopGroup = new NioEventLoopGroup(0, ioThreadFactory); + this.ioEventLoopGroup = new NioEventLoopGroup(ioGroupSize, ioThreadFactory); ThreadFactory adminThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(clusterName + "-admin-%d") + .setNameFormat(context.clusterName() + "-admin-%d") .build(); - int adminThreadCount = Math.min(2, Runtime.getRuntime().availableProcessors()); - this.adminEventLoopGroup = new DefaultEventLoopGroup(adminThreadCount, adminThreadFactory); + this.adminEventLoopGroup = new DefaultEventLoopGroup(adminGroupSize, adminThreadFactory); } @Override @@ -86,8 +107,12 @@ public void afterChannelInitialized(Channel channel) { @Override public Future onClose() { PromiseCombiner combiner = new PromiseCombiner(); - combiner.add(adminEventLoopGroup.shutdownGracefully()); - combiner.add(ioEventLoopGroup.shutdownGracefully()); + combiner.add( + adminEventLoopGroup.shutdownGracefully( + adminShutdownQuietPeriod, adminShutdownTimeout, adminShutdownUnit)); + combiner.add( + ioEventLoopGroup.shutdownGracefully( + ioShutdownQuietPeriod, ioShutdownTimeout, ioShutdownUnit)); DefaultPromise closeFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); combiner.finish(closeFuture); return closeFuture; diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 806c9e8d0f2..b489803b9d9 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -355,6 +355,26 @@ datastax-java-driver { // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] } + # Options related to the Netty event loop groups used internally by the driver. + netty { + # The event loop group used for I/O operations (reading and writing to Cassandra nodes). + io-group { + # The number of threads. + # If this is set to 0, the driver will use `Runtime.getRuntime().availableProcessors() * 2`. + size = 0 + # The options to shut down the event loop group gracefully when the driver closes. If a task + # gets submitted during the quiet period, it is accepted and the quiet period starts over. The + # timeout limits the overall shutdown time. + shutdown { quiet-period = 2, timeout = 15, unit = SECONDS } + } + # The event loop group used for admin tasks not related to request I/O (handle cluster events, + # refresh metadata, schedule reconnections, etc.) + admin-group { + size = 2 + shutdown { quiet-period = 2, timeout = 15, unit = SECONDS } + } + } + profiles { # This is where your custom profiles go, for example: # olap { From 4412ec4200998648300b62444ba88269aec85b1d Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 18 Jul 2017 18:12:42 -0700 Subject: [PATCH 128/742] Replace ThreadLocal with an instanceof check in BlockingOperation --- .../core/util/concurrent/BlockingOperation.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java index c69616aca78..f23076d4461 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java @@ -28,7 +28,6 @@ * callbacks. */ public class BlockingOperation { - private static ThreadLocal isDriverThread = ThreadLocal.withInitial(() -> false); /** * This method is invoked from each synchronous driver method, and checks that we are not on a @@ -40,7 +39,7 @@ public class BlockingOperation { * @throws IllegalStateException if a driver thread is executing this. */ public static void checkNotDriverThread() { - if (isDriverThread.get()) { + if (Thread.currentThread() instanceof InternalThread) { throw new IllegalStateException( "Detected a synchronous API call on a driver thread, " + "failing because this can cause deadlocks."); @@ -54,13 +53,13 @@ public static void checkNotDriverThread() { public static class SafeThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { - return new Thread(r) { - @Override - public void run() { - isDriverThread.set(true); - super.run(); - } - }; + return new InternalThread(r); + } + } + + private static class InternalThread extends Thread { + private InternalThread(Runnable runnable) { + super(runnable); } } } From c4e4047c9ce0525fe76d1e31e226f655a759cb41 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Jul 2017 11:46:59 -0700 Subject: [PATCH 129/742] Make write coalescer configurable --- .../api/core/config/CoreDriverOption.java | 4 +++ .../core/channel/DefaultWriteCoalescer.java | 25 ++++++++++--- .../channel/PassThroughWriteCoalescer.java | 36 +++++++++++++++++++ .../internal/core/channel/WriteCoalescer.java | 4 --- .../core/context/DefaultDriverContext.java | 9 +++-- core/src/main/resources/reference.conf | 11 ++++++ .../core/channel/ChannelFactoryTestBase.java | 2 +- 7 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index afb656f399e..6e66f97e0a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -44,6 +44,10 @@ public enum CoreDriverOption implements DriverOption { CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), + COALESCER_ROOT("connection.coalescer", true), + RELATIVE_COALESCER_MAX_RUNS("max-runs-with-no-work", false), + RELATIVE_COALESCER_INTERVAL("reschedule-interval", false), + // "Sub-option" for all the policies, etc. RELATIVE_POLICY_CLASS("class", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index 749d2834882..795162c855b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.channel; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPromise; @@ -25,6 +29,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -44,10 +49,20 @@ */ public class DefaultWriteCoalescer implements WriteCoalescer { private final int maxRunsWithNoWork; + private final long rescheduleIntervalNanos; private final ConcurrentMap flushers = new ConcurrentHashMap<>(); - public DefaultWriteCoalescer(int maxRunsWithNoWork) { - this.maxRunsWithNoWork = maxRunsWithNoWork; + public DefaultWriteCoalescer( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { + + DriverConfigProfile config = context.config().getDefaultProfile(); + this.maxRunsWithNoWork = + config.getInt(configRoot.concat(CoreDriverOption.RELATIVE_COALESCER_MAX_RUNS)); + this.rescheduleIntervalNanos = + config + .getDuration(configRoot.concat(CoreDriverOption.RELATIVE_COALESCER_INTERVAL)) + .toNanos(); } @Override @@ -81,8 +96,8 @@ private Flusher(EventLoop eventLoop) { private void enqueue(Write write) { boolean added = writes.offer(write); assert added; // always true (see MpscLinkedAtomicQueue implementation) - if (!running.get() && running.compareAndSet(false, true)) { - eventLoop.submit(this::runOnEventLoop); + if (running.compareAndSet(false, true)) { + eventLoop.execute(this::runOnEventLoop); } } @@ -119,7 +134,7 @@ private void runOnEventLoop() { } } if (!eventLoop.isShuttingDown()) { - eventLoop.submit(this::runOnEventLoop); + eventLoop.schedule(this::runOnEventLoop, rescheduleIntervalNanos, TimeUnit.NANOSECONDS); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java new file mode 100644 index 00000000000..b70a4d1ecc2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; + +/** No-op implementation of the write coalescer: each write is flushed immediately. */ +public class PassThroughWriteCoalescer implements WriteCoalescer { + + public PassThroughWriteCoalescer( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { + // nothing to do + } + + @Override + public ChannelFuture writeAndFlush(Channel channel, Object message) { + return channel.writeAndFlush(message); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java index 58c58084e33..5cc54e18e32 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java @@ -28,10 +28,6 @@ * to be accumulated and flushed together for better performance. */ public interface WriteCoalescer { - - /** Pass-through: all messages are written and flushed immediately. */ - WriteCoalescer NONE = ChannelOutboundInvoker::writeAndFlush; - /** * Writes and flushes the message to the channel, possibly at a later time, but the order of * messages must be preserved. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index d7df5a82942..8e82103cc18 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -33,7 +33,6 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; -import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.DefaultTopologyMonitor; @@ -236,7 +235,13 @@ protected Optional buildSslHandlerFactory() { } protected WriteCoalescer buildWriteCoalescer() { - return new DefaultWriteCoalescer(5); + CoreDriverOption rootOption = CoreDriverOption.COALESCER_ROOT; + return Reflection.buildFromConfig(this, rootOption, WriteCoalescer.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing write coalescer, check your configuration (%s)", rootOption))); } protected ChannelFactory buildChannelFactory() { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index b489803b9d9..dfaeeae03ab 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -146,6 +146,17 @@ datastax-java-driver { # results, it will be fetched in multiple requests. page-size = 5000 } + + # The component that coalesces writes on the connections. + # This is exposed mainly to facilitate tuning during development. You shouldn't have to adjust + # this. + coalescer { + class = com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer + # How many times the coalescer is allowed to reschedule itself when it did no work. + max-runs-with-no-work = 5 + # The reschedule interval. + reschedule-interval = 10 microseconds + } } request { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index c70ab6fc01a..109fed5ea5f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -129,7 +129,7 @@ public void setup() throws InterruptedException { new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); Mockito.when(context.sslHandlerFactory()).thenReturn(Optional.empty()); Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.writeCoalescer()).thenReturn(new DefaultWriteCoalescer(5)); + Mockito.when(context.writeCoalescer()).thenReturn(new PassThroughWriteCoalescer(null, null)); // Start local server ServerBootstrap serverBootstrap = From ceeeb8b10bc10d3cd847a4ce52e40c2270e0d8f3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Jul 2017 16:31:23 -0700 Subject: [PATCH 130/742] JAVA-1498: Add a cache above Typesafe config Motivation: Typesafe config re-parses the option from their string representation each time a getXxx method is called. Modifications: Add a cache in the config profile, to only call Typesafe config once, and cache the result (until the config gets reloaded). Result: Slight performance improvement (halves the execution time of session.executeAsync, even though that was already a fast method). --- changelog/README.md | 1 + .../typesafe/TypesafeDriverConfigProfile.java | 35 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index ff8ec4ed80c..12ccef0981b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1498: Add a cache above Typesafe config - [bug] JAVA-1547: Abort pending requests when connection dropped - [new feature] JAVA-1497: Port timestamp generators from 3.x - [improvement] JAVA-1539: Configure for deployment to Maven central diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 9daf486fbcc..068e96d5098 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -26,6 +26,9 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; public abstract class TypesafeDriverConfigProfile implements DriverConfigProfile { @@ -38,6 +41,8 @@ public abstract class TypesafeDriverConfigProfile implements DriverConfigProfile /** The actual options that will be used to answer {@code getXxx} calls. */ protected abstract Config getEffectiveOptions(); + protected final ConcurrentMap cache = new ConcurrentHashMap<>(); + @Override public boolean isDefined(DriverOption option) { return getEffectiveOptions().hasPath(option.getPath()); @@ -45,7 +50,7 @@ public boolean isDefined(DriverOption option) { @Override public boolean getBoolean(DriverOption option) { - return getEffectiveOptions().getBoolean(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getBoolean); } @Override @@ -55,7 +60,7 @@ public DriverConfigProfile withBoolean(DriverOption option, boolean value) { @Override public int getInt(DriverOption option) { - return getEffectiveOptions().getInt(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getInt); } @Override @@ -65,7 +70,7 @@ public DriverConfigProfile withInt(DriverOption option, int value) { @Override public Duration getDuration(DriverOption option) { - return getEffectiveOptions().getDuration(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getDuration); } @Override @@ -75,7 +80,7 @@ public DriverConfigProfile withDuration(DriverOption option, Duration value) { @Override public String getString(DriverOption option) { - return getEffectiveOptions().getString(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getString); } @Override @@ -85,7 +90,7 @@ public DriverConfigProfile withString(DriverOption option, String value) { @Override public List getStringList(DriverOption option) { - return getEffectiveOptions().getStringList(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getStringList); } @Override @@ -95,7 +100,7 @@ public DriverConfigProfile withStringList(DriverOption option, List valu @Override public long getBytes(DriverOption option) { - return getEffectiveOptions().getBytes(option.getPath()); + return getCached(option.getPath(), getEffectiveOptions()::getBytes); } @Override @@ -105,8 +110,12 @@ public DriverConfigProfile withBytes(DriverOption option, long value) { @Override public ConsistencyLevel getConsistencyLevel(DriverOption option) { - String name = getString(option); - return ConsistencyLevel.valueOf(name); + return getCached( + option.getPath(), + path -> { + String name = getEffectiveOptions().getString(path); + return ConsistencyLevel.valueOf(name); + }); } @Override @@ -114,6 +123,14 @@ public DriverConfigProfile withConsistencyLevel(DriverOption option, Consistency return with(option, value.toString()); } + private T getCached(String path, Function compute) { + // compute's signature guarantees we get a T, and this is the only place where we mutate the + // entry + @SuppressWarnings("unchecked") + T t = (T) cache.computeIfAbsent(path, compute); + return t; + } + private DriverConfigProfile with(DriverOption option, Object value) { Base base = getBaseProfile(); // Add the new option to any already derived options @@ -151,6 +168,7 @@ protected Config getEffectiveOptions() { void refresh(Config newOptions) { this.options = newOptions; + this.cache.clear(); if (derivedProfiles != null) { for (Derived derivedProfile : derivedProfiles) { derivedProfile.refresh(); @@ -195,6 +213,7 @@ static class Derived extends TypesafeDriverConfigProfile { void refresh() { this.effectiveOptions = addedOptions.withFallback(baseProfile.getEffectiveOptions()); + this.cache.clear(); } @Override From 3e736be7dac5f70e6513dcc1d141c35ad7075174 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Jul 2017 15:03:10 -0700 Subject: [PATCH 131/742] Specify usage of toString in contribution guidelines --- CONTRIBUTING.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7780f8fc7f..a32fd15249e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,6 +164,19 @@ cardinality). The driver never does the kind of processing that the stream API is intended for; the only large collections we manipulate are result sets, and these get passed on to the client directly. +### Never assume a specific format for `toString()` + +Only use `toString()` for debug logs or exception messages, and always assume that its format is +unspecified and can change at any time. + +If you need a specific string representation for a class, make it a dedicated method with a +documented format, for example `toCqlLiteral`. Otherwise it's too easy to lose track of the intended +usage and break things: for example, someone modifies your `toString()` method to make their logs +prettier, but unintentionally breaks the script export feature that expected it to produce CQL +literals. + +`toString()` can delegate to `toCqlLiteral()` if that is appropriate for logs. + ## Coding style -- test code From d2e5b3d17775c6c3e1c7798dd2532413028c4900 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Jul 2017 15:13:26 -0700 Subject: [PATCH 132/742] Explain difference between CqlDuration and java.time.Duration in the javadocs --- .../com/datastax/oss/driver/api/core/data/CqlDuration.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java index 464b824f271..0e66e9f16e5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -26,7 +26,9 @@ * A duration, as defined in CQL. * *

      It stores months, days, and seconds separately due to the fact that the number of days in a - * month varies, and a day can have 23 or 25 hours if a daylight saving is involved. + * month varies, and a day can have 23 or 25 hours if a daylight saving is involved. As such, this + * type differs from {@link java.time.Duration} (which only represents an amount between two points + * in time, regardless of the calendar). */ public final class CqlDuration { From 0defd5ea68c5105b774b535437b6693c991af1df Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 19 Jul 2017 16:07:08 -0700 Subject: [PATCH 133/742] Use empty column definitions for insert responses --- .../driver/internal/core/cql/Conversions.java | 4 +- .../core/cql/DefaultAsyncResultSet.java | 2 +- .../core/cql/DefaultColumnDefinitions.java | 55 ++------------ .../core/cql/EmptyColumnDefinitions.java | 74 +++++++++++++++++++ 4 files changed, 84 insertions(+), 51 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/EmptyColumnDefinitions.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 00fe7c4cd48..3c6cc54712e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -237,13 +237,13 @@ static DefaultPreparedStatement toPreparedStatement( ImmutableMap.copyOf(request.getCustomPayload())); } - private static DefaultColumnDefinitions toColumnDefinitions( + private static ColumnDefinitions toColumnDefinitions( RowsMetadata metadata, InternalDriverContext context) { ImmutableList.Builder definitions = ImmutableList.builder(); for (ColumnSpec columnSpec : metadata.columnSpecs) { definitions.add(new DefaultColumnDefinition(columnSpec, context)); } - return new DefaultColumnDefinitions(definitions.build()); + return DefaultColumnDefinitions.valueOf(definitions.build()); } static Throwable toThrowable(Node node, Error errorMessage) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 588b396aabc..5b0d2d7c7be 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -120,7 +120,7 @@ static AsyncResultSet empty(final ExecutionInfo executionInfo) { return new AsyncResultSet() { @Override public ColumnDefinitions getColumnDefinitions() { - return DefaultColumnDefinitions.EMPTY; + return EmptyColumnDefinitions.INSTANCE; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java index 0327ebb9469..0acc1e4bdc3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinitions.java @@ -24,16 +24,21 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; public class DefaultColumnDefinitions implements ColumnDefinitions { + static ColumnDefinitions valueOf(List definitions) { + return definitions.isEmpty() + ? EmptyColumnDefinitions.INSTANCE + : new DefaultColumnDefinitions(definitions); + } + private final List definitions; private final IdentifierIndex index; - public DefaultColumnDefinitions(List definitions) { + private DefaultColumnDefinitions(List definitions) { assert definitions != null && definitions.size() > 0; this.definitions = definitions; this.index = buildIndex(definitions); @@ -120,50 +125,4 @@ private Object readResolve() { return new DefaultColumnDefinitions(this.definitions); } } - - static final ColumnDefinitions EMPTY = - new ColumnDefinitions() { - @Override - public int size() { - return 0; - } - - @Override - public ColumnDefinition get(int i) { - throw new ArrayIndexOutOfBoundsException(); - } - - @Override - public boolean contains(String name) { - return false; - } - - @Override - public boolean contains(CqlIdentifier id) { - return false; - } - - @Override - public int firstIndexOf(String name) { - return -1; - } - - @Override - public int firstIndexOf(CqlIdentifier id) { - return -1; - } - - @Override - public boolean isDetached() { - return false; - } - - @Override - public void attach(AttachmentPoint attachmentPoint) {} - - @Override - public Iterator iterator() { - return Collections.emptyList().iterator(); - } - }; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/EmptyColumnDefinitions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/EmptyColumnDefinitions.java new file mode 100644 index 00000000000..e4ffd2c53d0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/EmptyColumnDefinitions.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import java.util.Collections; +import java.util.Iterator; + +/** + * The singleton that represents no column definitions (implemented as an enum which provides the + * serialization machinery for free). + */ +public enum EmptyColumnDefinitions implements ColumnDefinitions { + INSTANCE; + + @Override + public int size() { + return 0; + } + + @Override + public ColumnDefinition get(int i) { + throw new ArrayIndexOutOfBoundsException(); + } + + @Override + public boolean contains(String name) { + return false; + } + + @Override + public boolean contains(CqlIdentifier id) { + return false; + } + + @Override + public int firstIndexOf(String name) { + return -1; + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return -1; + } + + @Override + public boolean isDetached() { + return false; + } + + @Override + public void attach(AttachmentPoint attachmentPoint) {} + + @Override + public Iterator iterator() { + return Collections.emptyList().iterator(); + } +} From 8da747988db8446e2bd0092f4e05900700552c68 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Wed, 19 Jul 2017 14:03:58 -0500 Subject: [PATCH 134/742] JAVA-1554: Include VIEW and CDC in WriteType --- changelog/README.md | 3 ++- .../datastax/oss/driver/api/core/retry/WriteType.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 12ccef0981b..67d3843be0b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1554: Include VIEW and CDC in WriteType - [improvement] JAVA-1498: Add a cache above Typesafe config - [bug] JAVA-1547: Abort pending requests when connection dropped - [new feature] JAVA-1497: Port timestamp generators from 3.x @@ -18,4 +19,4 @@ - [improvement] JAVA-1496: Improve log messages - [new feature] JAVA-1501: Reprepare on the fly when we get an UNPREPARED response - [bug] JAVA-1499: Wait for load balancing policy at cluster initialization -- [new feature] JAVA-1495: Add prepared statements \ No newline at end of file +- [new feature] JAVA-1495: Add prepared statements diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java index 723e8fe7072..33f3bcdfcc1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java @@ -50,5 +50,15 @@ public enum WriteType { * have been applied. */ CAS, + /** + * Indicates that the timeout was related to acquiring locks needed for updating materialized + * views affected by write operation. + */ + VIEW, + /** + * Indicates that the timeout was related to acquiring space for change data capture logs for cdc + * tracked tables. + */ + CDC, ; } From 329c549df46a3a0b0aebdf2d15973132f575dcbf Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Jul 2017 10:09:06 -0700 Subject: [PATCH 135/742] Fix hashCode for collection data types --- .../oss/driver/internal/core/type/DefaultListType.java | 3 ++- .../datastax/oss/driver/internal/core/type/DefaultMapType.java | 2 +- .../datastax/oss/driver/internal/core/type/DefaultSetType.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java index b2f45219b64..84bc4250134 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultListType.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; +import java.util.Objects; public class DefaultListType implements ListType { @@ -72,7 +73,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return this.elementType.hashCode(); + return Objects.hash(DefaultListType.class, this.elementType); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java index 763a0641d11..937f3027de5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultMapType.java @@ -83,7 +83,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(keyType, valueType); + return Objects.hash(DefaultMapType.class, keyType, valueType); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java index a8869326b0a..1cd297b4bc0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultSetType.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import java.io.IOException; import java.io.ObjectInputStream; +import java.util.Objects; public class DefaultSetType implements SetType { @@ -72,7 +73,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return this.elementType.hashCode(); + return Objects.hash(DefaultSetType.class, this.elementType); } @Override From 0062d3fbc979aa39804f6f1007a6291ca9004ee4 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Jul 2017 17:00:52 -0500 Subject: [PATCH 136/742] Successfully complete future with SchemaChange result Until JAVA-1493 is implemented, simply complete futures that resulted in schema change. --- .../oss/driver/internal/core/cql/CqlRequestHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 39a4570006e..317b9818298 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -358,7 +358,7 @@ public void onResponse(Frame responseFrame) { Message responseMessage = responseFrame.message; if (responseMessage instanceof SchemaChange) { // TODO schema agreement, and chain setFinalResult to the result - setFinalError(new UnsupportedOperationException("TODO handle schema agreement")); + setFinalResult((Result) responseMessage, responseFrame, this); } else if (responseMessage instanceof Result) { LOG.debug("[{}] Got result, completing", logPrefix); setFinalResult((Result) responseMessage, responseFrame, this); From 59417d3ecca6ad97e75eecbbd819e906d8ae200c Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Wed, 19 Jul 2017 17:23:40 -0500 Subject: [PATCH 137/742] Split CachingCodecRegistry tests for clarity --- .../registry/CachingCodecRegistryTest.java | 148 +++++++++++++++--- 1 file changed, 127 insertions(+), 21 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 49dc09c529b..3dc11ae2a25 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -39,7 +39,9 @@ import com.google.common.util.concurrent.UncheckedExecutionException; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.Inet4Address; import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.time.Instant; import java.time.LocalDate; @@ -171,7 +173,7 @@ public void should_find_user_codec_for_custom_java_type() { } @Test - public void should_create_list_codec() { + public void should_create_list_codec_for_cql_and_java_types() { ListType cqlType = DataTypes.listOf(DataTypes.listOf(DataTypes.INT)); GenericType>> javaType = new GenericType>>() {}; List> value = ImmutableList.of(ImmutableList.of(1)); @@ -189,28 +191,46 @@ public void should_create_list_codec() { inOrder .verify(onCacheLookup) .accept(DataTypes.listOf(DataTypes.INT), GenericType.listOf(GenericType.INTEGER)); + } + + @Test + public void should_create_list_codec_for_cql_type() { + ListType cqlType = DataTypes.listOf(DataTypes.listOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + List> value = ImmutableList.of(ImmutableList.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); - codec = registry.codecFor(cqlType); + TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.listOf(DataTypes.INT), null); + } - codec = registry.codecFor(value); + @Test + public void should_create_list_codec_for_java_value() throws UnknownHostException { + ListType cqlType = DataTypes.listOf(DataTypes.listOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + List> value = ImmutableList.of(ImmutableList.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, javaType); inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER)); - - inOrder.verifyNoMoreInteractions(); } @Test - public void should_create_set_codec() { + public void should_create_set_codec_for_cql_and_java_types() { SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT)); GenericType>> javaType = new GenericType>>() {}; Set> value = ImmutableSet.of(ImmutableSet.of(1)); @@ -228,28 +248,46 @@ public void should_create_set_codec() { inOrder .verify(onCacheLookup) .accept(DataTypes.setOf(DataTypes.INT), GenericType.setOf(GenericType.INTEGER)); + } - codec = registry.codecFor(cqlType); + @Test + public void should_create_set_codec_for_cql_type() { + SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + Set> value = ImmutableSet.of(ImmutableSet.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.setOf(DataTypes.INT), null); + } - codec = registry.codecFor(value); + @Test + public void should_create_set_codec_for_java_value() { + SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT)); + GenericType>> javaType = new GenericType>>() {}; + Set> value = ImmutableSet.of(ImmutableSet.of(1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, javaType); inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER)); - - inOrder.verifyNoMoreInteractions(); } @Test - public void should_create_map_codec() { + public void should_create_map_codec_for_cql_and_java_types() { MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT)); GenericType>> javaType = new GenericType>>() {}; @@ -270,16 +308,38 @@ public void should_create_map_codec() { .accept( DataTypes.mapOf(DataTypes.INT, DataTypes.INT), GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER)); + } + + @Test + public void should_create_map_codec_for_cql_type() { + MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT)); + GenericType>> javaType = + new GenericType>>() {}; + Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); - codec = registry.codecFor(cqlType); + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.mapOf(DataTypes.INT, DataTypes.INT), null); + } + + @Test + public void should_create_map_codec_for_java_value() { + MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT)); + GenericType>> javaType = + new GenericType>>() {}; + Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); - codec = registry.codecFor(value); + TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(javaType)).isTrue(); @@ -288,12 +348,10 @@ public void should_create_map_codec() { inOrder .verify(onCacheLookup) .accept(null, GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER)); - - inOrder.verifyNoMoreInteractions(); } @Test - public void should_create_tuple_codec() { + public void should_create_tuple_codec_for_cql_and_java_types() { TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT)); TupleValue value = cqlType.newValue(); @@ -309,15 +367,34 @@ public void should_create_tuple_codec() { inOrder.verify(onCacheLookup).accept(cqlType, GenericType.TUPLE_VALUE); // field codecs are only looked up when fields are accessed, so no cache hit for list now - codec = registry.codecFor(cqlType); + } + + @Test + public void should_create_tuple_codec_for_cql_type() { + TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT)); + TupleValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); assertThat(codec.canEncode(TupleValue.class)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); + } - codec = registry.codecFor(value); + @Test + public void should_create_tuple_codec_for_java_value() { + TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT)); + TupleValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); @@ -329,7 +406,7 @@ public void should_create_tuple_codec() { } @Test - public void should_create_udt_codec() { + public void should_create_udt_codec_for_cql_and_java_types() { UserDefinedType cqlType = new UserDefinedTypeBuilder( CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) @@ -350,15 +427,44 @@ public void should_create_udt_codec() { inOrder.verify(onCacheLookup).accept(cqlType, GenericType.UDT_VALUE); // field codecs are only looked up when fields are accessed, so no cache hit for list now - codec = registry.codecFor(cqlType); + } + + @Test + public void should_create_udt_codec_for_cql_type() { + UserDefinedType cqlType = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.listOf(DataTypes.TEXT)) + .build(); + UdtValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); assertThat(codec.canEncode(UdtValue.class)).isTrue(); assertThat(codec.canEncode(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); + } + + @Test + public void should_create_udt_codec_for_java_value() { + UserDefinedType cqlType = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.listOf(DataTypes.TEXT)) + .build(); + UdtValue value = cqlType.newValue(); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); - codec = registry.codecFor(value); + TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); assertThat(codec.canDecode(cqlType)).isTrue(); assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); From 279e79e06ac3fae660d370f94a8586177fd3595a Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Jul 2017 09:21:02 -0700 Subject: [PATCH 138/742] Fix collection codec creation if first element is a subtype --- .../codec/registry/CachingCodecRegistry.java | 23 +++--- .../registry/CachingCodecRegistryTest.java | 72 +++++++++++++++++++ 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index e7d50f4b010..2008b84a062 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -158,17 +158,17 @@ public TypeCodec codecFor(T value) { return safeCast(getCachedCodec(null, javaType)); } - // Not exposed publicly, this is only used for the recursion from createCodec(GenericType) - private TypeCodec codecFor(GenericType javaType) { + // Not exposed publicly, this is only used for the recursion from createCovariantCodec(GenericType) + private TypeCodec covariantCodecFor(GenericType javaType) { LOG.trace("[{}] Looking up codec for Java type {}", logPrefix, javaType); for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { - if (primitiveCodec.canEncode(javaType)) { + if (primitiveCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) { LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } } for (TypeCodec userCodec : userCodecs) { - if (userCodec.canEncode(javaType)) { + if (userCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) { LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } @@ -218,7 +218,7 @@ protected TypeCodec createCodec(DataType cqlType, GenericType javaType) { assert cqlType != null; return createCodec(cqlType); } else if (cqlType == null) { - return createCodec(javaType); + return createCovariantCodec(javaType); } TypeToken token = javaType.__getToken(); if (cqlType instanceof ListType && List.class.isAssignableFrom(token.getRawType())) { @@ -273,28 +273,29 @@ protected TypeCodec createCodec(DataType cqlType, GenericType javaType) { } // Try to create a codec when we haven't found it in the cache. - // Variant where the CQL type is unknown. - private TypeCodec createCodec(GenericType javaType) { + // Variant where the CQL type is unknown. Note that this is only used for lookups by Java value, + // and therefore covariance is allowed. + private TypeCodec createCovariantCodec(GenericType javaType) { TypeToken token = javaType.__getToken(); if (List.class.isAssignableFrom(token.getRawType()) && token.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); GenericType elementType = GenericType.of(typeArguments[0]); - TypeCodec elementCodec = codecFor(elementType); + TypeCodec elementCodec = covariantCodecFor(elementType); return TypeCodecs.listOf(elementCodec); } else if (Set.class.isAssignableFrom(token.getRawType()) && token.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); GenericType elementType = GenericType.of(typeArguments[0]); - TypeCodec elementCodec = codecFor(elementType); + TypeCodec elementCodec = covariantCodecFor(elementType); return TypeCodecs.setOf(elementCodec); } else if (Map.class.isAssignableFrom(token.getRawType()) && token.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments(); GenericType keyType = GenericType.of(typeArguments[0]); GenericType valueType = GenericType.of(typeArguments[1]); - TypeCodec keyCodec = codecFor(keyType); - TypeCodec valueCodec = codecFor(valueType); + TypeCodec keyCodec = covariantCodecFor(keyType); + TypeCodec valueCodec = covariantCodecFor(valueType); return TypeCodecs.mapOf(keyCodec, valueCodec); } throw new CodecNotFoundException(null, javaType); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 3dc11ae2a25..ad552f50e39 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -229,6 +229,29 @@ public void should_create_list_codec_for_java_value() throws UnknownHostExceptio inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER)); } + @Test + public void should_create_list_codec_for_java_value_when_first_element_is_a_subtype() + throws UnknownHostException { + ListType cqlType = DataTypes.listOf(DataTypes.INET); + GenericType> javaType = new GenericType>() {}; + InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + // Because the actual implementation is a subclass, there is no exact match with the codec's + // declared type + assertThat(address).isInstanceOf(Inet4Address.class); + List value = ImmutableList.of(address); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec> codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + + inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(Inet4Address.class)); + } + @Test public void should_create_set_codec_for_cql_and_java_types() { SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT)); @@ -286,6 +309,29 @@ public void should_create_set_codec_for_java_value() { inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER)); } + @Test + public void should_create_set_codec_for_java_value_when_first_element_is_a_subtype() + throws UnknownHostException { + SetType cqlType = DataTypes.setOf(DataTypes.INET); + GenericType> javaType = new GenericType>() {}; + InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + // Because the actual implementation is a subclass, there is no exact match with the codec's + // declared type + assertThat(address).isInstanceOf(Inet4Address.class); + Set value = ImmutableSet.of(address); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec> codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + + inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(Inet4Address.class)); + } + @Test public void should_create_map_codec_for_cql_and_java_types() { MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT)); @@ -350,6 +396,32 @@ public void should_create_map_codec_for_java_value() { .accept(null, GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER)); } + @Test + public void should_create_map_codec_for_java_value_when_first_element_is_a_subtype() + throws UnknownHostException { + MapType cqlType = DataTypes.mapOf(DataTypes.INET, DataTypes.INET); + GenericType> javaType = + new GenericType>() {}; + InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + // Because the actual implementation is a subclass, there is no exact match with the codec's + // declared type + assertThat(address).isInstanceOf(Inet4Address.class); + Map value = ImmutableMap.of(address, address); + + TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup); + InOrder inOrder = Mockito.inOrder(onCacheLookup); + + TypeCodec> codec = registry.codecFor(value); + assertThat(codec).isNotNull(); + assertThat(codec.canDecode(cqlType)).isTrue(); + assertThat(codec.canEncode(javaType)).isTrue(); + assertThat(codec.canEncode(value)).isTrue(); + + inOrder + .verify(onCacheLookup) + .accept(null, GenericType.mapOf(Inet4Address.class, Inet4Address.class)); + } + @Test public void should_create_tuple_codec_for_cql_and_java_types() { TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT)); From a44bb8ad9b3fea6b26d85e1d58e4383d665983d0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Jul 2017 09:40:09 -0700 Subject: [PATCH 139/742] Rename TypeCodec's canEncode and canDecode methods In hindsight these are not good names because each type can be the source or the target depending on which way we're going. Revert to `accepts()` like in 3.x. --- .../driver/api/core/type/codec/TypeCodec.java | 20 +-- .../internal/core/type/codec/BigIntCodec.java | 4 +- .../internal/core/type/codec/BlobCodec.java | 4 +- .../core/type/codec/BooleanCodec.java | 4 +- .../core/type/codec/CqlDurationCodec.java | 4 +- .../internal/core/type/codec/CustomCodec.java | 4 +- .../internal/core/type/codec/DateCodec.java | 4 +- .../core/type/codec/DecimalCodec.java | 4 +- .../internal/core/type/codec/DoubleCodec.java | 4 +- .../internal/core/type/codec/FloatCodec.java | 4 +- .../internal/core/type/codec/InetCodec.java | 4 +- .../internal/core/type/codec/IntCodec.java | 4 +- .../internal/core/type/codec/ListCodec.java | 4 +- .../internal/core/type/codec/MapCodec.java | 4 +- .../internal/core/type/codec/SetCodec.java | 4 +- .../core/type/codec/SmallIntCodec.java | 4 +- .../internal/core/type/codec/StringCodec.java | 4 +- .../internal/core/type/codec/TimeCodec.java | 4 +- .../core/type/codec/TimeUuidCodec.java | 4 +- .../core/type/codec/TimestampCodec.java | 4 +- .../core/type/codec/TinyIntCodec.java | 4 +- .../internal/core/type/codec/TupleCodec.java | 4 +- .../internal/core/type/codec/UdtCodec.java | 4 +- .../internal/core/type/codec/UuidCodec.java | 4 +- .../internal/core/type/codec/VarIntCodec.java | 4 +- .../codec/registry/CachingCodecRegistry.java | 12 +- .../core/type/codec/TimeUuidCodecTest.java | 2 +- .../registry/CachingCodecRegistryTest.java | 120 +++++++++--------- 28 files changed, 125 insertions(+), 125 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java index 43f00889058..803477bef91 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java @@ -30,7 +30,7 @@ public interface TypeCodec { DataType getCqlType(); /** - * Whether this codec is capable of encoding the given Java type. + * Whether this codec is capable of processing the given Java type. * *

      The default implementation is invariant with respect to the passed argument * (through the usage of {@link GenericType#equals(Object)}) and it's strongly recommended not @@ -39,32 +39,32 @@ public interface TypeCodec { * *

      If the argument represents a Java primitive type, its wrapper type is considered instead. */ - default boolean canEncode(GenericType javaType) { + default boolean accepts(GenericType javaType) { Preconditions.checkNotNull(javaType); return getJavaType().__getToken().equals(javaType.__getToken().wrap()); } /** - * Whether this codec is capable of encoding the given Java type. + * Whether this codec is capable of processing the given Java type. * *

      The default implementation wraps the class in a generic type and calls {@link - * #canEncode(GenericType)}, therefore it is invariant as well. + * #accepts(GenericType)}, therefore it is invariant as well. * *

      Implementors are encouraged to override this method if there is a more efficient way. In * particular, if the codec targets a non-generic class, the check can be done with {@code * Class#isAssignableFrom}. Or even better, with {@code ==} if that class also happens to be * final. */ - default boolean canEncode(Class javaClass) { + default boolean accepts(Class javaClass) { Preconditions.checkNotNull(javaClass); - return canEncode(GenericType.of(javaClass)); + return accepts(GenericType.of(javaClass)); } /** * Whether this codec is capable of encoding the given Java object. * *

      The object's Java type is inferred from its runtime (raw) type, contrary to {@link - * #canEncode(GenericType)} which is capable of handling generic types. + * #accepts(GenericType)} which is capable of handling generic types. * *

      The default implementation is covariant with respect to the passed argument and * it's strongly recommended not to modify this behavior. This means that, by default, a @@ -81,13 +81,13 @@ default boolean canEncode(Class javaClass) { *

      Finally, if the codec targets a non-generic Java class, it might be possible to implement * this method with a simple {@code instanceof} check. */ - default boolean canEncode(Object value) { + default boolean accepts(Object value) { Preconditions.checkNotNull(value); return getJavaType().__getToken().isSupertypeOf(TypeToken.of(value.getClass())); } - /** Whether this codec is capable of decoding the given CQL type. */ - default boolean canDecode(DataType cqlType) { + /** Whether this codec is capable of processing the given CQL type. */ + default boolean accepts(DataType cqlType) { Preconditions.checkNotNull(cqlType); return this.getCqlType().equals(cqlType); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java index 62fc46039b7..a855c67a2de 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java @@ -34,12 +34,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Long; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Long.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java index 32a98c01279..602e6828920 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java @@ -35,12 +35,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof ByteBuffer; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return ByteBuffer.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java index bbc9976cdbf..29d90bf65a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java @@ -38,12 +38,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Boolean; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Boolean.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java index edad3067ffd..bf0aed5a4ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodec.java @@ -41,12 +41,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof CqlDuration; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == CqlDuration.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java index 3ccbfcb37b1..774dbd76e83 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java @@ -42,12 +42,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof ByteBuffer; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return ByteBuffer.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java index 1421b2053d8..ab11412eabb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java @@ -44,12 +44,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof LocalDate; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == LocalDate.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java index ef2ed52f521..041c625c6ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodec.java @@ -36,12 +36,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof BigDecimal; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return BigDecimal.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java index 0a62ef00bee..7e44ba834e1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java @@ -34,12 +34,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Double; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Double.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java index 6c238c0d912..4a1b99cf2d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java @@ -34,12 +34,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Float; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Float.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java index a5a3b4c7d37..4dd7988a17c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java @@ -38,12 +38,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof InetAddress; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return InetAddress.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java index d97e38d843a..2ce444fbe62 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java @@ -35,12 +35,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Integer; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Integer.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java index 38f08fa61d9..51d3f92b069 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java @@ -49,11 +49,11 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { if (List.class.isAssignableFrom(value.getClass())) { // runtime type ok, now check element type List list = (List) value; - return list.isEmpty() || elementCodec.canEncode(list.get(0)); + return list.isEmpty() || elementCodec.accepts(list.get(0)); } else { return false; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java index 5328a5f29a1..d1790ba82d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java @@ -49,7 +49,7 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { if (value instanceof Map) { // runtime type ok, now check key and value types Map map = (Map) value; @@ -57,7 +57,7 @@ public boolean canEncode(Object value) { return true; } Map.Entry entry = map.entrySet().iterator().next(); - return keyCodec.canEncode(entry.getKey()) && valueCodec.canEncode(entry.getValue()); + return keyCodec.accepts(entry.getKey()) && valueCodec.accepts(entry.getValue()); } return false; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java index 874f92a909b..8558b9dc69a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java @@ -50,11 +50,11 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { if (Set.class.isAssignableFrom(value.getClass())) { // runtime type ok, now check element type Set set = (Set) value; - return set.isEmpty() || elementCodec.canEncode(set.iterator().next()); + return set.isEmpty() || elementCodec.accepts(set.iterator().next()); } else { return false; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java index a7f389afea4..bae1aa493cd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java @@ -34,12 +34,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Short; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Short.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java index 24cd40b2c7f..d4f988cc184 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/StringCodec.java @@ -45,12 +45,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof String; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == String.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java index 0932a689878..1580e99142a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java @@ -41,12 +41,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof LocalTime; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == LocalTime.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java index 8336c6c302b..c1ae4960d71 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodec.java @@ -28,12 +28,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof UUID && ((UUID) value).version() == 1; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == UUID.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java index 76eb7748657..a5b3fd226a8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java @@ -72,12 +72,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Instant; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Instant.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java index 6a59409cfd7..30263afb58b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java @@ -34,12 +34,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof Byte; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == Byte.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java index dc18cf2e463..f5989810602 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java @@ -44,12 +44,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return (value instanceof TupleValue) && ((TupleValue) value).getType().equals(cqlType); } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return TupleValue.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 9d8653da334..5cf677983b3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -45,12 +45,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof UdtValue && ((UdtValue) value).getType().equals(cqlType); } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return UdtValue.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java index 1155ac0f3c9..0782dfbb44f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodec.java @@ -35,12 +35,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof UUID; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return javaClass == UUID.class; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java index 4433392c4e5..1ddfc0c3f54 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/VarIntCodec.java @@ -36,12 +36,12 @@ public DataType getCqlType() { } @Override - public boolean canEncode(Object value) { + public boolean accepts(Object value) { return value instanceof BigInteger; } @Override - public boolean canEncode(Class javaClass) { + public boolean accepts(Class javaClass) { return BigInteger.class.isAssignableFrom(javaClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index 2008b84a062..08889659240 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -82,12 +82,12 @@ protected CachingCodecRegistry(String logPrefix, TypeCodec... userCodecs) { public TypeCodec codecFor(DataType cqlType, GenericType javaType) { LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = PRIMITIVE_CODECS_BY_CODE.get(cqlType.getProtocolCode()); - if (primitiveCodec != null && primitiveCodec.canEncode(javaType)) { + if (primitiveCodec != null && primitiveCodec.accepts(javaType)) { LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { - if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { + if (userCodec.accepts(cqlType) && userCodec.accepts(javaType)) { LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } @@ -104,7 +104,7 @@ public TypeCodec codecFor(DataType cqlType, Class javaType) { return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { - if (userCodec.canDecode(cqlType) && userCodec.canEncode(javaType)) { + if (userCodec.accepts(cqlType) && userCodec.accepts(javaType)) { LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } @@ -121,7 +121,7 @@ public TypeCodec codecFor(DataType cqlType) { return safeCast(primitiveCodec); } for (TypeCodec userCodec : userCodecs) { - if (userCodec.canDecode(cqlType)) { + if (userCodec.accepts(cqlType)) { LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } @@ -135,13 +135,13 @@ public TypeCodec codecFor(T value) { LOG.trace("[{}] Looking up codec for object {}", logPrefix, value); for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { - if (primitiveCodec.canEncode(value)) { + if (primitiveCodec.accepts(value)) { LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return safeCast(primitiveCodec); } } for (TypeCodec userCodec : userCodecs) { - if (userCodec.canEncode(value)) { + if (userCodec.accepts(value)) { LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec); return safeCast(userCodec); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java index 6c323cd6374..fe51f9d0ba4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java @@ -40,7 +40,7 @@ public void should_encode_time_uuid() { @Test(expected = IllegalArgumentException.class) public void should_not_encode_non_time_uuid() { - assertThat(codec.canEncode(NOT_TIME_BASED)).isFalse(); + assertThat(codec.accepts(NOT_TIME_BASED)).isFalse(); encode(NOT_TIME_BASED); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index ad552f50e39..0c56b315240 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -183,9 +183,9 @@ public void should_create_list_codec_for_cql_and_java_types() { TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); // Cache lookup for the codec, and recursively for its subcodec inOrder.verify(onCacheLookup).accept(cqlType, javaType); inOrder @@ -204,9 +204,9 @@ public void should_create_list_codec_for_cql_type() { TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.listOf(DataTypes.INT), null); } @@ -222,9 +222,9 @@ public void should_create_list_codec_for_java_value() throws UnknownHostExceptio TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, javaType); inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER)); } @@ -245,9 +245,9 @@ public void should_create_list_codec_for_java_value_when_first_element_is_a_subt TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(Inet4Address.class)); } @@ -263,9 +263,9 @@ public void should_create_set_codec_for_cql_and_java_types() { TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); // Cache lookup for the codec, and recursively for its subcodec inOrder.verify(onCacheLookup).accept(cqlType, javaType); inOrder @@ -284,9 +284,9 @@ public void should_create_set_codec_for_cql_type() { TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.setOf(DataTypes.INT), null); } @@ -302,9 +302,9 @@ public void should_create_set_codec_for_java_value() { TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, javaType); inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER)); } @@ -325,9 +325,9 @@ public void should_create_set_codec_for_java_value_when_first_element_is_a_subty TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(Inet4Address.class)); } @@ -344,9 +344,9 @@ public void should_create_map_codec_for_cql_and_java_types() { TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); // Cache lookup for the codec, and recursively for its subcodec inOrder.verify(onCacheLookup).accept(cqlType, javaType); inOrder @@ -368,9 +368,9 @@ public void should_create_map_codec_for_cql_type() { TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); inOrder.verify(onCacheLookup).accept(DataTypes.mapOf(DataTypes.INT, DataTypes.INT), null); } @@ -387,9 +387,9 @@ public void should_create_map_codec_for_java_value() { TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(null, javaType); inOrder .verify(onCacheLookup) @@ -413,9 +413,9 @@ public void should_create_map_codec_for_java_value_when_first_element_is_a_subty TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(javaType)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(javaType)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder .verify(onCacheLookup) @@ -432,10 +432,10 @@ public void should_create_tuple_codec_for_cql_and_java_types() { TypeCodec codec = registry.codecFor(cqlType, GenericType.TUPLE_VALUE); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); - assertThat(codec.canEncode(TupleValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.accepts(TupleValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, GenericType.TUPLE_VALUE); // field codecs are only looked up when fields are accessed, so no cache hit for list now @@ -451,10 +451,10 @@ public void should_create_tuple_codec_for_cql_type() { TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); - assertThat(codec.canEncode(TupleValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.accepts(TupleValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); } @@ -468,10 +468,10 @@ public void should_create_tuple_codec_for_java_value() { TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.TUPLE_VALUE)).isTrue(); - assertThat(codec.canEncode(TupleValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.TUPLE_VALUE)).isTrue(); + assertThat(codec.accepts(TupleValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, GenericType.TUPLE_VALUE); inOrder.verifyNoMoreInteractions(); @@ -492,10 +492,10 @@ public void should_create_udt_codec_for_cql_and_java_types() { TypeCodec codec = registry.codecFor(cqlType, GenericType.UDT_VALUE); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); - assertThat(codec.canEncode(UdtValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.accepts(UdtValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, GenericType.UDT_VALUE); // field codecs are only looked up when fields are accessed, so no cache hit for list now @@ -516,10 +516,10 @@ public void should_create_udt_codec_for_cql_type() { TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); - assertThat(codec.canEncode(UdtValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.accepts(UdtValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, null); } @@ -538,10 +538,10 @@ public void should_create_udt_codec_for_java_value() { TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); - assertThat(codec.canDecode(cqlType)).isTrue(); - assertThat(codec.canEncode(GenericType.UDT_VALUE)).isTrue(); - assertThat(codec.canEncode(UdtValue.class)).isTrue(); - assertThat(codec.canEncode(value)).isTrue(); + assertThat(codec.accepts(cqlType)).isTrue(); + assertThat(codec.accepts(GenericType.UDT_VALUE)).isTrue(); + assertThat(codec.accepts(UdtValue.class)).isTrue(); + assertThat(codec.accepts(value)).isTrue(); inOrder.verify(onCacheLookup).accept(cqlType, GenericType.UDT_VALUE); inOrder.verifyNoMoreInteractions(); From 09ed90b33cf9a561e5999efa801becc4ea817f59 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 20 Jul 2017 10:29:25 -0500 Subject: [PATCH 140/742] Update TimeCodec.format to include full nanosecond resolution TimeCodec.format previously truncated LocalTime by not including anything beyond millisecond resolution. --- .../oss/driver/internal/core/type/codec/TimeCodec.java | 3 ++- .../oss/driver/internal/core/type/codec/TimeCodecTest.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java index 1580e99142a..3035714eaf3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodec.java @@ -28,7 +28,8 @@ public class TimeCodec implements TypeCodec { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + private static final DateTimeFormatter FORMATTER = + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS"); @Override public GenericType getJavaType() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java index aae03d65f41..c4a29c17ecc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -53,7 +54,9 @@ public void should_fail_to_decode_if_too_many_bytes() { public void should_format() { // No need to test various values because the codec delegates directly to the JDK's formatter, // which we assume does its job correctly. - assertThat(format(LocalTime.MIDNIGHT)).isEqualTo("'00:00:00.000'"); + assertThat(format(LocalTime.MIDNIGHT)).isEqualTo("'00:00:00.000000000'"); + assertThat(format(LocalTime.NOON.plus(13799999994L, ChronoUnit.NANOS))) + .isEqualTo("'12:00:13.799999994'"); assertThat(format(null)).isEqualTo("NULL"); } From 1f40079d31c7d9f5f33858e4ed7f6b3fe6049fb2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Jul 2017 16:27:51 -0700 Subject: [PATCH 141/742] JAVA-1546: Make all statement implementations immutable --- README.md | 2 + changelog/README.md | 1 + .../driver/api/core/cql/BatchStatement.java | 129 ++++++----- .../api/core/cql/BatchStatementBuilder.java | 89 ++++++++ .../api/core/cql/BatchableStatement.java | 8 +- .../oss/driver/api/core/cql/Bindable.java | 91 ++++++++ .../driver/api/core/cql/BoundStatement.java | 56 +---- .../api/core/cql/BoundStatementBuilder.java | 118 ++++++++++ .../api/core/cql/PreparedStatement.java | 25 +- .../driver/api/core/cql/SimpleStatement.java | 122 ++++++---- .../api/core/cql/SimpleStatementBuilder.java | 194 ++++++---------- .../oss/driver/api/core/cql/Statement.java | 103 ++++++++- .../driver/api/core/cql/StatementBuilder.java | 105 +++++++++ .../oss/driver/api/core/session/Session.java | 4 +- .../driver/internal/core/cql/Conversions.java | 2 - .../core/cql/DefaultBatchStatement.java | 213 +++++++++++++----- .../core/cql/DefaultBoundStatement.java | 186 +++++++++++---- .../core/cql/DefaultPreparedStatement.java | 29 ++- .../core/cql/DefaultSimpleStatement.java | 175 +++++++++++--- faq/README.md | 17 ++ upgrade_guide/README.md | 23 ++ 21 files changed, 1251 insertions(+), 441 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java create mode 100644 faq/README.md diff --git a/README.md b/README.md index 5d1404bad0b..2e69fdb0a48 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ are multiple modules, all prefixed with `java-driver-`. Refer to the [manual] fo [@DataStaxEng] has more news, including other drivers, Cassandra, and DSE. * [Changelog] * [Upgrade guide] +* [FAQ] [API docs]: http://www.datastax.com/drivers/java/4.0 [JIRA]: https://datastax-oss.atlassian.net/browse/JAVA @@ -39,6 +40,7 @@ are multiple modules, all prefixed with `java-driver-`. Refer to the [manual] fo [@DataStaxEng]: https://twitter.com/datastaxeng [Changelog]: changelog/ [Upgrade guide]: upgrade_guide/ +[FAQ]: faq/ ## License diff --git a/changelog/README.md b/changelog/README.md index 67d3843be0b..def408c71f0 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1546: Make all statement implementations immutable - [bug] JAVA-1554: Include VIEW and CDC in WriteType - [improvement] JAVA-1498: Add a cache above Typesafe config - [bug] JAVA-1547: Abort pending requests when connection dropped diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 43dd085cf39..e6496d27001 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -15,89 +15,112 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; -import java.nio.ByteBuffer; -import java.util.Map; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; /** * A statement that groups a number of other statements, so that they can be executed as a batch * (i.e. sent together as a single protocol frame). * - *

      The default implementation returned by the driver is mutable and NOT - * thread-safe: methods that return {@code BatchStatement} mutate the statement and return the - * same instance (except {@link #copy(ByteBuffer)}); all methods should be called from the thread - * that created the statement; if you use {@link Session#executeAsync(Statement)} asynchronous - * execution}, do not reuse the same instance for successive calls, as you run the risk of modifying - * the statement before the driver is done processing it. + *

      The default implementation returned by the driver is immutable and thread-safe. + * All mutating methods return a new instance. See also the static factory methods and builders in + * this interface. */ -public interface BatchStatement extends Statement, Iterable { +public interface BatchStatement extends Statement, Iterable> { /** Creates an instance of the default implementation for the given batch type. */ static BatchStatement newInstance(BatchType batchType) { - return new DefaultBatchStatement(batchType); + return new DefaultBatchStatement( + batchType, + new ArrayList<>(), + null, + null, + null, + Collections.emptyMap(), + false, + false, + Long.MIN_VALUE, + null); } - BatchType getBatchType(); - /** - * Adds a new statement to the batch. - * - *

      Note that, due to protocol limitations, simple statements with named values are currently - * not supported. + * Creates an instance of the default implementation for the given batch type, containing the + * given statements. */ - BatchStatement add(BatchableStatement statement); - - default BatchStatement addAll(Iterable statements) { - BatchStatement result = this; - for (BatchableStatement statement : statements) { - result = result.add(statement); - } - return result; + static BatchStatement newInstance(BatchType batchType, BatchableStatement... statements) { + return new DefaultBatchStatement( + batchType, + ImmutableList.copyOf(statements), + null, + null, + null, + Collections.emptyMap(), + false, + false, + Long.MIN_VALUE, + null); } - default BatchStatement addAll(BatchableStatement... statements) { - BatchStatement result = this; - for (BatchableStatement statement : statements) { - result = result.add(statement); - } - return result; + /** Returns a builder to create an instance of the default implementation. */ + static BatchStatementBuilder builder(BatchType batchType) { + return new BatchStatementBuilder(batchType); } - int size(); + /** + * Returns a builder to create an instance of the default implementation, copying the fields of + * the given statement. + */ + static BatchStatementBuilder builder(BatchStatement template) { + return new BatchStatementBuilder(template); + } - /** Clears the batch, removing all the statements added so far. */ - BatchStatement clear(); + BatchType getBatchType(); /** - * Sets the name of the configuration profile to use. + * Sets the batch type. * - *

      Note that this will be ignored if {@link #getConfigProfile()} return a non-null value. + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. */ - BatchStatement setConfigProfileName(String configProfileName); - - /** Sets the configuration profile to use. */ - DefaultBatchStatement setConfigProfile(DriverConfigProfile configProfile); + BatchStatement setBatchType(BatchType newBatchType); - /** Sets the custom payload to send alongside the request. */ - DefaultBatchStatement setCustomPayload(Map customPayload); + /** + * Adds a new statement to the batch. + * + *

      Note that, due to protocol limitations, simple statements with named values are currently + * not supported. + * + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + */ + BatchStatement add(BatchableStatement statement); /** - * Indicates whether the statement is idempotent. + * Adds new statements to the batch. + * + *

      Note that, due to protocol limitations, simple statements with named values are currently + * not supported. * - * @param idempotent true or false, or {@code null} to use the default defined in the - * configuration. + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. */ - DefaultBatchStatement setIdempotent(Boolean idempotent); + BatchStatement addAll(Iterable> statements); - /** Request tracing information for this statement. */ - BatchStatement setTracing(); + /** @see #addAll(Iterable) */ + default BatchStatement addAll(BatchableStatement... statements) { + return addAll(Arrays.asList(statements)); + } + + int size(); /** - * Sets the timestamp for this statement. If left unset, the {@link TimestampGenerator} will - * assign it when the statement gets executed. + * Clears the batch, removing all the statements added so far. + * + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. */ - BatchStatement setTimestamp(long timestamp); + BatchStatement clear(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java new file mode 100644 index 00000000000..392235c2295 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import java.util.Arrays; + +public class BatchStatementBuilder extends StatementBuilder { + + private BatchType batchType; + private ImmutableList.Builder> statementsBuilder; + private int statementsCount; + + public BatchStatementBuilder(BatchType batchType) { + this.batchType = batchType; + this.statementsBuilder = ImmutableList.builder(); + } + + public BatchStatementBuilder(BatchStatement template) { + super(template); + this.batchType = template.getBatchType(); + this.statementsBuilder = ImmutableList.>builder().addAll(template); + this.statementsCount = template.size(); + } + + /** @see BatchStatement#add(BatchableStatement) */ + public BatchStatementBuilder addStatement(BatchableStatement statement) { + if (statementsCount >= 0xFFFF) { + throw new IllegalStateException( + "Batch statement cannot contain more than " + 0xFFFF + " statements."); + } + statementsCount += 1; + statementsBuilder.add(statement); + return this; + } + + /** @see BatchStatement#addAll(Iterable) */ + public BatchStatementBuilder addStatements(Iterable> statements) { + int delta = Iterables.size(statements); + if (statementsCount + delta > 0xFFFF) { + throw new IllegalStateException( + "Batch statement cannot contain more than " + 0xFFFF + " statements."); + } + statementsCount += delta; + statementsBuilder.addAll(statements); + return this; + } + + /** @see BatchStatement#addAll(BatchableStatement[]) */ + public BatchStatementBuilder addStatements(BatchableStatement... statements) { + return addStatements(Arrays.asList(statements)); + } + + public BatchStatementBuilder clearStatements() { + statementsBuilder = ImmutableList.builder(); + statementsCount = 0; + return this; + } + + @Override + public BatchStatement build() { + return new DefaultBatchStatement( + batchType, + statementsBuilder.build(), + configProfileName, + configProfile, + null, + customPayloadBuilder.build(), + idempotent, + tracing, + timestamp, + pagingState); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java index 0983560df5d..0ef683344c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java @@ -15,5 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; -/** A statement that can be added to a CQL batch. */ -public interface BatchableStatement extends Statement {} +/** + * A statement that can be added to a CQL batch. + * + * @param the "self type" used for covariant returns in subtypes. + */ +public interface BatchableStatement> extends Statement {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java new file mode 100644 index 00000000000..cdb1abab1be --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.AccessibleByName; +import com.datastax.oss.driver.api.core.data.GettableById; +import com.datastax.oss.driver.api.core.data.GettableByName; +import com.datastax.oss.driver.api.core.data.SettableById; +import com.datastax.oss.driver.api.core.data.SettableByName; +import com.datastax.oss.protocol.internal.ProtocolConstants; + +/** A data container with the ability to unset values. */ +public interface Bindable> + extends GettableById, GettableByName, SettableById, SettableByName { + /** + * Whether the {@code i}th value has been set. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default boolean isSet(int i) { + return getBytesUnsafe(i) != ProtocolConstants.UNSET_VALUE; + } + + /** + * Whether the value for the first occurrence of {@code id} has been set. + * + *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the id is invalid. + */ + default boolean isSet(CqlIdentifier id) { + return getBytesUnsafe(id) != ProtocolConstants.UNSET_VALUE; + } + + /** + * Whether the value for the first occurrence of {@code name} has been set. + * + *

      This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the name is invalid. + */ + default boolean isSet(String name) { + return getBytesUnsafe(name) != ProtocolConstants.UNSET_VALUE; + } + + /** + * Unsets the {@code i}th value. This will leave the statement in the same state as if no setter + * was ever called for this value. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T unset(int i) { + return setBytesUnsafe(i, ProtocolConstants.UNSET_VALUE); + } + + /** + * Unsets the value for the first occurrence of {@code id}. This will leave the statement in the + * same state as if no setter was ever called for this value. + * + * @throws IndexOutOfBoundsException if the id is invalid. + */ + default T unset(CqlIdentifier id) { + return setBytesUnsafe(id, ProtocolConstants.UNSET_VALUE); + } + + /** + * Unsets the value for the first occurrence of {@code name}. This will leave the statement in the + * same state as if no setter was ever called for this value. + * + * @throws IndexOutOfBoundsException if the name is invalid. + */ + default T unset(String name) { + return setBytesUnsafe(name, ProtocolConstants.UNSET_VALUE); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index 0ffcaf19551..39f5a982041 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -15,71 +15,21 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.data.GettableById; -import com.datastax.oss.driver.api.core.data.GettableByName; -import com.datastax.oss.driver.api.core.data.SettableById; -import com.datastax.oss.driver.api.core.data.SettableByName; -import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.nio.ByteBuffer; import java.util.List; -import java.util.Map; /** * A prepared statement in its executable form, with values bound to the variables. * - *

      The default implementation provided by the driver is: - * - *

        - *
      • not thread-safe: all methods (setting values, etc.) must be called from the thread that - * created the instance. Besides, if you use {@link Session#executeAsync(Statement)} - * asynchronous execution}, do not reuse the same instance for multiple calls, as you run the - * risk of modifying the statement while the driver internals are still processing it. - *
      • mutable: all setters that return a {@code BoundStatement} modify and return the same - * instance, instead of creating a copy. - *
      + *

      The default implementation returned by the driver is immutable and thread-safe. + * All mutating methods return a new instance. */ public interface BoundStatement - extends BatchableStatement, - GettableById, - GettableByName, - SettableById, - SettableByName { + extends BatchableStatement, Bindable { /** The prepared statement that was used to create this statement. */ PreparedStatement getPreparedStatement(); /** The values to bind, in their serialized form. */ List getValues(); - - /** - * Sets the name of the configuration profile to use. - * - *

      Note that this will be ignored if {@link #getConfigProfile()} return a non-null value. - */ - BoundStatement setConfigProfileName(String configProfileName); - - /** Sets the configuration profile to use. */ - BoundStatement setConfigProfile(DriverConfigProfile configProfile); - - /** Sets the custom payload to send alongside the request. */ - BoundStatement setCustomPayload(Map customPayload); - - /** - * Indicates whether the statement is idempotent. - * - * @param idempotent true or false, or {@code null} to use the default defined in the - * configuration. - */ - BoundStatement setIdempotent(Boolean idempotent); - - /** Request tracing information for this statement. */ - BoundStatement setTracing(); - - /** - * Sets the timestamp for this statement. If left unset, the {@link TimestampGenerator} will - * assign it when the statement gets executed. - */ - BoundStatement setTimestamp(long timestamp); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java new file mode 100644 index 00000000000..d5ee0347f26 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import java.nio.ByteBuffer; +import java.util.Collections; + +public class BoundStatementBuilder extends StatementBuilder + implements Bindable { + + private final PreparedStatement preparedStatement; + private final ColumnDefinitions variableDefinitions; + private final ByteBuffer[] values; + private final CodecRegistry codecRegistry; + private final ProtocolVersion protocolVersion; + + public BoundStatementBuilder( + PreparedStatement preparedStatement, + ColumnDefinitions variableDefinitions, + CodecRegistry codecRegistry, + ProtocolVersion protocolVersion) { + this.preparedStatement = preparedStatement; + this.variableDefinitions = variableDefinitions; + this.values = new ByteBuffer[variableDefinitions.size()]; + for (int i = 0; i < values.length; i++) { + values[i] = ProtocolConstants.UNSET_VALUE; + } + this.codecRegistry = codecRegistry; + this.protocolVersion = protocolVersion; + } + + public BoundStatementBuilder(BoundStatement template) { + super(template); + this.preparedStatement = template.getPreparedStatement(); + this.variableDefinitions = template.getPreparedStatement().getVariableDefinitions(); + this.values = template.getValues().toArray(new ByteBuffer[this.variableDefinitions.size()]); + this.codecRegistry = template.codecRegistry(); + this.protocolVersion = template.protocolVersion(); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + return variableDefinitions.firstIndexOf(id); + } + + @Override + public int firstIndexOf(String name) { + return variableDefinitions.firstIndexOf(name); + } + + @Override + public BoundStatementBuilder setBytesUnsafe(int i, ByteBuffer v) { + values[i] = v; + return this; + } + + @Override + public ByteBuffer getBytesUnsafe(int i) { + return values[i]; + } + + @Override + public int size() { + return values.length; + } + + @Override + public DataType getType(int i) { + return variableDefinitions.get(i).getType(); + } + + @Override + public CodecRegistry codecRegistry() { + return codecRegistry; + } + + @Override + public ProtocolVersion protocolVersion() { + return protocolVersion; + } + + @Override + public BoundStatement build() { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 0ec1c842a8c..34919f18131 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -22,8 +22,7 @@ * A query with bind variables that has been pre-parsed by the database. * *

      Client applications create instances with {@link Session#prepare(SimpleStatement)}. Then they - * use {@link #bind()} to obtain a {@link BoundStatement}, an executable instance that associates a - * set of values with the bind variables. + * use {@link #bind(Object...)} to obtain an executable {@link BoundStatement}. * *

      The default prepared statement implementation returned by the driver is thread-safe. * Client applications can -- and are expected to -- prepare each query once and store the result in @@ -51,5 +50,25 @@ public interface PreparedStatement { */ ColumnDefinitions getResultSetDefinitions(); - BoundStatement bind(); + /** + * Builds an executable statement that associates a set of values with the bind variables. + * + *

      You can provide less values than the actual number of variables (or even none at all), in + * which case the remaining variables will be left unset. However, this method will throw an + * {@link IllegalArgumentException} if there are more values than variables. + * + *

      Note that the built-in bound statement implementation is immutable. If you need to set + * multiple execution parameters on the bound statement (such as {@link + * BoundStatement#setConfigProfileName(String)}, {@link + * BoundStatement#setPagingState(ByteBuffer)}, etc.), consider using {@link + * #boundStatementBuilder()} instead to avoid unnecessary allocations. + */ + BoundStatement bind(Object... values); + + /** + * Returns a builder to construct an executable statement. + * + * @see #bind(Object...) + */ + BoundStatementBuilder boundStatementBuilder(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 7b1e515854a..a0bd38937ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -36,55 +36,14 @@ * server to return metadata about the bind variables, which allows the driver to validate the * values earlier, and apply certain optimizations like token-aware routing. * - *

      The default simple statement implementation returned by the driver is immutable and - * thread-safe. If an application is going to reuse the same statement more than once, it is - * recommended to cache it (for example in a final field). + *

      The default implementation returned by the driver is immutable and thread-safe. + * All mutating methods return a new instance. See also the static factory methods and builders in + * this interface. + * + *

      If an application reuses the same statement more than once, it is recommended to cache it (for + * example in a final field). */ -public interface SimpleStatement extends BatchableStatement { - - /** - * The CQL query to execute. - * - *

      It may contain anonymous placeholders identified by a question mark, as in: - * - *

      -   *   SELECT username FROM user WHERE id = ?
      -   * 
      - * - * Or named placeholders prefixed by a column, as in: - * - *
      -   *   SELECT username FROM user WHERE id = :i
      -   * 
      - * - * @see #getPositionalValues() - * @see #getNamedValues() - */ - String getQuery(); - - /** - * A list of positional values to bind to anonymous placeholders. - * - *

      You can use either positional or named values, but not both. Therefore if this method - * returns a non-empty list, then {@link #getNamedValues()} must return an empty map, otherwise a - * runtime error will be thrown. - * - * @see #getQuery() - */ - List getPositionalValues(); - - /** - * A list of named values to bind to named placeholders. - * - *

      Names must be stripped of the leading column. - * - *

      You can use either positional or named values, but not both. Therefore if this method - * returns a non-empty map, then {@link #getPositionalValues()} must return an empty list, - * otherwise a runtime error will be thrown. - * - * @see #getQuery() - */ - Map getNamedValues(); +public interface SimpleStatement extends BatchableStatement { /** * Shortcut to create an instance of the default implementation with only a CQL query (see {@link @@ -144,4 +103,71 @@ static SimpleStatement newInstance(String cqlQuery, Map namedVal static SimpleStatementBuilder builder(String query) { return new SimpleStatementBuilder(query); } + + /** + * Returns a builder to create an instance of the default implementation, copying the fields of + * the given statement. + */ + static SimpleStatementBuilder builder(SimpleStatement template) { + return new SimpleStatementBuilder(template); + } + + String getQuery(); + + /** + * Sets the CQL query to execute. + * + *

      It may contain anonymous placeholders identified by a question mark, as in: + * + *

      +   *   SELECT username FROM user WHERE id = ?
      +   * 
      + * + * Or named placeholders prefixed by a column, as in: + * + *
      +   *   SELECT username FROM user WHERE id = :i
      +   * 
      + * + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + * + * @see #setPositionalValues(List) + * @see #setNamedValues(Map) + */ + SimpleStatement setQuery(String newQuery); + + List getPositionalValues(); + + /** + * Sets the positional values to bind to anonymous placeholders. + * + *

      You can use either positional or named values, but not both. Therefore if this method + * returns a non-empty list, then {@link #getNamedValues()} must return an empty map, otherwise a + * runtime error will be thrown. + * + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + * + * @see #setQuery(String) + */ + SimpleStatement setPositionalValues(List newPositionalValues); + + Map getNamedValues(); + + /** + * Sets the named values to bind to named placeholders. + * + *

      Names must be stripped of the leading column. + * + *

      You can use either positional or named values, but not both. Therefore if this method + * returns a non-empty map, then {@link #getPositionalValues()} must return an empty list, + * otherwise a runtime error will be thrown. + * + *

      The driver's built-in implementation is immutable, and returns a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + * + * @see #setQuery(String) + */ + SimpleStatement setNamedValues(Map newNamedValues); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 0185bcab5d4..56133eb22cc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -15,181 +15,113 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.nio.ByteBuffer; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; -public class SimpleStatementBuilder { +public class SimpleStatementBuilder + extends StatementBuilder { - private final String query; - private List positionalValues = Collections.emptyList(); - private ImmutableMap.Builder namedValues; - private String configProfileName; - private DriverConfigProfile configProfile; - private ImmutableMap.Builder customPayload; - private Boolean idempotent; - private boolean tracing; - private long timestamp = Long.MIN_VALUE; - private ByteBuffer pagingState; + private String query; + private ImmutableList.Builder positionalValuesBuilder; + private ImmutableMap.Builder namedValuesBuilder; public SimpleStatementBuilder(String query) { this.query = query; } - /** - * Adds a value for an anonymous placeholder. - * - *

      Values must be added in the order they appear in the query string. You can also use {@link - * #withPositionalValues(Object...)} to add multiple values at once. - * - * @throws IllegalArgumentException if named values were already added for this statement; you - * can't mix both. - * @see SimpleStatement#getPositionalValues() - */ - public SimpleStatementBuilder withPositionalValue(Object value) { - return withPositionalValues(value); - } - - /** - * Adds values for multiple anonymous placeholders. - * - *

      Values must be added in the order they appear in the query string. - * - * @throws IllegalArgumentException if named values were already added for this statement; you - * can't mix both. - * @see SimpleStatement#getPositionalValues() - */ - public SimpleStatementBuilder withPositionalValues(Object... values) { - if (namedValues != null) { + public SimpleStatementBuilder(SimpleStatement template) { + super(template); + if (!template.getPositionalValues().isEmpty() && !template.getNamedValues().isEmpty()) { throw new IllegalArgumentException( - "Can't have both positional and named values in a statement."); + "Illegal statement to copy, can't have both named and positional values"); } - if (positionalValues.isEmpty()) { - positionalValues = new ArrayList<>(); - } - Collections.addAll(positionalValues, values); - return this; - } - /** - * Adds a value for a named placeholder in the query string. - * - * @throws IllegalArgumentException if positional values were already added for this statement; - * you can't mix both. - * @see SimpleStatement#getPositionalValues() - */ - public SimpleStatementBuilder withNamedValue(String name, Object value) { - if (!positionalValues.isEmpty()) { - throw new IllegalArgumentException( - "Can't have both positional and named values in a statement."); + this.query = template.getQuery(); + if (!template.getPositionalValues().isEmpty()) { + this.positionalValuesBuilder = ImmutableList.builder().addAll(template.getPositionalValues()); } - if (namedValues == null) { - namedValues = ImmutableMap.builder(); + if (!template.getNamedValues().isEmpty()) { + this.namedValuesBuilder = + ImmutableMap.builder().putAll(template.getNamedValues()); } - namedValues.put(name, value); - return this; } - /** - * Sets the name of the config profile to use for this statement. - * - *

      Note that {@link #withConfigProfile(DriverConfigProfile)} will override this. - * - * @see SimpleStatement#getConfigProfileName() - */ - public SimpleStatementBuilder withConfigProfileName(String configProfileName) { - this.configProfileName = configProfileName; + public SimpleStatementBuilder withQuery(String query) { + this.query = query; return this; } - /** - * Sets the config profile to use for this statement. - * - * @see SimpleStatement#getConfigProfile() - */ - public SimpleStatementBuilder withConfigProfile(DriverConfigProfile configProfile) { - this.configProfile = configProfile; - this.configProfileName = null; + /** @see SimpleStatement#setPositionalValues(List) */ + public SimpleStatementBuilder addPositionalValue(Object value) { + if (namedValuesBuilder != null) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); + } + if (positionalValuesBuilder == null) { + positionalValuesBuilder = ImmutableList.builder(); + } + positionalValuesBuilder.add(value); return this; } - /** - * Adds an entry in the custom payload of this statement. - * - * @see SimpleStatement#getCustomPayload() - */ - public SimpleStatementBuilder withCustomPayload(String key, ByteBuffer value) { - if (customPayload == null) { - customPayload = ImmutableMap.builder(); + /** @see SimpleStatement#setPositionalValues(List) */ + public SimpleStatementBuilder addPositionalValues(Iterable values) { + if (namedValuesBuilder != null) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); + } + if (positionalValuesBuilder == null) { + positionalValuesBuilder = ImmutableList.builder(); } - customPayload.put(key, value); + positionalValuesBuilder.addAll(values); return this; } - /** - * Indicates whether this statement is idempotent. - * - *

      If this method is not called, the statement will fallback to the default defined in the - * configuration. - * - * @see SimpleStatement#isIdempotent() - */ - public SimpleStatementBuilder withIdempotence(boolean idempotent) { - this.idempotent = idempotent; - return this; + /** @see SimpleStatement#setPositionalValues(List) */ + public SimpleStatementBuilder addPositionalValues(Object... values) { + return addPositionalValues(Arrays.asList(values)); } - /** - * Indicates that tracing information should be requested when executing this statement. - * - *

      If this method is not called, the statement will not be tracing. - * - * @see SimpleStatement#isTracing() - */ - public SimpleStatementBuilder withTracing() { - this.tracing = true; + /** @see SimpleStatement#setPositionalValues(List) */ + public SimpleStatementBuilder clearPositionalValues() { + positionalValuesBuilder = null; return this; } - /** - * Sets the timestamp to use for this statement. - * - *

      If this method is not called, the {@link TimestampGenerator} will assign it when the - * statement gets executed. - * - * @see Statement#getTimestamp() - */ - public SimpleStatementBuilder withTimestamp(long timestamp) { - this.timestamp = timestamp; + /** @see SimpleStatement#setNamedValues(Map) */ + public SimpleStatementBuilder addNamedValue(String name, Object value) { + if (positionalValuesBuilder != null) { + throw new IllegalArgumentException( + "Can't have both positional and named values in a statement."); + } + if (namedValuesBuilder == null) { + namedValuesBuilder = ImmutableMap.builder(); + } + namedValuesBuilder.put(name, value); return this; } - /** - * Sets the paging state to use for this statement. - * - *

      Note that it is also possible to set the paging state on an existing statement with {@link - * Statement#copy(ByteBuffer)}. - * - * @see Statement#getPagingState() - */ - public SimpleStatementBuilder withPagingState(ByteBuffer pagingState) { - this.pagingState = pagingState; + /** @see SimpleStatement#setNamedValues(Map) */ + public SimpleStatementBuilder clearNamedValues() { + namedValuesBuilder = null; return this; } public SimpleStatement build() { return new DefaultSimpleStatement( query, - positionalValues, - (namedValues == null) ? Collections.emptyMap() : namedValues.build(), + (positionalValuesBuilder == null) + ? Collections.emptyList() + : positionalValuesBuilder.build(), + (namedValuesBuilder == null) ? Collections.emptyMap() : namedValuesBuilder.build(), configProfileName, configProfile, - (customPayload == null) ? Collections.emptyMap() : customPayload.build(), + (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), idempotent, tracing, timestamp, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 590ae1437f7..01bb0a22b23 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -15,30 +15,113 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.CompletionStage; -/** A request to execute a CQL query. */ -public interface Statement extends Request> { +/** + * A request to execute a CQL query. + * + * @param the "self type" used for covariant returns in subtypes. + */ +public interface Statement> + extends Request> { // Implementation note: "CqlRequest" would be a better name, but we keep "Statement" to match // previous driver versions. /** - * The query timestamp to send with the statement. + * Sets the name of the driver configuration profile that will be used for execution. + * + *

      For all the driver's built-in implementations, this method has no effect if {@link + * #getConfigProfile()} has been called with a non-null argument. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + */ + T setConfigProfileName(String newConfigProfileName); + + /** + * Sets the configuration profile to use for execution. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + */ + T setConfigProfile(DriverConfigProfile newProfile); + + /** + * NOT YET SUPPORTED -- sets the CQL keyspace to associate with the query. + * + *

      This will be available when CASSANDRA-10145 is merged in a + * stable server release. In the meantime, this method always throws {@link + * UnsupportedOperationException}. + */ + default T setKeyspace(String newKeyspace) { + throw new UnsupportedOperationException("Per-query keyspaces are not yet supported"); + } + + /** + * Sets the custom payload to use for execution. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + */ + T setCustomPayload(Map newCustomPayload); + + /** + * Sets the idempotence to use for execution. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + * + * @param newIdempotence a boolean instance to set a statement-specific value, or {@code null} to + * use the default idempotence defined in the configuration. + */ + T setIdempotent(Boolean newIdempotence); + + /** + * Sets tracing for execution. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. + */ + T setTracing(boolean newTracing); + + long getTimestamp(); + + /** + * Sets the query timestamp to send with the statement. * *

      If this is equal to {@link Long#MIN_VALUE}, the {@link TimestampGenerator} configured for * this driver instance will be used to generate a timestamp. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance. */ - long getTimestamp(); + T setTimestamp(long newTimestamp); ByteBuffer getPagingState(); - /** Creates a new statement with a different paging state. */ - // Implementation note: this is a simplistic first draft in order to move forward with paging, - // however more thought is needed about statement attributes: which belong to the API and which - // belong to DriverConfig, how you override them for a specific statement, whether statement - // implementations are immutable, etc. - Statement copy(ByteBuffer newPagingState); + /** + * Sets the paging state to send with the statement. + * + *

      All the driver's built-in implementations are immutable, and return a new instance from this + * method. However custom implementations may choose to be mutable and return the same instance; + * if you do so, you must override {@link #copy(ByteBuffer)}. + */ + T setPagingState(ByteBuffer newPagingState); + + /** + * Creates a new instance with a different paging state. + * + *

      Since all the built-in statement implementations in the driver are immutable, this method's + * default implementation delegates to {@link #setPagingState(ByteBuffer)}. However, if you write + * your own mutable implementation, make sure it returns a different instance. + */ + default T copy(ByteBuffer newPagingState) { + return setPagingState(newPagingState); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java new file mode 100644 index 00000000000..2038636837d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.google.common.collect.ImmutableMap; +import java.nio.ByteBuffer; +import java.util.Map; + +public abstract class StatementBuilder, S extends Statement> { + + @SuppressWarnings("unchecked") + private final T self = (T) this; + + protected String configProfileName; + protected DriverConfigProfile configProfile; + protected String keyspace; + protected ImmutableMap.Builder customPayloadBuilder; + protected Boolean idempotent; + protected boolean tracing; + protected long timestamp = Long.MIN_VALUE; + protected ByteBuffer pagingState; + + protected StatementBuilder() { + // nothing to do + } + + protected StatementBuilder(S template) { + this.configProfileName = template.getConfigProfileName(); + this.configProfile = template.getConfigProfile(); + this.customPayloadBuilder = + ImmutableMap.builder().putAll(template.getCustomPayload()); + this.idempotent = template.isIdempotent(); + this.tracing = template.isTracing(); + this.timestamp = template.getTimestamp(); + this.pagingState = template.getPagingState(); + } + + /** @see Statement#setConfigProfileName(String) */ + public T withConfigProfileName(String configProfileName) { + this.configProfileName = configProfileName; + return self; + } + + /** @see Statement#setConfigProfile(DriverConfigProfile) */ + public T withConfigProfile(DriverConfigProfile configProfile) { + this.configProfile = configProfile; + this.configProfileName = null; + return self; + } + + /** @see Statement#setCustomPayload(Map) */ + public T addCustomPayload(String key, ByteBuffer value) { + if (customPayloadBuilder == null) { + customPayloadBuilder = ImmutableMap.builder(); + } + customPayloadBuilder.put(key, value); + return self; + } + + /** @see Statement#setCustomPayload(Map) */ + public T clearCustomPayload() { + customPayloadBuilder = null; + return self; + } + + /** @see Statement#setIdempotent(Boolean) */ + public T withIdempotence(boolean idempotent) { + this.idempotent = idempotent; + return self; + } + + /** @see Statement#setTracing(boolean) */ + public T withTracing() { + this.tracing = true; + return self; + } + + /** @see Statement#setTimestamp(long) */ + public T withTimestamp(long timestamp) { + this.timestamp = timestamp; + return self; + } + + /** @see Statement#setPagingState(ByteBuffer) */ + public T withPagingState(ByteBuffer pagingState) { + this.pagingState = pagingState; + return self; + } + + public abstract S build(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 4c4e8d235c0..c228c83445f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -77,7 +77,7 @@ public interface Session extends AsyncAutoCloseable { *

      This is a convenience method that does the exact same thing as {@link #execute(Request)}, * but exposes a more user-friendly signature reminiscent of the 3.x API. */ - default ResultSet execute(Statement statement) { + default ResultSet execute(Statement statement) { return execute((Request>) statement); } @@ -98,7 +98,7 @@ default ResultSet execute(String query) { * #executeAsync(Statement)}, but exposes a more user-friendly signature reminiscent of the 3.x * API. */ - default CompletionStage executeAsync(Statement statement) { + default CompletionStage executeAsync(Statement statement) { return executeAsync((Request>) statement); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 3c6cc54712e..5e64e09a505 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -140,8 +140,6 @@ static Message toMessage( if (child instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) child; if (simpleStatement.getNamedValues().size() > 0) { - // We already check that in DefaultBatchStatement.add, but we could receive a custom - // implementation throw new IllegalArgumentException( String.format( "Batch statements cannot contain simple statements with named values " diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 737e3ee8aa7..9762d9126b9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -19,10 +19,9 @@ import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchType; import com.datastax.oss.driver.api.core.cql.BatchableStatement; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -30,33 +29,19 @@ public class DefaultBatchStatement implements BatchStatement { private final BatchType batchType; - private final List statements; - private String configProfileName; - private DriverConfigProfile configProfile; + private final List> statements; + private final String configProfileName; + private final DriverConfigProfile configProfile; private final String keyspace; - private Map customPayload; - private Boolean idempotent; - private boolean tracing; - private long timestamp; - private ByteBuffer pagingState; - - public DefaultBatchStatement(BatchType batchType) { - this( - batchType, - new ArrayList<>(), - null, - null, - null, - Collections.emptyMap(), - false, - false, - Long.MIN_VALUE, - null); - } - - private DefaultBatchStatement( + private final Map customPayload; + private final Boolean idempotent; + private final boolean tracing; + private final long timestamp; + private final ByteBuffer pagingState; + + public DefaultBatchStatement( BatchType batchType, - List statements, + List> statements, String configProfileName, DriverConfigProfile configProfile, String keyspace, @@ -66,7 +51,7 @@ private DefaultBatchStatement( long timestamp, ByteBuffer pagingState) { this.batchType = batchType; - this.statements = statements; + this.statements = ImmutableList.copyOf(statements); this.configProfileName = configProfileName; this.configProfile = configProfile; this.keyspace = keyspace; @@ -83,20 +68,60 @@ public BatchType getBatchType() { } @Override - public BatchStatement add(BatchableStatement statement) { + public BatchStatement setBatchType(BatchType newBatchType) { + return new DefaultBatchStatement( + newBatchType, + statements, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + + @Override + public BatchStatement add(BatchableStatement statement) { if (statements.size() >= 0xFFFF) { throw new IllegalStateException( "Batch statement cannot contain more than " + 0xFFFF + " statements."); - } else if (statement instanceof SimpleStatement - && ((SimpleStatement) statement).getNamedValues().size() > 0) { - throw new IllegalArgumentException( - String.format( - "Batch statements cannot contain simple statements with named values " - + "(offending statement: %s)", - ((SimpleStatement) statement).getQuery())); } else { - statements.add(statement); - return this; + return new DefaultBatchStatement( + batchType, + ImmutableList.>builder().addAll(statements).add(statement).build(), + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + } + + @Override + public BatchStatement addAll(Iterable> newStatements) { + if (statements.size() + Iterables.size(newStatements) > 0xFFFF) { + throw new IllegalStateException( + "Batch statement cannot contain more than " + 0xFFFF + " statements."); + } else { + return new DefaultBatchStatement( + batchType, + ImmutableList.>builder() + .addAll(statements) + .addAll(newStatements) + .build(), + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } } @@ -107,12 +132,21 @@ public int size() { @Override public BatchStatement clear() { - statements.clear(); - return this; + return new DefaultBatchStatement( + batchType, + ImmutableList.of(), + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override - public Iterator iterator() { + public Iterator> iterator() { return statements.iterator(); } @@ -122,10 +156,10 @@ public ByteBuffer getPagingState() { } @Override - public BatchStatement copy(ByteBuffer newPagingState) { + public BatchStatement setPagingState(ByteBuffer newPagingState) { return new DefaultBatchStatement( batchType, - new ArrayList<>(statements), + statements, configProfileName, configProfile, keyspace, @@ -142,9 +176,18 @@ public String getConfigProfileName() { } @Override - public BatchStatement setConfigProfileName(String configProfileName) { - this.configProfileName = configProfileName; - return this; + public BatchStatement setConfigProfileName(String newConfigProfileName) { + return new DefaultBatchStatement( + batchType, + statements, + newConfigProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override @@ -153,9 +196,18 @@ public DriverConfigProfile getConfigProfile() { } @Override - public DefaultBatchStatement setConfigProfile(DriverConfigProfile configProfile) { - this.configProfile = configProfile; - return this; + public DefaultBatchStatement setConfigProfile(DriverConfigProfile newProfile) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + newProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override @@ -163,6 +215,7 @@ public String getKeyspace() { return keyspace; } + @Override public DefaultBatchStatement setKeyspace(@SuppressWarnings("unused") String keyspace) { throw new UnsupportedOperationException( "Per-statement keyspaces are not supported currently, " @@ -175,9 +228,18 @@ public Map getCustomPayload() { } @Override - public DefaultBatchStatement setCustomPayload(Map customPayload) { - this.customPayload = customPayload; - return this; + public DefaultBatchStatement setCustomPayload(Map newCustomPayload) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + newCustomPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override @@ -186,9 +248,18 @@ public Boolean isIdempotent() { } @Override - public DefaultBatchStatement setIdempotent(Boolean idempotent) { - this.idempotent = idempotent; - return this; + public DefaultBatchStatement setIdempotent(Boolean newIdempotence) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + customPayload, + newIdempotence, + tracing, + timestamp, + pagingState); } @Override @@ -197,9 +268,18 @@ public boolean isTracing() { } @Override - public BatchStatement setTracing() { - this.tracing = true; - return this; + public BatchStatement setTracing(boolean newTracing) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + newTracing, + timestamp, + pagingState); } @Override @@ -208,8 +288,17 @@ public long getTimestamp() { } @Override - public BatchStatement setTimestamp(long timestamp) { - this.timestamp = timestamp; - return this; + public BatchStatement setTimestamp(long newTimestamp) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + newTimestamp, + pagingState); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 23edd0a5f14..539c3816395 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -20,51 +20,55 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; -import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import java.nio.ByteBuffer; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; public class DefaultBoundStatement implements BoundStatement { - private final DefaultPreparedStatement preparedStatement; + private final PreparedStatement preparedStatement; private final ColumnDefinitions variableDefinitions; - private final List values; - private String configProfileName; - private DriverConfigProfile configProfile; + private final ByteBuffer[] values; + private final String configProfileName; + private final DriverConfigProfile configProfile; private final String keyspace; - private Map customPayload; - private Boolean idempotent; + private final Map customPayload; + private final Boolean idempotent; + private final boolean tracing; + private final long timestamp; + private final ByteBuffer pagingState; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; - private boolean tracing; - private long timestamp = Long.MIN_VALUE; - private ByteBuffer pagingState; public DefaultBoundStatement( - DefaultPreparedStatement preparedStatement, + PreparedStatement preparedStatement, ColumnDefinitions variableDefinitions, + ByteBuffer[] values, String configProfileName, DriverConfigProfile configProfile, String keyspace, Map customPayload, Boolean idempotent, + boolean tracing, + long timestamp, + ByteBuffer pagingState, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { this.preparedStatement = preparedStatement; this.variableDefinitions = variableDefinitions; - this.values = new ArrayList<>(variableDefinitions.size()); - for (int i = 0; i < variableDefinitions.size(); i++) { - this.values.add(null); - } + this.values = values; this.configProfileName = configProfileName; this.configProfile = configProfile; this.keyspace = keyspace; this.customPayload = customPayload; this.idempotent = idempotent; + this.tracing = tracing; + this.timestamp = timestamp; + this.pagingState = pagingState; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; } @@ -101,23 +105,37 @@ public ProtocolVersion protocolVersion() { @Override public ByteBuffer getBytesUnsafe(int i) { - return values.get(i); + return values[i]; } @Override public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { - values.set(i, v); - return this; + ByteBuffer[] newValues = Arrays.copyOf(values, values.length); + newValues[i] = v; + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + newValues, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override - public DefaultPreparedStatement getPreparedStatement() { + public PreparedStatement getPreparedStatement() { return preparedStatement; } @Override public List getValues() { - return values; + return Arrays.asList(values); } @Override @@ -126,9 +144,21 @@ public String getConfigProfileName() { } @Override - public BoundStatement setConfigProfileName(String configProfileName) { - this.configProfileName = configProfileName; - return this; + public BoundStatement setConfigProfileName(String newConfigProfileName) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + newConfigProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -137,9 +167,21 @@ public DriverConfigProfile getConfigProfile() { } @Override - public BoundStatement setConfigProfile(DriverConfigProfile configProfile) { - this.configProfile = configProfile; - return this; + public BoundStatement setConfigProfile(DriverConfigProfile newConfigProfile) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + newConfigProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -153,9 +195,21 @@ public Map getCustomPayload() { } @Override - public BoundStatement setCustomPayload(Map customPayload) { - this.customPayload = customPayload; - return this; + public BoundStatement setCustomPayload(Map newCustomPayload) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + newCustomPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -164,9 +218,21 @@ public Boolean isIdempotent() { } @Override - public BoundStatement setIdempotent(Boolean idempotent) { - this.idempotent = idempotent; - return this; + public BoundStatement setIdempotent(Boolean newIdempotence) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + customPayload, + newIdempotence, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -175,9 +241,21 @@ public boolean isTracing() { } @Override - public BoundStatement setTracing() { - this.tracing = true; - return this; + public BoundStatement setTracing(boolean newTracing) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + newTracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -186,9 +264,21 @@ public long getTimestamp() { } @Override - public BoundStatement setTimestamp(long timestamp) { - this.timestamp = timestamp; - return this; + public BoundStatement setTimestamp(long newTimestamp) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + newTimestamp, + pagingState, + codecRegistry, + protocolVersion); } @Override @@ -197,8 +287,20 @@ public ByteBuffer getPagingState() { } @Override - public Statement copy(ByteBuffer newPagingState) { - this.pagingState = newPagingState; - return this; + public BoundStatement setPagingState(ByteBuffer newPagingState) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + customPayload, + idempotent, + tracing, + timestamp, + newPagingState, + codecRegistry, + protocolVersion); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 1007087b93f..118c7ef758a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -18,10 +18,13 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.session.RepreparePayload; +import com.datastax.oss.protocol.internal.ProtocolConstants; import java.nio.ByteBuffer; import java.util.Map; @@ -87,19 +90,43 @@ public ColumnDefinitions getResultSetDefinitions() { } @Override - public BoundStatement bind() { + public BoundStatement bind(Object... values) { + if (values.length > variableDefinitions.size()) { + throw new IllegalArgumentException( + String.format( + "Too many variables (expected %d, got %d)", + variableDefinitions.size(), values.length)); + } + ByteBuffer[] encodedValues = new ByteBuffer[variableDefinitions.size()]; + int i; + for (i = 0; i < values.length; i++) { + TypeCodec codec = codecRegistry.codecFor(variableDefinitions.get(i).getType()); + encodedValues[i] = codec.encode(values[i], protocolVersion); + } + for (; i < encodedValues.length; i++) { + encodedValues[i] = ProtocolConstants.UNSET_VALUE; + } return new DefaultBoundStatement( this, variableDefinitions, + encodedValues, configProfileName, configProfile, repreparePayload.keyspace, customPayloadForBoundStatements, idempotent, + false, + Long.MIN_VALUE, + null, codecRegistry, protocolVersion); } + @Override + public BoundStatementBuilder boundStatementBuilder() { + return new BoundStatementBuilder(this, variableDefinitions, codecRegistry, protocolVersion); + } + public RepreparePayload getRepreparePayload() { return this.repreparePayload; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 8af166ef3da..def5d45ff0c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -17,8 +17,9 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -35,34 +36,6 @@ public class DefaultSimpleStatement implements SimpleStatement { private final long timestamp; private final ByteBuffer pagingState; - public DefaultSimpleStatement(String query, List positionalValues) { - this( - query, - positionalValues, - Collections.emptyMap(), - null, - null, - Collections.emptyMap(), - null, - false, - Long.MIN_VALUE, - null); - } - - public DefaultSimpleStatement(String query, Map namedValues) { - this( - query, - Collections.emptyList(), - namedValues, - null, - null, - Collections.emptyMap(), - null, - false, - Long.MIN_VALUE, - null); - } - /** @see SimpleStatement#builder(String) */ public DefaultSimpleStatement( String query, @@ -75,9 +48,12 @@ public DefaultSimpleStatement( boolean tracing, long timestamp, ByteBuffer pagingState) { + if (!positionalValues.isEmpty() && !namedValues.isEmpty()) { + throw new IllegalArgumentException("Can't have both positional and named values"); + } this.query = query; - this.positionalValues = positionalValues; - this.namedValues = namedValues; + this.positionalValues = ImmutableList.copyOf(positionalValues); + this.namedValues = ImmutableMap.copyOf(namedValues); this.configProfileName = configProfileName; this.configProfile = configProfile; this.customPayload = customPayload; @@ -92,26 +68,101 @@ public String getQuery() { return query; } + @Override + public SimpleStatement setQuery(String newQuery) { + return new DefaultSimpleStatement( + newQuery, + positionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public List getPositionalValues() { return positionalValues; } + @Override + public SimpleStatement setPositionalValues(List newPositionalValues) { + return new DefaultSimpleStatement( + query, + newPositionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public Map getNamedValues() { return namedValues; } + @Override + public SimpleStatement setNamedValues(Map newNamedValues) { + return new DefaultSimpleStatement( + query, + positionalValues, + newNamedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public String getConfigProfileName() { return configProfileName; } + @Override + public SimpleStatement setConfigProfileName(String newConfigProfileName) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + newConfigProfileName, + configProfile, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public DriverConfigProfile getConfigProfile() { return configProfile; } + @Override + public SimpleStatement setConfigProfile(DriverConfigProfile newProfile) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + null, + newProfile, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public String getKeyspace() { // Not implemented yet, waiting for CASSANDRA-10145 to land in a release @@ -123,28 +174,88 @@ public Map getCustomPayload() { return customPayload; } + @Override + public SimpleStatement setCustomPayload(Map newCustomPayload) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + newCustomPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public Boolean isIdempotent() { return idempotent; } + @Override + public SimpleStatement setIdempotent(Boolean newIdempotence) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + newIdempotence, + tracing, + timestamp, + pagingState); + } + @Override public boolean isTracing() { return tracing; } + @Override + public SimpleStatement setTracing(boolean newTracing) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + newTracing, + timestamp, + pagingState); + } + @Override public long getTimestamp() { return timestamp; } + @Override + public SimpleStatement setTimestamp(long newTimestamp) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + customPayload, + idempotent, + tracing, + newTimestamp, + pagingState); + } + @Override public ByteBuffer getPagingState() { return pagingState; } @Override - public DefaultSimpleStatement copy(ByteBuffer newPagingState) { + public SimpleStatement setPagingState(ByteBuffer newPagingState) { return new DefaultSimpleStatement( query, positionalValues, diff --git a/faq/README.md b/faq/README.md new file mode 100644 index 00000000000..ee64b6ce08f --- /dev/null +++ b/faq/README.md @@ -0,0 +1,17 @@ +## Frequently asked questions + +### I'm modifying a statement and the changes get ignored, why? + +In driver 4, statement classes are immutable. All mutating methods return a new instance, so make +sure you don't accidentally ignore their result: + +```java +BoundStatement boundSelect = preparedSelect.bind(); + +// This doesn't work: setInt doesn't modify boundSelect in place: +boundSelect.setInt("k", key); +session.execute(boundSelect); + +// Instead, do this: +boundSelect = boundSelect.setInt("k", key); +``` \ No newline at end of file diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 068911b7fe3..356ef769168 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -41,6 +41,29 @@ client code (e.g. to wrap them and write delegates). Thanks to Java 8, factory methods can now be part of these interfaces directly, e.g. `Cluster.builder()`, `SimpleStatement.newInstance`. +#### Immutable statement types + +Simple, bound and batch statements implementations are now all immutable. This makes them +automatically thread-safe: you don't need to worry anymore about sharing them or reusing them +between asynchronous executions. + +One word of warning -- all mutating methods return a new instance, so make sure you don't +accidentally ignore their result: + +```java +BoundStatement boundSelect = preparedSelect.bind(); + +// This doesn't work: setInt doesn't modify boundSelect in place: +boundSelect.setInt("k", key); +session.execute(boundSelect); + +// Instead, do this: +boundSelect = boundSelect.setInt("k", key); +``` + +Note that, as indicated in the previous section, the public API exposes these types as interfaces: +if for some reason you prefer a mutable implementation, it's possible to write your own. + #### Generic session API `Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, From 06b37b3a8f04a13ec0b4b1d00e4742b26397c62d Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 20 Jul 2017 16:50:57 -0500 Subject: [PATCH 142/742] JAVA-1562: Fix various issues around heart beats Fixes the following issues around heart beats: - Repeated exception loop when connection is closed while waiting for response for heart beat. - Heart beats were being sent even if connection is actively reading responses. - Heart beats continue to get sent after channel starts closing. - Heart beats can be sent before protocol initialization completes. Also fixes an issue where DriverChannel.cancel() was short circuiting during graceful shutdown, preventing InFlightHandler from completing cancelled callbacks. --- changelog/README.md | 1 + .../internal/core/channel/ChannelFactory.java | 11 +-- .../core/channel/ConnectInitHandler.java | 6 +- .../internal/core/channel/DriverChannel.java | 3 - .../core/channel/HeartbeatHandler.java | 13 ++- .../core/channel/InFlightHandler.java | 16 ++- .../core/channel/ProtocolInitHandler.java | 17 +++- .../core/channel/ChannelFactoryTestBase.java | 7 +- .../core/channel/ProtocolInitHandlerTest.java | 97 +++++++++++++++++-- 9 files changed, 142 insertions(+), 29 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index def408c71f0..e362937d6b9 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1562: Fix various issues around heart beats - [improvement] JAVA-1546: Make all statement implementations immutable - [bug] JAVA-1554: Include VIEW and CDC in WriteType - [improvement] JAVA-1498: Add a cache above Typesafe config diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 4c2be72d663..a19cf415696 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -31,7 +31,6 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import io.netty.channel.ChannelPromise; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -189,8 +188,6 @@ protected void initChannel(Channel channel) throws Exception { int maxOrphanRequests = defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); - ChannelPromise closeStartedFuture = channel.newPromise(); - InFlightHandler inFlightHandler = new InFlightHandler( protocolVersion, @@ -198,11 +195,13 @@ protected void initChannel(Channel channel) throws Exception { maxOrphanRequests, setKeyspaceTimeoutMillis, availableIdsHolder, - closeStartedFuture, + channel.newPromise(), options.eventCallback, options.ownerLogPrefix); + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, options); + new ProtocolInitHandler( + context, protocolVersion, clusterName, options, heartbeatHandler); ChannelPipeline pipeline = channel.pipeline(); context @@ -212,8 +211,8 @@ protected void initChannel(Channel channel) throws Exception { pipeline .addLast("encoder", new FrameEncoder(context.frameCodec())) .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) + // Note: HeartbeatHandler is inserted here once init completes .addLast("inflight", inFlightHandler) - .addLast("heartbeat", new HeartbeatHandler(defaultConfigProfile)) .addLast("init", initHandler); context.nettyOptions().afterChannelInitialized(channel); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java index ab3e4b733c7..02c9904d46f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandler.java @@ -63,10 +63,12 @@ public void connect( protected abstract void onRealConnect(ChannelHandlerContext ctx); - protected void setConnectSuccess() { - if (initPromise.trySuccess()) { + protected boolean setConnectSuccess() { + boolean result = initPromise.trySuccess(); + if (result) { ctx.pipeline().remove(this); } + return result; } protected void setConnectFailure(Throwable cause) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 8a427077771..77e33b67c1e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -86,9 +86,6 @@ public Future write( * supports it). */ public void cancel(ResponseCallback responseCallback) { - if (closing.get()) { - throw new IllegalStateException("Driver channel is closing"); - } // To avoid creating an extra message, we adopt the convention that writing the callback // directly means cancellation writeCoalescer.writeAndFlush(channel, responseCallback); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index ce106849807..401bbd1976b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -105,10 +105,17 @@ void fail(String message, Throwable cause) { // all queries (including the heartbeat query itself) return; } - LOG.debug(ctx.channel().toString() + " Heartbeat query failed: " + message, cause); - // Notify InFlightHandler (fireExceptionCaught wouldn't work because the error has to go downstream) - ctx.write(new HeartbeatException(ctx.channel().remoteAddress(), message, cause)); + HeartbeatHandler.this.request = null; + if (message != null) { + LOG.debug("{} Heartbeat query failed: {}", ctx.channel(), message, cause); + } else { + LOG.debug("{} Heartbeat query failed", ctx.channel(), cause); + } + + // Notify InFlightHandler. + ctx.fireExceptionCaught( + new HeartbeatException(ctx.channel().remoteAddress(), message, cause)); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index c351e13d729..e78b5c39242 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; @@ -32,6 +33,7 @@ import com.google.common.collect.HashBiMap; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Promise; @@ -184,6 +186,11 @@ private void startGracefulShutdown(ChannelHandlerContext ctx) { LOG.debug("[{}] No pending queries, completing graceful shutdown now", logPrefix); ctx.channel().close(); } else { + // remove heartbeat handler from pipeline if present. + ChannelHandler heartbeatHandler = ctx.pipeline().get("heartbeat"); + if (heartbeatHandler != null) { + ctx.pipeline().remove(heartbeatHandler); + } LOG.debug("[{}] There are pending queries, delaying graceful shutdown", logPrefix); closingGracefully = true; closeStartedFuture.setSuccess(); @@ -253,7 +260,10 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } } else { // Otherwise fail all pending requests - abortAllInFlight(new ClosedConnectionException("Unexpected error on channel", cause)); + abortAllInFlight( + (cause instanceof HeartbeatException) + ? (HeartbeatException) cause + : new ClosedConnectionException("Unexpected error on channel", cause)); ctx.close(); } } @@ -304,7 +314,7 @@ private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { return responseCallback; } - private void abortAllInFlight(ClosedConnectionException cause) { + private void abortAllInFlight(DriverException cause) { abortAllInFlight(cause, null); } @@ -312,7 +322,7 @@ private void abortAllInFlight(ClosedConnectionException cause) { * @param ignore the ResponseCallback that called this method, if applicable (avoids a recursive * loop) */ - private void abortAllInFlight(ClosedConnectionException cause, ResponseCallback ignore) { + private void abortAllInFlight(DriverException cause, ResponseCallback ignore) { for (ResponseCallback responseCallback : inFlight.values()) { if (responseCallback != ignore) { responseCallback.onFailure(cause); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 5d737171221..cf7b9113bc6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -63,13 +63,16 @@ class ProtocolInitHandler extends ConnectInitHandler { private final DriverChannelOptions options; // might be null if this is the first channel to this cluster private final String expectedClusterName; + private final HeartbeatHandler heartbeatHandler; private String logPrefix; + private ChannelHandlerContext ctx; ProtocolInitHandler( InternalDriverContext internalDriverContext, ProtocolVersion protocolVersion, String expectedClusterName, - DriverChannelOptions options) { + DriverChannelOptions options, + HeartbeatHandler heartbeatHandler) { this.internalDriverContext = internalDriverContext; @@ -80,6 +83,7 @@ class ProtocolInitHandler extends ConnectInitHandler { this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; this.options = options; + this.heartbeatHandler = heartbeatHandler; this.logPrefix = options.ownerLogPrefix + "|connecting..."; } @@ -93,9 +97,20 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { @Override protected void onRealConnect(ChannelHandlerContext ctx) { LOG.debug("[{}] Starting channel initialization", logPrefix); + this.ctx = ctx; new InitRequest(ctx).send(); } + @Override + protected boolean setConnectSuccess() { + boolean result = super.setConnectSuccess(); + if (result) { + // add heartbeat to pipeline now that protocol is initialized. + ctx.pipeline().addBefore("inflight", "heartbeat", heartbeatHandler); + } + return result; + } + private enum Step { STARTUP, GET_CLUSTER_NAME, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 109fed5ea5f..cc5bde4a707 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -117,6 +117,8 @@ public void setup() throws InterruptedException { .thenReturn(Duration.ofMillis(100)); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) + .thenReturn(Duration.ofMillis(30000)); Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); @@ -243,8 +245,11 @@ protected void initChannel(Channel channel) throws Exception { channel.newPromise(), null, "test"); + + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); ProtocolInitHandler initHandler = - new ProtocolInitHandler(context, protocolVersion, clusterName, options); + new ProtocolInitHandler( + context, protocolVersion, clusterName, options, heartbeatHandler); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } }; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index ff0ec9538c7..cba85aae095 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -62,6 +62,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; private ProtocolVersionRegistry protocolVersionRegistry = new ProtocolVersionRegistry(); + private HeartbeatHandler heartbeatHandler; @Before @Override @@ -72,6 +73,8 @@ public void setup() { Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) + .thenReturn(Duration.ofMillis(30000)); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); @@ -88,6 +91,8 @@ public void setup() { channel.newPromise(), null, "test")); + + heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); } @Test @@ -97,7 +102,11 @@ public void should_initialize_without_authentication() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -120,6 +129,47 @@ public void should_initialize_without_authentication() { assertThat(connectFuture).isSuccess(); } + @Test + public void should_add_heartbeat_handler_to_pipeline_on_success() { + ProtocolInitHandler protocolInitHandler = + new ProtocolInitHandler( + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler); + + channel.pipeline().addLast("init", protocolInitHandler); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + // heartbeat should initially not be in pipeline + assertThat(channel.pipeline().get("heartbeat")).isNull(); + + // It should send a STARTUP message + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + Startup startup = (Startup) requestFrame.message; + assertThat(startup.options).doesNotContainKey("COMPRESSION"); + assertThat(connectFuture).isNotDone(); + + // Simulate a READY response + writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); + + // Simulate the cluster name check + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("someClusterName")); + + // Init should complete + assertThat(connectFuture).isSuccess(); + + // should have added heartbeat handler to pipeline. + assertThat(channel.pipeline().get("heartbeat")).isEqualTo(heartbeatHandler); + // should have removed itself from pipeline. + assertThat(channel.pipeline().last()).isNotEqualTo(protocolInitHandler); + } + @Test public void should_fail_to_initialize_if_init_query_times_out() throws InterruptedException { channel @@ -127,7 +177,11 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -147,7 +201,11 @@ public void should_initialize_with_authentication() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -205,7 +263,11 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, DriverChannelOptions.DEFAULT)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = Mockito.mock(AuthProvider.class); @@ -248,7 +310,8 @@ public void should_check_cluster_name_if_provided() { internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", - DriverChannelOptions.DEFAULT)); + DriverChannelOptions.DEFAULT, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -276,7 +339,8 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th internalDriverContext, CoreProtocolVersion.V4, "expectedClusterName", - DriverChannelOptions.DEFAULT)); + DriverChannelOptions.DEFAULT, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -301,7 +365,8 @@ public void should_initialize_with_keyspace() { .pipeline() .addLast( "init", - new ProtocolInitHandler(internalDriverContext, CoreProtocolVersion.V4, null, options)); + new ProtocolInitHandler( + internalDriverContext, CoreProtocolVersion.V4, null, options, heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -327,7 +392,11 @@ public void should_initialize_with_events() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + driverChannelOptions, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -356,7 +425,11 @@ public void should_initialize_with_keyspace_and_events() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + driverChannelOptions, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -385,7 +458,11 @@ public void should_fail_to_initialize_if_keyspace_is_invalid() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, driverChannelOptions)); + internalDriverContext, + CoreProtocolVersion.V4, + null, + driverChannelOptions, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); From 293035bf1a1f8c537743d7dfec2a09ec768bc486 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 24 Jul 2017 10:29:21 -0700 Subject: [PATCH 143/742] JAVA-1548: Retry idempotent statements on READ_TIMEOUT and UNAVAILABLE --- changelog/README.md | 1 + .../internal/core/cql/CqlRequestHandler.java | 30 ++++++++----------- .../core/cql/CqlRequestHandlerRetryTest.java | 19 ++++++++++-- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index e362937d6b9..c372ea1cef6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1548: Retry idempotent statements on READ_TIMEOUT and UNAVAILABLE - [bug] JAVA-1562: Fix various issues around heart beats - [improvement] JAVA-1546: Make all statement implementations immutable - [bug] JAVA-1554: Include VIEW and CDC in WriteType diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 317b9818298..4ab5a58c015 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -423,15 +423,13 @@ private void processErrorResponse(Error errorMessage) { if (error instanceof ReadTimeoutException) { ReadTimeoutException readTimeout = (ReadTimeoutException) error; decision = - isIdempotent - ? retryPolicy.onReadTimeout( - request, - readTimeout.getConsistencyLevel(), - readTimeout.getBlockFor(), - readTimeout.getReceived(), - readTimeout.wasDataPresent(), - retryCount) - : RetryDecision.RETHROW; + retryPolicy.onReadTimeout( + request, + readTimeout.getConsistencyLevel(), + readTimeout.getBlockFor(), + readTimeout.getReceived(), + readTimeout.wasDataPresent(), + retryCount); } else if (error instanceof WriteTimeoutException) { WriteTimeoutException writeTimeout = (WriteTimeoutException) error; decision = @@ -447,14 +445,12 @@ private void processErrorResponse(Error errorMessage) { } else if (error instanceof UnavailableException) { UnavailableException unavailable = (UnavailableException) error; decision = - isIdempotent - ? retryPolicy.onUnavailable( - request, - unavailable.getConsistencyLevel(), - unavailable.getRequired(), - unavailable.getAlive(), - retryCount) - : RetryDecision.RETHROW; + retryPolicy.onUnavailable( + request, + unavailable.getConsistencyLevel(), + unavailable.getRequired(), + unavailable.getAlive(), + retryCount); } else { decision = isIdempotent diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 445ee47dbd8..22070ad6786 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -237,14 +237,27 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( @Test @UseDataProvider("failureAndNotIdempotent") - public void should_rethrow_error_if_not_idempotent( + public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_rethrows( FailureScenario failureScenario, boolean defaultIdempotence, SimpleStatement statement) { + + // For two of the possible exceptions, the retry policy is called even if the statement is not + // idempotent + boolean shouldCallRetryPolicy = + (failureScenario.expectedExceptionClass.equals(UnavailableException.class) + || failureScenario.expectedExceptionClass.equals(ReadTimeoutException.class)); + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); failureScenario.mockRequestError(harnessBuilder, node1); harnessBuilder.withResponse(node2, defaultFrameOf(singleRow())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + + if (shouldCallRetryPolicy) { + failureScenario.mockRetryPolicyDecision( + harness.getContext().retryPolicy(), RetryDecision.RETHROW); + } + CompletionStage resultSetFuture = new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); @@ -254,7 +267,9 @@ public void should_rethrow_error_if_not_idempotent( error -> { assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); // When non idempotent, the policy is bypassed completely: - Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + if (!shouldCallRetryPolicy) { + Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + } }); } } From b6baa6a370ef8b1b17b757daad412a964b204fb0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 24 Jul 2017 14:51:08 -0700 Subject: [PATCH 144/742] Document and test retry policy, revisit default implementation --- .../api/core/retry/DefaultRetryPolicy.java | 22 +++- .../driver/api/core/retry/RetryPolicy.java | 109 +++++++++++++++++- .../driver/internal/core/cql/Conversions.java | 11 +- .../internal/core/cql/CqlPrepareHandler.java | 3 +- .../internal/core/cql/CqlRequestHandler.java | 3 +- .../core/retry/DefaultRetryPolicyTest.java | 86 ++++++++++++++ .../api/core/retry/RetryPolicyTestBase.java | 65 +++++++++++ 7 files changed, 286 insertions(+), 13 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index f8c48cca0dd..24daf85e978 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -17,7 +17,12 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; +import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; +import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.session.Request; /** @@ -106,21 +111,28 @@ public RetryDecision onUnavailable( /** * {@inheritDoc} * - *

      This implementation always retries on the next node. + *

      This implementation retries on the next node if the connection was closed, and rethrows + * (assuming a driver bug) in all other cases. */ @Override public RetryDecision onRequestAborted(Request request, Throwable error, int retryCount) { - return RetryDecision.RETRY_NEXT; + return (error instanceof ClosedConnectionException || error instanceof HeartbeatException) + ? RetryDecision.RETRY_NEXT + : RetryDecision.RETHROW; } /** * {@inheritDoc} * - *

      This implementation always retries on the next node. + *

      This implementation rethrows read and write failures, and retries other errors on the next + * node. */ @Override - public RetryDecision onErrorResponse(Request request, Throwable error, int retryCount) { - return RetryDecision.RETRY_NEXT; + public RetryDecision onErrorResponse( + Request request, CoordinatorException error, int retryCount) { + return (error instanceof ReadFailureException || error instanceof WriteFailureException) + ? RetryDecision.RETHROW + : RetryDecision.RETRY_NEXT; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java index fc5d32ac215..74e29d01cde 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -16,7 +16,20 @@ package com.datastax.oss.driver.api.core.retry; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; +import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; +import com.datastax.oss.driver.api.core.servererrors.OverloadedException; +import com.datastax.oss.driver.api.core.servererrors.ProtocolError; +import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; +import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.servererrors.TruncateException; +import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.session.Request; /** @@ -28,6 +41,22 @@ */ public interface RetryPolicy extends AutoCloseable { + /** + * Whether to retry when the server replied with a {@code READ_TIMEOUT} error; this indicates a + * server-side timeout during a read query, i.e. some replicas did not reply to the + * coordinator in time. + * + * @param request the request that timed out. + * @param cl the requested consistency level. + * @param blockFor the minimum number of replica acknowledgements/responses that were required to + * fulfill the operation. + * @param received the number of replica that had acknowledged/responded to the operation before + * it failed. + * @param dataPresent whether the actual data was amongst the received replica responses. See + * {@link ReadTimeoutException#wasDataPresent()}. + * @param retryCount how many times the retry policy has been invoked already for this request + * (not counting the current invocation). + */ RetryDecision onReadTimeout( Request request, ConsistencyLevel cl, @@ -36,6 +65,26 @@ RetryDecision onReadTimeout( boolean dataPresent, int retryCount); + /** + * Whether to retry when the server replied with a {@code WRITE_TIMEOUT} error; this indicates a + * server-side timeout during a write query, i.e. some replicas did not reply to the + * coordinator in time. + * + *

      Note that this method will only be invoked for {@link Request#isIdempotent()} idempotent} + * requests: when a write times out, it is impossible to determine with 100% certainty whether the + * mutation was applied or not, so the write is never safe to retry; the driver will rethrow the + * error directly, without invoking the retry policy. + * + * @param request the request that timed out. + * @param cl the requested consistency level. + * @param writeType the type of the write for which the timeout was raised. + * @param blockFor the minimum number of replica acknowledgements/responses that were required to + * fulfill the operation. + * @param received the number of replica that had acknowledged/responded to the operation before + * it failed. + * @param retryCount how many times the retry policy has been invoked already for this request + * (not counting the current invocation). + */ RetryDecision onWriteTimeout( Request request, ConsistencyLevel cl, @@ -44,12 +93,70 @@ RetryDecision onWriteTimeout( int received, int retryCount); + /** + * Whether to retry when the server replied with an {@code UNAVAILABLE} error; this indicates that + * the coordinator determined that there were not enough replicas alive to perform a query with + * the requested consistency level. + * + * @param request the request that timed out. + * @param cl the requested consistency level. + * @param required the number of replica acknowledgements/responses required to perform the + * operation (with its required consistency level). + * @param alive the number of replicas that were known to be alive by the coordinator node when it + * tried to execute the operation. + * @param retryCount how many times the retry policy has been invoked already for this request + * (not counting the current invocation). + */ RetryDecision onUnavailable( Request request, ConsistencyLevel cl, int required, int alive, int retryCount); + /** + * Whether to retry when a request was aborted before we could get a response from the server. + * + *

      This can happen in two cases: if the connection was closed due to an external event (this + * will manifest as a {@link ClosedConnectionException}, or {@link HeartbeatException} for a + * heartbeat failure); or if there was an unexpected error while decoding the response (this can + * only be a driver bug). + * + *

      Note that this method will only be invoked for {@link Request#isIdempotent()} idempotent} + * requests: when execution was aborted before getting a response, it is impossible to determine + * with 100% certainty whether a mutation was applied or not, so a write is never safe to retry; + * the driver will rethrow the error directly, without invoking the retry policy. + * + * @param request the request that was aborted. + * @param error the error. + * @param retryCount how many times the retry policy has been invoked already for this request + * (not counting the current invocation). + */ RetryDecision onRequestAborted(Request request, Throwable error, int retryCount); - RetryDecision onErrorResponse(Request request, Throwable error, int retryCount); + /** + * Whether to retry when the server replied with a recoverable error (other than {@code + * READ_TIMEOUT}, {@code WRITE_TIMEOUT} or {@code UNAVAILABLE}). + * + *

      This can happen for the following errors: {@link OverloadedException}, {@link ServerError}, + * {@link TruncateException}, {@link ReadFailureException}, {@link WriteFailureException}. + * + *

      The following errors are handled internally by the driver, and therefore will never + * be encountered in this method: + * + *

        + *
      • {@link BootstrappingException}: always retried on the next node; + *
      • {@link QueryValidationException} (and its subclasses), {@link FunctionFailureException} + * and {@link ProtocolError}: always rethrown. + *
      + * + *

      Note that this method will only be invoked for {@link Request#isIdempotent()} idempotent} + * requests: when execution was aborted before getting a response, it is impossible to determine + * with 100% certainty whether a mutation was applied or not, so a write is never safe to retry; + * the driver will rethrow the error directly, without invoking the retry policy. + * + * @param request the request that failed. + * @param error the error. + * @param retryCount how many times the retry policy has been invoked already for this request + * (not counting the current invocation). + */ + RetryDecision onErrorResponse(Request request, CoordinatorException error, int retryCount); /** Called when the cluster that this policy is associated with closes. */ @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 5e64e09a505..48a3b6200e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -35,6 +34,7 @@ import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.servererrors.AlreadyExistsException; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; import com.datastax.oss.driver.api.core.servererrors.InvalidConfigurationInQueryException; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; @@ -244,7 +244,7 @@ private static ColumnDefinitions toColumnDefinitions( return DefaultColumnDefinitions.valueOf(definitions.build()); } - static Throwable toThrowable(Node node, Error errorMessage) { + static CoordinatorException toThrowable(Node node, Error errorMessage) { switch (errorMessage.code) { case ProtocolConstants.ErrorCode.UNPREPARED: throw new AssertionError( @@ -254,9 +254,10 @@ static Throwable toThrowable(Node node, Error errorMessage) { case ProtocolConstants.ErrorCode.PROTOCOL_ERROR: return new ProtocolError(node, errorMessage.message); case ProtocolConstants.ErrorCode.AUTH_ERROR: - // This method is used for query execution, authentication errors are unlikely to happen at - // this stage (more during connection init), but there's no harm in handling them anyway - return new AuthenticationException(node.getConnectAddress(), errorMessage.message); + // This method is used for query execution, authentication errors should only happen during + // connection init + return new ProtocolError( + node, "Unexpected authentication error (" + errorMessage.message + ")"); case ProtocolConstants.ErrorCode.UNAVAILABLE: Unavailable unavailable = (Unavailable) errorMessage; return new UnavailableException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 971ce2d071f..89a64ab1e71 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; import com.datastax.oss.driver.api.core.servererrors.ProtocolError; import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; @@ -329,7 +330,7 @@ private void processErrorResponse(Error errorMessage) { "Unexpected server error for a PREPARE query" + errorMessage)); return; } - Throwable error = Conversions.toThrowable(node, errorMessage); + CoordinatorException error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 4ab5a58c015..23147f05b17 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; import com.datastax.oss.driver.api.core.servererrors.ProtocolError; import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; @@ -408,7 +409,7 @@ private void processErrorResponse(Error errorMessage) { }); return; } - Throwable error = Conversions.toThrowable(node, errorMessage); + CoordinatorException error = Conversions.toThrowable(node, errorMessage); if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java new file mode 100644 index 00000000000..a2239db9dcb --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.core.connection.HeartbeatException; +import com.datastax.oss.driver.api.core.servererrors.OverloadedException; +import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.servererrors.TruncateException; +import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; +import org.junit.Test; + +import static com.datastax.oss.driver.api.core.ConsistencyLevel.QUORUM; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; +import static com.datastax.oss.driver.api.core.retry.WriteType.BATCH_LOG; +import static com.datastax.oss.driver.api.core.retry.WriteType.SIMPLE; + +public class DefaultRetryPolicyTest extends RetryPolicyTestBase { + + public DefaultRetryPolicyTest() { + super(new DefaultRetryPolicy(null, null)); + } + + @Test + public void should_process_read_timeouts() { + assertOnReadTimeout(QUORUM, 2, 2, false, 0).isEqualTo(RETRY_SAME); + assertOnReadTimeout(QUORUM, 2, 2, false, 1).isEqualTo(RETHROW); + assertOnReadTimeout(QUORUM, 2, 2, true, 0).isEqualTo(RETHROW); + assertOnReadTimeout(QUORUM, 2, 1, true, 0).isEqualTo(RETHROW); + assertOnReadTimeout(QUORUM, 2, 1, false, 0).isEqualTo(RETHROW); + } + + @Test + public void should_process_write_timeouts() { + assertOnWriteTimeout(QUORUM, BATCH_LOG, 2, 0, 0).isEqualTo(RETRY_SAME); + assertOnWriteTimeout(QUORUM, BATCH_LOG, 2, 0, 1).isEqualTo(RETHROW); + assertOnWriteTimeout(QUORUM, SIMPLE, 2, 0, 0).isEqualTo(RETHROW); + } + + @Test + public void should_process_unavailable() { + assertOnUnavailable(QUORUM, 2, 1, 0).isEqualTo(RETRY_NEXT); + assertOnUnavailable(QUORUM, 2, 1, 1).isEqualTo(RETHROW); + } + + @Test + public void should_process_aborted_request() { + assertOnRequestAborted(ClosedConnectionException.class, 0).isEqualTo(RETRY_NEXT); + assertOnRequestAborted(ClosedConnectionException.class, 1).isEqualTo(RETRY_NEXT); + assertOnRequestAborted(HeartbeatException.class, 0).isEqualTo(RETRY_NEXT); + assertOnRequestAborted(HeartbeatException.class, 1).isEqualTo(RETRY_NEXT); + assertOnRequestAborted(Throwable.class, 0).isEqualTo(RETHROW); + } + + @Test + public void should_process_error_response() { + assertOnErrorResponse(ReadFailureException.class, 0).isEqualTo(RETHROW); + assertOnErrorResponse(ReadFailureException.class, 1).isEqualTo(RETHROW); + assertOnErrorResponse(WriteFailureException.class, 0).isEqualTo(RETHROW); + assertOnErrorResponse(WriteFailureException.class, 1).isEqualTo(RETHROW); + assertOnErrorResponse(WriteFailureException.class, 1).isEqualTo(RETHROW); + + assertOnErrorResponse(OverloadedException.class, 0).isEqualTo(RETRY_NEXT); + assertOnErrorResponse(OverloadedException.class, 1).isEqualTo(RETRY_NEXT); + assertOnErrorResponse(ServerError.class, 0).isEqualTo(RETRY_NEXT); + assertOnErrorResponse(ServerError.class, 1).isEqualTo(RETRY_NEXT); + assertOnErrorResponse(TruncateException.class, 0).isEqualTo(RETRY_NEXT); + assertOnErrorResponse(TruncateException.class, 1).isEqualTo(RETRY_NEXT); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java new file mode 100644 index 00000000000..ddff816c21e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; +import com.datastax.oss.driver.api.core.session.Request; +import org.assertj.core.api.Assert; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public abstract class RetryPolicyTestBase { + private final RetryPolicy policy; + + @Mock private Request request; + + protected RetryPolicyTestBase(RetryPolicy policy) { + this.policy = policy; + } + + protected Assert assertOnReadTimeout( + ConsistencyLevel cl, int blockFor, int received, boolean dataPresent, int retryCount) { + return assertThat( + policy.onReadTimeout(request, cl, blockFor, received, dataPresent, retryCount)); + } + + protected Assert assertOnWriteTimeout( + ConsistencyLevel cl, WriteType writeType, int blockFor, int received, int retryCount) { + return assertThat( + policy.onWriteTimeout(request, cl, writeType, blockFor, received, retryCount)); + } + + protected Assert assertOnUnavailable( + ConsistencyLevel cl, int required, int alive, int retryCount) { + return assertThat(policy.onUnavailable(request, cl, required, alive, retryCount)); + } + + protected Assert assertOnRequestAborted( + Class errorClass, int retryCount) { + return assertThat(policy.onRequestAborted(request, Mockito.mock(errorClass), retryCount)); + } + + protected Assert assertOnErrorResponse( + Class errorClass, int retryCount) { + return assertThat(policy.onErrorResponse(request, Mockito.mock(errorClass), retryCount)); + } +} From aee8174340ce9490d850d77845b9ead80e6adf49 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Jul 2017 09:38:15 -0700 Subject: [PATCH 145/742] Fix typo in config manual --- manual/configuration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/configuration/README.md b/manual/configuration/README.md index fe03e2dc454..a6d0675210c 100644 --- a/manual/configuration/README.md +++ b/manual/configuration/README.md @@ -69,7 +69,7 @@ of that library: The driver ships with a [reference.conf] that defines sensible defaults for all the options. That file is heavily documented, so refer to it for details about each option. It is included in the core -driver JAR, so it is in your application's classpath. If you to customize something, add an +driver JAR, so it is in your application's classpath. If you need to customize something, add an `application.conf` in your source tree and also place it in the classpath; since it inherits from `reference.conf`, you only need to redeclare what you override: From 4569b93937c1404d4458f49bb147547f85a63a4d Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Jul 2017 11:58:39 -0700 Subject: [PATCH 146/742] Add FAQ entry about CompletionStage --- faq/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/faq/README.md b/faq/README.md index ee64b6ce08f..d018d5753b8 100644 --- a/faq/README.md +++ b/faq/README.md @@ -14,4 +14,16 @@ session.execute(boundSelect); // Instead, do this: boundSelect = boundSelect.setInt("k", key); -``` \ No newline at end of file +``` + +### Why do asynchronous methods return `CompletionStage` instead of `CompletableFuture`? + +Because it's the right abstraction to use. A completable future, as its name indicates, is a future +that can be completed manually; that is not what we want to return from our API: the driver +completes the futures, not the user. + +Also, `CompletionStage` does not expose a `get()` method; one can view that as an encouragement to +use a fully asynchronous programming model (chaining callbacks instead of blocking for a result). + +At any rate, `CompletionStage` has a `toCompletableFuture()` method. In current JDK versions, every +`CompletionStage` is a `CompletableFuture`, so the conversion has no performance overhead. \ No newline at end of file From 4f7fc826e659ee11c9274dffc827cca3f2b78bbc Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Jul 2017 13:00:46 -0700 Subject: [PATCH 147/742] Fix JUnit scope --- core/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/pom.xml b/core/pom.xml index bcd62eb9108..08709bffa35 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -71,10 +71,12 @@ junit junit + test com.tngtech.java junit-dataprovider + test org.assertj From fabd035b5ae6b6822d5340ac6a7a2c56d9df1719 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 25 Jul 2017 16:16:18 -0500 Subject: [PATCH 148/742] Use empty map if custom payload is null --- .../oss/driver/api/core/cql/BatchStatementBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index 392235c2295..d8d060953dc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.Arrays; +import java.util.Collections; public class BatchStatementBuilder extends StatementBuilder { @@ -80,7 +81,7 @@ public BatchStatement build() { configProfileName, configProfile, null, - customPayloadBuilder.build(), + (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), idempotent, tracing, timestamp, From 7daa9a3435c07f8117c672f649d0ae3811667605 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Jul 2017 16:54:10 -0700 Subject: [PATCH 149/742] Move configuration doc to core manual --- manual/{ => core}/configuration/README.md | 2 +- upgrade_guide/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename manual/{ => core}/configuration/README.md (99%) diff --git a/manual/configuration/README.md b/manual/core/configuration/README.md similarity index 99% rename from manual/configuration/README.md rename to manual/core/configuration/README.md index a6d0675210c..6c67c900d05 100644 --- a/manual/configuration/README.md +++ b/manual/core/configuration/README.md @@ -452,4 +452,4 @@ config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); [config standard behavior]: https://github.com/typesafehub/config#standard-behavior [reference.conf]: https://github.com/datastax/java-driver/blob/4.x/core/src/main/resources/reference.conf [HOCON]: https://github.com/typesafehub/config/blob/master/HOCON.md -[API conventions]: ../api_conventions/ +[API conventions]: ../../api_conventions diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 356ef769168..f3727677781 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -28,7 +28,7 @@ choice for most deployments, since it allows configuration changes without recom application. This is fully customizable, including loading from different sources, or completely overriding the default implementation. -For more details, refer to the [manual](../manual/configuration). +For more details, refer to the [manual](../manual/core/configuration). [TypeSafe Config]: https://github.com/typesafehub/config From 98d77cbf623751e3fd841531f69e15e1dd277e07 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 25 Jul 2017 16:51:32 -0700 Subject: [PATCH 150/742] Document statements in the manual --- .../driver/api/core/cql/StatementBuilder.java | 7 + core/src/main/resources/reference.conf | 3 + manual/core/README.md | 40 +++ manual/core/statements/.nav | 3 + manual/core/statements/README.md | 45 +++ manual/core/statements/batch/README.md | 56 ++++ manual/core/statements/prepared/README.md | 257 ++++++++++++++++++ manual/core/statements/simple/README.md | 173 ++++++++++++ 8 files changed, 584 insertions(+) create mode 100644 manual/core/statements/.nav create mode 100644 manual/core/statements/README.md create mode 100644 manual/core/statements/batch/README.md create mode 100644 manual/core/statements/prepared/README.md create mode 100644 manual/core/statements/simple/README.md diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 2038636837d..4497c7c2151 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -20,6 +20,13 @@ import java.nio.ByteBuffer; import java.util.Map; +/** + * Handle options common to all statement builders. + * + * @see SimpleStatement#builder(String) + * @see BatchStatement#builder(BatchType) + * @see PreparedStatement#boundStatementBuilder() + */ public abstract class StatementBuilder, S extends Statement> { @SuppressWarnings("unchecked") diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index dfaeeae03ab..839217cc491 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -293,6 +293,9 @@ datastax-java-driver { # # Checking the table first avoids repreparing unnecessarily, but the cost of the query is not # always worth the improvement, especially if the number of statements is low. + # + # If the table does not exist, or the query fails for any other reason, the error is ignored + # and the driver proceeds to reprepare statements according to the other parameters. check-system-table = false # The maximum number of statements that should be reprepared. 0 or a negative value means no # limit. diff --git a/manual/core/README.md b/manual/core/README.md index 9972b559bb8..135e789541c 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -42,6 +42,46 @@ it. In this simple example, we can use a try-with-resources block because `Clust `java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods (`close`, `closeAsync`, `forceCloseAsync`) explicitly. +### CQL to Java type mapping + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      CQL3 data type Getter name Java type
      ascii getString java.lang.String
      bigint getLong long
      blob getBytes java.nio.ByteBuffer
      boolean getBoolean boolean
      counter getLong long
      date getLocalDate java.time.LocalDate
      decimal getBigDecimal java.math.BigDecimal
      double getDouble double
      duration getCqlDuration CqlDuration
      float getFloat float
      inet getInetAddress java.net.InetAddress
      int getInt int
      list getList java.util.List
      map getMap java.util.Map
      set getSet java.util.Set
      smallint getShort short
      text getString java.lang.String
      time getLocalTime java.time.LocalTime
      timestamp getInstant java.time.Instant
      timeuuid getUuid java.util.UUID
      tinyint getByte byte
      tuple getTupleValue TupleValue
      user-defined types getUDTValue UDTValue
      uuid getUuid java.util.UUID
      varchar getString java.lang.String
      varint getVarint java.math.BigInteger
      + +Sometimes the driver has to infer a CQL type from a Java type (for example when handling the values +of [simple statements](statements/simple/)); for those that have multiple CQL equivalents, it makes +the following choices: + +* `java.lang.String`: `text` +* `long`: `bigint` +* `java.util.UUID`: `uuid` + [Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html [Session]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html [ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html diff --git a/manual/core/statements/.nav b/manual/core/statements/.nav new file mode 100644 index 00000000000..0c001e3860e --- /dev/null +++ b/manual/core/statements/.nav @@ -0,0 +1,3 @@ +simple +prepared +batch \ No newline at end of file diff --git a/manual/core/statements/README.md b/manual/core/statements/README.md new file mode 100644 index 00000000000..3b9ac19e769 --- /dev/null +++ b/manual/core/statements/README.md @@ -0,0 +1,45 @@ +## Statements + +To execute a CQL query, you create a [Statement] instance and pass it to +[Session#execute][execute] or [Session#executeAsync][executeAsync]. The driver provides various +implementations: + +* [SimpleStatement](simple/): a simple implementation built directly from a character string. + Typically used for queries that are executed only once or a few times. +* [BoundStatement](prepared/): obtained by binding values to a prepared statement. Typically used + for queries that are executed often, with different values. +* [BatchStatement](batch/): a statement that groups multiple statements to be executed as a batch. + +All statement types share a [common set of options][StatementBuilder], that can be set through +either setters or a builder: + + + +* [configuration profile](../configuration/) name, or the configuration profile itself if it's been + built dynamically. +* custom payload to send arbitrary key/value pairs with the request (you only use this if you have + a custom query handler on the server). +* idempotent flag. +* tracing flag. +* query timestamp. +* paging state. + +When setting these options, keep in mind that statements are immutable, and every method returns a +different instance: + +```java +SimpleStatement statement = + SimpleStatement.newInstance("SELECT release_version FROM system.local"); + +// Won't work: statement isn't modified in place +statement.setConfigProfileName("oltp"); +statement.setIdempotent(true); + +// Do this instead: +statement = statement.setConfigProfileName("oltp").setIdempotent(true); +``` + +[Statement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Statement.html +[StatementBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/StatementBuilder.html +[execute]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Session.html#execute-com.datastax.oss.driver.api.core.cql.Statement- +[executeAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Session.html#executeAsync-com.datastax.oss.driver.api.core.cql.Statement- diff --git a/manual/core/statements/batch/README.md b/manual/core/statements/batch/README.md new file mode 100644 index 00000000000..fc3769e649a --- /dev/null +++ b/manual/core/statements/batch/README.md @@ -0,0 +1,56 @@ +## Batch statements + +Use [BatchStatement] to execute a set of queries as an atomic operation (refer to +[Batching inserts, updates and deletes][batch_dse] to understand how to use batching effectively): + +```java +PreparedStatement preparedInsertExpense = + session.prepare( + "INSERT INTO cyclist_expenses (cyclist_name, expense_id, amount, description, paid) " + + "VALUES (:name, :id, :amount, :description, :paid)"); +SimpleStatement simpleInsertBalance = + SimpleStatement.newInstance( + "INSERT INTO cyclist_expenses (cyclist_name, balance) VALUES (?, 0) IF NOT EXISTS", + "Vera ADRIAN"); + +BatchStatement batch = + BatchStatement.newInstance( + BatchType.LOGGED, + simpleInsertBalance, + preparedInsertExpense.bind("Vera ADRIAN", 1, 7.95f, "Breakfast", false)); + +session.execute(batch); +``` + +To create a new batch statement, use one of the static factory methods (as demonstrated above), or a +builder: + +```java +BatchStatement batch = + BatchStatement.builder(BatchType.LOGGED) + .addStatement(simpleInsertBalance) + .addStatement(preparedInsertExpense.bind("Vera ADRIAN", 1, 7.95f, "Breakfast", false)) + .build(); +``` + +Keep in mind that batch statements are immutable, and every method returns a different instance: + +```java +// Won't work: the object is not modified in place: +batch.setConfigProfileName("oltp"); + +// Do this instead: +batch = batch.setConfigProfileName("oltp"); +``` + +As shown in the examples above, batches can contain any combination of simple statements and bound +statements. A given batch can contain at most 65536 statements. Past this limit, addition methods +throw an `IllegalStateException`. + +In addition, simple statements with named parameters are currently not supported in batches (this is +due to a [protocol limitation][CASSANDRA-10246] that will be fixed in a future version). If you try +to execute such a batch, an `IllegalArgumentException` is thrown. + +[BatchStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BatchStatement.html +[batch_dse]: http://docs.datastax.com/en/dse/5.1/cql/cql/cql_using/useBatch.html +[CASSANDRA-10246]: https://issues.apache.org/jira/browse/CASSANDRA-10246 \ No newline at end of file diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md new file mode 100644 index 00000000000..f03098c569d --- /dev/null +++ b/manual/core/statements/prepared/README.md @@ -0,0 +1,257 @@ +## Prepared statements + +Use prepared statements for queries that are executed multiple times in your application: + +```java +PreparedStatement prepared = session.prepare( + "insert into product (sku, description) values (?, ?)"); + +BoundStatement bound = prepared.bind("234827", "Mouse"); +session.execute(bound); +``` + +When you prepare the statement, Cassandra parses the query string, caches the result and returns a +unique identifier (the `PreparedStatement` object keeps an internal reference to that identifier): + +```ditaa +client driver Cassandra +--+------------------------+----------------+------ + | | | + | session.prepare(query) | | + |----------------------->| | + | | PREPARE(query) | + | |--------------->| + | | | + | | | + | | | - compute id + | | | - parse query string + | | | - cache (id, parsed) + | | | + | | PREPARED(id) | + | |<---------------| + | PreparedStatement(id) | | + |<-----------------------| | +``` + +When you bind and execute a prepared statement, the driver only sends the identifier, which allows +Cassandra to skip the parsing phase: + +```ditaa +client driver Cassandra +--+---------------------------------+---------------------+------ + | | | + | session.execute(BoundStatement) | | + |-------------------------------->| | + | | EXECUTE(id, values) | + | |-------------------->| + | | | + | | | + | | | - get cache(id) + | | | - execute query + | | | + | | ROWS | + | |<--------------------| + | | | + |<--------------------------------| | +``` + +You should prepare only once, and cache the `PreparedStatement` in your application (it is +thread-safe). If you call `prepare` multiple times with the same query string, the driver will log a +warning. + +If you execute a query only once, a prepared statement is inefficient because it requires two round +trips. Consider a [simple statement](../simple/) instead. + +### Preparing + +The `Session.prepare` method accepts either a query string or a `SimpleStatement` object. If you use +the object variant, both the initial prepare request and future bound statements will share some of +the options of that simple statement: + +* initial prepare request: configuration profile name (or instance) and custom payload. +* bound statements: configuration profile name (or instance) and custom payload, idempotent flag. + +### Parameters and binding + +The prepared query string will usually contain placeholders, which can be either anonymous or named: + +```java +ps1 = session.prepare("insert into product (sku, description) values (?, ?)"); +ps2 = session.prepare("insert into product (sku, description) values (:s, :d)"); +``` + +To turn the statement into its executable form, you need to *bind* it in order to create a +[BoundStatement]. As shown previously, there is a shorthand to provide the parameters in the same +call: + +```java +BoundStatement bound = ps1.bind("324378", "LCD screen"); +``` + +You can also bind first, then use setters, which is slightly more explicit. Bound statements are +immutable, so each method returns a new instance; make sure you don't accidentally discard the +result: + +```java +// Positional setters: +BoundStatement bound = ps1.bind() + .setString(0, "324378") + .setString(1, "LCD screen"); + +// Named setters: +BoundStatement bound = ps2.bind() + .setString("s", "324378") + .setString("d", "LCD screen"); +``` + +Finally, you can use a builder to avoid creating intermediary instances, especially if you have a +lot of methods to call: + +```java +BoundStatement bound = + ps1 + .boundStatementBuilder() + .setString(0, "324378") + .setString(1, "LCD screen") + .withConfigProfileName("oltp") + .withTimestamp(123456789L) + .build(); +``` + +You can use named setters even if the query uses anonymous parameters; Cassandra names the +parameters after the column they apply to: + +```java +BoundStatement bound = ps1.bind() + .setString("sku", "324378") + .setString("description", "LCD screen"); +``` + +This can be ambiguous if the query uses the same column multiple times, like in `select * from sales +where sku = ? and date > ? and date < ?`. In these situations, use positional setters or named +parameters. + +With native protocol V3, all variables must be bound. With native protocol V4 or above, variables +can be left unset, in which case they will be ignored (no tombstones will be generated). If you're +reusing a bound statement, you can use the `unset` method to unset variables that were previously +set: + +```java +BoundStatement bound = ps1.bind() + .setString("sku", "324378") + .setString("description", "LCD screen"); + +// Positional: +bound = bound.unset("description"); + +// Named: +bound = bound.unset(1); +``` + +A bound statement also has getters to retrieve the values. Note that this has a small performance +overhead, since values are stored in their serialized form. + +Since bound statements are immutable, they are safe to reuse across threads and asynchronous +executions. + + +### How the driver prepares + +Cassandra does not replicate prepared statements across the cluster. It is the driver's +responsibility to ensure that each node's cache is up to date. It uses a number of strategies to +achieve this: + +1. When a statement is initially prepared, it is first sent to a single node in the cluster (this + avoids hitting all nodes in case the query string is wrong). Once that node replies + successfully, the driver re-prepares on all remaining nodes: + + ```ditaa + client driver node1 node2 node3 + --+------------------------+----------------+--------------+------+--- + | | | | | + | session.prepare(query) | | | | + |----------------------->| | | | + | | PREPARE(query) | | | + | |--------------->| | | + | | | | | + | | PREPARED(id) | | | + | |<---------------| | | + | | | | | + | | | | | + | | PREPARE(query) | | + | |------------------------------>| | + | | | | | + | | PREPARE(query) | | + | |------------------------------------->| + | | | | | + |<-----------------------| | | | + ``` + + The prepared statement identifier is deterministic (it's a hash of the query string), so it is + the same for all nodes. + +2. if a node crashes, it might lose all of its prepared statements (this depends on the version: + since Cassandra 3.10, prepared statements are stored in a table, and the node is able to + reprepare on its own when it restarts). So the driver keeps a client-side cache; anytime a node + is marked back up, the driver re-prepares all statements on it; + +3. finally, if the driver tries to execute a statement and finds out that the coordinator doesn't + know about it, it will re-prepare the statement on the fly (this is transparent for the client, + but will cost two extra roundtrips): + + ```ditaa + client driver node1 + --+-------------------------------+------------------------------+-- + | | | + |session.execute(boundStatement)| | + +------------------------------>| | + | | EXECUTE(id, values) | + | |----------------------------->| + | | | + | | UNPREPARED | + | |<-----------------------------| + | | | + | | | + | | PREPARE(query) | + | |----------------------------->| + | | | + | | PREPARED(id) | + | |<-----------------------------| + | | | + | | | + | | EXECUTE(id, values) | + | |----------------------------->| + | | | + | | ROWS | + | |<-----------------------------| + | | | + |<------------------------------| | + ``` + +You can customize these strategies through the [configuration](../../configuration/): + +* `datastax-java-driver.prepared-statements.prepare-on-all-nodes` controls whether statements are + initially re-prepared on other hosts (step 1 above); +* `datastax-java-driver.prepared-statements.reprepare-on-up` controls how statements are re-prepared + on a node that comes back up (step 2 above). + +Read the `reference.conf` file provided with the driver for a detailed description of each of those +options. + +### Avoid preparing 'SELECT *' queries + +Both the driver and Cassandra maintain a mapping of `PreparedStatement` queries to their metadata. +When a change is made to a table, such as a column being added or dropped, there is currently no +mechanism for Cassandra to invalidate the existing metadata. Because of this, the driver is not able +to properly react to these changes and will improperly read rows after a schema change is made. + +Therefore it is currently recommended to not create prepared statements for 'SELECT *' queries if +you plan on making schema changes involving adding or dropping columns. Instead, you should list all +columns of interest in your statement, i.e.: `SELECT a, b, c FROM tbl`. + +This will be addressed in a future release of both Cassandra and the driver. Follow +[CASSANDRA-10786] and [JAVA-1196] for more information. + +[BoundStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BoundStatement.html +[CASSANDRA-10786]: https://issues.apache.org/jira/browse/CASSANDRA-10786 +[JAVA-1196]: https://datastax-oss.atlassian.net/browse/JAVA-1196 \ No newline at end of file diff --git a/manual/core/statements/simple/README.md b/manual/core/statements/simple/README.md new file mode 100644 index 00000000000..0eac2e4c183 --- /dev/null +++ b/manual/core/statements/simple/README.md @@ -0,0 +1,173 @@ +## Simple statements + +Use [SimpleStatement] for queries that will be executed only once (or just a few times): + +```java +SimpleStatement statement = + SimpleStatement.newInstance( + "SELECT value FROM application_params WHERE name = 'greeting_message'"); +session.execute(statement); +``` + +Each time you execute a simple statement, Cassandra parses the query string again; nothing is cached +(neither on the client nor on the server): + +```ditaa +client driver Cassandra +--+----------------------------------+---------------------+------ + | | | + | session.execute(SimpleStatement) | | + |--------------------------------->| | + | | QUERY(query_string) | + | |-------------------->| + | | | + | | | + | | | - parse query string + | | | - execute query + | | | + | | ROWS | + | |<--------------------| + | | | + |<---------------------------------| | +``` + +If you execute the same query often (or a similar query with different column values), consider a +[prepared statement](../prepared/) instead. + +### Creating an instance + +The driver provides various ways to create simple statements instances. First, `SimpleStatement` has +a few static factory methods: + +```java +SimpleStatement statement = + SimpleStatement.newInstance( + "SELECT value FROM application_params WHERE name = 'greeting_message'"); +``` + +You can then use setter methods to configure additional options. Note that, like all statement +implementations, simple statements are immutable, so these methods return a new instance each time. +Make sure you don't ignore the result: + +```java +// WRONG: ignores the result +statement.setIdempotent(true); + +// Do this instead: +statement = statement.setIdempotent(true); +``` + +If you have many options to set, you can use a builder to avoid creating intermediary instances: + +```java +SimpleStatement statement = + SimpleStatement.builder("SELECT value FROM application_params WHERE name = 'greeting_message'") + .withIdempotence(true) + .build(); +``` + +Finally, `Session` provides a shorthand method when you only have a simple query string: + +```java +session.execute("SELECT value FROM application_params WHERE name = 'greeting_message'"); +``` + +### Using values + +Instead of hard-coding everything in the query string, you can use bind markers and provide values +separately: + +* by position: + + ```java + SimpleStatement.builder("SELECT value FROM application_params WHERE name = ?") + .addPositionalValues("greeting_message") + .build(); + ``` +* by name: + + ```java + SimpleStatement.builder("SELECT value FROM application_params WHERE name = :n") + .addNamedValue("n", "greeting_message") + .build(); + ``` + +This syntax has a few advantages: + +* if the values come from some other part of your code, it looks cleaner than doing the + concatenation yourself; +* you don't need to translate the values to their string representation. The driver will send them + alongside the query, in their serialized binary form. + +The number of values must match the number of placeholders in the query string, and their types must +match the database schema. Note that the driver does not parse simple statements, so it cannot +perform those checks on the client side; if you make a mistake, the query will be sent anyway, and +the server will reply with an error, that gets translated into a driver exception: + +```java +session.execute( + SimpleStatement.builder("SELECT value FROM application_params WHERE name = :n") + .addPositionalValues("greeting_message", "extra_value") + .build()); +// Exception in thread "main" com.datastax.oss.driver.api.core.servererrors.InvalidQueryException: +// Invalid amount of bind variables +``` + +### Type inference + +Another consequence of not parsing query strings is that the driver has to guess how to serialize +values, based on their Java type (see the [default type mappings](../../#cql-to-java-type-mapping)). +This can be tricky, in particular for numeric types: + +```java +// schema: create table bigints(b bigint primary key) +session.execute( + SimpleStatement.builder("INSERT INTO bigints (b) VALUES (?)") + .addPositionalValues(1) + .build()); +// Exception in thread "main" com.datastax.oss.driver.api.core.servererrors.InvalidQueryException: +// Expected 8 or 0 byte long (4) +``` + +The problem here is that the literal `1` has the Java type `int`. So the driver serializes it as a +CQL `int` (4 bytes), but the server expects a CQL `bigint` (8 bytes). The fix is to specify the +correct Java type: + +```java +session.execute( + SimpleStatement.builder("INSERT INTO bigints (b) VALUES (?)") + .addPositionalValues(1L) // long literal + .build()); +``` + +Similarly, strings are always serialized to `varchar`, so you could have a problem if you target an +`ascii` column: + +```java +// schema: create table ascii_quotes(id int primary key, t ascii) +session.execute( + SimpleStatement.builder("INSERT INTO ascii_quotes (id, t) VALUES (?, ?)") + .addPositionalValues(1, "Touché sir, touché...") + .build()); +// Exception in thread "main" com.datastax.oss.driver.api.core.servererrors.InvalidQueryException: +// Invalid byte for ascii: -61 +``` + +In that situation, there is no way to hint at the correct type. Fortunately, you can encode the +value manually as a workaround: + +```java +TypeCodec codec = cluster.getContext().codecRegistry().codecFor(DataTypes.ASCII); +ByteBuffer bytes = + codec.encode("Touché sir, touché...", cluster.getContext().protocolVersion()); + +session.execute( + SimpleStatement.builder("INSERT INTO ascii_quotes (id, t) VALUES (?, ?)") + .addPositionalValues(1, bytes) + .build()); +``` + +Or you could also use [prepared statements](../prepared/), which don't have this limitation since +parameter types are known in advance. + +[SimpleStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/SimpleStatement.html From 390dfd9842f7e7909e5f540152bd7693f02c09a7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Jul 2017 10:04:57 -0700 Subject: [PATCH 151/742] Adapt code for native-protocol change --- .../internal/core/adminrequest/AdminRequestHandler.java | 2 +- .../oss/driver/internal/core/adminrequest/AdminResult.java | 4 ++-- .../oss/driver/internal/core/channel/ProtocolInitHandler.java | 2 +- .../datastax/oss/driver/internal/core/cql/Conversions.java | 4 ++-- .../oss/driver/internal/core/cql/CqlRequestHandler.java | 2 +- .../com/datastax/oss/driver/internal/core/TestResponses.java | 3 ++- .../driver/internal/core/cql/CqlRequestHandlerTestBase.java | 3 ++- .../oss/driver/internal/core/session/ReprepareOnUpTest.java | 3 ++- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 916eea48f5a..cdf97f55c77 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -139,7 +139,7 @@ public void onResponse(Frame responseFrame) { LOG.debug("[{}] Got response {}", logPrefix, responseFrame.message); if (message instanceof Rows) { Rows rows = (Rows) message; - ByteBuffer pagingState = rows.metadata.pagingState; + ByteBuffer pagingState = rows.getMetadata().pagingState; AdminRequestHandler nextHandler = (pagingState == null) ? null : this.copy(pagingState); result.complete(new AdminResult(rows, nextHandler, channel.protocolVersion())); } else if (message instanceof Prepared) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java index 78f7e0b89ea..63d216cc750 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java @@ -42,10 +42,10 @@ public class AdminResult implements Iterable { private final ProtocolVersion protocolVersion; public AdminResult(Rows rows, AdminRequestHandler nextHandler, ProtocolVersion protocolVersion) { - this.data = rows.data; + this.data = rows.getData(); ImmutableMap.Builder columnSpecsBuilder = ImmutableMap.builder(); - for (ColumnSpec spec : rows.metadata.columnSpecs) { + for (ColumnSpec spec : rows.getMetadata().columnSpecs) { columnSpecsBuilder.put(spec.name, spec); } // Admin queries are simple selects only, so there are no duplicate names (if that ever diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index cf7b9113bc6..d59dc662241 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -228,7 +228,7 @@ void onResponse(Message response) { String.format("server replied '%s'", ((Error) response).message))); } else if (step == Step.GET_CLUSTER_NAME && response instanceof Rows) { Rows rows = (Rows) response; - List row = rows.data.poll(); + List row = rows.getData().poll(); String actualClusterName = getString(row, 0); if (expectedClusterName != null && !expectedClusterName.equals(actualClusterName)) { fail( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 48a3b6200e7..e528f99c625 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -206,9 +206,9 @@ static AsyncResultSet toResultSet( ColumnDefinitions columnDefinitions = (statement instanceof BoundStatement) ? ((BoundStatement) statement).getPreparedStatement().getResultSetDefinitions() - : toColumnDefinitions(rows.metadata, context); + : toColumnDefinitions(rows.getMetadata(), context); return new DefaultAsyncResultSet( - columnDefinitions, executionInfo, rows.data, session, context); + columnDefinitions, executionInfo, rows.getData(), session, context); } else if (result instanceof Prepared) { // This should never happen throw new IllegalArgumentException("Unexpected PREPARED response to a CQL query"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 23147f05b17..58ff9d429b7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -282,7 +282,7 @@ private void setFinalResult( private ExecutionInfo buildExecutionInfo( NodeResponseCallback callback, Result resultMessage, Frame responseFrame) { ByteBuffer pagingState = - (resultMessage instanceof Rows) ? ((Rows) resultMessage).metadata.pagingState : null; + (resultMessage instanceof Rows) ? ((Rows) resultMessage).getMetadata().pagingState : null; return new DefaultExecutionInfo( (Statement) request, callback.node, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java index f7bf9cf0b4c..97b2b827ecd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -17,6 +17,7 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.DefaultRows; import com.datastax.oss.protocol.internal.response.result.RawType; import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.RowsMetadata; @@ -40,6 +41,6 @@ public static Rows clusterNameResponse(String actualClusterName) { RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); Queue> data = Lists.newLinkedList(); data.add(Lists.newArrayList(ByteBuffer.wrap(actualClusterName.getBytes(Charsets.UTF_8)))); - return new Rows(metadata, data); + return new DefaultRows(metadata, data); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index f459db4c879..5f01f59de11 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -23,6 +23,7 @@ import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.DefaultRows; import com.datastax.oss.protocol.internal.response.result.RawType; import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.RowsMetadata; @@ -84,7 +85,7 @@ protected static Message singleRow() { new int[] {}); Queue> data = new LinkedList<>(); data.add(ImmutableList.of(Bytes.fromHexString("0x68656C6C6F2C20776F726C64"))); - return new Rows(metadata, data); + return new DefaultRows(metadata, data); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index ea584bcb8d4..cf56bcdde10 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -27,6 +27,7 @@ import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.datastax.oss.protocol.internal.response.result.DefaultRows; import com.datastax.oss.protocol.internal.response.result.RawType; import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.RowsMetadata; @@ -334,6 +335,6 @@ private Rows preparedIdRows(char... values) { for (char value : values) { data.add(ImmutableList.of(Bytes.fromHexString("0x0" + value))); } - return new Rows(rowsMetadata, data); + return new DefaultRows(rowsMetadata, data); } } From 4623db3ff2ebb452d126ea80660215ae9e04a60f Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Jul 2017 10:30:31 -0700 Subject: [PATCH 152/742] JAVA-1577: Set default consistency level to LOCAL_ONE --- changelog/README.md | 1 + core/src/main/resources/reference.conf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index c372ea1cef6..bb521c2c306 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1577: Set default consistency level to LOCAL_ONE - [bug] JAVA-1548: Retry idempotent statements on READ_TIMEOUT and UNAVAILABLE - [bug] JAVA-1562: Fix various issues around heart beats - [improvement] JAVA-1546: Make all statement implementations immutable diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 839217cc491..c25e736df3e 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -171,7 +171,7 @@ datastax-java-driver { # # This option can be changed at runtime, the new value will be used for requests issued after # the change. It can be overridden in a profile. - consistency = ONE + consistency = LOCAL_ONE # The page size. This controls how many rows will be retrieved simultaneously in a single # network roundtrip (the goal being to avoid loading too many results in memory at the same From 4db24b50f5fccd2fe58dd5b7874ccf8a490b8f3e Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Jul 2017 11:05:18 -0700 Subject: [PATCH 153/742] Upgrade native-protocol to 1.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4aea88355fb..5767c3755d5 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ com.datastax.oss native-protocol - 1.4.0-SNAPSHOT + 1.4.0 io.netty From 2bc7afe7ea82f518e27f3e7399a5d96a4be7c9d4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 26 Jul 2017 11:52:00 -0700 Subject: [PATCH 154/742] JAVA-1541: Reorganize configuration connection.max-frame-length => protocol.max-frame-length auth-provider => protocol.auth-provider pooling.local.connections => connection.pool.local.size pooling.remote.connections => connection.pool.remote.size retry-policy => request.retry-policy speculative-execution-policy => request.speculative-execution-policy timestamp-generator => request.timestamp-generator --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 19 ++- .../internal/core/channel/ChannelFactory.java | 2 +- .../internal/core/pool/ChannelPool.java | 4 +- core/src/main/resources/reference.conf | 132 +++++++++--------- .../core/pool/ChannelPoolInitTest.java | 10 +- .../core/pool/ChannelPoolKeyspaceTest.java | 4 +- .../core/pool/ChannelPoolReconnectTest.java | 4 +- .../core/pool/ChannelPoolResizeTest.java | 28 ++-- .../core/pool/ChannelPoolShutdownTest.java | 4 +- 10 files changed, 106 insertions(+), 102 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index bb521c2c306..3b9bc27a3a4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1541: Reorganize configuration - [improvement] JAVA-1577: Set default consistency level to LOCAL_ONE - [bug] JAVA-1548: Retry idempotent statements on READ_TIMEOUT and UNAVAILABLE - [bug] JAVA-1562: Fix various issues around heart beats diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 6e66f97e0a7..47555fd362b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -22,17 +22,21 @@ */ public enum CoreDriverOption implements DriverOption { CONTACT_POINTS("contact-points", false), + PROTOCOL_VERSION("protocol.version", false), + PROTOCOL_MAX_FRAME_LENGTH("protocol.max-frame-length", true), + CLUSTER_NAME("cluster-name", false), CONFIG_RELOAD_INTERVAL("config-reload-interval", false), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), CONNECTION_SET_KEYSPACE_TIMEOUT("connection.set-keyspace-timeout", true), - CONNECTION_MAX_FRAME_LENGTH("connection.max-frame-length", true), CONNECTION_MAX_REQUESTS("connection.max-requests-per-connection", true), CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), CONNECTION_MAX_ORPHAN_REQUESTS("connection.max-orphan-requests", true), + CONNECTION_POOL_LOCAL_SIZE("connection.pool.local.size", true), + CONNECTION_POOL_REMOTE_SIZE("connection.pool.remote.size", true), REQUEST_TIMEOUT("request.timeout", true), REQUEST_CONSISTENCY("request.consistency", true), @@ -40,6 +44,8 @@ public enum CoreDriverOption implements DriverOption { REQUEST_SERIAL_CONSISTENCY("request.serial-consistency", true), REQUEST_WARN_IF_SET_KEYSPACE("request.warn-if-set-keyspace", true), REQUEST_DEFAULT_IDEMPOTENCE("request.default-idempotence", true), + RETRY_POLICY_ROOT("request.retry-policy", true), + SPECULATIVE_EXECUTION_POLICY_ROOT("request.speculative-execution-policy", true), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), @@ -51,12 +57,8 @@ public enum CoreDriverOption implements DriverOption { // "Sub-option" for all the policies, etc. RELATIVE_POLICY_CLASS("class", false), - RETRY_POLICY_ROOT("retry-policy", true), - LOAD_BALANCING_POLICY_ROOT("load-balancing-policy", true), - SPECULATIVE_EXECUTION_POLICY_ROOT("speculative-execution-policy", true), - RECONNECTION_POLICY_ROOT("connection.reconnection-policy", true), RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY("base-delay", false), RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY("max-delay", false), @@ -68,12 +70,9 @@ public enum CoreDriverOption implements DriverOption { REPREPARE_MAX_PARALLELISM("prepared-statements.reprepare-on-up.max-parallelism", false), REPREPARE_TIMEOUT("prepared-statements.reprepare-on-up.timeout", false), - POOLING_LOCAL_CONNECTIONS("pooling.local.connections", true), - POOLING_REMOTE_CONNECTIONS("pooling.remote.connections", true), - ADDRESS_TRANSLATOR_ROOT("address-translator", true), - AUTH_PROVIDER_ROOT("auth-provider", false), + AUTH_PROVIDER_ROOT("protocol.auth-provider", false), RELATIVE_PLAIN_TEXT_AUTH_USERNAME("username", false), RELATIVE_PLAIN_TEXT_AUTH_PASSWORD("password", false), @@ -83,7 +82,7 @@ public enum CoreDriverOption implements DriverOption { METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), - TIMESTAMP_GENERATOR_ROOT("timestamp-generator", true), + TIMESTAMP_GENERATOR_ROOT("request.timestamp-generator", true), RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD("drift-warning.threshold", false), RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL("drift-warning.interval", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index a19cf415696..771d6402cc8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -182,7 +182,7 @@ protected void initChannel(Channel channel) throws Exception { .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) .toMillis(); int maxFrameLength = - (int) defaultConfigProfile.getBytes(CoreDriverOption.CONNECTION_MAX_FRAME_LENGTH); + (int) defaultConfigProfile.getBytes(CoreDriverOption.PROTOCOL_MAX_FRAME_LENGTH); int maxRequestsPerConnection = defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); int maxOrphanRequests = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index f326a0aca39..4fb5930cc7c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -509,8 +509,8 @@ private int getConfiguredSize(NodeDistance distance) { .getDefaultProfile() .getInt( (distance == NodeDistance.LOCAL) - ? CoreDriverOption.POOLING_LOCAL_CONNECTIONS - : CoreDriverOption.POOLING_REMOTE_CONNECTIONS); + ? CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE + : CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE); } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index c25e736df3e..91201bade7f 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -41,6 +41,22 @@ datastax-java-driver { # connecting to lower-version nodes later. You should force the lowest common protocol version # in that case. // version = V4 + + # The maximum length of the frames supported by the driver. Beyond that limit, requests will + # fail with an exception + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. + max-frame-length = 256 MB + + # The components that handles authentication on each new connection. + auth-provider { + # This property is optional; if it is not present, no authentication will occur. + // class = com.datastax.driver.api.core.auth.PlainTextAuthProvider + # Sample configuration for the plain-text provider: + // username = cassandra + // password = cassandra + } } # A name that uniquely identifies the driver instance created from this configuration. This is @@ -58,18 +74,10 @@ datastax-java-driver { # To disable periodic reloading, set this to 0. config-reload-interval = 5 minutes - retry-policy { - class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy - } - load-balancing-policy { class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy } - speculative-execution-policy { - class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy - } - connection { # The timeout to use for internal queries that run as part of the initialization process, just # after we open a connection. If this timeout fires, the initialization of the connection will @@ -125,12 +133,6 @@ datastax-java-driver { # issued after the change. timeout = ${datastax-java-driver.connection.init-query-timeout} } - # The maximum length of the frames supported by the driver. Beyond that limit, requests will - # fail with an exception - # - # This option can be changed at runtime, the new value will be used for new connections created - # after the change. - max-frame-length = 256 MB reconnection-policy { class = com.datastax.oss.driver.api.core.connection.ExponentialReconnectionPolicy @@ -157,6 +159,21 @@ datastax-java-driver { # The reschedule interval. reschedule-interval = 10 microseconds } + + # The driver maintains a connection pool to each node, according to the distance assigned to it + # by the load balancing policy. If the distance is IGNORED, no connections are maintained. + pool { + local { + # The number of connections in the pool. + # + # This option can be changed at runtime; when the change is detected, all active pools will + # adjust their size. + size = 1 + } + remote { + size = 1 + } + } } request { @@ -213,36 +230,44 @@ datastax-java-driver { # This option can be changed at runtime, the new value will be used for requests issued after # the change. It can be overridden in a profile. default-idempotence = false - } - # The generator that assigns a microsecond timestamp to each query sent by the driver. - timestamp-generator { - # The implementation to use. Built-in options are (all from the package - # com.datastax.oss.driver.api.core.time): - # - AtomicTimestampGenerator: timestamps are guaranteed to be unique across all client threads. - # - ThreadLocalTimestampGenerator: timestamps that are guaranteed to be unique within each - # thread only. - # - ServerSideTimestampGenerator: do not generate timestamps, let the server assign them. - class = com.datastax.oss.driver.api.core.time.AtomicTimestampGenerator - - # To guarantee that queries are applied on the server in the same order as the client issued - # them, timestamps must be strictly increasing. But this means that, if the driver sends more - # than one query per microsecond, timestamps will drift in the future. While this could happen - # occasionally under high load, it should not be a regular occurrence. Therefore the built-in - # implementations log a warning to detect potential issues. - drift-warning { - # How far in the future timestamps are allowed to drift before the warning is logged. - # If it is undefined or set to 0, warnings are disabled. - threshold = 1 second - # How often the warning will be logged if timestamps keep drifting above the threshold. - interval = 10 seconds + # The generator that assigns a microsecond timestamp to each request. + timestamp-generator { + # The implementation to use. Built-in options are (all from the package + # com.datastax.oss.driver.api.core.time): + # - AtomicTimestampGenerator: timestamps are guaranteed to be unique across all client threads. + # - ThreadLocalTimestampGenerator: timestamps that are guaranteed to be unique within each + # thread only. + # - ServerSideTimestampGenerator: do not generate timestamps, let the server assign them. + class = com.datastax.oss.driver.api.core.time.AtomicTimestampGenerator + + # To guarantee that queries are applied on the server in the same order as the client issued + # them, timestamps must be strictly increasing. But this means that, if the driver sends more + # than one query per microsecond, timestamps will drift in the future. While this could happen + # occasionally under high load, it should not be a regular occurrence. Therefore the built-in + # implementations log a warning to detect potential issues. + drift-warning { + # How far in the future timestamps are allowed to drift before the warning is logged. + # If it is undefined or set to 0, warnings are disabled. + threshold = 1 second + # How often the warning will be logged if timestamps keep drifting above the threshold. + interval = 10 seconds + } + + # Whether to force the driver to use Java's millisecond-precision system clock. + # If this is false, the driver will try to access the microsecond-precision OS clock via native + # calls (and fallback to the Java one if the native calls fail). + # Unless you explicitly want to avoid native calls, there's no reason to change this. + force-java-clock = false + } + + retry-policy { + class = com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy } - # Whether to force the driver to use Java's millisecond-precision system clock. - # If this is false, the driver will try to access the microsecond-precision OS clock via native - # calls (and fallback to the Java one if the native calls fail). - # Unless you explicitly want to avoid native calls, there's no reason to change this. - force-java-clock = false + speculative-execution-policy { + class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy + } } prepared-statements { @@ -308,21 +333,6 @@ datastax-java-driver { } } - # The driver maintains a connection pool to each node, according to the distance assigned to it - # by the load balancing policy. If the distance is IGNORED, no connections are maintained. - pooling { - local { - # The number of connections in the pool. - # - # This option can be changed at runtime; when the change is detected, all active pools will - # adjust their size. - connections = 1 - } - remote { - connections = 1 - } - } - metadata { # Topology events are external signals that inform the driver of the state of Cassandra nodes # (by default, they correspond to gossip events received on the control connection). @@ -340,6 +350,7 @@ datastax-java-driver { max-events = 20 } } + # The address translator to use to convert the addresses sent by Cassandra nodes into ones that # the driver uses to connect. # This is only needed if the nodes are not directly reachable from the driver (for example, the @@ -349,14 +360,7 @@ datastax-java-driver { # This default implementation always returns the same address unchanged. class = com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator } - # The auth provider that will handle authentication for each new connection to a server. - auth-provider { - # This property is optional; if it is not present, no authentication will occur. - // class = com.datastax.driver.api.core.auth.PlainTextAuthProvider - # Sample configuration for the plain-text provider: - // username = cassandra - // password = cassandra - } + # The SSL engine factory that will initialize an SSL engine for each new connection to a server. ssl-engine-factory { # This property is optional; if it is not present, SSL won't be activated. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 5572183aa2d..8e32dd33184 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -38,7 +38,7 @@ public class ChannelPoolInitTest extends ChannelPoolTestBase { @Test public void should_initialize_when_all_channels_succeed() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -65,7 +65,7 @@ public void should_initialize_when_all_channels_succeed() throws Exception { @Test public void should_initialize_when_all_channels_fail() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -88,7 +88,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { @Test public void should_indicate_when_keyspace_failed_on_all_channels() { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -107,7 +107,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { @Test public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); ClusterNameMismatchException error = new ClusterNameMismatchException(ADDRESS, "actual", "expected"); @@ -134,7 +134,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { // Short delay so we don't have to wait in the test Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index 5a76faf50e9..1343e823329 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -33,7 +33,7 @@ public class ChannelPoolKeyspaceTest extends ChannelPoolTestBase { @Test public void should_switch_keyspace_on_existing_channels() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -69,7 +69,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { public void should_switch_keyspace_on_pending_channels() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); CompletableFuture channel1Future = new CompletableFuture<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 1b03d7a7860..7194063b246 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -37,7 +37,7 @@ public class ChannelPoolReconnectTest extends ChannelPoolTestBase { public void should_reconnect_when_channel_closes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -88,7 +88,7 @@ public void should_reconnect_when_channel_closes() throws Exception { public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index 5f741958e5e..4d12f9bada1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -36,8 +36,8 @@ public class ChannelPoolResizeTest extends ChannelPoolTestBase { @Test public void should_shrink_outside_of_reconnection() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -77,8 +77,8 @@ public void should_shrink_outside_of_reconnection() throws Exception { public void should_shrink_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -138,8 +138,8 @@ public void should_shrink_during_reconnection() throws Exception { public void should_grow_outside_of_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -188,8 +188,8 @@ public void should_grow_outside_of_reconnection() throws Exception { public void should_grow_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -261,7 +261,7 @@ public void should_grow_during_reconnection() throws Exception { public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -290,7 +290,7 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc assertThat(pool.channels).containsOnly(channel1, channel2); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); @@ -312,7 +312,7 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc public void should_resize_during_reconnection_if_config_changes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -350,7 +350,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(4); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); @@ -385,7 +385,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti public void should_ignore_config_change_if_not_relevant() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(2); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -408,7 +408,7 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); // Config changes, but not for our distance - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_REMOTE_CONNECTIONS)).thenReturn(1); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(1); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 6a2c627bfa4..178322c2506 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -38,7 +38,7 @@ public class ChannelPoolShutdownTest extends ChannelPoolTestBase { public void should_close_all_channels_when_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -109,7 +109,7 @@ public void should_close_all_channels_when_closed() throws Exception { public void should_force_close_all_channels_when_force_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.POOLING_LOCAL_CONNECTIONS)).thenReturn(3); + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); From 92bfcf0a6e350b076daeec45b52925b1b6476902 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Wed, 26 Jul 2017 16:59:14 -0500 Subject: [PATCH 155/742] Fix commented class for auth-provider and ssl-engine-factory --- core/src/main/resources/reference.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 91201bade7f..f8026cfcb93 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -52,7 +52,7 @@ datastax-java-driver { # The components that handles authentication on each new connection. auth-provider { # This property is optional; if it is not present, no authentication will occur. - // class = com.datastax.driver.api.core.auth.PlainTextAuthProvider + // class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider # Sample configuration for the plain-text provider: // username = cassandra // password = cassandra @@ -364,7 +364,7 @@ datastax-java-driver { # The SSL engine factory that will initialize an SSL engine for each new connection to a server. ssl-engine-factory { # This property is optional; if it is not present, SSL won't be activated. - // class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory + // class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory # Sample configuration for the default SSL factory: # The cipher suites to enable when creating an SSLEngine for a connection. # This property is optional. If it is not present, the driver won't explicitly enable cipher From 588e2a713046152b6d6a1c36529318e98de8a207 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 27 Jul 2017 12:00:43 -0500 Subject: [PATCH 156/742] Integration test module and tests (#2) --- build.yaml | 18 + .../LoadBalancingPolicyWrapperTest.java | 10 +- .../ScheduledTaskCapturingEventLoop.java | 2 +- integration-tests/pom.xml | 142 +++ .../driver/api/core/ProtocolVersionIT.java | 90 ++ .../core/auth/PlainTextAuthProviderIT.java | 71 ++ .../driver/api/core/cql/AsyncResultSetIT.java | 185 ++++ .../driver/api/core/cql/BatchStatementIT.java | 278 ++++++ .../api/core/cql/SimpleStatementIT.java | 266 ++++++ .../oss/driver/api/core/data/DataTypeIT.java | 817 ++++++++++++++++++ .../api/core/heartbeat/HeartbeatIT.java | 315 +++++++ .../api/core/retry/DefaultRetryPolicyIT.java | 429 +++++++++ .../core/ssl/DefaultSslEngineFactoryIT.java | 48 + ...faultSslEngineFactoryWithClientAuthIT.java | 52 ++ ...ineFactoryWithClientAuthNotProvidedIT.java | 49 ++ ...ineFactoryWithTruststoreNotProvidedIT.java | 42 + .../oss/driver/categories/IsolatedTests.java | 19 + .../oss/driver/categories/LongTests.java | 19 + .../src/test/resources/client.crt | 19 + .../src/test/resources/client.key | 28 + .../src/test/resources/client.keystore | Bin 0 -> 2292 bytes .../src/test/resources/client.truststore | Bin 0 -> 1009 bytes .../src/test/resources/logback-test.xml | 28 + .../src/test/resources/server.keystore | Bin 0 -> 2299 bytes .../src/test/resources/server.truststore | Bin 0 -> 1004 bytes pom.xml | 21 +- test-infra/pom.xml | 57 ++ .../api/testinfra/CassandraRequirement.java | 39 + .../api/testinfra/CassandraResourceRule.java | 49 ++ .../driver/api/testinfra/ccm/CcmBridge.java | 310 +++++++ .../oss/driver/api/testinfra/ccm/CcmRule.java | 43 + .../api/testinfra/ccm/CustomCcmRule.java | 97 +++ .../api/testinfra/cluster/ClusterRule.java | 207 +++++ .../SortingLoadBalancingPolicy.java | 96 ++ .../testinfra/simulacron/SimulacronRule.java | 84 ++ .../api/testinfra/utils/ConditionChecker.java | 150 ++++ .../driver/api/testinfra/utils/NodeUtils.java | 55 ++ .../internal/testinfra/ccm/BaseCcmRule.java | 123 +++ .../testinfra/cluster/TestConfigLoader.java | 41 + 39 files changed, 4295 insertions(+), 4 deletions(-) create mode 100644 build.yaml create mode 100644 integration-tests/pom.xml create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java create mode 100644 integration-tests/src/test/resources/client.crt create mode 100644 integration-tests/src/test/resources/client.key create mode 100644 integration-tests/src/test/resources/client.keystore create mode 100644 integration-tests/src/test/resources/client.truststore create mode 100644 integration-tests/src/test/resources/logback-test.xml create mode 100644 integration-tests/src/test/resources/server.keystore create mode 100644 integration-tests/src/test/resources/server.truststore create mode 100644 test-infra/pom.xml create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000000..571582df2d0 --- /dev/null +++ b/build.yaml @@ -0,0 +1,18 @@ +java: + - oraclejdk8 +os: + - ubuntu/trusty64/m3.large +cassandra: + - '2.1' + - '2.2' + - '3.0' + - '3.11' +build: + - type: maven + version: 3.2.5 + goals: verify -Plong + properties: | + ccm.cassandraVersion=$CCM_CASSANDRA_VERSION + - xunit: + - "**/target/surefire-reports/TEST-*.xml" + - "**/target/failsafe-reports/TEST-*.xml" diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 1d071eca1e6..b073572d1f9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -174,7 +174,7 @@ public void should_propagate_node_states_to_policy_after_init() { } @Test - public void should_accumulate_events_during_init_and_replay() { + public void should_accumulate_events_during_init_and_replay() throws InterruptedException { // Given // Hack to obtain concurrency: the main thread blocks in init, while another thread fires an // event on the bus @@ -206,7 +206,13 @@ public void should_accumulate_events_during_init_and_replay() { wrapper.init(); // Then - assertThat(thread.isAlive()).isFalse(); + // wait for init launch to signal that runnable is complete. + initLatch.await(100, TimeUnit.MILLISECONDS); Mockito.verify(loadBalancingPolicy).onDown(node1); + if (thread.isAlive()) { + // thread still completing - sleep for 100ms to allow thread to complete. + Thread.sleep(100); + } + assertThat(thread.isAlive()).isFalse(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index c7b288d12d6..09ae3ba900c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -107,7 +107,7 @@ public CapturedTask nextTask() { public void waitForNonScheduledTasks() { ScheduledFuture f = super.schedule(() -> null, 5, TimeUnit.NANOSECONDS); try { - Uninterruptibles.getUninterruptibly(f, 100, TimeUnit.MILLISECONDS); + Uninterruptibles.getUninterruptibly(f, 1, TimeUnit.SECONDS); } catch (ExecutionException e) { fail("unexpected error", e.getCause()); } catch (TimeoutException e) { diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml new file mode 100644 index 00000000000..81c890f883c --- /dev/null +++ b/integration-tests/pom.xml @@ -0,0 +1,142 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-SNAPSHOT + + + java-driver-integration-tests + jar + + DataStax Java driver for Apache Cassandra® - integration tests + + + + com.datastax.oss + java-driver-test-infra + ${project.parent.version} + test + + + com.tngtech.java + junit-dataprovider + test + + + ch.qos.logback + logback-classic + test + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + short-tests + + integration-test + verify + + + + + com.datastax.oss.driver.categories.LongTests,com.datastax.oss.driver.categories.IsolatedTests + + short + classes + 8 + + + + + + + + + + short + + + false + + + + long + + + false + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + long-tests + + integration-test + verify + + + com.datastax.oss.driver.categories.LongTests + long + + + + + isolated-tests + + integration-test + verify + + + com.datastax.oss.driver.categories.IsolatedTests + isolated + + 1 + false + + + + + + + + + + diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java new file mode 100644 index 00000000000..fdfaa743101 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import org.junit.Rule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProtocolVersionIT { + + @Rule public CcmRule ccm = CcmRule.getInstance(); + + @Rule public ClusterRule cluster = new ClusterRule(ccm, false, false); + + @CassandraRequirement( + min = "2.1", + max = "2.2", + description = "required to downgrade to an older version" + ) + @Test + public void should_downgrade_to_v3() { + try (Cluster v3cluster = cluster.defaultCluster()) { + assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); + + Session session = v3cluster.connect(); + session.execute("select * from system.local"); + } + } + + @CassandraRequirement( + min = "2.1", + max = "2.2", + description = "required to downgrade to an older version" + ) + @Test + public void should_fail_if_provided_version_isnt_supported() { + try (Cluster v4cluster = cluster.defaultCluster("protocol.version = V4")) { + assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(3); + + Session session = v4cluster.connect(); + session.execute("select * from system.local"); + } catch (AllNodesFailedException anfe) { + Throwable cause = anfe.getErrors().values().iterator().next(); + assertThat(cause).isInstanceOf(UnsupportedProtocolVersionException.class); + UnsupportedProtocolVersionException unsupportedException = + (UnsupportedProtocolVersionException) cause; + assertThat(unsupportedException.getAttemptedVersions()).containsOnly(CoreProtocolVersion.V4); + } + } + + @CassandraRequirement(min = "2.2", description = "required to meet default protocol version") + @Test + public void should_not_downgrade() { + try (Cluster v4cluster = cluster.defaultCluster()) { + assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(4); + + Session session = v4cluster.connect(); + session.execute("select * from system.local"); + } + } + + @CassandraRequirement(min = "2.2", description = "required to use an older protocol version") + @Test + public void should_use_explicitly_provided_protocol_version() { + try (Cluster v3cluster = cluster.defaultCluster("protocol.version = V3")) { + assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); + + Session session = v3cluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java new file mode 100644 index 00000000000..e49a6f0c19b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class PlainTextAuthProviderIT { + + @ClassRule + public static CustomCcmRule ccm = + CustomCcmRule.builder() + .withCassandraConfiguration("authenticator", "PasswordAuthenticator") + .withJvmArgs("-Dcassandra.superuser_setup_delay_ms=0") + .build(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); + + @Test + public void should_connect_with_credentials() { + try (Cluster authCluster = + cluster.defaultCluster( + "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", + "protocol.auth-provider.username = cassandra", + "protocol.auth-provider.password = cassandra")) { + Session session = authCluster.connect(); + session.execute("select * from system.local"); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_with_invalid_credentials() { + try (Cluster authCluster = + cluster.defaultCluster( + "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", + "protocol.auth-provider.username = baduser", + "protocol.auth-provider.password = badpass")) { + Session session = authCluster.connect(); + session.execute("select * from system.local"); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_without_credentials() { + try (Cluster plainCluster = cluster.defaultCluster()) { + Session session = plainCluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java new file mode 100644 index 00000000000..cd982d6a84b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AsyncResultSetIT { + + private static final int PAGE_SIZE = 100; + private static final int ROWS_PER_PARTITION = 1000; + private static final String PARTITION_KEY1 = "part"; + private static final String PARTITION_KEY2 = "part2"; + + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule + public static ClusterRule cluster = new ClusterRule(ccm, "request.page-size = " + PAGE_SIZE); + + @BeforeClass + public static void setupSchema() { + // create table and load data across two partitions so we can test paging across tokens. + cluster + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test (k0 text, k1 int, v int, PRIMARY KEY(k0, k1))") + .withConfigProfile(cluster.slowProfile()) + .build()); + + PreparedStatement prepared = + cluster.session().prepare("INSERT INTO test (k0, k1, v) VALUES (?, ?, ?)"); + + BatchStatementBuilder batchPart1 = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder batchPart2 = BatchStatement.builder(BatchType.UNLOGGED); + for (int i = 0; i < ROWS_PER_PARTITION; i++) { + batchPart1.addStatement(prepared.bind(PARTITION_KEY1, i, i)); + batchPart2.addStatement( + prepared.bind(PARTITION_KEY2, i + ROWS_PER_PARTITION, i + ROWS_PER_PARTITION)); + } + + cluster.session().execute(batchPart1.withConfigProfile(cluster.slowProfile()).build()); + cluster.session().execute(batchPart2.withConfigProfile(cluster.slowProfile()).build()); + } + + @Test + public void should_only_iterate_over_rows_in_current_page() throws Exception { + // very basic test that just ensures that iterating over an AsyncResultSet only visits the first page. + CompletionStage result = + cluster + .session() + .executeAsync( + SimpleStatement.builder("SELECT * FROM test where k0 = ?") + .addPositionalValue(PARTITION_KEY1) + .build()); + + AsyncResultSet rs = result.toCompletableFuture().get(); + + // Should only receive rows in page. + assertThat(rs.remaining()).isEqualTo(PAGE_SIZE); + assertThat(rs.hasMorePages()).isTrue(); + + Iterator rowIt = rs.iterator(); + for (int i = 0; i < PAGE_SIZE; i++) { + Row row = rowIt.next(); + assertThat(row.getString("k0")).isEqualTo(PARTITION_KEY1); + assertThat(row.getInt("k1")).isEqualTo(i); + assertThat(row.getInt("v")).isEqualTo(i); + } + } + + @Test + public void should_iterate_over_all_pages_asynchronously_single_partition() throws Exception { + // Validates async paging behavior over single partition. + CompletionStage result = + cluster + .session() + .executeAsync( + SimpleStatement.builder("SELECT * FROM test where k0 = ?") + .addPositionalValue(PARTITION_KEY1) + .build()) + .thenCompose(new AsyncResultSetConsumingFunction()); + + PageStatistics stats = result.toCompletableFuture().get(); + + assertThat(stats.rows).isEqualTo(ROWS_PER_PARTITION); + assertThat(stats.pages).isEqualTo((int) (Math.ceil(ROWS_PER_PARTITION / (double) PAGE_SIZE))); + } + + @Test + public void should_iterate_over_all_pages_asynchronously_cross_partition() throws Exception { + // Validates async paging behavior over a range query. + CompletionStage result = + cluster + .session() + .executeAsync("SELECT * FROM test") + .thenCompose(new AsyncResultSetConsumingFunction()); + + PageStatistics stats = result.toCompletableFuture().get(); + + assertThat(stats.rows).isEqualTo(ROWS_PER_PARTITION * 2); + assertThat(stats.pages) + .isEqualTo((int) (Math.ceil(ROWS_PER_PARTITION * 2 / (double) PAGE_SIZE))); + } + + private static class PageStatistics { + int rows; + int pages; + + PageStatistics(int rows, int pages) { + this.rows = rows; + this.pages = pages; + } + } + + private static class AsyncResultSetConsumingFunction + implements Function> { + + // number of rows paged before exercising this function. + private final int rowsSoFar; + // number of pages encountered before exercising this function. + private final int pagesSoFar; + + AsyncResultSetConsumingFunction() { + this(0, 0); + } + + AsyncResultSetConsumingFunction(int rowsSoFar, int pagesSoFar) { + this.rowsSoFar = rowsSoFar; + this.pagesSoFar = pagesSoFar; + } + + @Override + public CompletionStage apply(AsyncResultSet result) { + int consumedRows = rowsSoFar; + + // Only count page if it has rows. + int pages = result.remaining() == 0 ? pagesSoFar : pagesSoFar + 1; + + // iterate over page and ensure data is in order. + for (Row row : result) { + int v = row.getInt("v"); + if (v != consumedRows) { + CompletableFuture next = new CompletableFuture<>(); + next.completeExceptionally( + new Exception(String.format("Expected v == %d, got %d.", consumedRows, v))); + return next; + } + consumedRows++; + } + + if (result.hasMorePages()) { + return result + .fetchNextPage() + .thenComposeAsync(new AsyncResultSetConsumingFunction(consumedRows, pages)); + } else { + CompletableFuture next = new CompletableFuture<>(); + next.complete(new PageStatistics(consumedRows, pages)); + return next; + } + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java new file mode 100644 index 00000000000..daedb312a26 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import java.util.Iterator; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BatchStatementIT { + + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + + @Rule public TestName name = new TestName(); + + private static final int batchCount = 100; + + @BeforeClass + public static void createTable() { + String[] schemaStatements = + new String[] { + "CREATE TABLE test (k0 text, k1 int, v int, PRIMARY KEY (k0, k1))", + "CREATE TABLE counter1 (k0 text PRIMARY KEY, c counter)", + "CREATE TABLE counter2 (k0 text PRIMARY KEY, c counter)", + "CREATE TABLE counter3 (k0 text PRIMARY KEY, c counter)", + }; + + for (String schemaStatement : schemaStatements) { + cluster + .session() + .execute( + SimpleStatement.newInstance(schemaStatement).setConfigProfile(cluster.slowProfile())); + } + } + + @Test + public void should_execute_batch_of_simple_statements_with_variables() { + // Build a batch of batchCount simple statements, each with their own positional variables. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + for (int i = 0; i < batchCount; i++) { + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ?, ?)", name.getMethodName())) + .addPositionalValues(i, i + 1) + .build(); + builder.addStatement(insert); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + verifyBatchInsert(); + } + + @Test + public void should_execute_batch_of_bound_statements_with_variables() { + // Build a batch of batchCount statements with bound statements, each with their own positional variables. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) + .build(); + PreparedStatement preparedStatement = cluster.session().prepare(insert); + + for (int i = 0; i < batchCount; i++) { + builder.addStatement(preparedStatement.bind(i, i + 1)); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + verifyBatchInsert(); + } + + @Test + public void should_execute_batch_of_bound_statements_with_named_variables() { + // Build a batch of batchCount statements with bound statements, each with their own named variable values. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + PreparedStatement preparedStatement = + cluster.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); + + for (int i = 0; i < batchCount; i++) { + builder.addStatement( + preparedStatement + .boundStatementBuilder() + .setString("k0", name.getMethodName()) + .setInt("k1", i) + .setInt("v", i + 1) + .build()); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + verifyBatchInsert(); + } + + @Test + public void should_execute_batch_of_bound_and_simple_statements_with_variables() { + // Build a batch of batchCount statements with simple and bound statements alternating. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) + .build(); + PreparedStatement preparedStatement = cluster.session().prepare(insert); + + for (int i = 0; i < batchCount; i++) { + if (i % 2 == 1) { + SimpleStatement simpleInsert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ?, ?)", name.getMethodName())) + .addPositionalValues(i, i + 1) + .build(); + builder.addStatement(simpleInsert); + } else { + builder.addStatement(preparedStatement.bind(i, i + 1)); + } + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + verifyBatchInsert(); + } + + @Test + public void should_execute_cas_batch() { + // Build a batch with CAS operations on the same partition. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ? , ?) IF NOT EXISTS", + name.getMethodName())) + .build(); + PreparedStatement preparedStatement = cluster.session().prepare(insert); + + for (int i = 0; i < batchCount; i++) { + builder.addStatement(preparedStatement.bind(i, i + 1)); + } + + BatchStatement batchStatement = builder.build(); + ResultSet result = cluster.session().execute(batchStatement); + assertThat(result.wasApplied()).isTrue(); + + verifyBatchInsert(); + + // re execute same batch and ensure wasn't applied. + result = cluster.session().execute(batchStatement); + assertThat(result.wasApplied()).isFalse(); + } + + @Test + public void should_execute_counter_batch() { + // should be able to do counter increments in a counter batch. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.COUNTER); + + for (int i = 1; i <= 3; i++) { + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "UPDATE counter%d set c = c + %d where k0 = '%s'", + i, i, name.getMethodName())) + .build(); + builder.addStatement(insert); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + for (int i = 1; i <= 3; i++) { + ResultSet result = + cluster + .session() + .execute( + String.format( + "SELECT c from counter%d where k0 = '%s'", i, name.getMethodName())); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + Row row = result.iterator().next(); + assertThat(row.getLong("c")).isEqualTo(i); + } + } + + @Test(expected = InvalidQueryException.class) + public void should_fail_logged_batch_with_counter_increment() { + // should not be able to do counter inserts in a unlogged batch. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.LOGGED); + + for (int i = 1; i <= 3; i++) { + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "UPDATE counter%d set c = c + %d where k0 = '%s'", + i, i, name.getMethodName())) + .build(); + builder.addStatement(insert); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + } + + @Test(expected = InvalidQueryException.class) + public void should_fail_counter_batch_with_non_counter_increment() { + // should not be able to do a counter batch if it contains a non-counter increment statement. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.COUNTER); + + for (int i = 1; i <= 3; i++) { + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "UPDATE counter%d set c = c + %d where k0 = '%s'", + i, i, name.getMethodName())) + .build(); + builder.addStatement(insert); + } + // add a non-counter increment statement. + SimpleStatement simpleInsert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ?, ?)", name.getMethodName())) + .addPositionalValues(1, 2) + .build(); + builder.addStatement(simpleInsert); + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + } + + private void verifyBatchInsert() { + // validate data inserted by the batch. + Statement select = + SimpleStatement.builder("SELECT * from test where k0 = ?") + .addPositionalValue(name.getMethodName()) + .build(); + + ResultSet result = cluster.session().execute(select); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); + + Iterator rows = result.iterator(); + for (int i = 0; i < batchCount; i++) { + Row row = rows.next(); + assertThat(row.getString("k0")).isEqualTo(name.getMethodName()); + assertThat(row.getInt("k1")).isEqualTo(i); + assertThat(row.getInt("v")).isEqualTo(i + 1); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java new file mode 100644 index 00000000000..86b17a6821b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import java.util.concurrent.TimeUnit; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleStatementIT { + + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, "request.page-size = 20"); + + @Rule public TestName name = new TestName(); + + private static final String KEY = "test"; + + @BeforeClass + public static void setupSchema() { + // table where every column forms the primary key. + cluster + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") + .withConfigProfile(cluster.slowProfile()) + .build()); + for (int i = 0; i < 100; i++) { + cluster + .session() + .execute( + SimpleStatement.builder("INSERT INTO test (k, v) VALUES (?, ?)") + .addPositionalValues(KEY, i) + .build()); + } + + // table with simple primary key, single cell. + cluster + .session() + .execute( + SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v int)") + .withConfigProfile(cluster.slowProfile()) + .build()); + } + + @Test + public void should_use_paging_state_when_copied() { + Statement st = + SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); + ResultSet result = cluster.session().execute(st); + + // given a query created from a copy of a previous query with paging state from previous queries response. + st = st.copy(result.getExecutionInfo().getPagingState()); + + // when executing that query. + result = cluster.session().execute(st); + + // then the response should start on the page boundary. + assertThat(result.iterator().next().getInt("v")).isEqualTo(20); + } + + @Test + public void should_use_paging_state_when_provided_to_new_statement() { + Statement st = + SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); + ResultSet result = cluster.session().execute(st); + + // given a query created from a copy of a previous query with paging state from previous queries response. + st = + SimpleStatement.builder(String.format("SELECT v FROM test where k='%s'", KEY)) + .withPagingState(result.getExecutionInfo().getPagingState()) + .build(); + + // when executing that query. + result = cluster.session().execute(st); + + // then the response should start on the page boundary. + assertThat(result.iterator().next().getInt("v")).isEqualTo(20); + } + + @Test + @Ignore + public void should_fail_if_using_paging_state_from_different_query() { + Statement st = + SimpleStatement.builder("SELECT v FROM test WHERE k=:k").addNamedValue("k", KEY).build(); + ResultSet result = cluster.session().execute(st); + + // TODO Expect PagingStateException + + // given a new different query and providing the paging state from the previous query + // then an exception should be thrown indicating incompatible paging state + SimpleStatement.builder("SELECT v FROM test") + .withPagingState(result.getExecutionInfo().getPagingState()) + .build(); + } + + @Test + public void should_use_timestamp_when_set() { + // given inserting data with a timestamp 40 days in the past. + long timestamp = System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(40, TimeUnit.DAYS); + SimpleStatement insert = + SimpleStatement.builder("INSERT INTO test2 (k, v) values (?, ?)") + .addPositionalValues(name.getMethodName(), 0) + .withTimestamp(timestamp) + .build(); + + cluster.session().execute(insert); + + // when retrieving writetime of cell from that insert. + SimpleStatement select = + SimpleStatement.builder("SELECT writetime(v) as wv from test2 where k = ?") + .addPositionalValue(name.getMethodName()) + .build(); + + ResultSet result = cluster.session().execute(select); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + // then the writetime should equal the timestamp provided. + Row row = result.iterator().next(); + assertThat(row.getLong("wv")).isEqualTo(timestamp); + } + + @Test + @Ignore + public void should_use_tracing_when_set() { + // TODO currently there's no way to validate tracing was set since trace id is not set + // also write test to verify it is not set. + ResultSet result = + cluster + .session() + .execute(SimpleStatement.builder("select * from test").withTracing().build()); + } + + @Test + public void should_use_positional_values() { + // given a statement with positional values + SimpleStatement insert = + SimpleStatement.builder("INSERT into test2 (k, v) values (?, ?)") + .addPositionalValue(name.getMethodName()) + .addPositionalValue(4) + .build(); + + // when executing that statement + cluster.session().execute(insert); + + // then we should be able to retrieve the data as inserted. + SimpleStatement select = + SimpleStatement.builder("select k,v from test2 where k=?") + .addPositionalValue(name.getMethodName()) + .build(); + + ResultSet result = cluster.session().execute(select); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + Row row = result.iterator().next(); + assertThat(row.getString("k")).isEqualTo(name.getMethodName()); + assertThat(row.getInt("v")).isEqualTo(4); + } + + @Test(expected = InvalidQueryException.class) + public void should_fail_when_too_many_positional_values_provided() { + // given a statement with more bound values than anticipated (3 given vs. 2 expected) + SimpleStatement insert = + SimpleStatement.builder("INSERT into test (k, v) values (?, ?)") + .addPositionalValues(KEY, 0, 7) + .build(); + + // when executing that statement + cluster.session().execute(insert); + + // then the server will throw an InvalidQueryException which is thrown up to the client. + } + + @Test(expected = InvalidQueryException.class) + public void should_fail_when_not_enough_positional_values_provided() { + // given a statement with not enough bound values (1 given vs. 2 expected) + SimpleStatement insert = + SimpleStatement.builder("SELECT * from test where k = ? and v = ?") + .addPositionalValue(KEY) + .build(); + + // when executing that statement + cluster.session().execute(insert); + + // then the server will throw an InvalidQueryException which is thrown up to the client. + } + + @Test + public void should_use_named_values() { + // given a statement with named values + SimpleStatement insert = + SimpleStatement.builder("INSERT into test2 (k, v) values (:k, :v)") + .addNamedValue("k", name.getMethodName()) + .addNamedValue("v", 7) + .build(); + + // when executing that statement + cluster.session().execute(insert); + + // then we should be able to retrieve the data as inserted. + SimpleStatement select = + SimpleStatement.builder("select k,v from test2 where k=:k") + .addNamedValue("k", name.getMethodName()) + .build(); + + ResultSet result = cluster.session().execute(select); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + Row row = result.iterator().next(); + assertThat(row.getString("k")).isEqualTo(name.getMethodName()); + assertThat(row.getInt("v")).isEqualTo(7); + } + + @Test(expected = InvalidQueryException.class) + public void should_fail_when_named_value_missing() { + // given a statement with a missing named value (:k) + SimpleStatement insert = + SimpleStatement.builder("SELECT * from test where k = :k and v = :v") + .addNamedValue(":v", 0) + .build(); + + // when executing that statement + cluster.session().execute(insert); + + // then the server will throw an InvalidQueryException which is thrown up to the client. + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_when_mixing_named_and_positional_values() { + SimpleStatement.builder("SELECT * from test where k = :k and v = :v") + .addNamedValue(":k", KEY) + .addPositionalValue(0) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_when_mixing_positional_and_named_values() { + SimpleStatement.builder("SELECT * from test where k = :k and v = :v") + .addPositionalValue(0) + .addNamedValue(":k", KEY) + .build(); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java new file mode 100644 index 00000000000..191cb41718f --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -0,0 +1,817 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.data; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.CustomType; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.internal.core.type.DefaultListType; +import com.datastax.oss.driver.internal.core.type.DefaultMapType; +import com.datastax.oss.driver.internal.core.type.DefaultSetType; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; +import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeThat; + +@RunWith(DataProviderRunner.class) +public class DataTypeIT { + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + + @Rule public TestName name = new TestName(); + + private static final Map typeToColumnName = new HashMap<>(); + + private static final AtomicInteger typeCounter = new AtomicInteger(); + + private static final Map, String> userTypeToTypeName = new HashMap<>(); + + private static final AtomicInteger keyCounter = new AtomicInteger(); + + private static final String tableName = "data_types_it"; + + @DataProvider + public static Object[][] primitiveTypeSamples() { + InetAddress address = null; + try { + address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); + } catch (UnknownHostException uhae) { + fail("Could not get address from 127.0.0.1 for some reason"); + } + + Object[][] samples = + new Object[][] { + new Object[] {DataTypes.ASCII, "ascii"}, + new Object[] {DataTypes.BIGINT, Long.MAX_VALUE}, + new Object[] {DataTypes.BIGINT, null, 0L}, + new Object[] {DataTypes.BLOB, Bytes.fromHexString("0xCAFE")}, + new Object[] {DataTypes.BOOLEAN, Boolean.TRUE}, + new Object[] {DataTypes.BOOLEAN, null, false}, + new Object[] {DataTypes.DECIMAL, new BigDecimal("12.3E+7")}, + new Object[] {DataTypes.DOUBLE, Double.MAX_VALUE}, + new Object[] {DataTypes.DOUBLE, null, 0.0}, + new Object[] {DataTypes.FLOAT, Float.MAX_VALUE}, + new Object[] {DataTypes.FLOAT, null, 0.0f}, + new Object[] {DataTypes.INET, address}, + new Object[] {DataTypes.TINYINT, Byte.MAX_VALUE}, + new Object[] {DataTypes.TINYINT, null, (byte) 0}, + new Object[] {DataTypes.SMALLINT, Short.MAX_VALUE}, + new Object[] {DataTypes.SMALLINT, null, (short) 0}, + new Object[] {DataTypes.INT, Integer.MAX_VALUE}, + new Object[] {DataTypes.INT, null, 0}, + new Object[] {DataTypes.DURATION, CqlDuration.from("PT30H20M")}, + new Object[] {DataTypes.TEXT, "text"}, + new Object[] {DataTypes.TIMESTAMP, Instant.ofEpochMilli(872835240000L)}, + new Object[] {DataTypes.DATE, LocalDate.ofEpochDay(16071)}, + new Object[] {DataTypes.TIME, LocalTime.ofNanoOfDay(54012123450000L)}, + new Object[] { + DataTypes.TIMEUUID, UUID.fromString("FE2B4360-28C6-11E2-81C1-0800200C9A66") + }, + new Object[] {DataTypes.UUID, UUID.fromString("067e6162-3b6f-4ae2-a171-2470b63dff00")}, + new Object[] { + DataTypes.VARINT, new BigInteger(Integer.toString(Integer.MAX_VALUE) + "000") + } + }; + + CassandraVersion version = ccm.getCassandraVersion(); + // Filter types if they aren't supported by cassandra version in use. + return Arrays.stream(samples) + .filter( + o -> { + DataType dataType = (DataType) o[0]; + if (dataType == DataTypes.DURATION) { + return version.compareTo(CassandraVersion.parse("3.10")) >= 0; + } else if (dataType == DataTypes.TINYINT + || dataType == DataTypes.SMALLINT + || dataType == DataTypes.DATE + || dataType == DataTypes.TIME) { + return version.compareTo(CassandraVersion.parse("2.2.0")) >= 0; + } + return true; + }) + .toArray(Object[][]::new); + } + + @DataProvider + @SuppressWarnings("unchecked") + public static Object[][] typeSamples() { + Object[][] primitiveSamples = primitiveTypeSamples(); + + // Build additional data samples from primitive type samples. For each sample: + // 1) include the sample itself. + // 2) include list. + // 3) include set. + // 4) include map + // 5) include map + // 6) include tuple + // 7) include udt + return Arrays.stream(primitiveSamples) + .flatMap( + o -> { + List samples = new ArrayList<>(); + samples.add(o); + + if (o[1] == null) { + // Don't use null values in collections. + return samples.stream(); + } + + DataType dataType = (DataType) o[0]; + + // list of type. + ListType listType = new DefaultListType((DataType) o[0], false); + List data = Collections.singletonList(o[1]); + samples.add(new Object[] {listType, data}); + + // set of type. + if (dataType != DataTypes.DURATION) { + // durations can't be in sets. + SetType setType = new DefaultSetType((DataType) o[0], false); + Set s = Collections.singleton(o[1]); + samples.add(new Object[] {setType, s}); + } + + // map of int, type. + MapType mapOfTypeElement = new DefaultMapType(DataTypes.INT, (DataType) o[0], false); + Map mElement = new HashMap(); + mElement.put(0, o[1]); + samples.add(new Object[] {mapOfTypeElement, mElement}); + + // map of type, int. + if (dataType != DataTypes.DURATION) { + // durations can't be map keys. + MapType mapOfTypeKey = new DefaultMapType((DataType) o[0], DataTypes.INT, false); + Map mKey = new HashMap(); + mKey.put(o[1], 0); + samples.add(new Object[] {mapOfTypeKey, mKey}); + } + + // tuple of int, type. + List types = new ArrayList<>(); + types.add(DataTypes.INT); + types.add(dataType); + TupleType tupleType = new DefaultTupleType(types); + TupleValue tupleValue = tupleType.newValue(); + tupleValue.setInt(0, 0); + setValue(1, tupleValue, dataType, o[1]); + samples.add(new Object[] {tupleType, tupleValue}); + + // udt of int, type. + final AtomicInteger fieldNameCounter = new AtomicInteger(); + List typeNames = + types + .stream() + .map( + n -> CqlIdentifier.fromCql("field_" + fieldNameCounter.incrementAndGet())) + .collect(Collectors.toList()); + + UserDefinedType udt = + new DefaultUserDefinedType( + CqlIdentifier.fromCql(cluster.keyspace()), + CqlIdentifier.fromCql(userTypeFor(types)), + typeNames, + types); + + UdtValue udtValue = udt.newValue(); + udtValue.setInt(0, 0); + setValue(1, udtValue, dataType, o[1]); + samples.add(new Object[] {udt, udtValue}); + + return samples.stream(); + }) + .toArray(Object[][]::new); + } + + @BeforeClass + public static void createTable() { + // Create a table with all types being tested with. + // This is a bit more lenient than creating a table for each sample, which would put a lot of burden on C* and + // the filesystem. + int counter = 0; + + List columnData = new ArrayList<>(); + + for (Object[] sample : typeSamples()) { + DataType dataType = (DataType) sample[0]; + + if (!typeToColumnName.containsKey(dataType)) { + int columnIndex = ++counter; + String columnName = "column_" + columnIndex; + typeToColumnName.put(dataType, columnName); + columnData.add(String.format("%s %s", columnName, typeFor(dataType))); + } + } + + cluster + .session() + .execute( + SimpleStatement.builder( + String.format( + "CREATE TABLE IF NOT EXISTS %s (k int primary key, %s)", + tableName, String.join(",", columnData))) + .withConfigProfile(cluster.slowProfile()) + .build()); + } + + private static String columnNameFor(DataType dataType) { + return typeToColumnName.get(dataType); + } + + private static int nextKey() { + return keyCounter.incrementAndGet(); + } + + @UseDataProvider("typeSamples") + @Test + public void should_insert_non_primary_key_column_simple_statement_using_format( + DataType dataType, K value, K expectedPrimitiveValue) { + TypeCodec codec = cluster.cluster().getContext().codecRegistry().codecFor(dataType); + + int key = nextKey(); + String columnName = columnNameFor(dataType); + + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO %s (k, %s) values (?, %s)", + tableName, columnName, codec.format(value))) + .addPositionalValue(key) + .build(); + + cluster.session().execute(insert); + + SimpleStatement select = + SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) + .addPositionalValue(key) + .build(); + + readValue(select, dataType, value, expectedPrimitiveValue); + } + + @UseDataProvider("typeSamples") + @Test + public void should_insert_non_primary_key_column_simple_statement_positional_value( + DataType dataType, K value, K expectedPrimitiveValue) { + // TODO: Remove assumption once JAVA-1569 is fixed. + assumeThat(value, notNullValue()); + + int key = nextKey(); + String columnName = columnNameFor(dataType); + + SimpleStatement insert = + SimpleStatement.builder( + String.format("INSERT INTO %s (k, %s) values (?, ?)", tableName, columnName)) + .addPositionalValues(key, value) + .build(); + + cluster.session().execute(insert); + + SimpleStatement select = + SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) + .addPositionalValue(key) + .build(); + + readValue(select, dataType, value, expectedPrimitiveValue); + } + + @UseDataProvider("typeSamples") + @Test + public void should_insert_non_primary_key_column_simple_statement_named_value( + DataType dataType, K value, K expectedPrimitiveValue) { + // TODO: Remove assumption once JAVA-1569 is fixed. + assumeThat(value, notNullValue()); + + int key = nextKey(); + String columnName = columnNameFor(dataType); + + SimpleStatement insert = + SimpleStatement.builder( + String.format("INSERT INTO %s (k, %s) values (:k, :v)", tableName, columnName)) + .addNamedValue("k", key) + .addNamedValue("v", value) + .build(); + + cluster.session().execute(insert); + + SimpleStatement select = + SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) + .addNamedValue("k", key) + .build(); + + readValue(select, dataType, value, expectedPrimitiveValue); + } + + @UseDataProvider("typeSamples") + @Test + public void should_insert_non_primary_key_column_bound_statement_positional_value( + DataType dataType, K value, K expectedPrimitiveValue) { + int key = nextKey(); + String columnName = columnNameFor(dataType); + + SimpleStatement insert = + SimpleStatement.builder( + String.format("INSERT INTO %s (k, %s) values (?, ?)", tableName, columnName)) + .build(); + + PreparedStatement preparedInsert = cluster.session().prepare(insert); + BoundStatementBuilder boundBuilder = preparedInsert.boundStatementBuilder(); + boundBuilder = setValue(0, boundBuilder, DataTypes.INT, key); + boundBuilder = setValue(1, boundBuilder, dataType, value); + BoundStatement boundInsert = boundBuilder.build(); + cluster.session().execute(boundInsert); + + SimpleStatement select = + SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) + .build(); + + PreparedStatement preparedSelect = cluster.session().prepare(select); + BoundStatement boundSelect = setValue(0, preparedSelect.bind(), DataTypes.INT, key); + + readValue(boundSelect, dataType, value, expectedPrimitiveValue); + } + + @UseDataProvider("typeSamples") + @Test + public void should_insert_non_primary_key_column_bound_statement_named_value( + DataType dataType, K value, K expectedPrimitiveValue) { + int key = nextKey(); + String columnName = columnNameFor(dataType); + + SimpleStatement insert = + SimpleStatement.builder( + String.format("INSERT INTO %s (k, %s) values (:k, :v)", tableName, columnName)) + .build(); + + PreparedStatement preparedInsert = cluster.session().prepare(insert); + BoundStatementBuilder boundBuilder = preparedInsert.boundStatementBuilder(); + boundBuilder = setValue("k", boundBuilder, DataTypes.INT, key); + boundBuilder = setValue("v", boundBuilder, dataType, value); + BoundStatement boundInsert = boundBuilder.build(); + cluster.session().execute(boundInsert); + + SimpleStatement select = + SimpleStatement.builder( + String.format("SELECT %s FROM %s where k=:k", columnName, tableName)) + .build(); + + PreparedStatement preparedSelect = cluster.session().prepare(select); + BoundStatement boundSelect = setValue("k", preparedSelect.bind(), DataTypes.INT, key); + boundSelect.setInt("k", key); + + readValue(boundSelect, dataType, value, expectedPrimitiveValue); + } + + private static > S setValue( + int index, S bs, DataType dataType, Object value) { + TypeCodec codec = + cluster.cluster() != null + ? cluster.cluster().getContext().codecRegistry().codecFor(dataType) + : null; + + // set to null if value is null instead of getting possible NPE when casting from null to primitive. + if (value == null) { + return bs.setToNull(index); + } + + switch (dataType.getProtocolCode()) { + case ProtocolConstants.DataType.ASCII: + case ProtocolConstants.DataType.VARCHAR: + bs = bs.setString(index, (String) value); + break; + case ProtocolConstants.DataType.BIGINT: + bs = bs.setLong(index, (long) value); + break; + case ProtocolConstants.DataType.BLOB: + bs = bs.setByteBuffer(index, (ByteBuffer) value); + break; + case ProtocolConstants.DataType.BOOLEAN: + bs = bs.setBoolean(index, (boolean) value); + break; + case ProtocolConstants.DataType.DECIMAL: + bs = bs.setBigDecimal(index, (BigDecimal) value); + break; + case ProtocolConstants.DataType.DOUBLE: + bs = bs.setDouble(index, (double) value); + break; + case ProtocolConstants.DataType.FLOAT: + bs = bs.setFloat(index, (float) value); + break; + case ProtocolConstants.DataType.INET: + bs = bs.setInetAddress(index, (InetAddress) value); + break; + case ProtocolConstants.DataType.TINYINT: + bs = bs.setByte(index, (byte) value); + break; + case ProtocolConstants.DataType.SMALLINT: + bs = bs.setShort(index, (short) value); + break; + case ProtocolConstants.DataType.INT: + bs = bs.setInt(index, (int) value); + break; + case ProtocolConstants.DataType.DURATION: + bs = bs.setCqlDuration(index, (CqlDuration) value); + break; + case ProtocolConstants.DataType.TIMESTAMP: + bs = bs.setInstant(index, (Instant) value); + break; + case ProtocolConstants.DataType.DATE: + bs = bs.setLocalDate(index, (LocalDate) value); + break; + case ProtocolConstants.DataType.TIME: + bs = bs.setLocalTime(index, (LocalTime) value); + break; + case ProtocolConstants.DataType.TIMEUUID: + case ProtocolConstants.DataType.UUID: + bs = bs.setUuid(index, (UUID) value); + break; + case ProtocolConstants.DataType.VARINT: + bs = bs.setBigInteger(index, (BigInteger) value); + break; + case ProtocolConstants.DataType.CUSTOM: + if (((CustomType) dataType) + .getClassName() + .equals("org.apache.cassandra.db.marshal.DurationType")) { + bs = bs.setCqlDuration(index, (CqlDuration) value); + break; + } + case ProtocolConstants.DataType.LIST: + case ProtocolConstants.DataType.SET: + case ProtocolConstants.DataType.MAP: + bs = bs.set(index, value, codec); + break; + case ProtocolConstants.DataType.TUPLE: + bs = bs.setTupleValue(index, (TupleValue) value); + break; + case ProtocolConstants.DataType.UDT: + bs = bs.setUdtValue(index, (UdtValue) value); + break; + default: + fail("Unhandled DataType " + dataType); + } + return bs; + } + + private static > S setValue( + String name, S bs, DataType dataType, Object value) { + TypeCodec codec = + cluster.cluster() != null + ? cluster.cluster().getContext().codecRegistry().codecFor(dataType) + : null; + + // set to null if value is null instead of getting possible NPE when casting from null to primitive. + if (value == null) { + return bs.setToNull(name); + } + + switch (dataType.getProtocolCode()) { + case ProtocolConstants.DataType.ASCII: + case ProtocolConstants.DataType.VARCHAR: + bs = bs.setString(name, (String) value); + break; + case ProtocolConstants.DataType.BIGINT: + bs = bs.setLong(name, (long) value); + break; + case ProtocolConstants.DataType.BLOB: + bs = bs.setByteBuffer(name, (ByteBuffer) value); + break; + case ProtocolConstants.DataType.BOOLEAN: + bs = bs.setBoolean(name, (boolean) value); + break; + case ProtocolConstants.DataType.DECIMAL: + bs = bs.setBigDecimal(name, (BigDecimal) value); + break; + case ProtocolConstants.DataType.DOUBLE: + bs = bs.setDouble(name, (double) value); + break; + case ProtocolConstants.DataType.FLOAT: + bs = bs.setFloat(name, (float) value); + break; + case ProtocolConstants.DataType.INET: + bs = bs.setInetAddress(name, (InetAddress) value); + break; + case ProtocolConstants.DataType.TINYINT: + bs = bs.setByte(name, (byte) value); + break; + case ProtocolConstants.DataType.SMALLINT: + bs = bs.setShort(name, (short) value); + break; + case ProtocolConstants.DataType.INT: + bs = bs.setInt(name, (int) value); + break; + case ProtocolConstants.DataType.DURATION: + bs = bs.setCqlDuration(name, (CqlDuration) value); + break; + case ProtocolConstants.DataType.TIMESTAMP: + bs = bs.setInstant(name, (Instant) value); + break; + case ProtocolConstants.DataType.DATE: + bs = bs.setLocalDate(name, (LocalDate) value); + break; + case ProtocolConstants.DataType.TIME: + bs = bs.setLocalTime(name, (LocalTime) value); + break; + case ProtocolConstants.DataType.TIMEUUID: + case ProtocolConstants.DataType.UUID: + bs = bs.setUuid(name, (UUID) value); + break; + case ProtocolConstants.DataType.VARINT: + bs = bs.setBigInteger(name, (BigInteger) value); + break; + case ProtocolConstants.DataType.CUSTOM: + if (((CustomType) dataType) + .getClassName() + .equals("org.apache.cassandra.db.marshal.DurationType")) { + bs = bs.setCqlDuration(name, (CqlDuration) value); + break; + } + case ProtocolConstants.DataType.LIST: + case ProtocolConstants.DataType.SET: + case ProtocolConstants.DataType.MAP: + bs = bs.set(name, value, codec); + break; + case ProtocolConstants.DataType.TUPLE: + bs = bs.setTupleValue(name, (TupleValue) value); + break; + case ProtocolConstants.DataType.UDT: + bs = bs.setUdtValue(name, (UdtValue) value); + break; + default: + fail("Unhandled DataType " + dataType); + } + return bs; + } + + private void readValue( + Statement select, DataType dataType, K value, K expectedPrimitiveValue) { + TypeCodec codec = cluster.cluster().getContext().codecRegistry().codecFor(dataType); + ResultSet result = cluster.session().execute(select); + + String columnName = columnNameFor(dataType); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + Row row = result.iterator().next(); + + K expectedValue = expectedPrimitiveValue != null ? expectedPrimitiveValue : value; + + switch (dataType.getProtocolCode()) { + case ProtocolConstants.DataType.ASCII: + case ProtocolConstants.DataType.VARCHAR: + assertThat(row.getString(columnName)).isEqualTo(expectedValue); + assertThat(row.getString(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.BIGINT: + assertThat(row.getLong(columnName)).isEqualTo(expectedValue); + assertThat(row.getLong(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.BLOB: + assertThat(row.getByteBuffer(columnName)).isEqualTo(expectedValue); + assertThat(row.getByteBuffer(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.BOOLEAN: + assertThat(row.getBoolean(columnName)).isEqualTo(expectedValue); + assertThat(row.getBoolean(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.DECIMAL: + assertThat(row.getBigDecimal(columnName)).isEqualTo(expectedValue); + assertThat(row.getBigDecimal(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.DOUBLE: + assertThat(row.getDouble(columnName)).isEqualTo(expectedValue); + assertThat(row.getDouble(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.FLOAT: + assertThat(row.getFloat(columnName)).isEqualTo(expectedValue); + assertThat(row.getFloat(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.INET: + assertThat(row.getInetAddress(columnName)).isEqualTo(expectedValue); + assertThat(row.getInetAddress(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.TINYINT: + assertThat(row.getByte(columnName)).isEqualTo(expectedValue); + assertThat(row.getByte(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.SMALLINT: + assertThat(row.getShort(columnName)).isEqualTo(expectedValue); + assertThat(row.getShort(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.INT: + assertThat(row.getInt(columnName)).isEqualTo(expectedValue); + assertThat(row.getInt(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.DURATION: + assertThat(row.getCqlDuration(columnName)).isEqualTo(expectedValue); + assertThat(row.getCqlDuration(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.TIMESTAMP: + assertThat(row.getInstant(columnName)).isEqualTo(expectedValue); + assertThat(row.getInstant(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.DATE: + assertThat(row.getLocalDate(columnName)).isEqualTo(expectedValue); + assertThat(row.getLocalDate(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.TIME: + assertThat(row.getLocalTime(columnName)).isEqualTo(expectedValue); + assertThat(row.getLocalTime(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.TIMEUUID: + case ProtocolConstants.DataType.UUID: + assertThat(row.getUuid(columnName)).isEqualTo(expectedValue); + assertThat(row.getUuid(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.VARINT: + assertThat(row.getBigInteger(columnName)).isEqualTo(expectedValue); + assertThat(row.getBigInteger(0)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.CUSTOM: + if (((CustomType) dataType) + .getClassName() + .equals("org.apache.cassandra.db.marshal.DurationType")) { + assertThat(row.getCqlDuration(columnName)).isEqualTo(expectedValue); + assertThat(row.getCqlDuration(0)).isEqualTo(expectedValue); + break; + } + case ProtocolConstants.DataType.LIST: + case ProtocolConstants.DataType.MAP: + case ProtocolConstants.DataType.SET: + assertThat(row.get(columnName, codec)).isEqualTo(expectedValue); + assertThat(row.get(0, codec)).isEqualTo(expectedValue); + break; + case ProtocolConstants.DataType.TUPLE: + // TODO: Replace this when JAVA-1572 is fixed + TupleValue returnedValue = row.getTupleValue(columnName); + TupleValue exValue = (TupleValue) expectedValue; + + assertThat(returnedValue.getType()).isEqualTo(exValue.getType()); + + for (int i = 0; i < exValue.getType().getComponentTypes().size(); i++) { + DataType compType = exValue.getType().getComponentTypes().get(i); + TypeCodec typeCodec = + cluster.cluster().getContext().codecRegistry().codecFor(compType); + assertThat(returnedValue.get(i, typeCodec)).isEqualTo(exValue.get(i, typeCodec)); + } + + //assertThat(row.getTupleValue(columnName)).isEqualTo(expectedValue); + //assertThat(row.getTupleValue(0)).isEqualTo(expectedValue); + return; // return instead of break here since we don't want to compare using decode output since it has same problem. + case ProtocolConstants.DataType.UDT: + // TODO: Replace this when JAVA-1572 is fixed + UdtValue returnedUdtValue = row.getUdtValue(columnName); + UdtValue exUdtValue = (UdtValue) expectedValue; + + assertThat(returnedUdtValue.getType()).isEqualTo(exUdtValue.getType()); + + for (int i = 0; i < exUdtValue.getType().getFieldTypes().size(); i++) { + DataType compType = exUdtValue.getType().getFieldTypes().get(i); + String compName = exUdtValue.getType().getFieldNames().get(i).asCql(); + + TypeCodec typeCodec = + cluster.cluster().getContext().codecRegistry().codecFor(compType); + + assertThat(returnedUdtValue.get(compName, typeCodec)) + .isEqualTo(exUdtValue.get(compName, typeCodec)); + assertThat(returnedUdtValue.get(i, typeCodec)).isEqualTo(exUdtValue.get(i, typeCodec)); + } + return; + default: + fail("Unhandled DataType " + dataType); + } + + if (value == null) { + assertThat(row.isNull(columnName)).isTrue(); + assertThat(row.isNull(0)).isTrue(); + } + + // Decode directly using the codec + assertThat(codec.decode(row.getBytesUnsafe(columnName), cluster.getHighestProtocolVersion())) + .isEqualTo(value); + assertThat(codec.decode(row.getBytesUnsafe(0), cluster.getHighestProtocolVersion())) + .isEqualTo(value); + } + + private static String typeFor(DataType dataType) { + // TODO: Replace this when JAVA-1573 is fixed. + String typeName; + if (dataType instanceof CustomType) { + typeName = "'" + ((CustomType) dataType).getClassName() + "'"; + } else if (dataType instanceof ListType) { + typeName = "list<" + typeFor(((ListType) dataType).getElementType()) + ">"; + } else if (dataType instanceof SetType) { + typeName = "set<" + typeFor(((SetType) dataType).getElementType()) + ">"; + } else if (dataType instanceof MapType) { + MapType mapType = (MapType) dataType; + typeName = + "map<" + typeFor(mapType.getKeyType()) + "," + typeFor(mapType.getValueType()) + ">"; + } else if (dataType instanceof TupleType) { + TupleType tupleType = (TupleType) dataType; + String dtPart = + tupleType + .getComponentTypes() + .stream() + .map(DataTypeIT::typeFor) + .collect(Collectors.joining(",")); + + typeName = "tuple<" + dtPart + ">"; + } else if (dataType instanceof UserDefinedType) { + UserDefinedType udt = (UserDefinedType) dataType; + + // Create type if it doesn't already exist. + List fieldParts = new ArrayList<>(); + for (int i = 0; i < udt.getFieldNames().size(); i++) { + String fieldName = udt.getFieldNames().get(i).asCql(); + String fieldType = typeFor(udt.getFieldTypes().get(i)); + fieldParts.add(fieldName + " " + fieldType); + } + + cluster + .session() + .execute( + SimpleStatement.builder( + String.format( + "CREATE TYPE IF NOT EXISTS %s (%s)", + udt.getName().asCql(), String.join(",", fieldParts))) + .withConfigProfile(cluster.slowProfile()) + .build()); + + typeName = "frozen<" + udt.getName().asCql() + ">"; + } else { + typeName = dataType.toString(); + } + return typeName; + } + + private static String userTypeFor(List dataTypes) { + if (userTypeToTypeName.containsKey(dataTypes)) { + return userTypeToTypeName.get(dataTypes); + } else { + String typeName = "udt_" + typeCounter.incrementAndGet(); + userTypeToTypeName.put(dataTypes, typeName); + return typeName; + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java new file mode 100644 index 00000000000..33a663a4f17 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.heartbeat; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.cluster.QueryLog; +import com.datastax.oss.simulacron.common.request.Options; +import com.datastax.oss.simulacron.common.stubbing.CloseType; +import com.datastax.oss.simulacron.common.stubbing.DisconnectAction; +import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; +import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.RejectScope; +import java.net.SocketAddress; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static com.datastax.oss.driver.api.testinfra.utils.ConditionChecker.checkThat; +import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForDown; +import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForUp; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noResult; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class HeartbeatIT { + + @ClassRule + public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + + @Rule + public ClusterRule cluster = + new ClusterRule( + simulacron, + true, + false, + "connection.heartbeat.interval = 1 second", + "connection.heartbeat.timeout = 500 milliseconds", + "connection.init-query-timeout = 2 seconds", + "connection.reconnection-policy.max-delay = 1 second"); + + private static final String queryStr = "select * from foo"; + + private BoundNode nonControlNode; + private BoundNode controlNode; + private SocketAddress controlConnection; + + @Before + public void setUp() { + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + Optional nonControlNodeOption = + simulacron + .cluster() + .getNodes() + .stream() + .filter(n -> n.getActiveConnections() == 0) + .findFirst(); + + if (nonControlNodeOption.isPresent()) { + nonControlNode = nonControlNodeOption.get(); + } else { + fail("Non-control node not found"); + } + + Optional controlNodeOption = + simulacron + .cluster() + .getNodes() + .stream() + .filter(n -> n.getActiveConnections() == 1) + .findFirst(); + + if (controlNodeOption.isPresent()) { + controlNode = controlNodeOption.get(); + controlConnection = controlNode.getConnections().getConnections().get(0); + } else { + fail("Control node not found"); + } + } + + @Test + public void node_should_go_down_gracefully_when_connection_closed_during_heartbeat() + throws InterruptedException { + // Create session to initialize pools. + cluster.newSession(); + + // Node should be up. + Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); + assertThat(node.getState()).isEqualTo(NodeState.UP); + + // Stop listening for new connections (so it can't reconnect) + nonControlNode.rejectConnections(0, RejectScope.UNBIND); + + int heartbeatCount = getHeartbeatsForNode(nonControlNode).size(); + // When node receives a heartbeat, close the connection. + nonControlNode.prime( + when(Options.INSTANCE) + .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); + + // Wait for heartbeat and for node to subsequently close it's connection. + waitForDown(node); + + // Should have been a heartbeat received since that's what caused the disconnect. + assertThat(getHeartbeatsForNode(nonControlNode).size()).isGreaterThan(heartbeatCount); + } + + private static final Predicate optionsRequest = (l) -> l.getQuery().equals("OPTIONS"); + + @Test + public void should_not_send_heartbeat_during_protocol_initialization() + throws InterruptedException { + // Configure node to reject startup. + nonControlNode.rejectConnections(0, RejectScope.REJECT_STARTUP); + Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); + + // Create session to initialize pools. + cluster.newSession(); + + // wait for node to go down as result of startup failing. + waitForDown(node); + + // no heartbeats should have been sent while protocol was initializing. + assertThat(getHeartbeatsForNode(nonControlNode)).isEmpty(); + + // node should be down since there were no successful connections. + assertThat(node.getState()).isEqualTo(NodeState.DOWN); + + // start accepting connections again. + nonControlNode.acceptConnections(); + + // listen for heartbeats on node. + AtomicInteger heartbeats = new AtomicInteger(); + nonControlNode.registerQueryListener( + (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); + + // wait a heartbeat to be sent (indicating node is up and sending heartbeats) + checkThat(() -> heartbeats.get() > 0).every(100).becomesTrue(); + assertThat(getHeartbeatsForNode(nonControlNode)).isNotEmpty(); + assertThat(node.getState()).isEqualTo(NodeState.UP); + } + + @Test + public void should_send_heartbeat_on_interval() throws InterruptedException { + // Prime a simple query so we get at least some results + simulacron + .cluster() + .prime(when(queryStr).then(PrimeDsl.rows().row("column1", "1", "column2", "2"))); + + // listen for heartbeats on node. + AtomicInteger controlNodeHeartbeats = new AtomicInteger(); + controlNode.registerQueryListener( + (n, l) -> controlNodeHeartbeats.incrementAndGet(), true, optionsRequest); + + // Ensure heartbeats are received on control node, even when no sessions are present. + checkThat(() -> controlNodeHeartbeats.get() > 0).becomesTrue(); + + // Create session to initialize pools. + Session session = cluster.newSession(); + + // Ensure we get a heartbeat after a second. + AtomicInteger heartbeats = new AtomicInteger(); + nonControlNode.registerQueryListener( + (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); + + checkThat(() -> heartbeats.get() > 0).becomesTrue(); + + // count all options received not from control connection. + AtomicInteger nonControlHeartbeats = null; + + // Make a bunch of queries over two seconds. This should preempt any heartbeats. + for (int i = 0; i < 20; i++) { + assertThat(session.execute(queryStr)).hasSize(1); + assertThat(session.execute(queryStr)).hasSize(1); + + // after first write, start counting number of heartbeats. + if (i == 0) { + nonControlHeartbeats = registerHeartbeatListener(); + } + MILLISECONDS.sleep(100); + } + + // No heartbeats should be sent, except those on the control connection. + assertThat(nonControlHeartbeats.get()).isZero(); + + // Wait for 2 more heartbeats to be sent (one on each node). + AtomicInteger fNonControlHeartbeats = nonControlHeartbeats; + checkThat(() -> fNonControlHeartbeats.get() >= 2).becomesTrue(); + } + + @Test + public void should_send_heartbeat_when_requests_being_written_but_nothing_received() + throws InterruptedException { + // Prime a query that will never return a response. + String noResponseQueryStr = "delay"; + simulacron.cluster().prime(when(noResponseQueryStr).then(noResult())); + + AtomicInteger heartbeats = registerHeartbeatListener(); + + // Send requests over 2.5 seconds. + Session session = cluster.newSession(); + for (int i = 0; i < 25; i++) { + session.executeAsync(noResponseQueryStr); + session.executeAsync(noResponseQueryStr); + MILLISECONDS.sleep(100); + } + + // We should expect at least 4 heartbeats (2 from each node's connection) + assertThat(heartbeats.get()).isGreaterThanOrEqualTo(4); + } + + @Test + public void should_close_connection_when_heartbeat_times_out() { + cluster.newSession(); + + Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); + + // Node should be up. + assertThat(node.getState()).isEqualTo(NodeState.UP); + + // Ensure we get some heartbeats and the node remains up. + AtomicInteger heartbeats = new AtomicInteger(); + nonControlNode.registerQueryListener( + (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); + + checkThat(() -> heartbeats.get() >= 2).becomesTrue(); + + // configure node to not respond to options request, which should cause a timeout. + nonControlNode.prime(when(Options.INSTANCE).then(noResult())); + heartbeats.set(0); + + // wait for heartbeat to be sent. + checkThat(() -> heartbeats.get() == 1).becomesTrue(); + heartbeats.set(0); + + // node should go down because heartbeat was not set. + waitForDown(node); + + // clear prime so now responds to options request again. + nonControlNode.clearPrimes(); + + // wait for node to come up again and ensure heartbeats are successful and node remains up. + waitForUp(node); + + checkThat(() -> heartbeats.get() >= 2).becomesTrue(); + assertThat(node.getState()).isEqualTo(NodeState.UP); + } + + @Test + @Category(LongTests.class) + public void should_not_send_heartbeat_when_disabled() throws InterruptedException { + // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any + try (Session session = + cluster.defaultCluster("connection.heartbeat.interval = 0 second").connect()) { + AtomicInteger heartbeats = registerHeartbeatListener(); + SECONDS.sleep(35); + + assertThat(heartbeats.get()).isZero(); + } + } + + /** + * Registers a query listener that increments the returned {@link AtomicInteger} whenever a + * heartbeat is received on the non-control connection. + * + * @return integer that represents current count of heartbeats. + */ + private AtomicInteger registerHeartbeatListener() { + AtomicInteger nonControlHeartbeats = new AtomicInteger(); + simulacron + .cluster() + .registerQueryListener( + (n, l) -> nonControlHeartbeats.incrementAndGet(), + false, + (l) -> optionsRequest.test(l) && !l.getConnection().equals(controlConnection)); + return nonControlHeartbeats; + } + + private List getHeartbeatsForNode(BoundNode node) { + return node.getLogs() + .getQueryLogs() + .stream() + .filter(l -> l.getQuery().equals("OPTIONS")) + .collect(Collectors.toList()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java new file mode 100644 index 00000000000..c7f2c189ea0 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.retry; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.stubbing.CloseType; +import com.datastax.oss.simulacron.common.stubbing.DisconnectAction; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Arrays; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.datastax.oss.simulacron.common.codec.ConsistencyLevel.LOCAL_QUORUM; +import static com.datastax.oss.simulacron.common.codec.WriteType.BATCH_LOG; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.readTimeout; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.unavailable; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.writeTimeout; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +@RunWith(DataProviderRunner.class) +public class DefaultRetryPolicyIT { + + public static @ClassRule SimulacronRule simulacron = + new SimulacronRule(ClusterSpec.builder().withNodes(3)); + public @Rule ClusterRule cluster = + new ClusterRule( + simulacron, + "request.default-idempotence = true", + "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy"); + + private static String queryStr = "select * from foo"; + private static final SimpleStatement query = SimpleStatement.builder(queryStr).build(); + + @Before + public void clear() { + // clear activity logs and primes between tests since simulacron instance is shared. + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + } + + private void assertQueryCount(int expected) { + assertThat( + simulacron + .cluster() + .getLogs() + .getQueryLogs() + .stream() + .filter(l -> l.getQuery().equals(queryStr))) + .as("Expected query count to be %d", expected) + .hasSize(expected); + } + + private void assertQueryCount(int node, int expected) { + assertThat( + simulacron + .cluster() + .node(node) + .getLogs() + .getQueryLogs() + .stream() + .filter(l -> l.getQuery().equals(queryStr))) + .as("Expected query count to be %d for node %d", expected, node) + .hasSize(expected); + } + + @Test + public void should_not_retry_on_read_timeout_when_data_present() { + // given a node that will respond to query with a read timeout where data is present. + simulacron.cluster().node(0).prime(when(queryStr).then(readTimeout(LOCAL_QUORUM, 1, 3, true))); + + try { + // when executing a query + cluster.session().execute(query); + fail("Expected a ReadTimeoutException"); + } catch (ReadTimeoutException rte) { + // then a read timeout exception is thrown + assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getReceived()).isEqualTo(1); + assertThat(rte.getBlockFor()).isEqualTo(3); + assertThat(rte.wasDataPresent()).isTrue(); + } + + // should not have been retried. + assertQueryCount(1); + } + + @Test + public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() { + // given a node that will respond to a query with a read timeout where 2 out of 3 responses are received. + // in this case, digest requests succeeded, but not the data request. + simulacron.cluster().node(0).prime(when(queryStr).then(readTimeout(LOCAL_QUORUM, 2, 3, false))); + + try { + // when executing a query + cluster.session().execute(query); + fail("Expected a ReadTimeoutException"); + } catch (ReadTimeoutException rte) { + // then a read timeout exception is thrown + assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getReceived()).isEqualTo(2); + assertThat(rte.getBlockFor()).isEqualTo(3); + assertThat(rte.wasDataPresent()).isFalse(); + } + + // should not have been retried. + assertQueryCount(1); + } + + @Test + public void should_retry_on_read_timeout_when_enough_responses_and_data_not_present() { + // given a node that will respond to a query with a read timeout where 3 out of 3 responses are received, + // but data is not present. + simulacron.cluster().node(0).prime(when(queryStr).then(readTimeout(LOCAL_QUORUM, 3, 3, false))); + + try { + // when executing a query. + cluster.session().execute(query); + fail("Expected a ReadTimeoutException"); + } catch (ReadTimeoutException rte) { + // then a read timeout exception is thrown. + assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getReceived()).isEqualTo(3); + assertThat(rte.getBlockFor()).isEqualTo(3); + assertThat(rte.wasDataPresent()).isFalse(); + } + + // there should have been a retry, and it should have been executed on the same host. + assertQueryCount(2); + assertQueryCount(0, 2); + } + + @Test + public void should_retry_on_next_host_on_connection_error_if_idempotent() { + // given a node that will close its connection as result of receiving a query. + simulacron + .cluster() + .node(0) + .prime( + when(queryStr) + .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); + + // when executing a query. + ResultSet result = cluster.session().execute(query); + // then we should get a response, and the execution info on the result set indicates there was an error on + // the host that received the query. + assertThat(result.getExecutionInfo().getErrors()).hasSize(1); + Map.Entry error = result.getExecutionInfo().getErrors().get(0); + assertThat(error.getKey().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); + assertThat(error.getValue()).isInstanceOf(ClosedConnectionException.class); + // the host that returned the response should be node 1. + assertThat(result.getExecutionInfo().getCoordinator().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); + + // should have been retried. + assertQueryCount(2); + // expected query on node 0. + assertQueryCount(0, 1); + // expected retry on node 1. + assertQueryCount(1, 1); + } + + @Test + public void should_keep_retrying_on_next_host_on_connection_error() { + // given a request for which every node will close its connection upon receiving it. + simulacron + .cluster() + .prime( + when(queryStr) + .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); + + try { + // when executing a query. + cluster.session().execute(query); + fail("AllNodesFailedException expected"); + } catch (AllNodesFailedException ex) { + // then an AllNodesFailedException should be raised indicating that all nodes failed the request. + assertThat(ex.getErrors()).hasSize(3); + } + + // should have been tried on all nodes. + assertQueryCount(3); + // expected query on node 0. + assertQueryCount(0, 1); + // expected retry on node 1. + assertQueryCount(1, 1); + // expected query on node 2. + assertQueryCount(2, 1); + } + + @Test + public void should_not_retry_on_connection_error_if_non_idempotent() { + // given a node that will close its connection as result of receiving a query. + simulacron + .cluster() + .node(0) + .prime( + when(queryStr) + .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); + + try { + // when executing a non-idempotent query. + cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + fail("ClosedConnectionException expected"); + } catch (ClosedConnectionException ex) { + // then a ClosedConnectionException should be raised, indicating that the connection closed while handling + // the request on that node. + // this clearly indicates that the request wasn't retried. + // Exception should indicate that node 0 was the failing node. + // TODO: Validate the address on the connection if made available. + } + + // should not have been retried. + assertQueryCount(1); + } + + @Test + public void should_retry_on_write_timeout_if_write_type_batch_log() { + // given a node that will respond to query with a write timeout with write type of batch log. + simulacron + .cluster() + .node(0) + .prime(when(queryStr).then(writeTimeout(LOCAL_QUORUM, 1, 3, BATCH_LOG))); + + try { + // when executing a query. + cluster.session().execute(queryStr); + fail("WriteTimeoutException expected"); + } catch (WriteTimeoutException wte) { + // then a write timeout exception is thrown + assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getReceived()).isEqualTo(1); + assertThat(wte.getBlockFor()).isEqualTo(3); + assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); + } + + // there should have been a retry, and it should have been executed on the same host. + assertQueryCount(2); + assertQueryCount(0, 2); + } + + /** + * @return All WriteTypes that are not BATCH_LOG, on write timeout of these, the driver should not + * retry. + */ + @DataProvider + public static Object[] nonBatchLogWriteTypes() { + return Arrays.stream(com.datastax.oss.simulacron.common.codec.WriteType.values()) + .filter(wt -> wt != BATCH_LOG) + .toArray(); + } + + @UseDataProvider("nonBatchLogWriteTypes") + @Test + public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( + com.datastax.oss.simulacron.common.codec.WriteType writeType) { + // given a node that will respond to query with a write timeout with write type that is not batch log. + simulacron + .cluster() + .node(0) + .prime(when(queryStr).then(writeTimeout(LOCAL_QUORUM, 1, 3, writeType))); + + try { + // when executing a query. + cluster.session().execute(queryStr); + fail("WriteTimeoutException expected"); + } catch (WriteTimeoutException wte) { + // then a write timeout exception is thrown + assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getReceived()).isEqualTo(1); + assertThat(wte.getBlockFor()).isEqualTo(3); + } + + // should not have been retried. + assertQueryCount(1); + } + + @Test + public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_idempotent() { + // given a node that will respond to query with a write timeout with write type of batch log. + simulacron + .cluster() + .node(0) + .prime(when(queryStr).then(writeTimeout(LOCAL_QUORUM, 1, 3, BATCH_LOG))); + + try { + // when executing a non-idempotent query. + cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + fail("WriteTimeoutException expected"); + } catch (WriteTimeoutException wte) { + // then a write timeout exception is thrown + assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getReceived()).isEqualTo(1); + assertThat(wte.getBlockFor()).isEqualTo(3); + assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); + } + + // should not have been retried. + assertQueryCount(1); + } + + @Test + public void should_retry_on_next_host_on_unavailable() { + // given a node that will respond to a query with an unavailable. + simulacron.cluster().node(0).prime(when(queryStr).then(unavailable(LOCAL_QUORUM, 3, 0))); + + // when executing a query. + ResultSet result = cluster.session().execute(queryStr); + // then we should get a response, and the execution info on the result set indicates there was an error on + // the host that received the query. + assertThat(result.getExecutionInfo().getErrors()).hasSize(1); + Map.Entry error = result.getExecutionInfo().getErrors().get(0); + assertThat(error.getKey().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); + assertThat(error.getValue()).isInstanceOf(UnavailableException.class); + // the host that returned the response should be node 1. + assertThat(result.getExecutionInfo().getCoordinator().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); + + // should have been retried on another host. + assertQueryCount(2); + assertQueryCount(0, 1); + assertQueryCount(1, 1); + } + + @Test + public void should_only_retry_once_on_unavailable() { + // given two nodes that will respond to a query with an unavailable. + simulacron.cluster().node(0).prime(when(queryStr).then(unavailable(LOCAL_QUORUM, 3, 0))); + simulacron.cluster().node(1).prime(when(queryStr).then(unavailable(LOCAL_QUORUM, 3, 0))); + + try { + // when executing a query. + cluster.session().execute(queryStr); + } catch (UnavailableException ue) { + // then we should get an unavailable exception with the host being node 1 (since it was second tried). + assertThat(ue.getCoordinator().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); + assertThat(ue.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(ue.getRequired()).isEqualTo(3); + assertThat(ue.getAlive()).isEqualTo(0); + } + + // should have been retried on another host. + assertQueryCount(2); + assertQueryCount(0, 1); + assertQueryCount(1, 1); + } + + @Test + public void should_keep_retrying_on_next_host_on_error_response() { + // given every node responding with a server error. + simulacron.cluster().prime(when(queryStr).then(serverError("this is a server error"))); + + try { + // when executing a query. + cluster.session().execute(queryStr); + } catch (AllNodesFailedException e) { + // then we should get an all nodes failed exception, indicating the query was tried each node. + assertThat(e.getErrors()).hasSize(3); + for (Throwable t : e.getErrors().values()) { + assertThat(t).isInstanceOf(ServerError.class); + } + } + + // should have been tried on all nodes. + assertQueryCount(3); + // expected query on node 0. + assertQueryCount(0, 1); + // expected retry on node 1. + assertQueryCount(1, 1); + // expected query on node 2. + assertQueryCount(2, 1); + } + + @Test + public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() { + // given every node responding with a server error. + simulacron.cluster().prime(when(queryStr).then(serverError("this is a server error"))); + + try { + // when executing a query that is not idempotent + cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + } catch (ServerError e) { + // then should get a server error from first host. + assertThat(e.getMessage()).isEqualTo("this is a server error"); + } + + // should have been tried on all nodes. + assertQueryCount(1); + // expected query on node 0. + assertQueryCount(0, 1); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java new file mode 100644 index 00000000000..b26097f6f7d --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.IsolatedTests; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(IsolatedTests.class) +public class DefaultSslEngineFactoryIT { + + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); + + @Test + public void should_connect_with_ssl() { + System.setProperty( + "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); + try (Cluster sslCluster = + cluster.defaultCluster( + "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { + Session session = sslCluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java new file mode 100644 index 00000000000..6188103dc40 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.IsolatedTests; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(IsolatedTests.class) +public class DefaultSslEngineFactoryWithClientAuthIT { + + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); + + @Test + public void should_connect_with_ssl_using_client_auth() { + System.setProperty( + "javax.net.ssl.keyStore", CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.keyStorePassword", CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD); + System.setProperty( + "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); + try (Cluster sslCluster = + cluster.defaultCluster( + "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { + Session session = sslCluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java new file mode 100644 index 00000000000..262904fcc89 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.IsolatedTests; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(IsolatedTests.class) +public class DefaultSslEngineFactoryWithClientAuthNotProvidedIT { + + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() { + System.setProperty( + "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); + try (Cluster sslCluster = + cluster.defaultCluster( + "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { + Session session = sslCluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java new file mode 100644 index 00000000000..6718356b515 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.IsolatedTests; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(IsolatedTests.class) +public class DefaultSslEngineFactoryWithTruststoreNotProvidedIT { + + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_if_not_using_ssl() { + try (Cluster plainCluster = cluster.defaultCluster()) { + Session session = plainCluster.connect(); + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java b/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java new file mode 100644 index 00000000000..d7970ba4b78 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.categories; + +/** Defines a classification of tests that should be run in their own jvm fork. */ +public class IsolatedTests {} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java b/integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java new file mode 100644 index 00000000000..c0c2c73a79e --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.categories; + +/** Defines a classification of tests that are slow. */ +public interface LongTests {} diff --git a/integration-tests/src/test/resources/client.crt b/integration-tests/src/test/resources/client.crt new file mode 100644 index 00000000000..241e5f545d6 --- /dev/null +++ b/integration-tests/src/test/resources/client.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIERLZiJzANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENsYXJhMRYwFAYDVQQKEw1EYXRhU3RheCBJ +bmMuMRowGAYDVQQLExFEcml2ZXJzIGFuZCBUb29sczEWMBQGA1UEAxMNRHJpdmVyIENsaWVudDAe +Fw0xNTAzMTIwMTA4MjRaFw0xNTA2MTAwMTA4MjRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExFjAUBgNVBAoTDURhdGFTdGF4IEluYy4x +GjAYBgNVBAsTEURyaXZlcnMgYW5kIFRvb2xzMRYwFAYDVQQDEw1Ecml2ZXIgQ2xpZW50MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq0J0EoZQnOv2KRrvwA+1ZL9VZ3hDdQMwkDfitoGN +B6upvMUZpf8W+ReQmaY6yacYJthHzsZTd3G97Bw81/3VNHQB9PnXGmbupMLVXeFXysSCs1nPEdJl +TBbJXWHSh41AE4ejJaoCoTuigKGwI9lTbOOPDz/WMcio9nagsCJdsdG2+TxmR7RlyzEIANJ0wpnL +JEIeJmRS2loLVuCU4lZ9hDLN57cP9jEVD4Hk2kJD4Exx7G9HQFH+/63H6XtEDZsJcYldR7yBNsGr +pz9CupULCS1R40ePQEIlUXhM4ft/hsljQybLQvvfXNVTvk5WgY7LNaBJy6A/Tfg32SXEn3wUvwID +AQABoyEwHzAdBgNVHQ4EFgQUt+JDOeziZzHNYTFU/FL9PhDGqSQwDQYJKoZIhvcNAQELBQADggEB +ADOYpa1f9dPcVLq3RiMytajHo3YJ0AQqGRzVgngkeRFSdhyy/y+/8D0/V5s6QbNt/l6x3FxkoiTR +1Lptf96eylnS5AkGQTgogJP53cSNrqkDL0IyyvErSiATEXNpBKz6ivY+e5J1GLTfX9Ylu8limzIq +Y6YBnr8fMLD6XWraxtzzkJ9NIPhhaz696rxqr8ix6uy0mgxR/7/jUglreimZkLW40/qiABgX7Evw +UqpuJWmqNbQP9UXecx/UJ0hdxxxuxkZsoRoQwWYhkeT4aGCLJv/hjiNTfFAt23uHe0LVfW/HqykW +KoEj8F08mJVe5ZfpjF974i5qO9PU9XxvLfLjNvo= +-----END CERTIFICATE----- diff --git a/integration-tests/src/test/resources/client.key b/integration-tests/src/test/resources/client.key new file mode 100644 index 00000000000..05bb6fad83d --- /dev/null +++ b/integration-tests/src/test/resources/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrQnQShlCc6/Yp +Gu/AD7Vkv1VneEN1AzCQN+K2gY0Hq6m8xRml/xb5F5CZpjrJpxgm2EfOxlN3cb3s +HDzX/dU0dAH0+dcaZu6kwtVd4VfKxIKzWc8R0mVMFsldYdKHjUATh6MlqgKhO6KA +obAj2VNs448PP9YxyKj2dqCwIl2x0bb5PGZHtGXLMQgA0nTCmcskQh4mZFLaWgtW +4JTiVn2EMs3ntw/2MRUPgeTaQkPgTHHsb0dAUf7/rcfpe0QNmwlxiV1HvIE2waun +P0K6lQsJLVHjR49AQiVReEzh+3+GyWNDJstC+99c1VO+TlaBjss1oEnLoD9N+DfZ +JcSffBS/AgMBAAECggEAMHATNEoY8skqTmX3+XJ3847KMQGq0qWcTq3/yW7K3KiI +0YNNxc1oSfuIQmzpo69G/XWembUuVlItTWKPMufwLW3CP++KD0WdqawRfQQHOKpr +7R4xmvDPBb5MJcVNLlmdDekHE9gJ9mBPjeItV3ZYSivygnWjt2DxqQPUXvzZUzlu +munh3H5x6ehXVHDYGzosPgTpfmLHdlNfvF4x9bcklMMbCOyoPttXB2uHWOvUIS+/ +2YEkPmJfZdpudI7RqN75yYi7N8+gpnCTp530zA2yONyZ8THqEG/0nWy+02/zm5sm +gs1saYNwXME2IPekZNM/pJh2DtnTcxZaUt84q2nhAQKBgQDi8mgvE8ekbs6DLfKK +YAtTuOcLRpuvJqxtiQecqaumzgZnmHtkm6yuDNjieqB6OITudP4NdhPpyvOFJw46 +zTHMpGqZboxHuxoxMOgmyeiO+cdSwGHobr1zUcT8jVmLH7A+LtL5hHi+733EbCRh +sF04Vq9L46Q52mhcZKbs56U8MQKBgQDBLwotnOJH7hZD5sKS0o8/Sfj3pgzXEDpL +RfnrBPGhLn+1zhPEYsEW3mKI/yHiOZHNXZMQ6oYmxThg03qKTjaY8OIm8sg/zrlZ +M+o3wVnAzayrhw5gZ8DzqioHhEUMOAwwRFXRpfxqj8regrLjE9KaYty8ZYAFtwuH +W2S3+MVT7wKBgGQx7XlLXErmeNpFgN1Cxf1ylt7Nj5Jmmp3Jb8jkx9ne/8jg8ylZ +6YT2OxLSXONY7Kdyk29SADyp05WnxoqDaUcWF9IhkmFg45FwLC5j2f61nCCWuyMp +MQ8mvLdbmHrpxJ/PgGmU6NIzXe1IaU+P07g53S6+FBVOreCMt33ET5khAoGAGgKz +ZCDTdsvfw5S2bf5buzHCi9WXtP1CXBA37iTkQ8d2+oucrbx+Mw4ORlPTxBnsP7Jx +sr1hAqdbR+4xeZ2+TCliycu2mqDC4/fReWBXLVaEATRWAzT1DdnDfu+YPGTvfzA0 +Pd4TdmWV8w+19k0c9hyJi/Q+oIZczwTHMt4T85ECgYAe4J0ht6b6kPEG3d9vxmMN +T23S+ucYLHnfT1nacTuBZnMphWHhSqf8UJloIGpusxDU84MdAp22Jpd9SfPi9KK9 +yZY9WDJGeb0Yk7ML1R5GcAAkM78lUw/rS2VfMjQFnnUl2jVMS8adcm8/vHcpkcn7 +MufMEZzDpeO/aI8nbClktw== +-----END PRIVATE KEY----- diff --git a/integration-tests/src/test/resources/client.keystore b/integration-tests/src/test/resources/client.keystore new file mode 100644 index 0000000000000000000000000000000000000000..f1030fdb3773f7107e47ee48cc6645a70065a050 GIT binary patch literal 2292 zcmd5-`8N~_8=j4f$ufS!TxCB+O*{8VrMk5o3$7P2>`yZrQTs+II<` zk}TQgtF9R%M6x9!OBAwPb-wSM`zL%qygxk8d*1iF=Q+=L-o2&0B>(^bIw;_;;&=1$ za;FC#KqBl`>O26z2SBqRJ7{5k!BhM|2v8LS2Lky35EkUqT_$5+WT&+APE1zRT-$t6 z2^ZAGZ1lGneEF3|Sa*``(S+&^l+@@cnfW#nn1wPvzXuObiE&@qU8DF`Smd!=G_Nu` ztD}c?A9hZD_FtHA9m)EZWJnJ^{A>bvotCs77T!p{!>=aZsY zWmr;?@FkN*vj5k*E~l8ebXw;mKTA`htj|>rt8R&kRNQWS9O5(8X*tjMXyy=H94EvS z8F4c7W^b8Xpp1KYkyfwjZ|+8cXLA(7&c}FY4L?|oOAfYhWLdQCNN^>+oj8UZNhA1u zH4B}1S9Sk;pOOUPER6rM0#}MBxugEoCfe@oi#LUQs8X(}xHMs|Q5HG~7i7>pAE(S_ zpUYu@>P2Mi#gA(YH_xxi@1z{%->t;yOOPibE_UC%jVO0GH?_Flq!kX!?*Jq+jdov8 zi`-l@uTyi@{3z%2?zaAh$&x6KP&B`wm?TA%8QatKLc_i8Z}P1*_PF#XurAJL)l{|` zr;pi}RKQNS8wZ_TMcjOXt*~fWN`f8dDh!;2J(izu6EkCkM9A|yIrxR zqq!B0S*N&>l(Ocb&$k!lO;5S(MmVP^qw*cb>rXuSxQfCa%|zs07*Qk5vG+6f*w4K~ z^Ci@$A@%em<5C0L?ni~8>*T|&Ocm1oEs)?GFFiK7e!b6XCPh6vH8Y{kD#Ng|alAY- zT6#S)9zMs?p77Oga8H%MaY$CS+9uPwx?uIswbe>dyUqA8&S}tLInJ?OX{>!W67DkjCfrtYrW~(q^wC5`HRiI@ z=d~lXwG-@s>epjxU*t3VWNxsf2-VxrsioV5iQ}vY7ZLqw8;IA%3XpEwfwSg@8cVL14zjVqj<}Kln0)9|5{d!XVJ_pFjX1 zh^P8^dH4m;y{H&5^pT(BAq0#>r3X@xcpqv26(f!o`^gF-gb36?Dk+c}iZrLYp)gWt ziJv?S0Vf1_1-l0@kW@MiN%r&eVf=3fd@%E?cd+VbhNESU31PI*8W=1ZgFb`Rbo>Ri zG3fuo|CcjaK)HVkc<^1oETG82+W`uKSU@140vCAXmes@0-&CZQUx+-RwO#fM#Rq}W zsX9}QG0BH2%AUQHEZP^}I+mJQe6FuVLTT9abvG%*zjaYs@9i#6GZ47G^;XK`%by)Q z%A{R?7wfUZ8#u?EDBee*a^jN>5%GnJ<)8xHN6`hf@*^al={q6@Lzv#uZ^8Msa+JD( z#w|S$(*}1oMgYJG?8sy*;AE6&HlvO(+Yf(C*+#@-U(Gd%e8U_SiJ2M2;Xe@l7yV2P zt@rk;dgj9kLfKIN1d8di8143o5(8YzeHc{DdfN1kAx_aclsNe#>Q!@Q zdTwbOlfu@@H)rP?T-?+dQS8cNinW2jKmf4tBw7}I;=lqYgdyS(v8E~f*~KYO%quE} zykoPgFWOzE@J|sBTzDWN5TKD!ROP(E9V53inaE?Gl=c(`L;r%PN=oxsp$a$PHo?;M z`|52g=MC(#&lx@T-E*oNyFz=UFfiES8}&Z7-+?m&I$MGWkdMZ@JRb%8=D0&(9oo3VLlbpk+ mP}g+1gBwggwY6#OZSNuHwd=ZLK?!WZIKbB*-3uzE@Baq)1oIgH literal 0 HcmV?d00001 diff --git a/integration-tests/src/test/resources/client.truststore b/integration-tests/src/test/resources/client.truststore new file mode 100644 index 0000000000000000000000000000000000000000..ac2b87c44056d0f22f5d5a5f14eba5f36f08c74d GIT binary patch literal 1009 zcmezO_TO6u1_mY|W(3o$dHE@+hCrSVca+!~2G$5YQv*u|2IkEMP0Z^JnwX|9U}j=u zVq($e+HlT*myJ`a&7bV8AyP{ zxrGH?iZaVmi;5Kz^HLN-^7C_w4J8f4L2}H(0?vuW#X#|*LlF+Q^p+HGRt>=hOw?ujHkkR#)TZE4ciRbLPSJxc{q?KS+FacqdstWkuwxR{`m=wlg+O z@5|O!t5I9E(OEQkRi)6F_0bhK%sdACU4v5(8X z8Cu<8-NT&5z9mL$yMpHGu-gfxVs6no5?cxbH2!a~PfeRLalXg9#78#Aj+E|x$G!X8 zqynZu)0f<0UCY1Dp6~KEi=E5xY5NLGJ$YH%7w5bWSYByhGF~F`I*O^&is9Lu&s*Fa zQU$<2g+4YI1_Rc4r(OT}}!$P1HlgbWu|u`}J+y_boZ zk%4isqJg}DEHJWV`B=nQL^kifCdl2JCKdPd_ECuq^OOF4;d4h0L||G31|lPaZcSI~ zr`e9p(me_O@%MUzFQIbS6qCD>--4b5ooX7%Z* zH^g5=E^ph3*$S7~xxF#0M{D+}&SEW@w0STJi1+Krrfv4oG%$p0x``u-X}bMtTUOvE@Tc5yS>ySXMPs`3nw4f zT{XNkA^*Wsjc4uUn>Z3U6h8hu&>b-Ijrsek + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/server.keystore b/integration-tests/src/test/resources/server.keystore new file mode 100644 index 0000000000000000000000000000000000000000..a8959b188881855b12126a6f4cfd082a1d1854d2 GIT binary patch literal 2299 zcmd6o`9IVP7sqEc82i|>3|X???_^7nghKYQ$1s>tOvW;nOIOU4E$fsmWS*>1Aroq3 zsZ2^qD5SDw88j8Lq`11z^TYiUo*&K+@6Y+=ygui=K4<6i&Sww^1lcR_KLrmX2N5uP zWQX*0ssVxE5P%BX0fcz?wRpfVuqH$l42FYXRM=vOhu@{z%zYJ>*%#Lu`g_Qck?-TS zeDm?`9nZH)^)%J)1!aoXJeo>KiAZgFXw;xz#VEHqR=pBC5M}>KvrOJ*Rbs)HHea?jMG)`%z-gPn>p4NmV zu|x9OR%KgjE6iO=c@>i;r?9*JQKj@@)v_O3hfWsv*vKZet-AgpXFtjrd>ER1N4Z5I zPB|*x)miM*dg`RE3}bmd$1#s#b^;nBTy+%FVUgvje1ZFBs5ZVkEy9jih8erMhVRh? zitcmz;$mQL<0NK28*hh;*=+K*w6&}~avnWpHgG>ZJ1Jh|wm#0}gYOZUN5uVe5ysj< z?S|V!N9bk08mBHTA``eQ!Ijus?_+{}{a>hMjr{pibH8ZMFG0`pTP8tftwYaOjxedOYz* zirX3m2ofepDnF{fmXzwQtXsLv=nnGco9TN`VPCfX{3|QaL(1Y2$ZWt2WNVf;P;3`2LZCFsE;6lZ&`nx+% zEhD-Ut*ln@TAp6wQd8FcRp~Ic8Fa&L7am;3pIseS9z52Z@eW-$%kaOic=r&LkuUx6 zLemUVyh^p?bu5azL)%36g)RiY-LJVQv`ziB_)JH)JtM*9%H#c0qCtE(V_*(hSWM9x zt7*BQYL`Dp3&VA3ZJD#5wRhDo+P~CX z48N7!GF?bQ)$hPcY-q8yetduuWrRu|=@Tg|h)HgV+f8@OBZPnZakAAwmnW^8!9BRWNYlGt%60S-(MoN^RABQdHQ*HE+v^s0`NsN+ zckf{ z02SH*P$7j3C)!2j_7Xi_Rz`QJ9~eHkbfEDQpuV15V{3-2DlvjGPwnNjn- zq}=jX2!bCon_6qEt0-@}(0{7ScsL27S1G>a4M{%?nk!vpo;arP%B}L{ZpTM^SISco zgln4hfM*ljTCx1czQbqT4K3s56}$%MBr(j5cm!mW^$-FDgTNI^fFdBjw`_SKm?TWR zne|o_nMIVvZH@OxHsvmjD0& literal 0 HcmV?d00001 diff --git a/integration-tests/src/test/resources/server.truststore b/integration-tests/src/test/resources/server.truststore new file mode 100644 index 0000000000000000000000000000000000000000..1a3cfeea19f285ccadca883055fcd4ce16243d6d GIT binary patch literal 1004 zcmezO_TO6u1_mY|W(3o0$vK&+c_l!u4|n?leg@VEJyQcq1_tJ}22IQ>4VsuHE?{P2 zWMX1**_Nbkz{|#|)#lOmotKf3o0Y+!vBi+vfRl|ml!Z;0DKywn*gz1(;SlC>PRz+n z%P-2yOf(cR5CRFZ3v&l2=9MHWIOil5B^rtuh=4@7gn3;OOA>=i5-Suu^OE%pr3@rM z;@rZ5E=8GTsYS&KiFqjsA^G_^#V|e0Ks{hNpiv;V8_0?C8k!my8yXoH8dw;aM1i?x zh6YgXU~y&>qY`oeGO{u-H!<=v7&I|*F*PwVGOTtg5o!yV^ZJ{n)cXVcTT}LjrdK$Z zG8;@Vf3&T!mwolhJx3*%{ulcxK4IoEtCPzm)NZ(+I~H7Cxc7~W&Go-mO-dNQ{Jbue z_HN0ctFaHmPaSF69C=>wQmT*G$=Jk8?Y$1d?Tb}cF)g%S)Ua@a^3C9!$Nl{F*9=dr z_*S-HgHr6qi`#zMq`7ZNJ#EOraH-_b%+o4Pa%w3-x1zYi9!z-@R@-89_W5@HZ-%1$ zjZbblIY00ze3S3)5cu!^+T$;)U3h167IwzE?`bqUxO%z0)2^x9oVtOJ-TNJ!R0At~ z9{#RxJDKdPcG~Ip{g|u4`~1Qh`%arK@I1Z1-uH+3P1PgwYee=lF*7nSE><*SDPwSDg}eeWH$ZR-~YkZK76*7ARWOKC8Xqf!-?vdVgE1C73j81*j_EHcQEY4(E^Q-HdUG=0=i7ofzuc_`nnKau- zD|s2?y#4Y98-B%R-8y#X^Mv`n3O^FF?e@OflePZD##e8)%;E|BzyEO%XLgn5%n4g} zT>iC)K|=hE_lKZWd8(PKOt9N@_yN~~Q Vm;T89sG|8j+0p5tsMrp-y#QZ-c@_Ww literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index 5767c3755d5..e1bbdb8c781 100644 --- a/pom.xml +++ b/pom.xml @@ -35,11 +35,16 @@ core + test-infra + integration-tests true UTF-8 + UTF-8 + + true @@ -87,7 +92,7 @@ com.tngtech.java junit-dataprovider - 1.10.0 + 1.12.0 org.assertj @@ -99,6 +104,16 @@ mockito-core 2.7.19 + + com.datastax.oss.simulacron + simulacron-native-server + 0.5.0 + + + org.apache.commons + commons-exec + 1.3 + @@ -123,6 +138,10 @@ maven-surefire-plugin 2.19.1 + + maven-failsafe-plugin + 2.19.1 + maven-shade-plugin 3.0.0 diff --git a/test-infra/pom.xml b/test-infra/pom.xml new file mode 100644 index 00000000000..b39556c8908 --- /dev/null +++ b/test-infra/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-SNAPSHOT + + + java-driver-test-infra + jar + + DataStax Java driver for Apache Cassandra® - test infrastructure tools + + + + com.datastax.oss + java-driver-core + ${project.parent.version} + + + junit + junit + + + org.assertj + assertj-core + + + com.datastax.oss.simulacron + simulacron-native-server + + + org.apache.commons + commons-exec + + + diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java new file mode 100644 index 00000000000..191b8c0cedb --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation for a Class or Method that defines a Cassandra Version requirement. If the cassandra + * version in use does not meet the version requirement, the test is skipped. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface CassandraRequirement { + + /** @return The minimum version required to execute this test, i.e. "2.0.13" */ + String min() default ""; + + /** + * @return the maximum exclusive version allowed to execute this test, i.e. "2.2" means only tests + * < "2.2" may execute this test. + */ + String max() default ""; + + /** @return The description returned if this version requirement is not met. */ + String description() default "Does not meet version requirement."; +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java new file mode 100644 index 00000000000..4e88ed1651b --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Set; +import org.junit.rules.ExternalResource; + +/** + * An {@link ExternalResource} which provides a {@link #setUp()} method for initializing the + * resource externally (instead of making users use rule chains) and a {@link #getContactPoints()} + * for accessing the contact points of the cassandra cluster. + */ +public abstract class CassandraResourceRule extends ExternalResource { + + public synchronized void setUp() { + try { + this.before(); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + /** + * @return Default contact points associated with this cassandra resource. By default returns + * 127.0.0.1 + */ + public Set getContactPoints() { + return Collections.singleton(new InetSocketAddress("127.0.0.1", 9042)); + } + + /** @return The highest protocol version supported by this resource. */ + public abstract ProtocolVersion getHighestProtocolVersion(); +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java new file mode 100644 index 00000000000..02f032e04f1 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.ccm; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteStreamHandler; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.Executor; +import org.apache.commons.exec.LogOutputStream; +import org.apache.commons.exec.PumpStreamHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.netty.util.internal.PlatformDependent.isWindows; + +public class CcmBridge implements AutoCloseable { + + private static final Logger logger = LoggerFactory.getLogger(CcmBridge.class); + + private final CassandraVersion cassandraVersion; + + private final int nodes[]; + + private final Path directory; + + private final AtomicBoolean started = new AtomicBoolean(); + + private final AtomicBoolean created = new AtomicBoolean(); + + private final String ipPrefix; + + private final Map initialConfiguration; + + private final String jvmArgs; + + public static final CassandraVersion DEFAULT_CASSANDRA_VERSION = + CassandraVersion.parse(System.getProperty("ccm.cassandraVersion", "3.11.0")); + + public static final String DEFAULT_CLIENT_TRUSTSTORE_PASSWORD = "cassandra1sfun"; + public static final String DEFAULT_CLIENT_TRUSTSTORE_PATH = "/client.truststore"; + + public static final File DEFAULT_CLIENT_TRUSTSTORE_FILE = + createTempStore(DEFAULT_CLIENT_TRUSTSTORE_PATH); + + public static final String DEFAULT_CLIENT_KEYSTORE_PASSWORD = "cassandra1sfun"; + public static final String DEFAULT_CLIENT_KEYSTORE_PATH = "/client.keystore"; + + public static final File DEFAULT_CLIENT_KEYSTORE_FILE = + createTempStore(DEFAULT_CLIENT_KEYSTORE_PATH); + + // Contains the same keypair as the client keystore, but in format usable by OpenSSL + public static final File DEFAULT_CLIENT_PRIVATE_KEY_FILE = createTempStore("/client.key"); + public static final File DEFAULT_CLIENT_CERT_CHAIN_FILE = createTempStore("/client.crt"); + + public static final String DEFAULT_SERVER_TRUSTSTORE_PASSWORD = "cassandra1sfun"; + public static final String DEFAULT_SERVER_TRUSTSTORE_PATH = "/server.truststore"; + + private static final File DEFAULT_SERVER_TRUSTSTORE_FILE = + createTempStore(DEFAULT_SERVER_TRUSTSTORE_PATH); + + public static final String DEFAULT_SERVER_KEYSTORE_PASSWORD = "cassandra1sfun"; + public static final String DEFAULT_SERVER_KEYSTORE_PATH = "/server.keystore"; + + private static final File DEFAULT_SERVER_KEYSTORE_FILE = + createTempStore(DEFAULT_SERVER_KEYSTORE_PATH); + + private CcmBridge( + Path directory, + CassandraVersion cassandraVersion, + int nodes[], + String ipPrefix, + Map initialConfiguration, + Collection jvmArgs) { + this.directory = directory; + this.cassandraVersion = cassandraVersion; + this.nodes = nodes; + this.ipPrefix = ipPrefix; + this.initialConfiguration = initialConfiguration; + + StringBuilder allJvmArgs = new StringBuilder(""); + String quote = isWindows() ? "\"" : ""; + for (String jvmArg : jvmArgs) { + // Windows requires jvm arguments to be quoted, while *nix requires unquoted. + allJvmArgs.append(" "); + allJvmArgs.append(quote); + allJvmArgs.append("--jvm_arg="); + allJvmArgs.append(jvmArg); + allJvmArgs.append(quote); + } + this.jvmArgs = allJvmArgs.toString(); + } + + public CassandraVersion getCassandraVersion() { + return cassandraVersion; + } + + public void create() { + if (created.compareAndSet(false, true)) { + execute( + "create", + "ccm_1", + "-v", + cassandraVersion.toString(), + "-i", + ipPrefix, + "-n", + Arrays.stream(nodes).mapToObj(n -> "" + n).collect(Collectors.joining(":"))); + + for (Map.Entry conf : initialConfiguration.entrySet()) { + execute("updateconf", String.format("%s:%s", conf.getKey(), conf.getValue())); + } + } + } + + public void start() { + if (started.compareAndSet(false, true)) { + execute("start", jvmArgs, "--wait-for-binary-proto"); + } + } + + public void stop() { + if (started.compareAndSet(true, false)) { + execute("stop"); + } + } + + public void remove() { + execute("remove"); + } + + synchronized void execute(String... args) { + String command = + "ccm " + String.join(" ", args) + " --config-dir=" + directory.toFile().getAbsolutePath(); + logger.debug("Executing: " + command); + + CommandLine cli = CommandLine.parse(command); + ExecuteWatchdog watchDog = new ExecuteWatchdog(TimeUnit.MINUTES.toMillis(10)); + try (LogOutputStream outStream = + new LogOutputStream() { + @Override + protected void processLine(String line, int logLevel) { + logger.debug("ccmout> {}", line); + } + }; + LogOutputStream errStream = + new LogOutputStream() { + @Override + protected void processLine(String line, int logLevel) { + logger.warn("ccmerr> {}", line); + } + }) { + Executor executor = new DefaultExecutor(); + ExecuteStreamHandler streamHandler = new PumpStreamHandler(outStream, errStream); + executor.setStreamHandler(streamHandler); + executor.setWatchdog(watchDog); + + int retValue = executor.execute(cli); + if (retValue != 0) { + logger.error( + "Non-zero exit code ({}) returned from executing ccm command: {}", retValue, command); + } + } catch (IOException ex) { + if (watchDog.killedProcess()) { + throw new RuntimeException("The command '" + command + "' was killed after 10 minutes"); + } else { + throw new RuntimeException("The command '" + command + "' failed to execute"); + } + } + } + + @Override + public void close() { + remove(); + } + + /** + * Extracts a keystore from the classpath into a temporary file. + * + *

      This is needed as the keystore could be part of a built test jar used by other projects, and + * they need to be extracted to a file system so cassandra may use them. + * + * @param storePath Path in classpath where the keystore exists. + * @return The generated File. + */ + private static File createTempStore(String storePath) { + File f = null; + try (InputStream trustStoreIs = CcmBridge.class.getResourceAsStream(storePath)) { + f = File.createTempFile("server", ".store"); + logger.debug("Created store file {} for {}.", f, storePath); + try (OutputStream trustStoreOs = new FileOutputStream(f)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = trustStoreIs.read(buffer)) != -1) { + trustStoreOs.write(buffer, 0, len); + } + } + } catch (IOException e) { + logger.warn("Failure to write keystore, SSL-enabled servers may fail to start.", e); + } + return f; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int[] nodes = {1}; + private final Map cassandraConfiguration = new LinkedHashMap<>(); + private final List jvmArgs = new ArrayList<>(); + private String ipPrefix = "127.0.0."; + private CassandraVersion cassandraVersion = CcmBridge.DEFAULT_CASSANDRA_VERSION; + + private final Path directory; + + private Builder() { + try { + this.directory = Files.createTempDirectory("ccm"); + } catch (IOException e) { + // change to unchecked for now. + throw new RuntimeException(e); + } + // disable auto_snapshot by default to reduce disk usage when destroying schema. + withCassandraConfiguration("auto_snapshot", "false"); + } + + public Builder withCassandraConfiguration(String key, Object value) { + cassandraConfiguration.put(key, value); + return this; + } + + public Builder withJvmArgs(String... jvmArgs) { + Collections.addAll(this.jvmArgs, jvmArgs); + return this; + } + + public Builder withCassandraVersion(CassandraVersion cassandraVersion) { + this.cassandraVersion = cassandraVersion; + return this; + } + + public Builder withNodes(int... nodes) { + this.nodes = nodes; + return this; + } + + public Builder withIpPrefix(String ipPrefix) { + this.ipPrefix = ipPrefix; + return this; + } + + /** Enables SSL encryption. */ + public Builder withSsl() { + cassandraConfiguration.put("client_encryption_options.enabled", "true"); + cassandraConfiguration.put( + "client_encryption_options.keystore", DEFAULT_SERVER_KEYSTORE_FILE.getAbsolutePath()); + cassandraConfiguration.put( + "client_encryption_options.keystore_password", DEFAULT_SERVER_KEYSTORE_PASSWORD); + return this; + } + + /** Enables client authentication. This also enables encryption ({@link #withSsl()}. */ + public Builder withSslAuth() { + withSsl(); + cassandraConfiguration.put("client_encryption_options.require_client_auth", "true"); + cassandraConfiguration.put( + "client_encryption_options.truststore", DEFAULT_SERVER_TRUSTSTORE_FILE.getAbsolutePath()); + cassandraConfiguration.put( + "client_encryption_options.truststore_password", DEFAULT_SERVER_TRUSTSTORE_PASSWORD); + return this; + } + + public CcmBridge build() { + return new CcmBridge( + directory, cassandraVersion, nodes, ipPrefix, cassandraConfiguration, jvmArgs); + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java new file mode 100644 index 00000000000..a2fd4398feb --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.ccm; + +import com.datastax.oss.driver.internal.testinfra.ccm.BaseCcmRule; + +/** + * A rule that creates a globally shared single node Ccm cluster that is only shut down after the + * JVM exists. + * + *

      Note that this rule should be considered mutually exclusive with {@link CustomCcmRule}. + * Creating instances of these rules can create resource issues. + */ +public class CcmRule extends BaseCcmRule { + + private static final CcmRule INSTANCE = new CcmRule(); + + private CcmRule() { + super(CcmBridge.builder().build()); + } + + @Override + protected void after() { + // override after so we don't remove when done. + } + + public static CcmRule getInstance() { + return INSTANCE; + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java new file mode 100644 index 00000000000..3943052568f --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.ccm; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.internal.testinfra.ccm.BaseCcmRule; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A rule that creates a ccm cluster that can be used in a test. This should be used if you plan on + * creating clusters with unique configurations, such as using multiple nodes, authentication, ssl + * and so on. If you do not plan on doing this at all in your tests, consider using {@link CcmRule} + * which creates a global single node CCM cluster that may be shared among tests. + * + *

      Note that this rule should be considered mutually exclusive with {@link CcmRule}. Creating + * instances of these rules can create resource issues. + */ +public class CustomCcmRule extends BaseCcmRule { + + private static AtomicReference current = new AtomicReference<>(); + + CustomCcmRule(CcmBridge ccmBridge) { + super(ccmBridge); + } + + @Override + protected void before() { + if (current.get() == null && current.compareAndSet(null, this)) { + super.before(); + } else if (current.get() != this) { + throw new IllegalStateException( + "Attempting to use a Ccm rule while another is in use. This is disallowed"); + } + } + + @Override + protected void after() { + super.after(); + current.compareAndSet(this, null); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final CcmBridge.Builder bridgeBuilder = CcmBridge.builder(); + + public Builder withNodes(int... nodes) { + bridgeBuilder.withNodes(nodes); + return this; + } + + public Builder withCassandraConfiguration(String key, Object value) { + bridgeBuilder.withCassandraConfiguration(key, value); + return this; + } + + public Builder withJvmArgs(String... jvmArgs) { + bridgeBuilder.withJvmArgs(jvmArgs); + return this; + } + + public Builder withCassandraVersion(CassandraVersion cassandraVersion) { + bridgeBuilder.withCassandraVersion(cassandraVersion); + return this; + } + + public Builder withSsl() { + bridgeBuilder.withSsl(); + return this; + } + + public Builder withSslAuth() { + bridgeBuilder.withSslAuth(); + return this; + } + + public CustomCcmRule build() { + return new CustomCcmRule(bridgeBuilder.build()); + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java new file mode 100644 index 00000000000..8f3549cc3f7 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.cluster; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.internal.core.DefaultCluster; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A rule for creating and managing lifecycle of {@link Cluster} instances. A default cluster is + * created on set up which the user can use to create new {@link Session}s with using {@link + * #newSession()}. New {@link Cluster} instances can be created using the cluster methods provided + * any {@link Cluster} created in this way will be closed when the rule is cleaned up. + */ +public class ClusterRule extends CassandraResourceRule { + + // the ccm rule to depend on + private final CassandraResourceRule cassandraResource; + + // the default cluster that is auto created for this rule. + private Cluster defaultCluster; + + // the default session that is auto created for this rule and is tied to the given keyspace. + private Session defaultSession; + + // clusters created by this rule. + private final Collection clusters = new ArrayList<>(); + + private final String[] defaultClusterOptions; + + private static final AtomicInteger keyspaceId = new AtomicInteger(); + + private final String keyspace = "ks_" + keyspaceId.getAndIncrement(); + + private DriverConfigProfile slowProfile; + + private boolean createDefaultCluster; + private boolean createDefaultSession; + + /** + * Creates a ClusterRule wrapping the provided resource. + * + * @param cassandraResource resource to create clusters for. + * @param options The config options to pass to the default created cluster. + */ + public ClusterRule(CassandraResourceRule cassandraResource, String... options) { + this(cassandraResource, true, true, options); + } + + /** + * Creates a ClusterRule wrapping the provided resource. + * + * @param cassandraResource resource to create clusters for. + * @param createDefaultCluster whether or not to create a default cluster on initialization. + * @param createDefaultSession whether or not to create a default session on initialization. + * @param options The config options to pass to the default created cluster. + */ + public ClusterRule( + CassandraResourceRule cassandraResource, + boolean createDefaultCluster, + boolean createDefaultSession, + String... options) { + this.cassandraResource = cassandraResource; + this.defaultClusterOptions = options; + this.createDefaultCluster = createDefaultCluster; + this.createDefaultSession = createDefaultSession; + } + + @Override + protected void before() { + // ensure resource is initialized before initializing the defaultCluster. + cassandraResource.setUp(); + if (createDefaultCluster) { + defaultCluster = defaultCluster(defaultClusterOptions); + clusters.add(defaultCluster); + + slowProfile = + defaultCluster + .getContext() + .config() + .getDefaultProfile() + .withString(CoreDriverOption.REQUEST_TIMEOUT, "30s"); + // TODO: Make this more pleasant + if (createDefaultSession) { + if (!(cassandraResource instanceof SimulacronRule)) { + createKeyspace(); + defaultSession = defaultCluster.connect(CqlIdentifier.fromCql(keyspace)); + } else { + defaultSession = defaultCluster.connect(); + } + } + } + } + + private void createKeyspace() { + try (Session session = newSession()) { + SimpleStatement createKeyspace = + SimpleStatement.builder( + String.format( + "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", + keyspace)) + .withConfigProfile(slowProfile) + .build(); + session.execute(createKeyspace); + } + } + + private void dropKeyspace() { + if (createDefaultSession) { + defaultSession.execute( + SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace)) + .withConfigProfile(slowProfile) + .build()); + } + } + + @Override + protected void after() { + if (createDefaultCluster) { + if (!(cassandraResource instanceof SimulacronRule)) { + dropKeyspace(); + } + } + clusters.forEach( + c -> { + if (!c.closeFuture().toCompletableFuture().isDone()) { + c.close(); + } + }); + } + + /** @return the default cluster created with this rule. */ + public Cluster cluster() { + return defaultCluster; + } + + /** @return the default session created with this rule. */ + public Session session() { + return defaultSession; + } + + /** @return keyspace associated with this rule. */ + public String keyspace() { + return keyspace; + } + + /** @return a config profile where the request timeout is 30 seconds. * */ + public DriverConfigProfile slowProfile() { + return slowProfile; + } + + /** + * @return A {@link DefaultCluster} instance using the nodes in 0th DC as contact points and + * defaults for all other configuration. Registers the returned cluster with the rule so it is + * closed on completion. + */ + public Cluster defaultCluster(String... options) { + Cluster cluster = + Cluster.builder() + .addContactPoints(getContactPoints()) + .withConfigLoader(new TestConfigLoader(options)) + .build(); + clusters.add(cluster); + return cluster; + } + + @Override + public ProtocolVersion getHighestProtocolVersion() { + return cassandraResource.getHighestProtocolVersion(); + } + + @Override + public Set getContactPoints() { + return cassandraResource.getContactPoints(); + } + + /** @return a new session from the default cluster associated with this rule. */ + public Session newSession() { + return defaultCluster.connect(); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java new file mode 100644 index 00000000000..01d4e2d3adf --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.loadbalancing; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import java.net.InetAddress; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; +import java.util.TreeSet; + +public class SortingLoadBalancingPolicy implements LoadBalancingPolicy { + + @SuppressWarnings("unused") + public SortingLoadBalancingPolicy(DriverContext context, DriverOption option) { + // constructor needed for loading via config. + } + + private byte[] empty = {}; + private final Set nodes = + new TreeSet<>( + (node1, node2) -> { + // compare address bytes, byte by byte. + byte[] address1 = + node1.getBroadcastAddress().map(InetAddress::getAddress).orElse(empty); + byte[] address2 = + node2.getBroadcastAddress().map(InetAddress::getAddress).orElse(empty); + + // ipv6 vs ipv4, favor ipv6. + if (address1.length != address2.length) { + return address1.length - address2.length; + } + + for (int i = 0; i < address1.length; i++) { + int b1 = address1[i] & 0xFF; + int b2 = address2[i] & 0xFF; + if (b1 != b2) { + return b1 - b2; + } + } + return 0; + }); + + public SortingLoadBalancingPolicy() {} + + @Override + public void init(Set nodes, DistanceReporter distanceReporter) { + this.nodes.addAll(nodes); + this.nodes.forEach(n -> distanceReporter.setDistance(n, NodeDistance.LOCAL)); + } + + @Override + public Queue newQueryPlan() { + return new LinkedList<>(nodes); + } + + @Override + public void onAdd(Node node) { + this.nodes.add(node); + } + + @Override + public void onUp(Node node) { + onAdd(node); + } + + @Override + public void onDown(Node node) { + onRemove(node); + } + + @Override + public void onRemove(Node node) { + this.nodes.remove(node); + } + + @Override + public void close() {} +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java new file mode 100644 index 00000000000..f41c3135201 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.simulacron; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.server.AddressResolver; +import com.datastax.oss.simulacron.server.BoundCluster; +import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.Server; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.stream.Collectors; + +public class SimulacronRule extends CassandraResourceRule { + // TODO perhaps share server some other way + private static final Server server = + Server.builder().withAddressResolver(new AddressResolver.Inet4Resolver(9043)).build(); + + private final ClusterSpec clusterSpec; + private BoundCluster boundCluster; + + public SimulacronRule(ClusterSpec clusterSpec) { + this.clusterSpec = clusterSpec; + } + + public SimulacronRule(ClusterSpec.Builder clusterSpec) { + this(clusterSpec.build()); + } + + /** + * Convenient fluent name for getting at bound cluster. + * + * @return default bound cluster for this simulacron instance. + */ + public BoundCluster cluster() { + return boundCluster; + } + + public BoundCluster getBoundCluster() { + return boundCluster; + } + + @Override + protected void before() { + boundCluster = server.register(clusterSpec); + } + + @Override + protected void after() { + boundCluster.close(); + } + + /** @return All nodes in first data center. */ + @Override + public Set getContactPoints() { + return boundCluster + .dc(0) + .getNodes() + .stream() + .map(BoundNode::inetSocketAddress) + .collect(Collectors.toSet()); + } + + @Override + public ProtocolVersion getHighestProtocolVersion() { + return CoreProtocolVersion.V4; + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java new file mode 100644 index 00000000000..0a1c9171580 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.utils; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BooleanSupplier; + +import static org.assertj.core.api.Fail.fail; + +public class ConditionChecker { + + private static final int DEFAULT_PERIOD_MILLIS = 500; + + private static final int DEFAULT_TIMEOUT_MILLIS = 60000; + + public static class ConditionCheckerBuilder { + + private long timeout = DEFAULT_TIMEOUT_MILLIS; + + private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; + + private long period = DEFAULT_PERIOD_MILLIS; + + private TimeUnit periodUnit = TimeUnit.MILLISECONDS; + + private final BooleanSupplier predicate; + + ConditionCheckerBuilder(BooleanSupplier predicate) { + this.predicate = predicate; + } + + public ConditionCheckerBuilder every(long period, TimeUnit unit) { + this.period = period; + periodUnit = unit; + return this; + } + + public ConditionCheckerBuilder every(long periodMillis) { + period = periodMillis; + periodUnit = TimeUnit.MILLISECONDS; + return this; + } + + public ConditionCheckerBuilder before(long timeout, TimeUnit unit) { + this.timeout = timeout; + timeoutUnit = unit; + return this; + } + + public ConditionCheckerBuilder before(long timeoutMillis) { + timeout = timeoutMillis; + timeoutUnit = TimeUnit.MILLISECONDS; + return this; + } + + @SuppressWarnings("unchecked") + public void becomesTrue() { + new ConditionChecker(predicate, period, periodUnit).await(timeout, timeoutUnit); + } + + @SuppressWarnings("unchecked") + public void becomesFalse() { + new ConditionChecker(() -> !predicate.getAsBoolean(), period, periodUnit) + .await(timeout, timeoutUnit); + } + } + + public static ConditionCheckerBuilder checkThat(BooleanSupplier predicate) { + return new ConditionCheckerBuilder(predicate); + } + + private final BooleanSupplier predicate; + private final Lock lock; + private final Condition condition; + private final Timer timer; + + @SuppressWarnings("unchecked") + public ConditionChecker(BooleanSupplier predicate, long period, TimeUnit periodUnit) { + this.predicate = predicate; + lock = new ReentrantLock(); + condition = lock.newCondition(); + timer = new Timer("condition-checker", true); + timer.schedule( + new TimerTask() { + @Override + public void run() { + checkCondition(); + } + }, + 0, + periodUnit.toMillis(period)); + } + + /** Waits until the predicate becomes true, or a timeout occurs, whichever happens first. */ + public void await(long timeout, TimeUnit unit) { + boolean interrupted = false; + long nanos = unit.toNanos(timeout); + lock.lock(); + try { + while (!evalCondition()) { + if (nanos <= 0L) + fail( + String.format( + "Timeout after %s %s while waiting for condition", + timeout, unit.toString().toLowerCase())); + try { + nanos = condition.awaitNanos(nanos); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + timer.cancel(); + if (interrupted) Thread.currentThread().interrupt(); + } + } + + private void checkCondition() { + lock.lock(); + try { + if (evalCondition()) { + condition.signal(); + } + } finally { + lock.unlock(); + } + } + + private boolean evalCondition() { + return predicate.getAsBoolean(); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java new file mode 100644 index 00000000000..ddd8543cba8 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.utils; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class NodeUtils { + + private static final Logger logger = LoggerFactory.getLogger(NodeUtils.class); + + private static final int TEST_BASE_NODE_WAIT = 60; + + public static void waitForUp(Node node) { + waitFor(node, TEST_BASE_NODE_WAIT, NodeState.UP); + } + + public static void waitForUp(Node node, int timeoutSeconds) { + waitFor(node, timeoutSeconds, NodeState.UP); + } + + public static void waitForDown(Node node) { + waitFor(node, TEST_BASE_NODE_WAIT * 3, NodeState.DOWN); + } + + public static void waitForDown(Node node, int timeoutSeconds) { + waitFor(node, timeoutSeconds, NodeState.DOWN); + } + + public static void waitFor(Node node, int timeoutSeconds, NodeState nodeState) { + logger.debug("Waiting for node {} to enter state {}", node, nodeState); + ConditionChecker.checkThat(() -> node.getState().equals(nodeState)) + .every(100, MILLISECONDS) + .before(timeoutSeconds, SECONDS) + .becomesTrue(); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java new file mode 100644 index 00000000000..47d992bd89a --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.testinfra.ccm; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import org.junit.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public abstract class BaseCcmRule extends CassandraResourceRule { + + private final CcmBridge ccmBridge; + + private final CassandraVersion cassandraVersion; + + public BaseCcmRule(CcmBridge ccmBridge) { + this.ccmBridge = ccmBridge; + this.cassandraVersion = ccmBridge.getCassandraVersion(); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + try { + ccmBridge.remove(); + } catch (Exception e) { + // silently remove as may have already been removed. + } + })); + } + + @Override + protected void before() { + ccmBridge.create(); + ccmBridge.start(); + } + + @Override + protected void after() { + ccmBridge.remove(); + } + + @Override + public Statement apply(Statement base, Description description) { + CassandraRequirement cassandraRequirement = + description.getAnnotation(CassandraRequirement.class); + + if (cassandraRequirement != null) { + // if the configured cassandra cassandraRequirement exceeds the one being used skip this test. + if (!cassandraRequirement.min().isEmpty()) { + CassandraVersion minVersion = CassandraVersion.parse(cassandraRequirement.min()); + if (minVersion.compareTo(cassandraVersion) > 0) { + // Create a statement which simply indicates that the configured cassandra cassandraRequirement is too old for this test. + return new Statement() { + + @Override + public void evaluate() throws Throwable { + throw new AssumptionViolatedException( + "Test requires C* " + + minVersion + + " but " + + cassandraVersion + + " is configured. Description: " + + cassandraRequirement.description()); + } + }; + } + } + + if (!cassandraRequirement.max().isEmpty()) { + // if the test version exceeds the maximum configured one, fail out. + CassandraVersion maxVersion = CassandraVersion.parse(cassandraRequirement.max()); + + if (maxVersion.compareTo(cassandraVersion) <= 0) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + throw new AssumptionViolatedException( + "Test requires C* less than " + + maxVersion + + " but " + + cassandraVersion + + " is configured. Description: " + + cassandraRequirement.description()); + } + }; + } + } + } + return super.apply(base, description); + } + + public CassandraVersion getCassandraVersion() { + return cassandraVersion; + } + + @Override + public ProtocolVersion getHighestProtocolVersion() { + if (cassandraVersion.compareTo(CassandraVersion.parse("2.2")) >= 0) { + return CoreProtocolVersion.V4; + } else { + return CoreProtocolVersion.V3; + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java new file mode 100644 index 00000000000..82147ad523e --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.testinfra.cluster; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +public class TestConfigLoader extends DefaultDriverConfigLoader { + + public TestConfigLoader(String... customOptions) { + super(() -> buildConfig(customOptions), CoreDriverOption.values()); + } + + private static Config buildConfig(String... customOptions) { + String customConfig = String.join("\n", customOptions); + // Add additional config for overriding quiet period on netty shutdown. + String additionalCustomConfig = + String.join( + "\n", + customConfig, + "netty.io-group.shutdown.quiet-period = 0", + "netty.admin-group.shutdown.quiet-period = 0"); + return ConfigFactory.parseString(additionalCustomConfig) + .withFallback(DEFAULT_CONFIG_SUPPLIER.get()); + } +} From 8e254ccf49d2bbb267964cfdd88d8579442e2b73 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 27 Jul 2017 16:05:52 -0500 Subject: [PATCH 157/742] Add integration tests for unset values and profiles --- .../core/config/DriverConfigProfileIT.java | 217 ++++++++++++++++++ .../driver/api/core/cql/BatchStatementIT.java | 67 +++++- .../driver/api/core/cql/BoundStatementIT.java | 140 +++++++++++ .../api/testinfra/cluster/ClusterRule.java | 8 +- 4 files changed, 424 insertions(+), 8 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java new file mode 100644 index 00000000000..fa4e43bb3d6 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.cql.BatchStatement; +import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; +import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.servererrors.ServerError; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.cluster.QueryLog; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class DriverConfigProfileIT { + + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + + @Rule public CcmRule ccm = CcmRule.getInstance(); + + @Rule public ClusterRule cluster = new ClusterRule(simulacron, false, false); + + @Rule public ClusterRule ccmCluster = new ClusterRule(ccm, false, false); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + // TODO: Test with reprepare on all nodes profile configuration + + @Test + public void should_fail_if_config_profile_specified_doesnt_exist() { + try (Cluster profileCluster = cluster.defaultCluster()) { + Session session = profileCluster.connect(); + + SimpleStatement statement = + SimpleStatement.builder("select * from system.local") + .withConfigProfileName("IDONTEXIST") + .build(); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Unknown profile 'IDONTEXIST'. Check your configuration"); + session.execute(statement); + } + } + + @Test + public void should_use_profile_request_timeout() { + try (Cluster profileCluster = cluster.defaultCluster("profiles.olap.request.timeout = 10s")) { + String query = "mockquery"; + // configure query with delay of 2 seconds. + simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); + Session session = profileCluster.connect(); + + // Execute query without profile, should timeout with default (0.5s). + try { + session.execute(query); + fail("Should have timed out"); + } catch (DriverTimeoutException e) { + // expected. + } + + // Execute query with profile, should not timeout since waits up to 10 seconds. + session.execute(SimpleStatement.builder(query).withConfigProfileName("olap").build()); + } + } + + @Test + public void should_use_profile_default_idempotence() { + try (Cluster profileCluster = + cluster.defaultCluster("profiles.idem.request.default-idempotence = true")) { + String query = "mockquery"; + // configure query with server error which should invoke onRequestError in retry policy. + simulacron.cluster().prime(when(query).then(serverError("fail"))); + + Session session = profileCluster.connect(); + + // Execute query without profile, should fail because couldn't be retried. + try { + session.execute(query); + fail("Should have failed with server error"); + } catch (ServerError e) { + // expected. + } + + // Execute query with profile, should retry on all hosts since query is idempotent. + thrown.expect(AllNodesFailedException.class); + session.execute(SimpleStatement.builder(query).withConfigProfileName("idem").build()); + } + } + + @Test + public void should_use_profile_consistency() { + try (Cluster profileCluster = + cluster.defaultCluster( + "profiles.cl.request.consistency = LOCAL_QUORUM", + "profiles.cl.request.serial-consistency = LOCAL_SERIAL")) { + String query = "mockquery"; + + Session session = profileCluster.connect(); + + // Execute query without profile, should use default CLs (LOCAL_ONE, SERIAL). + session.execute(query); + + Optional log = + simulacron + .cluster() + .getLogs() + .getQueryLogs() + .stream() + .filter(q -> q.getQuery().equals(query)) + .findFirst(); + + assertThat(log) + .isPresent() + .hasValueSatisfying( + (l) -> { + assertThat(l.getConsistency().toString()).isEqualTo("LOCAL_ONE"); + assertThat(l.getSerialConsistency().toString()).isEqualTo("SERIAL"); + }); + + simulacron.cluster().clearLogs(); + + // Execute query with profile, should use profile CLs + session.execute(SimpleStatement.builder(query).withConfigProfileName("cl").build()); + + log = + simulacron + .cluster() + .getLogs() + .getQueryLogs() + .stream() + .filter(q -> q.getQuery().equals(query)) + .findFirst(); + + assertThat(log) + .isPresent() + .hasValueSatisfying( + (l) -> { + assertThat(l.getConsistency().toString()).isEqualTo("LOCAL_QUORUM"); + assertThat(l.getSerialConsistency().toString()).isEqualTo("LOCAL_SERIAL"); + }); + } + } + + @Test + public void should_use_profile_page_size() { + try (Cluster profileCluster = + ccmCluster.defaultCluster( + "request.page-size = 100", "profiles.smallpages.request.page-size = 10")) { + ccmCluster.createKeyspace(profileCluster); + + Session session = profileCluster.connect(CqlIdentifier.fromCql(ccmCluster.keyspace())); + + // load 500 rows (value beyond page size). + session.execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test (k int, v int, PRIMARY KEY (k,v))") + .withConfigProfile(ccmCluster.slowProfile()) + .build()); + PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); + BatchStatementBuilder bs = + BatchStatement.builder(BatchType.UNLOGGED).withConfigProfile(ccmCluster.slowProfile()); + for (int i = 0; i < 500; i++) { + bs.addStatement(prepared.bind(i)); + } + session.execute(bs.build()); + + String query = "SELECT * FROM test where k=0"; + // Execute query without profile, should use global page size (100) + ResultSet result = session.execute(query); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); + result.fetchNextPage(); + // next fetch should also be 100 pages. + assertThat(result.getAvailableWithoutFetching()).isEqualTo(200); + + // Execute query with profile, should use profile page size + result = + session.execute( + SimpleStatement.builder(query).withConfigProfileName("smallpages").build()); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + // next fetch should also be 10 pages. + result.fetchNextPage(); + assertThat(result.getAvailableWithoutFetching()).isEqualTo(20); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index daedb312a26..710cf28f5f5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -16,11 +16,11 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import java.util.Iterator; -import org.junit.BeforeClass; -import org.junit.ClassRule; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -29,16 +29,16 @@ public class BatchStatementIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + @Rule public CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + @Rule public ClusterRule cluster = new ClusterRule(ccm); @Rule public TestName name = new TestName(); private static final int batchCount = 100; - @BeforeClass - public static void createTable() { + @Before + public void createTable() { String[] schemaStatements = new String[] { "CREATE TABLE test (k0 text, k1 int, v int, PRIMARY KEY (k0, k1))", @@ -96,6 +96,61 @@ public void should_execute_batch_of_bound_statements_with_variables() { verifyBatchInsert(); } + @Test + @CassandraRequirement(min = "2.2") + public void should_execute_batch_of_bound_statements_with_unset_values() { + // Build a batch of batchCount statements with bound statements, each with their own positional variables. + BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + SimpleStatement insert = + SimpleStatement.builder( + String.format( + "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) + .build(); + PreparedStatement preparedStatement = cluster.session().prepare(insert); + + for (int i = 0; i < batchCount; i++) { + builder.addStatement(preparedStatement.bind(i, i + 1)); + } + + BatchStatement batchStatement = builder.build(); + cluster.session().execute(batchStatement); + + verifyBatchInsert(); + + BatchStatementBuilder builder2 = BatchStatement.builder(BatchType.UNLOGGED); + for (int i = 0; i < batchCount; i++) { + BoundStatement boundStatement = preparedStatement.bind(i, i + 2); + // unset v every 20 statements. + if (i % 20 == 0) { + boundStatement.unset(1); + } + builder.addStatement(boundStatement); + } + + cluster.session().execute(builder2.build()); + + Statement select = + SimpleStatement.builder("SELECT * from test where k0 = ?") + .addPositionalValue(name.getMethodName()) + .build(); + + ResultSet result = cluster.session().execute(select); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); + + Iterator rows = result.iterator(); + for (int i = 0; i < batchCount; i++) { + Row row = rows.next(); + assertThat(row.getString("k0")).isEqualTo(name.getMethodName()); + assertThat(row.getInt("k1")).isEqualTo(i); + // value should be from first insert (i + 1) if at row divisble by 20, otherwise second. + int expectedValue = i % 20 == 0 ? i + 1 : i + 2; + if (i % 20 == 0) { + assertThat(row.getInt("v")).isEqualTo(expectedValue); + } + } + } + @Test public void should_execute_batch_of_bound_statements_with_named_variables() { // Build a batch of batchCount statements with bound statements, each with their own named variable values. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java new file mode 100644 index 00000000000..5da4f02e947 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BoundStatementIT { + + @Rule public CcmRule ccm = CcmRule.getInstance(); + + @Rule public ClusterRule cluster = new ClusterRule(ccm, "request.page-size = 20"); + + @Rule public TestName name = new TestName(); + + private static final int VALUE = 7; + + @Before + public void setupSchema() { + // table with simple primary key, single cell. + cluster + .session() + .execute( + SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v0 int)") + .withConfigProfile(cluster.slowProfile()) + .build()); + } + + @Test(expected = IllegalStateException.class) + @Ignore + public void should_not_allow_unset_value_on_bound_statement_when_protocol_less_than_v4() { + // TODO reenable this if JAVA-1584 is fixed. + try (Cluster v3Cluster = cluster.defaultCluster("protocol.version = V3")) { + Session session = v3Cluster.connect(CqlIdentifier.fromCql(cluster.keyspace())); + PreparedStatement prepared = + session.prepare("INSERT INTO test2 (k, v0, v1) values (?, ?, ?)"); + + BoundStatement boundStatement = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .unset(1) + .setString(2, name.getMethodName()) + .build(); + + session.execute(boundStatement); + } + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_not_write_tombstone_if_value_is_implicitly_unset() { + PreparedStatement prepared = + cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + + cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + + BoundStatement boundStatement = + prepared.boundStatementBuilder().setString(0, name.getMethodName()).build(); + + verifyUnset(boundStatement); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_write_tombstone_if_value_is_explicitly_unset() { + PreparedStatement prepared = + cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + + cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + + BoundStatement boundStatement = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, VALUE + 1) // set initially, will be unset later + .build(); + + verifyUnset(boundStatement.unset(1)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { + PreparedStatement prepared = + cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + + cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + + BoundStatement boundStatement = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, VALUE + 1) // set initially, will be unset later + .unset(1) + .build(); + + verifyUnset(boundStatement); + } + + private void verifyUnset(BoundStatement boundStatement) { + cluster.session().execute(boundStatement.unset(1)); + + // Verify that no tombstone was written by reading data back and ensuring initial value is retained. + ResultSet result = + cluster + .session() + .execute( + SimpleStatement.builder("SELECT v0 from test2 where k = ?") + .addPositionalValue(name.getMethodName()) + .build()); + + Row row = result.iterator().next(); + assertThat(row.getInt(0)).isEqualTo(VALUE); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 8f3549cc3f7..1375948f25e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -118,8 +118,12 @@ protected void before() { } } - private void createKeyspace() { - try (Session session = newSession()) { + public void createKeyspace() { + createKeyspace(defaultCluster); + } + + public void createKeyspace(Cluster defaultCluster) { + try (Session session = defaultCluster.connect()) { SimpleStatement createKeyspace = SimpleStatement.builder( String.format( From 1a98b6f30f1be61d5af5f90ec86a61fbbafd69c4 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 27 Jul 2017 17:32:00 -0500 Subject: [PATCH 158/742] Use slow profile for schema query --- .../driver/api/core/config/DriverConfigProfileIT.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index fa4e43bb3d6..62242a8661e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -177,7 +177,10 @@ public void should_use_profile_consistency() { public void should_use_profile_page_size() { try (Cluster profileCluster = ccmCluster.defaultCluster( - "request.page-size = 100", "profiles.smallpages.request.page-size = 10")) { + "request.page-size = 100", + "profiles.slow.request.timeout = 30s", + "profiles.smallpages.request.page-size = 10")) { + ccmCluster.createKeyspace(profileCluster); Session session = profileCluster.connect(CqlIdentifier.fromCql(ccmCluster.keyspace())); @@ -186,11 +189,11 @@ public void should_use_profile_page_size() { session.execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k int, v int, PRIMARY KEY (k,v))") - .withConfigProfile(ccmCluster.slowProfile()) + .withConfigProfileName("slow") .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(BatchType.UNLOGGED).withConfigProfile(ccmCluster.slowProfile()); + BatchStatement.builder(BatchType.UNLOGGED).withConfigProfileName("slow"); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } From c6fe76399446bf23ac3ceeba57bc00201075ec59 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 27 Jul 2017 17:38:02 -0500 Subject: [PATCH 159/742] Also use slow profile for create keyspace query --- .../driver/api/core/config/DriverConfigProfileIT.java | 9 ++++++++- .../oss/driver/api/testinfra/cluster/ClusterRule.java | 6 +----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 62242a8661e..085702b84c8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -181,7 +181,14 @@ public void should_use_profile_page_size() { "profiles.slow.request.timeout = 30s", "profiles.smallpages.request.page-size = 10")) { - ccmCluster.createKeyspace(profileCluster); + SimpleStatement createKeyspace = + SimpleStatement.builder( + String.format( + "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", + ccmCluster.keyspace())) + .withConfigProfileName("slow") + .build(); + profileCluster.connect().execute(createKeyspace); Session session = profileCluster.connect(CqlIdentifier.fromCql(ccmCluster.keyspace())); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 1375948f25e..9dd9a0935ab 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -118,11 +118,7 @@ protected void before() { } } - public void createKeyspace() { - createKeyspace(defaultCluster); - } - - public void createKeyspace(Cluster defaultCluster) { + private void createKeyspace() { try (Session session = defaultCluster.connect()) { SimpleStatement createKeyspace = SimpleStatement.builder( From faece8f67e9bd44a52b32a51c9e0f52b7e60fdff Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 27 Jul 2017 11:18:14 -0700 Subject: [PATCH 160/742] Add constant speculative execution policy + allow 0 delay --- .../api/core/config/CoreDriverOption.java | 2 + .../ConstantSpeculativeExecutionPolicy.java | 63 ++++++++++++++ .../specex/NoSpeculativeExecutionPolicy.java | 2 +- .../specex/SpeculativeExecutionPolicy.java | 4 +- .../internal/core/cql/CqlRequestHandler.java | 4 +- core/src/main/resources/reference.conf | 13 +++ ...onstantSpeculativeExecutionPolicyTest.java | 87 +++++++++++++++++++ ...equestHandlerSpeculativeExecutionTest.java | 2 +- .../core/cql/RequestHandlerTestHarness.java | 11 +++ 9 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 47555fd362b..cdd7b5da10a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -46,6 +46,8 @@ public enum CoreDriverOption implements DriverOption { REQUEST_DEFAULT_IDEMPOTENCE("request.default-idempotence", true), RETRY_POLICY_ROOT("request.retry-policy", true), SPECULATIVE_EXECUTION_POLICY_ROOT("request.speculative-execution-policy", true), + RELATIVE_SPECULATIVE_EXECUTION_MAX("max-executions", false), + RELATIVE_SPECULATIVE_EXECUTION_DELAY("delay", false), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java new file mode 100644 index 00000000000..8839b19b286 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.specex; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.Request; + +/** + * A policy that schedules a configurable number of speculative executions, separated by a fixed + * delay. + * + *

      See the (commented) sample configuration in {@code reference.conf} for detailed explanations + * about each option. + */ +public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { + + private final int maxExecutions; + private final long constantDelayMillis; + + public ConstantSpeculativeExecutionPolicy(DriverContext context, DriverOption configRoot) { + DriverConfigProfile config = context.config().getDefaultProfile(); + this.maxExecutions = + config.getInt(configRoot.concat(CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_MAX)); + if (this.maxExecutions < 1) { + throw new IllegalArgumentException("Max must be at least 1"); + } + this.constantDelayMillis = + config + .getDuration(configRoot.concat(CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_DELAY)) + .toMillis(); + if (this.constantDelayMillis < 0) { + throw new IllegalArgumentException("Delay must be positive or 0"); + } + } + + @Override + public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { + assert runningExecutions >= 1; + return (runningExecutions < maxExecutions) ? constantDelayMillis : -1; + } + + @Override + public void close() { + // nothing to do + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java index 14f696571ca..c1e04ca19e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -32,7 +32,7 @@ public NoSpeculativeExecutionPolicy( @Override public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { // never start speculative executions - return 0; + return -1; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java index 7324775d09b..42be8abd586 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java @@ -34,8 +34,8 @@ public interface SpeculativeExecutionPolicy extends AutoCloseable { * initial, non-speculative request). For example, if this is 2 it means the initial attempt * was sent, then the driver scheduled a first speculative execution, and it is now asking for * the delay until the second speculative execution. - * @return the time (in milliseconds) until a speculative request is sent to the next node, or - * zero or a negative value to stop sending requests. + * @return the time (in milliseconds) until a speculative request is sent to the next node, or 0 + * to send it immediately, or a negative value to stop sending requests. */ long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 58ff9d429b7..d38f59b3acf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -132,7 +132,7 @@ public class CqlRequestHandler if (isIdempotent) { // Start the initial execution long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); - if (nextExecution > 0) { + if (nextExecution >= 0) { LOG.debug("[{}] Scheduling first speculative execution in {} ms", logPrefix, nextExecution); this.pendingExecutions = new CopyOnWriteArrayList<>(); this.pendingExecutions.add( @@ -180,7 +180,7 @@ private void startExecution() { int execution = executions.incrementAndGet(); LOG.trace("[{}] Starting speculative execution {}", logPrefix, execution); long nextDelay = speculativeExecutionPolicy.nextExecution(keyspace, request, execution + 1); - if (nextDelay > 0) { + if (nextDelay >= 0) { LOG.trace( "[{}] Scheduling {}th speculative execution in {} ms", logPrefix, diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index f8026cfcb93..2d487d475d1 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -266,7 +266,20 @@ datastax-java-driver { } speculative-execution-policy { + # Don't schedule any speculative executions class = com.datastax.oss.driver.api.core.specex.NoSpeculativeExecutionPolicy + + # Schedule a fixed number of executions, with a fixed delay + // class = com.datastax.oss.driver.api.core.specex.ConstantSpeculativeExecutionPolicy + # The maximum number of executions (including the initial, non-speculative execution). + # This must be at least one. + // max-executions = 3 + # The delay between each execution. 0 is allowed, and will result in all executions being sent + # simultaneously when the request starts. + # Note that sub-millisecond precision is not supported, any excess precision information will + # be dropped; in particular, delays of less than 1 millisecond are equivalent to 0. + # This must be positive or 0. + // delay = 100 milliseconds } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java new file mode 100644 index 00000000000..90d7c859ddd --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.specex; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.Request; +import java.time.Duration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.datastax.oss.driver.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class ConstantSpeculativeExecutionPolicyTest { + @Mock private DriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultProfile; + @Mock private Request request; + + @Before + public void setup() { + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + } + + private void mockOptions(int maxExecutions, long constantDelayMillis) { + Mockito.when( + defaultProfile.getInt( + CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT.concat( + CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_MAX))) + .thenReturn(maxExecutions); + Mockito.when( + defaultProfile.getDuration( + CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT.concat( + CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_DELAY))) + .thenReturn(Duration.ofMillis(constantDelayMillis)); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_if_delay_negative() { + mockOptions(1, -10); + new ConstantSpeculativeExecutionPolicy( + context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_if_max_less_than_one() { + mockOptions(0, 10); + new ConstantSpeculativeExecutionPolicy( + context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + } + + @Test + public void should_return_delay_until_max() { + mockOptions(3, 10); + SpeculativeExecutionPolicy policy = + new ConstantSpeculativeExecutionPolicy( + context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + + // Initial execution starts, schedule first speculative execution + assertThat(policy.nextExecution(null, request, 1)).isEqualTo(10); + // First speculative execution starts, schedule second one + assertThat(policy.nextExecution(null, request, 2)).isEqualTo(10); + // Second speculative execution starts, we're at 3 => stop + assertThat(policy.nextExecution(null, request, 3)).isNegative(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 693d11e5665..c0e34da2f62 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -78,7 +78,7 @@ public void should_schedule_speculative_executions( .thenReturn(firstExecutionDelay); Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 2)) .thenReturn(secondExecutionDelay); - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(0L); + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(-1L); new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .asyncResult(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 9feb9501841..22d6b328234 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -16,11 +16,13 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -46,6 +48,9 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; + /** * Provides the environment to test a request handler, where a query plan can be defined, and the * behavior of each successive node simulated. @@ -98,6 +103,12 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); Mockito.when(context.retryPolicy()).thenReturn(retryPolicy); + + // Disable speculative executions by default + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(CqlIdentifier.class), any(Request.class), anyInt())) + .thenReturn(-1L); Mockito.when(context.speculativeExecutionPolicy()).thenReturn(speculativeExecutionPolicy); Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); From 8d6eedd8a58204243f3500e5e46e6c0289d63879 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 27 Jul 2017 17:02:49 -0700 Subject: [PATCH 161/742] Add integration test for speculative executions --- .../internal/core/cql/CqlRequestHandler.java | 75 +++-- ...equestHandlerSpeculativeExecutionTest.java | 35 +++ .../core/specex/SpeculativeExecutionIT.java | 273 ++++++++++++++++++ 3 files changed, 353 insertions(+), 30 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index d38f59b3acf..d482485da76 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -84,13 +84,22 @@ public class CqlRequestHandler private final CompletableFuture result; private final Message message; private final EventExecutor scheduler; - // How many speculative executions are currently running (not counting the initial execution). - // All executions share the same query plan, they stop either when the request completes or the - // query plan is empty. - private final AtomicInteger executions; + /** + * How many speculative executions are currently running (including the initial execution). We + * track this in order to know when to fail the request if all executions have reached the end of + * the query plan. + */ + private final AtomicInteger activeExecutionsCount; + /** + * How many speculative executions have started (excluding the initial execution), whether they + * have completed or not. We track this in order to fill {@link + * ExecutionInfo#getSpeculativeExecutionCount()}. + */ + private final AtomicInteger startedSpeculativeExecutionsCount; + private final Duration timeout; private final ScheduledFuture timeoutFuture; - private final List> pendingExecutions; + private final List> scheduledExecutions; private final List inFlightCallbacks; private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; @@ -127,28 +136,30 @@ public class CqlRequestHandler this.retryPolicy = context.retryPolicy(); this.speculativeExecutionPolicy = context.speculativeExecutionPolicy(); - this.executions = new AtomicInteger(0); + this.activeExecutionsCount = new AtomicInteger(1); + this.startedSpeculativeExecutionsCount = new AtomicInteger(0); if (isIdempotent) { - // Start the initial execution - long nextExecution = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); - if (nextExecution >= 0) { - LOG.debug("[{}] Scheduling first speculative execution in {} ms", logPrefix, nextExecution); - this.pendingExecutions = new CopyOnWriteArrayList<>(); - this.pendingExecutions.add( - scheduler.schedule(this::startExecution, nextExecution, TimeUnit.MILLISECONDS)); + // Schedule the first speculative execution if applicable + long nextDelay = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); + if (nextDelay >= 0) { + LOG.debug("[{}] Scheduling speculative execution 1 in {} ms", logPrefix, nextDelay); + this.scheduledExecutions = new CopyOnWriteArrayList<>(); + this.scheduledExecutions.add( + scheduler.schedule(() -> startExecution(1), nextDelay, TimeUnit.MILLISECONDS)); } else { LOG.debug( "[{}] Speculative execution policy returned {}, no next execution", logPrefix, - nextExecution); - this.pendingExecutions = null; // we'll never need this so avoid allocation + nextDelay); + this.scheduledExecutions = null; // we'll never need this so avoid allocation } } else { LOG.debug("[{}] Request is not idempotent, no speculative executions", logPrefix); - this.pendingExecutions = null; + this.scheduledExecutions = null; } this.inFlightCallbacks = new CopyOnWriteArrayList<>(); + // Start the initial execution sendRequest(null, 0, 0); } @@ -175,26 +186,29 @@ private ScheduledFuture scheduleTimeout(Duration timeout) { } } - private void startExecution() { + private void startExecution(int currentExecutionIndex) { if (!result.isDone()) { - int execution = executions.incrementAndGet(); - LOG.trace("[{}] Starting speculative execution {}", logPrefix, execution); - long nextDelay = speculativeExecutionPolicy.nextExecution(keyspace, request, execution + 1); + LOG.trace("[{}] Starting speculative execution {}", logPrefix, currentExecutionIndex); + activeExecutionsCount.incrementAndGet(); + startedSpeculativeExecutionsCount.incrementAndGet(); + long nextDelay = + speculativeExecutionPolicy.nextExecution(keyspace, request, currentExecutionIndex + 1); if (nextDelay >= 0) { LOG.trace( - "[{}] Scheduling {}th speculative execution in {} ms", + "[{}] Scheduling speculative execution {} in {} ms", logPrefix, - execution + 1, + currentExecutionIndex + 1, nextDelay); - this.pendingExecutions.add( - scheduler.schedule(this::startExecution, nextDelay, TimeUnit.MILLISECONDS)); + scheduledExecutions.add( + scheduler.schedule( + () -> startExecution(currentExecutionIndex + 1), nextDelay, TimeUnit.MILLISECONDS)); } else { LOG.trace( "[{}] Speculative execution policy returned {}, no next execution", logPrefix, nextDelay); } - sendRequest(null, execution, 0); + sendRequest(null, currentExecutionIndex, 0); } } @@ -202,8 +216,9 @@ private void startExecution() { * Sends the request to the next available node. * * @param node if not null, it will be attempted first before the rest of the query plan. + * @param currentExecutionIndex 0 for the initial execution, 1 for the first speculative one, etc. */ - private void sendRequest(Node node, int execution, int retryCount) { + private void sendRequest(Node node, int currentExecutionIndex, int retryCount) { if (result.isDone()) { return; } @@ -218,13 +233,13 @@ private void sendRequest(Node node, int execution, int retryCount) { } if (channel == null) { // We've reached the end of the query plan without finding any node to write to - if (!result.isDone() && executions.decrementAndGet() == -1) { + if (!result.isDone() && activeExecutionsCount.decrementAndGet() == 0) { // We're the last execution so fail the result setFinalError(AllNodesFailedException.fromErrors(this.errors)); } } else { NodeResponseCallback nodeResponseCallback = - new NodeResponseCallback(node, channel, execution, retryCount, logPrefix); + new NodeResponseCallback(node, channel, currentExecutionIndex, retryCount, logPrefix); channel .write(message, request.isTracing(), request.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); @@ -249,7 +264,7 @@ private void cancelScheduledTasks() { if (this.timeoutFuture != null) { this.timeoutFuture.cancel(false); } - List> pendingExecutionsSnapshot = this.pendingExecutions; + List> pendingExecutionsSnapshot = this.scheduledExecutions; if (pendingExecutionsSnapshot != null) { for (ScheduledFuture future : pendingExecutionsSnapshot) { future.cancel(false); @@ -286,8 +301,8 @@ private ExecutionInfo buildExecutionInfo( return new DefaultExecutionInfo( (Statement) request, callback.node, + startedSpeculativeExecutionsCount.get(), callback.execution, - executions.get(), errors, pagingState, responseFrame); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index c0e34da2f62..059400cf6bd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -154,6 +155,40 @@ public void should_not_start_execution_if_result_complete( } } + @Test + @UseDataProvider("idempotentConfig") + public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement statement) { + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); + // No configured behaviors => will yield an empty query plan + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + SpeculativeExecutionPolicy speculativeExecutionPolicy = + harness.getContext().speculativeExecutionPolicy(); + long firstExecutionDelay = 100L; + Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + .thenReturn(firstExecutionDelay); + + CompletionStage resultSetFuture = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .asyncResult(); + + harness.nextScheduledTask(); // Discard the timeout task + + // We schedule a first speculative execution before even detecting that the query plan is + // empty + ScheduledTaskCapturingEventLoop.CapturedTask task = harness.nextScheduledTask(); + assertThat(task).isNotNull(); + assertThat(task.getInitialDelay(TimeUnit.MILLISECONDS)).isEqualTo(100); + + assertThat(resultSetFuture) + .isFailed( + error -> { + assertThat(error).isInstanceOf(NoNodeAvailableException.class); + }); + } + } + @Test @UseDataProvider("idempotentConfig") public void should_fail_if_no_more_nodes_and_initial_execution_is_last( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java new file mode 100644 index 00000000000..0eca3287ea9 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.specex; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.isBootstrapping; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; + +public class SpeculativeExecutionIT { + + // Note: it looks like shorter delays cause precision issues with Netty timers + private static final long SPECULATIVE_DELAY = 100; + + private static String QUERY_STRING = "select * from foo"; + private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); + + // Shared across all tests methods. + public static @ClassRule SimulacronRule simulacron = + new SimulacronRule(ClusterSpec.builder().withNodes(3)); + + @Before + public void clear() { + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + } + + @Test + public void should_not_start_speculative_executions_if_not_idempotent() { + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY.setIdempotent(false)); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); + + assertQueryCount(0, 1); + assertQueryCount(1, 0); + assertQueryCount(2, 0); + } + } + + @Test + public void should_complete_from_first_speculative_execution_if_faster() { + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + primeNode(1, when(QUERY_STRING).then(noRows())); + + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 0); + } + } + + @Test + public void should_complete_from_initial_execution_if_speculative_is_started_but_slower() { + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + primeNode( + 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 0); + } + } + + @Test + public void should_complete_from_second_speculative_execution_if_faster() { + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + primeNode( + 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + primeNode(2, when(QUERY_STRING).then(noRows())); + + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 1); + } + } + + @Test + public void should_retry_within_initial_execution() { + // This triggers a retry on the next node: + primeNode(0, when(QUERY_STRING).then(isBootstrapping())); + primeNode(1, when(QUERY_STRING).then(noRows())); + + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 0); + } + } + + @Test + public void should_retry_within_speculative_execution() { + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + // This triggers a retry on the next node: + primeNode(1, when(QUERY_STRING).then(isBootstrapping())); + primeNode(2, when(QUERY_STRING).then(noRows())); + + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 1); + } + } + + @Test + public void should_wait_for_last_execution_to_complete() { + // Initial execution uses node0 which takes a long time to reply + primeNode( + 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + // specex1 runs fast, but only encounters failing nodes and stops + primeNode(1, when(QUERY_STRING).then(isBootstrapping())); + primeNode(2, when(QUERY_STRING).then(isBootstrapping())); + + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 1); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_fail_if_all_executions_reach_end_of_query_plan() { + // Each execution gets a BOOTSTRAPPING response, but by the time it retries, the query plan will + // be empty. + for (int i = 0; i < 3; i++) { + primeNode( + i, + when(QUERY_STRING) + .then(isBootstrapping()) + .delay((3 - i) * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); + } + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + Session session = cluster.connect(); + session.execute(QUERY); + } finally { + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 1); + } + } + + @Test + public void should_allow_zero_delay() { + // All executions start at the same time, but one of them is faster + for (int i = 0; i < 2; i++) { + primeNode( + i, when(QUERY_STRING).then(noRows()).delay(SPECULATIVE_DELAY / 2, TimeUnit.MILLISECONDS)); + } + primeNode(2, when(QUERY_STRING).then(noRows())); + + try (Cluster cluster = buildCluster(3, 0)) { + Session session = cluster.connect(); + ResultSet resultSet = session.execute(QUERY); + + assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); + assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); + + assertQueryCount(0, 1); + assertQueryCount(1, 1); + assertQueryCount(2, 1); + } + } + + // Build a new Cluster instance for each test, because we need different configurations + private Cluster buildCluster(int maxSpeculativeExecutions, long speculativeDelayMs) { + return Cluster.builder() + .addContactPoints(simulacron.getContactPoints()) + .withConfigLoader( + new TestConfigLoader( + String.format("request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), + "request.default-idempotence = true", + "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", + "request.speculative-execution-policy.class = com.datastax.oss.driver.api.core.specex.ConstantSpeculativeExecutionPolicy", + String.format( + "request.speculative-execution-policy.max-executions = %d", + maxSpeculativeExecutions), + String.format( + "request.speculative-execution-policy.delay = %d milliseconds", + speculativeDelayMs))) + .build(); + } + + private void primeNode(int id, PrimeDsl.PrimeBuilder primeBuilder) { + simulacron.cluster().node(id).prime(primeBuilder); + } + + private void assertQueryCount(int node, int expected) { + assertThat( + simulacron + .cluster() + .node(node) + .getLogs() + .getQueryLogs() + .stream() + .filter(l -> l.getQuery().equals(QUERY_STRING))) + .as("Expected query count to be %d for node %d", expected, node) + .hasSize(expected); + } +} From c071862dce0af2a72d12bb97ae9a588aee180efa Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Wed, 26 Jul 2017 23:16:44 -0500 Subject: [PATCH 162/742] JAVA-1583: Handle write failure in ChannelHandlerRequest In cases where write fails, i.e. writing to closed channel or ssl handshake failure, it is possible that ChannelHandlerRequest.onFailure is invoked and timeoutFuture is never set. Simply null checking timeoutFuture avoids NPE being thrown here. --- .../driver/internal/core/channel/ChannelHandlerRequest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java index d75b745b719..a31160a87ff 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java @@ -80,7 +80,10 @@ public final void onResponse(Frame responseFrame) { @Override public final void onFailure(Throwable error) { - timeoutFuture.cancel(true); + // timeoutFuture may not have been assigned if write failed. + if (timeoutFuture != null) { + timeoutFuture.cancel(true); + } fail(describe() + ": unexpected failure", error); } From 35de1a8ebc8eeff831d13ee06290f76a84cbb7c0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 14:52:13 -0700 Subject: [PATCH 163/742] Update changelog for 1583 --- changelog/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog/README.md b/changelog/README.md index 3b9bc27a3a4..afd53723eb7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [bug] JAVA-1583: Handle write failure in ChannelHandlerRequest - [improvement] JAVA-1541: Reorganize configuration - [improvement] JAVA-1577: Set default consistency level to LOCAL_ONE - [bug] JAVA-1548: Retry idempotent statements on READ_TIMEOUT and UNAVAILABLE From 5f4b65a6d79482fbcab20deaaec777ea7488edfb Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 14:33:58 -0700 Subject: [PATCH 164/742] Refactor ClusterRule to create exactly 1 instance Introduce ClusterUtils for tests that need to create instances themselves. --- .../driver/api/core/ProtocolVersionIT.java | 12 +- .../core/auth/PlainTextAuthProviderIT.java | 12 +- .../core/config/DriverConfigProfileIT.java | 41 ++-- .../driver/api/core/cql/BoundStatementIT.java | 10 +- .../oss/driver/api/core/data/DataTypeIT.java | 6 +- .../api/core/heartbeat/HeartbeatIT.java | 35 ++-- .../core/specex/SpeculativeExecutionIT.java | 27 +-- .../core/ssl/DefaultSslEngineFactoryIT.java | 7 +- ...faultSslEngineFactoryWithClientAuthIT.java | 7 +- ...ineFactoryWithClientAuthNotProvidedIT.java | 7 +- ...ineFactoryWithTruststoreNotProvidedIT.java | 6 +- .../api/testinfra/cluster/ClusterRule.java | 189 ++++++------------ .../testinfra/cluster/ClusterRuleBuilder.java | 75 +++++++ .../api/testinfra/cluster/ClusterUtils.java | 139 +++++++++++++ 14 files changed, 358 insertions(+), 215 deletions(-) create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java index fdfaa743101..28352d59af8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import org.junit.Rule; import org.junit.Test; @@ -28,8 +28,6 @@ public class ProtocolVersionIT { @Rule public CcmRule ccm = CcmRule.getInstance(); - @Rule public ClusterRule cluster = new ClusterRule(ccm, false, false); - @CassandraRequirement( min = "2.1", max = "2.2", @@ -37,7 +35,7 @@ public class ProtocolVersionIT { ) @Test public void should_downgrade_to_v3() { - try (Cluster v3cluster = cluster.defaultCluster()) { + try (Cluster v3cluster = ClusterUtils.newCluster(ccm)) { assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); Session session = v3cluster.connect(); @@ -52,7 +50,7 @@ public void should_downgrade_to_v3() { ) @Test public void should_fail_if_provided_version_isnt_supported() { - try (Cluster v4cluster = cluster.defaultCluster("protocol.version = V4")) { + try (Cluster v4cluster = ClusterUtils.newCluster(ccm, "protocol.version = V4")) { assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(3); Session session = v4cluster.connect(); @@ -69,7 +67,7 @@ public void should_fail_if_provided_version_isnt_supported() { @CassandraRequirement(min = "2.2", description = "required to meet default protocol version") @Test public void should_not_downgrade() { - try (Cluster v4cluster = cluster.defaultCluster()) { + try (Cluster v4cluster = ClusterUtils.newCluster(ccm)) { assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(4); Session session = v4cluster.connect(); @@ -80,7 +78,7 @@ public void should_not_downgrade() { @CassandraRequirement(min = "2.2", description = "required to use an older protocol version") @Test public void should_use_explicitly_provided_protocol_version() { - try (Cluster v3cluster = cluster.defaultCluster("protocol.version = V3")) { + try (Cluster v3cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); Session session = v3cluster.connect(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index e49a6f0c19b..30a20eeab6a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.LongTests; import org.junit.ClassRule; import org.junit.Test; @@ -35,12 +35,11 @@ public class PlainTextAuthProviderIT { .withJvmArgs("-Dcassandra.superuser_setup_delay_ms=0") .build(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); - @Test public void should_connect_with_credentials() { try (Cluster authCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = cassandra", "protocol.auth-provider.password = cassandra")) { @@ -52,7 +51,8 @@ public void should_connect_with_credentials() { @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_invalid_credentials() { try (Cluster authCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = baduser", "protocol.auth-provider.password = badpass")) { @@ -63,7 +63,7 @@ public void should_not_connect_with_invalid_credentials() { @Test(expected = AllNodesFailedException.class) public void should_not_connect_without_credentials() { - try (Cluster plainCluster = cluster.defaultCluster()) { + try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { Session session = plainCluster.connect(); session.execute("select * from system.local"); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 085702b84c8..20411504ec7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -28,7 +28,7 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; @@ -50,17 +50,13 @@ public class DriverConfigProfileIT { @Rule public CcmRule ccm = CcmRule.getInstance(); - @Rule public ClusterRule cluster = new ClusterRule(simulacron, false, false); - - @Rule public ClusterRule ccmCluster = new ClusterRule(ccm, false, false); - @Rule public ExpectedException thrown = ExpectedException.none(); // TODO: Test with reprepare on all nodes profile configuration @Test public void should_fail_if_config_profile_specified_doesnt_exist() { - try (Cluster profileCluster = cluster.defaultCluster()) { + try (Cluster profileCluster = ClusterUtils.newCluster(simulacron)) { Session session = profileCluster.connect(); SimpleStatement statement = @@ -76,7 +72,8 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { @Test public void should_use_profile_request_timeout() { - try (Cluster profileCluster = cluster.defaultCluster("profiles.olap.request.timeout = 10s")) { + try (Cluster profileCluster = + ClusterUtils.newCluster(simulacron, "profiles.olap.request.timeout = 10s")) { String query = "mockquery"; // configure query with delay of 2 seconds. simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); @@ -98,7 +95,7 @@ public void should_use_profile_request_timeout() { @Test public void should_use_profile_default_idempotence() { try (Cluster profileCluster = - cluster.defaultCluster("profiles.idem.request.default-idempotence = true")) { + ClusterUtils.newCluster(simulacron, "profiles.idem.request.default-idempotence = true")) { String query = "mockquery"; // configure query with server error which should invoke onRequestError in retry policy. simulacron.cluster().prime(when(query).then(serverError("fail"))); @@ -122,7 +119,8 @@ public void should_use_profile_default_idempotence() { @Test public void should_use_profile_consistency() { try (Cluster profileCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + simulacron, "profiles.cl.request.consistency = LOCAL_QUORUM", "profiles.cl.request.serial-consistency = LOCAL_SERIAL")) { String query = "mockquery"; @@ -176,31 +174,24 @@ public void should_use_profile_consistency() { @Test public void should_use_profile_page_size() { try (Cluster profileCluster = - ccmCluster.defaultCluster( - "request.page-size = 100", - "profiles.slow.request.timeout = 30s", - "profiles.smallpages.request.page-size = 10")) { + ClusterUtils.newCluster( + ccm, "request.page-size = 100", "profiles.smallpages.request.page-size = 10")) { - SimpleStatement createKeyspace = - SimpleStatement.builder( - String.format( - "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", - ccmCluster.keyspace())) - .withConfigProfileName("slow") - .build(); - profileCluster.connect().execute(createKeyspace); + CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); + DriverConfigProfile slowProfile = ClusterUtils.slowProfile(profileCluster); + ClusterUtils.createKeyspace(profileCluster, keyspace, slowProfile); - Session session = profileCluster.connect(CqlIdentifier.fromCql(ccmCluster.keyspace())); + Session session = profileCluster.connect(keyspace); // load 500 rows (value beyond page size). session.execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k int, v int, PRIMARY KEY (k,v))") - .withConfigProfileName("slow") + .withConfigProfile(slowProfile) .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(BatchType.UNLOGGED).withConfigProfileName("slow"); + BatchStatement.builder(BatchType.UNLOGGED).withConfigProfile(slowProfile); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } @@ -222,6 +213,8 @@ public void should_use_profile_page_size() { // next fetch should also be 10 pages. result.fetchNextPage(); assertThat(result.getAvailableWithoutFetching()).isEqualTo(20); + + ClusterUtils.dropKeyspace(profileCluster, keyspace, slowProfile); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 5da4f02e947..f6c3897613b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -17,10 +17,12 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -54,8 +56,11 @@ public void setupSchema() { @Ignore public void should_not_allow_unset_value_on_bound_statement_when_protocol_less_than_v4() { // TODO reenable this if JAVA-1584 is fixed. - try (Cluster v3Cluster = cluster.defaultCluster("protocol.version = V3")) { - Session session = v3Cluster.connect(CqlIdentifier.fromCql(cluster.keyspace())); + try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { + CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); + DriverConfigProfile slowProfile = ClusterUtils.slowProfile(v3Cluster); + ClusterUtils.createKeyspace(v3Cluster, keyspace, slowProfile); + Session session = v3Cluster.connect(keyspace); PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0, v1) values (?, ?, ?)"); @@ -68,6 +73,7 @@ public void should_not_allow_unset_value_on_bound_statement_when_protocol_less_t .build(); session.execute(boundStatement); + ClusterUtils.dropKeyspace(v3Cluster, keyspace, slowProfile); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 191cb41718f..27d25d3f1e1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -230,7 +230,7 @@ public static Object[][] typeSamples() { UserDefinedType udt = new DefaultUserDefinedType( - CqlIdentifier.fromCql(cluster.keyspace()), + cluster.keyspace(), CqlIdentifier.fromCql(userTypeFor(types)), typeNames, types); @@ -748,9 +748,9 @@ private void readValue( } // Decode directly using the codec - assertThat(codec.decode(row.getBytesUnsafe(columnName), cluster.getHighestProtocolVersion())) + assertThat(codec.decode(row.getBytesUnsafe(columnName), ccm.getHighestProtocolVersion())) .isEqualTo(value); - assertThat(codec.decode(row.getBytesUnsafe(0), cluster.getHighestProtocolVersion())) + assertThat(codec.decode(row.getBytesUnsafe(0), ccm.getHighestProtocolVersion())) .isEqualTo(value); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index 33a663a4f17..c7067a1dd41 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -15,12 +15,14 @@ */ package com.datastax.oss.driver.api.core.heartbeat; +import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import com.datastax.oss.simulacron.common.request.Options; @@ -59,14 +61,14 @@ public class HeartbeatIT { @Rule public ClusterRule cluster = - new ClusterRule( - simulacron, - true, - false, - "connection.heartbeat.interval = 1 second", - "connection.heartbeat.timeout = 500 milliseconds", - "connection.init-query-timeout = 2 seconds", - "connection.reconnection-policy.max-delay = 1 second"); + ClusterRule.builder(simulacron) + .withDefaultSession(false) + .withOptions( + "connection.heartbeat.interval = 1 second", + "connection.heartbeat.timeout = 500 milliseconds", + "connection.init-query-timeout = 2 seconds", + "connection.reconnection-policy.max-delay = 1 second") + .build(); private static final String queryStr = "select * from foo"; @@ -112,7 +114,7 @@ public void setUp() { public void node_should_go_down_gracefully_when_connection_closed_during_heartbeat() throws InterruptedException { // Create session to initialize pools. - cluster.newSession(); + cluster.cluster().connect(); // Node should be up. Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); @@ -144,7 +146,7 @@ public void should_not_send_heartbeat_during_protocol_initialization() Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); // Create session to initialize pools. - cluster.newSession(); + cluster.cluster().connect(); // wait for node to go down as result of startup failing. waitForDown(node); @@ -185,7 +187,7 @@ public void should_send_heartbeat_on_interval() throws InterruptedException { checkThat(() -> controlNodeHeartbeats.get() > 0).becomesTrue(); // Create session to initialize pools. - Session session = cluster.newSession(); + Session session = cluster.cluster().connect(); // Ensure we get a heartbeat after a second. AtomicInteger heartbeats = new AtomicInteger(); @@ -227,7 +229,7 @@ public void should_send_heartbeat_when_requests_being_written_but_nothing_receiv AtomicInteger heartbeats = registerHeartbeatListener(); // Send requests over 2.5 seconds. - Session session = cluster.newSession(); + Session session = cluster.cluster().connect(); for (int i = 0; i < 25; i++) { session.executeAsync(noResponseQueryStr); session.executeAsync(noResponseQueryStr); @@ -240,7 +242,7 @@ public void should_send_heartbeat_when_requests_being_written_but_nothing_receiv @Test public void should_close_connection_when_heartbeat_times_out() { - cluster.newSession(); + cluster.cluster().connect(); Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); @@ -279,8 +281,9 @@ public void should_close_connection_when_heartbeat_times_out() { @Category(LongTests.class) public void should_not_send_heartbeat_when_disabled() throws InterruptedException { // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any - try (Session session = - cluster.defaultCluster("connection.heartbeat.interval = 0 second").connect()) { + try (Cluster cluster = + ClusterUtils.newCluster(simulacron, "connection.heartbeat.interval = 0 second")) { + cluster.connect(); AtomicInteger heartbeats = registerHeartbeatListener(); SECONDS.sleep(35); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 0eca3287ea9..7c4ea363b0c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; import java.util.concurrent.TimeUnit; @@ -237,21 +237,16 @@ public void should_allow_zero_delay() { // Build a new Cluster instance for each test, because we need different configurations private Cluster buildCluster(int maxSpeculativeExecutions, long speculativeDelayMs) { - return Cluster.builder() - .addContactPoints(simulacron.getContactPoints()) - .withConfigLoader( - new TestConfigLoader( - String.format("request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), - "request.default-idempotence = true", - "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "request.speculative-execution-policy.class = com.datastax.oss.driver.api.core.specex.ConstantSpeculativeExecutionPolicy", - String.format( - "request.speculative-execution-policy.max-executions = %d", - maxSpeculativeExecutions), - String.format( - "request.speculative-execution-policy.delay = %d milliseconds", - speculativeDelayMs))) - .build(); + return ClusterUtils.newCluster( + simulacron, + String.format("request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), + "request.default-idempotence = true", + "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", + "request.speculative-execution-policy.class = com.datastax.oss.driver.api.core.specex.ConstantSpeculativeExecutionPolicy", + String.format( + "request.speculative-execution-policy.max-executions = %d", maxSpeculativeExecutions), + String.format( + "request.speculative-execution-policy.delay = %d milliseconds", speculativeDelayMs)); } private void primeNode(int id, PrimeDsl.PrimeBuilder primeBuilder) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index b26097f6f7d..0ed630390b4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -30,8 +30,6 @@ public class DefaultSslEngineFactoryIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); - @Test public void should_connect_with_ssl() { System.setProperty( @@ -39,7 +37,8 @@ public void should_connect_with_ssl() { System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); try (Cluster sslCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { Session session = sslCluster.connect(); session.execute("select * from system.local"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index 6188103dc40..87a8e238983 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -30,8 +30,6 @@ public class DefaultSslEngineFactoryWithClientAuthIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); - @Test public void should_connect_with_ssl_using_client_auth() { System.setProperty( @@ -43,7 +41,8 @@ public void should_connect_with_ssl_using_client_auth() { System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); try (Cluster sslCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { Session session = sslCluster.connect(); session.execute("select * from system.local"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java index 262904fcc89..5ed97192356 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -31,8 +31,6 @@ public class DefaultSslEngineFactoryWithClientAuthNotProvidedIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); - @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() { System.setProperty( @@ -40,7 +38,8 @@ public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); try (Cluster sslCluster = - cluster.defaultCluster( + ClusterUtils.newCluster( + ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { Session session = sslCluster.connect(); session.execute("select * from system.local"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java index 6718356b515..bfecd7dad9a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -30,11 +30,9 @@ public class DefaultSslEngineFactoryWithTruststoreNotProvidedIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, false, false); - @Test(expected = AllNodesFailedException.class) public void should_not_connect_if_not_using_ssl() { - try (Cluster plainCluster = cluster.defaultCluster()) { + try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { Session session = plainCluster.connect(); session.execute("select * from system.local"); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 9dd9a0935ab..411a6011577 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -17,156 +17,125 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -import com.datastax.oss.driver.internal.core.DefaultCluster; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; +import org.junit.rules.ExternalResource; /** - * A rule for creating and managing lifecycle of {@link Cluster} instances. A default cluster is - * created on set up which the user can use to create new {@link Session}s with using {@link - * #newSession()}. New {@link Cluster} instances can be created using the cluster methods provided - * any {@link Cluster} created in this way will be closed when the rule is cleaned up. + * Creates and manages a {@link Cluster} instance for a test. + * + *

      Use it in conjunction with a {@link CassandraResourceRule} that creates the server resource to + * connect to: + * + *

      {@code
      + * public static @ClassRule CcmRule server = CcmRule.getInstance();
      + *
      + * // Or: public static @ClassRule SimulacronRule server =
      + * //    new SimulacronRule(ClusterSpec.builder().withNodes(3));
      + *
      + * public static @ClassRule ClusterRule cluster = new ClusterRule(server);
      + *
      + * public void @Test should_do_something() {
      + *   cluster.session().execute("some query");
      + * }
      + * }
      + * + * Optionally, it can also create a dedicated keyspace (useful to isolate tests that share a common + * server), and initialize a session. + * + *

      If you would rather create a new keyspace manually in each test, see the utility methods in + * {@link ClusterUtils}. */ -public class ClusterRule extends CassandraResourceRule { +public class ClusterRule extends ExternalResource { - // the ccm rule to depend on + // the CCM or Simulacron rule to depend on private final CassandraResourceRule cassandraResource; + private final CqlIdentifier keyspace; + private final boolean createDefaultSession; + private final String[] defaultClusterOptions; // the default cluster that is auto created for this rule. - private Cluster defaultCluster; + private Cluster cluster; // the default session that is auto created for this rule and is tied to the given keyspace. private Session defaultSession; - // clusters created by this rule. - private final Collection clusters = new ArrayList<>(); - - private final String[] defaultClusterOptions; - - private static final AtomicInteger keyspaceId = new AtomicInteger(); - - private final String keyspace = "ks_" + keyspaceId.getAndIncrement(); - private DriverConfigProfile slowProfile; - private boolean createDefaultCluster; - private boolean createDefaultSession; - /** - * Creates a ClusterRule wrapping the provided resource. + * Returns a builder to construct an instance with a fluent API. * * @param cassandraResource resource to create clusters for. - * @param options The config options to pass to the default created cluster. */ + public static ClusterRuleBuilder builder(CassandraResourceRule cassandraResource) { + return new ClusterRuleBuilder(cassandraResource); + } + + /** @see #builder(CassandraResourceRule) */ public ClusterRule(CassandraResourceRule cassandraResource, String... options) { this(cassandraResource, true, true, options); } - /** - * Creates a ClusterRule wrapping the provided resource. - * - * @param cassandraResource resource to create clusters for. - * @param createDefaultCluster whether or not to create a default cluster on initialization. - * @param createDefaultSession whether or not to create a default session on initialization. - * @param options The config options to pass to the default created cluster. - */ + /** @see #builder(CassandraResourceRule) */ public ClusterRule( CassandraResourceRule cassandraResource, - boolean createDefaultCluster, + boolean createKeyspace, boolean createDefaultSession, String... options) { this.cassandraResource = cassandraResource; - this.defaultClusterOptions = options; - this.createDefaultCluster = createDefaultCluster; + this.keyspace = + (cassandraResource instanceof SimulacronRule || !createKeyspace) + ? null + : ClusterUtils.uniqueKeyspaceId(); this.createDefaultSession = createDefaultSession; + this.defaultClusterOptions = options; } @Override protected void before() { // ensure resource is initialized before initializing the defaultCluster. cassandraResource.setUp(); - if (createDefaultCluster) { - defaultCluster = defaultCluster(defaultClusterOptions); - clusters.add(defaultCluster); - - slowProfile = - defaultCluster - .getContext() - .config() - .getDefaultProfile() - .withString(CoreDriverOption.REQUEST_TIMEOUT, "30s"); - // TODO: Make this more pleasant - if (createDefaultSession) { - if (!(cassandraResource instanceof SimulacronRule)) { - createKeyspace(); - defaultSession = defaultCluster.connect(CqlIdentifier.fromCql(keyspace)); - } else { - defaultSession = defaultCluster.connect(); - } - } - } - } + cluster = ClusterUtils.newCluster(cassandraResource, defaultClusterOptions); - private void createKeyspace() { - try (Session session = defaultCluster.connect()) { - SimpleStatement createKeyspace = - SimpleStatement.builder( - String.format( - "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", - keyspace)) - .withConfigProfile(slowProfile) - .build(); - session.execute(createKeyspace); - } - } + slowProfile = ClusterUtils.slowProfile(cluster); - private void dropKeyspace() { + if (keyspace != null) { + ClusterUtils.createKeyspace(cluster, keyspace, slowProfile); + } if (createDefaultSession) { - defaultSession.execute( - SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace)) - .withConfigProfile(slowProfile) - .build()); + defaultSession = cluster.connect(keyspace); } } @Override protected void after() { - if (createDefaultCluster) { - if (!(cassandraResource instanceof SimulacronRule)) { - dropKeyspace(); - } + if (keyspace != null) { + ClusterUtils.dropKeyspace(cluster, keyspace, slowProfile); } - clusters.forEach( - c -> { - if (!c.closeFuture().toCompletableFuture().isDone()) { - c.close(); - } - }); + cluster.close(); } - /** @return the default cluster created with this rule. */ + /** @return the cluster created with this rule. */ public Cluster cluster() { - return defaultCluster; + return cluster; } - /** @return the default session created with this rule. */ + /** + * @return the default session created with this rule, or {@code null} if no default session was + * created. + */ public Session session() { return defaultSession; } - /** @return keyspace associated with this rule. */ - public String keyspace() { + /** + * @return the identifier of the keyspace associated with this rule, or {@code null} if no + * keyspace was created (this is always the case if the server resource is a {@link + * SimulacronRule}). + */ + public CqlIdentifier keyspace() { return keyspace; } @@ -174,34 +143,4 @@ public String keyspace() { public DriverConfigProfile slowProfile() { return slowProfile; } - - /** - * @return A {@link DefaultCluster} instance using the nodes in 0th DC as contact points and - * defaults for all other configuration. Registers the returned cluster with the rule so it is - * closed on completion. - */ - public Cluster defaultCluster(String... options) { - Cluster cluster = - Cluster.builder() - .addContactPoints(getContactPoints()) - .withConfigLoader(new TestConfigLoader(options)) - .build(); - clusters.add(cluster); - return cluster; - } - - @Override - public ProtocolVersion getHighestProtocolVersion() { - return cassandraResource.getHighestProtocolVersion(); - } - - @Override - public Set getContactPoints() { - return cassandraResource.getContactPoints(); - } - - /** @return a new session from the default cluster associated with this rule. */ - public Session newSession() { - return defaultCluster.connect(); - } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java new file mode 100644 index 00000000000..c8e6d376cc5 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.cluster; + +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; + +public class ClusterRuleBuilder { + + private final CassandraResourceRule cassandraResource; + private boolean createDefaultSession = true; + private boolean createKeyspace = true; + private String[] options = new String[] {}; + + public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { + this.cassandraResource = cassandraResource; + } + + /** + * Whether to create a keyspace. + * + *

      If this is set, the rule will create a keyspace with a name unique to this test (this allows + * multiple tests to run concurrently against the same server resource), and make the name + * available through {@link ClusterRule#keyspace()}. If a {@link #createDefaultSession default + * session} is created, it will be connected to this keyspace. + * + *

      If this method is not called, the default value is {@code true}. + * + *

      Note that this option is only valid with a {@link CcmRule}. If the server resource is a + * {@link SimulacronRule}, this option is ignored, no keyspace gets created, and {@link + * ClusterRule#keyspace()} returns {@code null}. + */ + public ClusterRuleBuilder withKeyspace(boolean createKeyspace) { + this.createKeyspace = createKeyspace; + return this; + } + + /** + * Whether to create a default session from the {@code Cluster}. + * + *

      If this is set, the rule will create a session and make it available through {@link + * ClusterRule#session()}. If a {@link #createKeyspace keyspace} was created, the session will be + * connected to it. + * + *

      If this method is not called, the default value is {@code true}. + */ + public ClusterRuleBuilder withDefaultSession(boolean createDefaultSession) { + this.createDefaultSession = createDefaultSession; + return this; + } + + /** A set of options to override in the cluster configuration. */ + public ClusterRuleBuilder withOptions(String... options) { + this.options = options; + return this; + } + + public ClusterRule build() { + return new ClusterRule(cassandraResource, createKeyspace, createDefaultSession, options); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java new file mode 100644 index 00000000000..1fb1bde2c1e --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.cluster; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Utility methods to manage {@link Cluster} instances manually. + * + *

      Use this if you need to initialize a new cluster instance in each test method: + * + *

      {@code
      + * public static @ClassRule CcmRule server = CcmRule.getInstance();
      + *
      + * // Or: public static @ClassRule SimulacronRule server =
      + * //    new SimulacronRule(ClusterSpec.builder().withNodes(3));
      + *
      + * public void @Test should_do_something() {
      + *   try (Cluster cluster = TestUtils.newCluster(server)) {
      + *     Session session = cluster.connect();
      + *     session.execute("some query");
      + *   }
      + * }
      + * }
      + * + * The instances returned by {@link #newCluster(CassandraResourceRule, String...)} are not managed + * automatically, you need to close them yourself (this is done with a try-with-resources block in + * the example above). + * + *

      If you can share the same {@code Cluster} instance between all test methods, {@link + * ClusterRule} provides a simpler alternative. + */ +public class ClusterUtils { + private static final AtomicInteger keyspaceId = new AtomicInteger(); + + /** + * Creates a new instance of the driver's default {@code Cluster} implementation, using the nodes + * in the 0th DC of the provided Cassandra resource as contact points, and the default + * configuration augmented with the provided options. + */ + public static Cluster newCluster(CassandraResourceRule cassandraResource, String... options) { + return Cluster.builder() + .addContactPoints(cassandraResource.getContactPoints()) + .withConfigLoader(new TestConfigLoader(options)) + .build(); + } + + /** + * Generates a keyspace identifier that is guaranteed to be unique in the current classloader. + * + *

      This is useful to isolate tests that share a common server resource. + */ + public static CqlIdentifier uniqueKeyspaceId() { + return CqlIdentifier.fromCql("ks_" + keyspaceId.getAndIncrement()); + } + + /** Creates a keyspace through the given cluster instance, with the given profile. */ + public static void createKeyspace( + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + try (Session session = cluster.connect()) { + SimpleStatement createKeyspace = + SimpleStatement.builder( + String.format( + "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", + keyspace.asCql())) + .withConfigProfile(profile) + .build(); + session.execute(createKeyspace); + } + } + + /** + * Calls {@link #createKeyspace(Cluster, CqlIdentifier, DriverConfigProfile)} with {@link + * #slowProfile(Cluster)} as the third argument. + * + *

      Note that this creates a derived profile for each invocation, which has a slight performance + * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, + * and storing it in a local variable so it can be reused. + */ + public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { + createKeyspace(cluster, keyspace, slowProfile(cluster)); + } + + /** Drops a keyspace through the given cluster instance, with the given profile. */ + public static void dropKeyspace( + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + try (Session session = cluster.connect()) { + session.execute( + SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql())) + .withConfigProfile(profile) + .build()); + } + } + + /** + * Calls {@link #dropKeyspace(Cluster, CqlIdentifier, DriverConfigProfile)} with {@link + * #slowProfile(Cluster)} as the third argument. + * + *

      Note that this creates a derived profile for each invocation, which has a slight performance + * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, + * and storing it in a local variable so it can be reused. + */ + public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { + dropKeyspace(cluster, keyspace, slowProfile(cluster)); + } + + /** + * Builds a profile derived from the given cluster's default profile, with a higher request + * timeout (30 seconds) that is appropriate for DML operations. + */ + public static DriverConfigProfile slowProfile(Cluster cluster) { + return cluster + .getContext() + .config() + .getDefaultProfile() + .withString(CoreDriverOption.REQUEST_TIMEOUT, "30s"); + } +} From e1d046520eb20f811d016f931a025ff1002865b5 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 14:40:08 -0700 Subject: [PATCH 165/742] Decode using exact protocol version instead of server's highest --- .../com/datastax/oss/driver/api/core/data/DataTypeIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 27d25d3f1e1..0c79c1a9e7c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -748,10 +749,9 @@ private void readValue( } // Decode directly using the codec - assertThat(codec.decode(row.getBytesUnsafe(columnName), ccm.getHighestProtocolVersion())) - .isEqualTo(value); - assertThat(codec.decode(row.getBytesUnsafe(0), ccm.getHighestProtocolVersion())) - .isEqualTo(value); + ProtocolVersion protocolVersion = cluster.cluster().getContext().protocolVersion(); + assertThat(codec.decode(row.getBytesUnsafe(columnName), protocolVersion)).isEqualTo(value); + assertThat(codec.decode(row.getBytesUnsafe(0), protocolVersion)).isEqualTo(value); } private static String typeFor(DataType dataType) { From 23dfa48c229370e767674dd08c133df7c4d4f66f Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 28 Jul 2017 17:03:26 -0500 Subject: [PATCH 166/742] Synchronize CcmRule#before If there are multiple tests using CcmRule and parallel testing is enabled, we need to override and synchronize before() so ccm is only started once. --- .../oss/driver/api/testinfra/ccm/CcmRule.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index a2fd4398feb..eef178ec551 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -28,10 +28,21 @@ public class CcmRule extends BaseCcmRule { private static final CcmRule INSTANCE = new CcmRule(); + private volatile boolean started = false; + private CcmRule() { super(CcmBridge.builder().build()); } + @Override + protected synchronized void before() { + if (!started) { + // synchronize before so blocks on other before() call waiting to finish. + super.before(); + started = true; + } + } + @Override protected void after() { // override after so we don't remove when done. From ad17ef7ece0c76f79bbf9f00f8aef6f0c9872261 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 15:50:51 -0700 Subject: [PATCH 167/742] Increase timeouts in zero-delay speculative execution test --- .../oss/driver/api/core/specex/SpeculativeExecutionIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 7c4ea363b0c..d5a0e947e82 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -218,9 +218,9 @@ public void should_allow_zero_delay() { // All executions start at the same time, but one of them is faster for (int i = 0; i < 2; i++) { primeNode( - i, when(QUERY_STRING).then(noRows()).delay(SPECULATIVE_DELAY / 2, TimeUnit.MILLISECONDS)); + i, when(QUERY_STRING).then(noRows()).delay(2 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); } - primeNode(2, when(QUERY_STRING).then(noRows())); + primeNode(2, when(QUERY_STRING).then(noRows()).delay(SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); try (Cluster cluster = buildCluster(3, 0)) { Session session = cluster.connect(); From 94ad631d619756984aa819e68d14b249f70dc268 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 28 Jul 2017 16:03:41 -0500 Subject: [PATCH 168/742] JAVA-1586: Throw underlying exception when codec not found in cache --- changelog/README.md | 1 + .../codec/registry/DefaultCodecRegistry.java | 18 +- .../registry/CachingCodecRegistryTest.java | 8 +- .../type/codec/registry/CodecRegistryIT.java | 173 ++++++++++++++++++ 4 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java diff --git a/changelog/README.md b/changelog/README.md index afd53723eb7..0e268d52dd4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha1 (in progress) +- [improvement] JAVA-1586: Throw underlying exception when codec not found in cache - [bug] JAVA-1583: Handle write failure in ChannelHandlerRequest - [improvement] JAVA-1541: Reorganize configuration - [improvement] JAVA-1577: Set default consistency level to LOCAL_ONE diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java index a06ebfc0544..c8420795437 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java @@ -15,13 +15,17 @@ */ package com.datastax.oss.driver.internal.core.type.codec.registry; +import com.datastax.oss.driver.api.core.DriverExecutionException; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -87,7 +91,19 @@ public DefaultCodecRegistry(String logPrefix, TypeCodec... userCodecs) { @Override protected TypeCodec getCachedCodec(DataType cqlType, GenericType javaType) { LOG.trace("[{}] Checking cache", logPrefix); - return cache.getUnchecked(new CacheKey(cqlType, javaType)); + try { + return cache.getUnchecked(new CacheKey(cqlType, javaType)); + } catch (UncheckedExecutionException | ExecutionError e) { + // unwrap exception cause and throw it directly. + Throwable cause = e.getCause(); + if (cause != null) { + Throwables.throwIfUnchecked(cause); + throw new DriverExecutionException(cause); + } else { + // Should never happen, throw just in case + throw new RuntimeException(e.getMessage()); + } + } } public static final class CacheKey { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 0c56b315240..3009636055b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.type.SetType; import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -36,7 +37,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.UncheckedExecutionException; import java.math.BigDecimal; import java.math.BigInteger; import java.net.Inet4Address; @@ -552,19 +552,19 @@ public void should_not_find_codec_if_java_type_unknown() { try { CodecRegistry.DEFAULT.codecFor(StringBuilder.class); fail("Should not have found a codec for ANY <-> StringBuilder"); - } catch (UncheckedExecutionException e) { + } catch (CodecNotFoundException e) { // expected } try { CodecRegistry.DEFAULT.codecFor(DataTypes.TEXT, StringBuilder.class); fail("Should not have found a codec for varchar <-> StringBuilder"); - } catch (UncheckedExecutionException e) { + } catch (CodecNotFoundException e) { // expected } try { CodecRegistry.DEFAULT.codecFor(new StringBuilder()); fail("Should not have found a codec for ANY <-> StringBuilder"); - } catch (UncheckedExecutionException e) { + } catch (CodecNotFoundException e) { // expected } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java new file mode 100644 index 00000000000..867505b3b1b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.type.codec.registry; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.internal.core.type.codec.IntCodec; +import java.nio.ByteBuffer; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CodecRegistryIT { + + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + + @Rule public TestName name = new TestName(); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void createSchema() { + // table with simple primary key, single cell. + cluster + .session() + .execute( + SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k text primary key, v int)") + .withConfigProfile(cluster.slowProfile()) + .build()); + } + + // A simple codec that allows float values to be used for cassandra int column type. + private static class FloatCIntCodec implements TypeCodec { + + private static final IntCodec intCodec = new IntCodec(); + + @Override + public GenericType getJavaType() { + return GenericType.of(Float.class); + } + + @Override + public DataType getCqlType() { + return DataTypes.INT; + } + + @Override + public ByteBuffer encode(Float value, ProtocolVersion protocolVersion) { + return intCodec.encode(value.intValue(), protocolVersion); + } + + @Override + public Float decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return intCodec.decode(bytes, protocolVersion).floatValue(); + } + + @Override + public String format(Float value) { + return intCodec.format(value.intValue()); + } + + @Override + public Float parse(String value) { + return intCodec.parse(value).floatValue(); + } + } + + @Test + public void should_throw_exception_if_no_codec_registered_for_type_set() { + PreparedStatement prepared = cluster.session().prepare("INSERT INTO test (k, v) values (?, ?)"); + + thrown.expect(CodecNotFoundException.class); + + // float value for int column should not work since no applicable codec. + prepared.boundStatementBuilder().setString(0, name.getMethodName()).setFloat(1, 3.14f).build(); + } + + @Test + public void should_throw_exception_if_no_codec_registered_for_type_get() { + PreparedStatement prepared = cluster.session().prepare("INSERT INTO test (k, v) values (?, ?)"); + + BoundStatement insert = + prepared.boundStatementBuilder().setString(0, name.getMethodName()).setInt(1, 2).build(); + cluster.session().execute(insert); + + ResultSet result = + cluster + .session() + .execute( + SimpleStatement.builder("SELECT v from TEST where k = ?") + .addPositionalValue(name.getMethodName()) + .build()); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + // should not be able to access int column as float as no codec is registered to handle that. + Row row = result.iterator().next(); + + thrown.expect(CodecNotFoundException.class); + + assertThat(row.getFloat("v")).isEqualTo(3.0f); + } + + @Test + public void should_be_able_to_register_and_use_custom_codec() { + // create a cluster with a registered codec from Float <-> cql int. + try (Cluster codecCluster = + Cluster.builder() + .addTypeCodecs(new FloatCIntCodec()) + .addContactPoints(ccm.getContactPoints()) + .build()) { + Session session = codecCluster.connect(CqlIdentifier.fromCql(cluster.keyspace())); + + PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); + + // float value for int column should work. + BoundStatement insert = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setFloat(1, 3.14f) + .build(); + session.execute(insert); + + ResultSet result = + session.execute( + SimpleStatement.builder("SELECT v from TEST where k = ?") + .addPositionalValue(name.getMethodName()) + .build()); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + + // should be able to retrieve value back as float, some precision is lost due to going from int -> float. + Row row = result.iterator().next(); + assertThat(row.getFloat("v")).isEqualTo(3.0f); + assertThat(row.getFloat(0)).isEqualTo(3.0f); + } + } +} From ce045eb13036bfbc6adc89c46ea5eadb99ecb81f Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 16:55:31 -0700 Subject: [PATCH 169/742] Fix merge error --- .../driver/api/core/type/codec/registry/CodecRegistryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 867505b3b1b..cc65ac1c2cf 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -143,7 +143,7 @@ public void should_be_able_to_register_and_use_custom_codec() { .addTypeCodecs(new FloatCIntCodec()) .addContactPoints(ccm.getContactPoints()) .build()) { - Session session = codecCluster.connect(CqlIdentifier.fromCql(cluster.keyspace())); + Session session = codecCluster.connect(cluster.keyspace()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); From d49b57776b0d3a7fec938c854a4ab939c67b5fe8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 16:09:42 -0700 Subject: [PATCH 170/742] Add Cluster.connect integration tests --- .../oss/driver/api/core/ConnectIT.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java new file mode 100644 index 00000000000..391f878af8b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConnectIT { + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule + public static ClusterRule cluster = ClusterRule.builder(ccm).withDefaultSession(false).build(); + + @Test + public void should_connect_to_existing_keyspace() { + CqlIdentifier keyspace = cluster.keyspace(); + try (Session session = cluster.cluster().connect(keyspace)) { + assertThat(session.getKeyspace()).isEqualTo(keyspace); + } + } + + @Test + public void should_connect_with_no_keyspace() { + try (Session session = cluster.cluster().connect()) { + assertThat(session.getKeyspace()).isNull(); + } + } + + @Test(expected = InvalidKeyspaceException.class) + public void should_fail_to_connect_to_non_existent_keyspace() { + CqlIdentifier keyspace = CqlIdentifier.fromInternal("does not exist"); + cluster.cluster().connect(keyspace); + } +} From e43a5e922dc67c15491a9dbff140ed0ca75032c4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 29 Jul 2017 17:52:55 -0700 Subject: [PATCH 171/742] Fix HeartbeatException javadoc --- .../oss/driver/api/core/connection/HeartbeatException.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java index 079f2e4308a..355053378ab 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.api.core.connection; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Request; import java.net.SocketAddress; /** @@ -23,8 +25,8 @@ * *

      Heartbeat queries are sent automatically on idle connections, to ensure that they are still * alive. If a heartbeat query fails, the connection is closed, and all pending queries are aborted. - * Depending on the retry policy, the heartbeat exception can either be rethrown directly to the - * client, or the driver tries the next host in the query plan. + * The exception will be passed to {@link RetryPolicy#onRequestAborted(Request, Throwable, int)}, + * which decides what to do next (the default policy retries the query on the next node). */ public class HeartbeatException extends DriverException { From c3d2c1edb49bb887d80199faec5969ffb75a31c6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 28 Jul 2017 16:53:05 -0700 Subject: [PATCH 172/742] Add frame length integration test --- .../connection/FrameTooLongException.java | 45 +++++++ .../internal/core/channel/ChannelFactory.java | 2 +- .../core/channel/InFlightHandler.java | 30 ++--- .../internal/core/cql/CqlRequestHandler.java | 32 +++-- .../internal/core/protocol/FrameDecoder.java | 6 + .../internal/core/protocol/FrameEncoder.java | 18 ++- .../core/protocol/FrameDecoderTest.java | 4 +- .../api/core/connection/FrameLengthIT.java | 111 ++++++++++++++++++ 8 files changed, 217 insertions(+), 31 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java new file mode 100644 index 00000000000..0e9f9ccb889 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import com.datastax.oss.driver.api.core.DriverException; +import java.net.SocketAddress; + +/** + * Thrown when an incoming or outgoing protocol frame exceeds the limit defined by {@code + * protocol.max-frame-length} in the configuration. + * + *

      This error is always rethrown directly to the client, without any retry attempt. + */ +public class FrameTooLongException extends DriverException { + + private final SocketAddress address; + + public FrameTooLongException(SocketAddress address, String message) { + super(message, null, false); + this.address = address; + } + + /** The address of the node that encountered the error. */ + public SocketAddress getAddress() { + return address; + } + + @Override + public DriverException copy() { + return new FrameTooLongException(address, getMessage()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 771d6402cc8..b43c73baac4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -209,7 +209,7 @@ protected void initChannel(Channel channel) throws Exception { .map(f -> f.newSslHandler(channel, address)) .map(h -> pipeline.addLast("ssl", h)); pipeline - .addLast("encoder", new FrameEncoder(context.frameCodec())) + .addLast("encoder", new FrameEncoder(context.frameCodec(), maxFrameLength)) .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) // Note: HeartbeatHandler is inserted here once init completes .addLast("inflight", inFlightHandler) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index e78b5c39242..e611a257e1c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -140,14 +140,16 @@ private void write(ChannelHandlerContext ctx, RequestMessage message, ChannelPro inFlight.put(streamId, message.responseCallback); ChannelFuture writeFuture = ctx.write(frame, promise); - if (message.responseCallback.holdStreamId()) { - writeFuture.addListener( - future -> { - if (future.isSuccess()) { + writeFuture.addListener( + future -> { + if (future.isSuccess()) { + if (message.responseCallback.holdStreamId()) { message.responseCallback.onStreamIdAssigned(streamId); } - }); - } + } else { + release(streamId, ctx); + } + }); } private void cancel( @@ -240,15 +242,15 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception /** Called if an exception was thrown while processing an inbound event (i.e. a response). */ @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (cause instanceof FrameDecodingException) { - int streamId = ((FrameDecodingException) cause).streamId; + public void exceptionCaught(ChannelHandlerContext ctx, Throwable exception) throws Exception { + if (exception instanceof FrameDecodingException) { + int streamId = ((FrameDecodingException) exception).streamId; LOG.debug("[{}] Error while decoding response on stream id {}", logPrefix, streamId); if (streamId >= 0) { // We know which request matches the failing response, fail that one only ResponseCallback responseCallback = release(streamId, ctx); try { - responseCallback.onFailure(cause.getCause()); + responseCallback.onFailure(exception.getCause()); } catch (Throwable t) { LOG.warn("[{}] Unexpected error while invoking failure handler", logPrefix, t); } @@ -256,14 +258,14 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E LOG.warn( "[{}] Unexpected error while decoding incoming event frame", logPrefix, - cause.getCause()); + exception.getCause()); } } else { // Otherwise fail all pending requests abortAllInFlight( - (cause instanceof HeartbeatException) - ? (HeartbeatException) cause - : new ClosedConnectionException("Unexpected error on channel", cause)); + (exception instanceof HeartbeatException) + ? (HeartbeatException) exception + : new ClosedConnectionException("Unexpected error on channel", exception)); ctx.close(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index d482485da76..cc55fc8d69c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; @@ -56,6 +57,7 @@ import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.response.result.Void; import com.datastax.oss.protocol.internal.util.Bytes; +import io.netty.handler.codec.EncoderException; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @@ -345,13 +347,19 @@ private NodeResponseCallback( @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { - LOG.debug( - "[{}] Failed to send request on {}, trying next node (cause: {})", - logPrefix, - channel, - future.cause()); - recordError(node, future.cause()); - sendRequest(null, execution, retryCount); // try next node + Throwable error = future.cause(); + if (error instanceof EncoderException + && error.getCause() instanceof FrameTooLongException) { + setFinalError(error.getCause()); + } else { + LOG.debug( + "[{}] Failed to send request on {}, trying next node (cause: {})", + logPrefix, + channel, + error); + recordError(node, error); + sendRequest(null, execution, retryCount); // try next node + } } else { LOG.debug("[{}] Request sent on {}", logPrefix, channel); if (result.isDone()) { @@ -504,10 +512,12 @@ public void onFailure(Throwable error) { return; } LOG.debug("[{}] Request failure, processing: {}", logPrefix, error.toString()); - RetryDecision decision = - isIdempotent - ? retryPolicy.onRequestAborted(request, error, retryCount) - : RetryDecision.RETHROW; + RetryDecision decision; + if (!isIdempotent || error instanceof FrameTooLongException) { + decision = RetryDecision.RETHROW; + } else { + decision = retryPolicy.onRequestAborted(request, error, retryCount); + } processRetryDecision(decision, error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java index 19d7564ec09..1fb2869cffd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -15,10 +15,12 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.protocol.internal.FrameCodec; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.TooLongFrameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +59,10 @@ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception LOG.warn("Unexpected error while reading stream id", e1); streamId = -1; } + if (e instanceof TooLongFrameException) { + // Translate the Netty error to our own type + e = new FrameTooLongException(ctx.channel().remoteAddress(), e.getMessage()); + } throw new FrameDecodingException(streamId, e); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java index a1ed25cc06f..09967fb53e0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameEncoder.java @@ -15,8 +15,10 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; @@ -25,14 +27,24 @@ @ChannelHandler.Sharable public class FrameEncoder extends MessageToMessageEncoder { - private final FrameCodec frameCodec; + private final FrameCodec frameCodec; + private final int maxFrameLength; - public FrameEncoder(FrameCodec frameCodec) { + public FrameEncoder(FrameCodec frameCodec, int maxFrameLength) { + super(Frame.class); this.frameCodec = frameCodec; + this.maxFrameLength = maxFrameLength; } @Override protected void encode(ChannelHandlerContext ctx, Frame frame, List out) throws Exception { - out.add(frameCodec.encode(frame)); + ByteBuf buffer = frameCodec.encode(frame); + int actualLength = buffer.readableBytes(); + if (actualLength > maxFrameLength) { + throw new FrameTooLongException( + ctx.channel().remoteAddress(), + String.format("Outgoing frame length exceeds %d: %d", maxFrameLength, actualLength)); + } + out.add(buffer); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java index e780bafc458..895de2adb74 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; import com.datastax.oss.driver.internal.core.util.ByteBufs; import com.datastax.oss.protocol.internal.Compressor; @@ -23,7 +24,6 @@ import com.datastax.oss.protocol.internal.response.AuthSuccess; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.TooLongFrameException; import org.junit.Before; import org.junit.Test; @@ -92,7 +92,7 @@ public void should_fail_to_decode_if_payload_is_valid_but_too_long() { } catch (FrameDecodingException e) { // Then assertThat(e.streamId).isEqualTo(42); - assertThat(e.getCause()).isInstanceOf(TooLongFrameException.class); + assertThat(e.getCause()).isInstanceOf(FrameTooLongException.class); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java new file mode 100644 index 00000000000..896e626b3bb --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy; +import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class FrameLengthIT { + public static @ClassRule SimulacronRule simulacron = + new SimulacronRule(ClusterSpec.builder().withNodes(1)); + + @ClassRule + public static ClusterRule cluster = + new ClusterRule( + simulacron, + "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", + "request.retry-policy.class = \"com.datastax.oss.driver.api.core.connection.FrameLengthIT$AlwaysRetryAbortedPolicy\"", + "protocol.max-frame-length = 100 kilobytes"); + + private static final SimpleStatement LARGE_QUERY = + SimpleStatement.newInstance("select * from foo").setIdempotent(true); + private static final SimpleStatement SLOW_QUERY = + SimpleStatement.newInstance("select * from bar"); + + private static final Buffer ONE_HUNDRED_KB = ByteBuffer.allocate(100 * 1024).limit(100 * 1024); + + @Before + public void primeQueries() { + simulacron + .cluster() + .prime( + when(LARGE_QUERY.getQuery()) + .then(rows().row("result", ONE_HUNDRED_KB).columnTypes("result", "blob").build())); + simulacron + .cluster() + .prime(when(SLOW_QUERY.getQuery()).then(noRows()).delay(60, TimeUnit.SECONDS)); + } + + @Test(expected = FrameTooLongException.class) + public void should_fail_if_request_exceeds_max_frame_length() { + cluster + .session() + .execute(SimpleStatement.newInstance("insert into foo (k) values (?)", ONE_HUNDRED_KB)); + } + + @Test + public void should_fail_if_response_exceeds_max_frame_length() { + CompletionStage slowResultFuture = cluster.session().executeAsync(SLOW_QUERY); + try { + cluster.session().execute(LARGE_QUERY); + fail("Expected a " + FrameTooLongException.class.getSimpleName()); + } catch (FrameTooLongException e) { + // expected + } + // Check that the error does not abort other requests on the same connection + assertThat(slowResultFuture.toCompletableFuture()).isNotCompleted(); + } + + /** + * A retry policy that always retries aborted requests. + * + *

      We use this to validate that {@link FrameTooLongException} is never passed to the policy (if + * it were, then this policy would retry it, and the exception thrown to the client would be an + * {@link AllNodesFailedException}). + */ + public static class AlwaysRetryAbortedPolicy extends DefaultRetryPolicy { + public AlwaysRetryAbortedPolicy(DriverContext context, DriverOption configRoot) { + super(context, configRoot); + } + + @Override + public RetryDecision onRequestAborted(Request request, Throwable error, int retryCount) { + return RetryDecision.RETRY_NEXT; + } + } +} From db1ae9adb5b4a7b2fa79a45e7976a43b78332176 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 31 Jul 2017 08:17:26 -0700 Subject: [PATCH 173/742] Adjust title in upgrade guide --- upgrade_guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index f3727677781..58f084a5b05 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -1,6 +1,6 @@ ## Upgrade guide -### 3.x to 4.0.0 +### 4.0.0-alpha1 Java driver 4 is **not binary compatible** with previous versions. However, most of the concepts remain unchanged, and the new API will look very familiar to 2.x and 3.x users. From 9b0317cf14d14c99a4b1ab17fc6aa37b9230296f Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 31 Jul 2017 09:46:42 -0700 Subject: [PATCH 174/742] Don't install integration-test JAR either --- integration-tests/pom.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 81c890f883c..b864fd710a8 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -52,8 +52,15 @@ + + + org.apache.maven.plugins + maven-install-plugin + + true + + - org.apache.maven.plugins maven-deploy-plugin From 4d1efd15b7791ca417e602c02094ce63ce5552bb Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 31 Jul 2017 09:52:22 -0700 Subject: [PATCH 175/742] Pin version of install and deploy plugins --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index e1bbdb8c781..547c383fb16 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,14 @@ maven-release-plugin 2.5.3 + + maven-install-plugin + 2.4 + + + maven-deploy-plugin + 2.7 + From 86ada75804b505134ce82a80f070c3538a16ec48 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 31 Jul 2017 10:34:19 -0700 Subject: [PATCH 176/742] [maven-release-plugin] prepare release 4.0.0-alpha1 --- core/pom.xml | 6 ++---- integration-tests/pom.xml | 6 ++---- pom.xml | 8 +++----- test-infra/pom.xml | 6 ++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 08709bffa35..d7076fb05be 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-SNAPSHOT + 4.0.0-alpha1 java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index b864fd710a8..e0f2bb5936c 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-SNAPSHOT + 4.0.0-alpha1 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 547c383fb16..2b79475fbb9 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,12 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-SNAPSHOT + 4.0.0-alpha1 pom DataStax Java driver for Apache Cassandra® @@ -359,7 +357,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-alpha1 diff --git a/test-infra/pom.xml b/test-infra/pom.xml index b39556c8908..237afc3bd93 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-SNAPSHOT + 4.0.0-alpha1 java-driver-test-infra From 13b2567b837bd06ee6fb436d0fbfcd0f646f703b Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 31 Jul 2017 10:34:56 -0700 Subject: [PATCH 177/742] [maven-release-plugin] prepare for next development iteration --- core/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- test-infra/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index d7076fb05be..bfc0c37d332 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha1 + 4.0.0-alpha2-SNAPSHOT java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index e0f2bb5936c..5868c23d6d1 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha1 + 4.0.0-alpha2-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 2b79475fbb9..6767534a7b8 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha1 + 4.0.0-alpha2-SNAPSHOT pom DataStax Java driver for Apache Cassandra® @@ -357,7 +357,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-alpha1 + HEAD diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 237afc3bd93..371b548d179 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha1 + 4.0.0-alpha2-SNAPSHOT java-driver-test-infra From 46e6a2d804de751c5e659b98da57613cd4204d87 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 1 Aug 2017 09:05:39 -0700 Subject: [PATCH 178/742] Prepare changelog for next version --- changelog/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 0e268d52dd4..e4d48118dc6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,10 @@ -### 4.0.0-alpha1 (in progress) +### 4.0.0-alpha2 (in progress) + + +### 4.0.0-alpha1 - [improvement] JAVA-1586: Throw underlying exception when codec not found in cache - [bug] JAVA-1583: Handle write failure in ChannelHandlerRequest From 54673bb04e9ae2aa6b54e3d1d77254fd7d72dfe1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 1 Aug 2017 14:41:49 -0700 Subject: [PATCH 179/742] Fix minor issue in javadocs. --- .../driver/api/core/data/GettableById.java | 56 +++++++++---------- .../driver/api/core/data/GettableByName.java | 56 +++++++++---------- .../driver/api/core/data/SettableById.java | 54 +++++++++--------- .../driver/api/core/data/SettableByName.java | 54 +++++++++--------- 4 files changed, 110 insertions(+), 110 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java index f72ddcb3853..70c6ee81421 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -51,7 +51,7 @@ public interface GettableById extends GettableByIndex, AccessibleById { * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative * methods. If you change the buffer's index or its contents in any way, any other getter * invocation for this value will have unpredictable results. - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default ByteBuffer getBytesUnsafe(CqlIdentifier id) { return getBytesUnsafe(firstIndexOf(id)); @@ -66,7 +66,7 @@ default ByteBuffer getBytesUnsafe(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default boolean isNull(CqlIdentifier id) { return isNull(firstIndexOf(id)); @@ -89,7 +89,7 @@ default boolean isNull(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T get(CqlIdentifier id, TypeCodec codec) { return get(firstIndexOf(id), codec); @@ -109,7 +109,7 @@ default T get(CqlIdentifier id, TypeCodec codec) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T get(CqlIdentifier id, GenericType targetType) { @@ -129,7 +129,7 @@ default T get(CqlIdentifier id, GenericType targetType) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T get(CqlIdentifier id, Class targetClass) { @@ -159,7 +159,7 @@ default T get(CqlIdentifier id, Class targetClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default Object getObject(CqlIdentifier id) { @@ -182,7 +182,7 @@ default Object getObject(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default boolean getBoolean(CqlIdentifier id) { return getBoolean(firstIndexOf(id)); @@ -203,7 +203,7 @@ default boolean getBoolean(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default byte getByte(CqlIdentifier id) { return getByte(firstIndexOf(id)); @@ -225,7 +225,7 @@ default byte getByte(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default double getDouble(CqlIdentifier id) { return getDouble(firstIndexOf(id)); @@ -247,7 +247,7 @@ default double getDouble(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default float getFloat(CqlIdentifier id) { return getFloat(firstIndexOf(id)); @@ -269,7 +269,7 @@ default float getFloat(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default int getInt(CqlIdentifier id) { return getInt(firstIndexOf(id)); @@ -290,7 +290,7 @@ default int getInt(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default long getLong(CqlIdentifier id) { return getLong(firstIndexOf(id)); @@ -312,7 +312,7 @@ default long getLong(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default short getShort(CqlIdentifier id) { return getShort(firstIndexOf(id)); @@ -329,7 +329,7 @@ default short getShort(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default Instant getInstant(CqlIdentifier id) { return getInstant(firstIndexOf(id)); @@ -346,7 +346,7 @@ default Instant getInstant(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default LocalDate getLocalDate(CqlIdentifier id) { return getLocalDate(firstIndexOf(id)); @@ -363,7 +363,7 @@ default LocalDate getLocalDate(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default LocalTime getLocalTime(CqlIdentifier id) { return getLocalTime(firstIndexOf(id)); @@ -380,7 +380,7 @@ default LocalTime getLocalTime(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default ByteBuffer getByteBuffer(CqlIdentifier id) { return getByteBuffer(firstIndexOf(id)); @@ -397,7 +397,7 @@ default ByteBuffer getByteBuffer(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default String getString(CqlIdentifier id) { return getString(firstIndexOf(id)); @@ -414,7 +414,7 @@ default String getString(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default BigInteger getBigInteger(CqlIdentifier id) { return getBigInteger(firstIndexOf(id)); @@ -431,7 +431,7 @@ default BigInteger getBigInteger(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default BigDecimal getBigDecimal(CqlIdentifier id) { return getBigDecimal(firstIndexOf(id)); @@ -448,7 +448,7 @@ default BigDecimal getBigDecimal(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default UUID getUuid(CqlIdentifier id) { return getUuid(firstIndexOf(id)); @@ -465,7 +465,7 @@ default UUID getUuid(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default InetAddress getInetAddress(CqlIdentifier id) { return getInetAddress(firstIndexOf(id)); @@ -482,7 +482,7 @@ default InetAddress getInetAddress(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default CqlDuration getCqlDuration(CqlIdentifier id) { return getCqlDuration(firstIndexOf(id)); @@ -502,7 +502,7 @@ default CqlDuration getCqlDuration(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default List getList(CqlIdentifier id, Class elementsClass) { return getList(firstIndexOf(id), elementsClass); @@ -522,7 +522,7 @@ default List getList(CqlIdentifier id, Class elementsClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default Set getSet(CqlIdentifier id, Class elementsClass) { return getSet(firstIndexOf(id), elementsClass); @@ -542,7 +542,7 @@ default Set getSet(CqlIdentifier id, Class elementsClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default Map getMap(CqlIdentifier id, Class keyClass, Class valueClass) { return getMap(firstIndexOf(id), keyClass, valueClass); @@ -559,7 +559,7 @@ default Map getMap(CqlIdentifier id, Class keyClass, Class va *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default UdtValue getUdtValue(CqlIdentifier id) { return getUdtValue(firstIndexOf(id)); @@ -576,7 +576,7 @@ default UdtValue getUdtValue(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default TupleValue getTupleValue(CqlIdentifier id) { return getTupleValue(firstIndexOf(id)); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java index e56841fc93f..6e7d49b5c17 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -50,7 +50,7 @@ public interface GettableByName extends GettableByIndex, AccessibleByName { * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative * methods. If you change the buffer's index or its contents in any way, any other getter * invocation for this value will have unpredictable results. - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default ByteBuffer getBytesUnsafe(String name) { return getBytesUnsafe(firstIndexOf(name)); @@ -65,7 +65,7 @@ default ByteBuffer getBytesUnsafe(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default boolean isNull(String name) { return isNull(firstIndexOf(name)); @@ -88,7 +88,7 @@ default boolean isNull(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T get(String name, TypeCodec codec) { return get(firstIndexOf(name), codec); @@ -109,7 +109,7 @@ default T get(String name, TypeCodec codec) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T get(String name, GenericType targetType) { @@ -130,7 +130,7 @@ default T get(String name, GenericType targetType) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T get(String name, Class targetClass) { @@ -160,7 +160,7 @@ default T get(String name, Class targetClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default Object getObject(String name) { @@ -182,7 +182,7 @@ default Object getObject(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default boolean getBoolean(String name) { return getBoolean(firstIndexOf(name)); @@ -203,7 +203,7 @@ default boolean getBoolean(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default byte getByte(String name) { return getByte(firstIndexOf(name)); @@ -224,7 +224,7 @@ default byte getByte(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default double getDouble(String name) { return getDouble(firstIndexOf(name)); @@ -245,7 +245,7 @@ default double getDouble(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default float getFloat(String name) { return getFloat(firstIndexOf(name)); @@ -266,7 +266,7 @@ default float getFloat(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default int getInt(String name) { return getInt(firstIndexOf(name)); @@ -287,7 +287,7 @@ default int getInt(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default long getLong(String name) { return getLong(firstIndexOf(name)); @@ -308,7 +308,7 @@ default long getLong(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default short getShort(String name) { return getShort(firstIndexOf(name)); @@ -325,7 +325,7 @@ default short getShort(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default Instant getInstant(String name) { return getInstant(firstIndexOf(name)); @@ -342,7 +342,7 @@ default Instant getInstant(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default LocalDate getLocalDate(String name) { return getLocalDate(firstIndexOf(name)); @@ -359,7 +359,7 @@ default LocalDate getLocalDate(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default LocalTime getLocalTime(String name) { return getLocalTime(firstIndexOf(name)); @@ -376,7 +376,7 @@ default LocalTime getLocalTime(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default ByteBuffer getByteBuffer(String name) { return getByteBuffer(firstIndexOf(name)); @@ -393,7 +393,7 @@ default ByteBuffer getByteBuffer(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default String getString(String name) { return getString(firstIndexOf(name)); @@ -410,7 +410,7 @@ default String getString(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default BigInteger getBigInteger(String name) { return getBigInteger(firstIndexOf(name)); @@ -427,7 +427,7 @@ default BigInteger getBigInteger(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default BigDecimal getBigDecimal(String name) { return getBigDecimal(firstIndexOf(name)); @@ -444,7 +444,7 @@ default BigDecimal getBigDecimal(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default UUID getUuid(String name) { return getUuid(firstIndexOf(name)); @@ -461,7 +461,7 @@ default UUID getUuid(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default InetAddress getInetAddress(String name) { return getInetAddress(firstIndexOf(name)); @@ -478,7 +478,7 @@ default InetAddress getInetAddress(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default CqlDuration getCqlDuration(String name) { return getCqlDuration(firstIndexOf(name)); @@ -498,7 +498,7 @@ default CqlDuration getCqlDuration(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default List getList(String name, Class elementsClass) { return getList(firstIndexOf(name), elementsClass); @@ -518,7 +518,7 @@ default List getList(String name, Class elementsClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default Set getSet(String name, Class elementsClass) { return getSet(firstIndexOf(name), elementsClass); @@ -538,7 +538,7 @@ default Set getSet(String name, Class elementsClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default Map getMap(String name, Class keyClass, Class valueClass) { return getMap(firstIndexOf(name), keyClass, valueClass); @@ -555,7 +555,7 @@ default Map getMap(String name, Class keyClass, Class valueCl *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default UdtValue getUdtValue(String name) { return getUdtValue(firstIndexOf(name)); @@ -572,7 +572,7 @@ default UdtValue getUdtValue(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default TupleValue getTupleValue(String name) { return getTupleValue(firstIndexOf(name)); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index 8462690f7b8..051e08e414c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -50,7 +50,7 @@ public interface SettableById> * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() * duplicate} it beforehand. If you change the buffer's index or its contents in any way, * further usage of this data will have unpredictable results. - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setBytesUnsafe(CqlIdentifier id, ByteBuffer v) { return setBytesUnsafe(firstIndexOf(id), v); @@ -67,7 +67,7 @@ default DataType getType(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setToNull(CqlIdentifier id) { return setToNull(firstIndexOf(id)); @@ -87,7 +87,7 @@ default T setToNull(CqlIdentifier id) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T set(CqlIdentifier id, V v, TypeCodec codec) { return set(firstIndexOf(id), v, codec); @@ -104,7 +104,7 @@ default T set(CqlIdentifier id, V v, TypeCodec codec) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T set(CqlIdentifier id, V v, GenericType targetType) { @@ -121,7 +121,7 @@ default T set(CqlIdentifier id, V v, GenericType targetType) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T set(CqlIdentifier id, V v, Class targetClass) { @@ -139,7 +139,7 @@ default T set(CqlIdentifier id, V v, Class targetClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setBoolean(CqlIdentifier id, boolean v) { return setBoolean(firstIndexOf(id), v); @@ -156,7 +156,7 @@ default T setBoolean(CqlIdentifier id, boolean v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setByte(CqlIdentifier id, byte v) { return setByte(firstIndexOf(id), v); @@ -173,7 +173,7 @@ default T setByte(CqlIdentifier id, byte v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setDouble(CqlIdentifier id, double v) { return setDouble(firstIndexOf(id), v); @@ -190,7 +190,7 @@ default T setDouble(CqlIdentifier id, double v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setFloat(CqlIdentifier id, float v) { return setFloat(firstIndexOf(id), v); @@ -207,7 +207,7 @@ default T setFloat(CqlIdentifier id, float v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setInt(CqlIdentifier id, int v) { return setInt(firstIndexOf(id), v); @@ -224,7 +224,7 @@ default T setInt(CqlIdentifier id, int v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setLong(CqlIdentifier id, long v) { return setLong(firstIndexOf(id), v); @@ -241,7 +241,7 @@ default T setLong(CqlIdentifier id, long v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setShort(CqlIdentifier id, short v) { return setShort(firstIndexOf(id), v); @@ -255,7 +255,7 @@ default T setShort(CqlIdentifier id, short v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setInstant(CqlIdentifier id, Instant v) { return setInstant(firstIndexOf(id), v); @@ -269,7 +269,7 @@ default T setInstant(CqlIdentifier id, Instant v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setLocalDate(CqlIdentifier id, LocalDate v) { return setLocalDate(firstIndexOf(id), v); @@ -283,7 +283,7 @@ default T setLocalDate(CqlIdentifier id, LocalDate v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setLocalTime(CqlIdentifier id, LocalTime v) { return setLocalTime(firstIndexOf(id), v); @@ -297,7 +297,7 @@ default T setLocalTime(CqlIdentifier id, LocalTime v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setByteBuffer(CqlIdentifier id, ByteBuffer v) { return setByteBuffer(firstIndexOf(id), v); @@ -311,7 +311,7 @@ default T setByteBuffer(CqlIdentifier id, ByteBuffer v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setString(CqlIdentifier id, String v) { return setString(firstIndexOf(id), v); @@ -325,7 +325,7 @@ default T setString(CqlIdentifier id, String v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setBigInteger(CqlIdentifier id, BigInteger v) { return setBigInteger(firstIndexOf(id), v); @@ -339,7 +339,7 @@ default T setBigInteger(CqlIdentifier id, BigInteger v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setBigDecimal(CqlIdentifier id, BigDecimal v) { return setBigDecimal(firstIndexOf(id), v); @@ -353,7 +353,7 @@ default T setBigDecimal(CqlIdentifier id, BigDecimal v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setUuid(CqlIdentifier id, UUID v) { return setUuid(firstIndexOf(id), v); @@ -367,7 +367,7 @@ default T setUuid(CqlIdentifier id, UUID v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setInetAddress(CqlIdentifier id, InetAddress v) { return setInetAddress(firstIndexOf(id), v); @@ -381,7 +381,7 @@ default T setInetAddress(CqlIdentifier id, InetAddress v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setCqlDuration(CqlIdentifier id, CqlDuration v) { return setCqlDuration(firstIndexOf(id), v); @@ -398,7 +398,7 @@ default T setCqlDuration(CqlIdentifier id, CqlDuration v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setList(CqlIdentifier id, List v, Class elementsClass) { return setList(firstIndexOf(id), v, elementsClass); @@ -415,7 +415,7 @@ default T setList(CqlIdentifier id, List v, Class elementsClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setSet(CqlIdentifier id, Set v, Class elementsClass) { return setSet(firstIndexOf(id), v, elementsClass); @@ -432,7 +432,7 @@ default T setSet(CqlIdentifier id, Set v, Class elementsClass) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setMap(CqlIdentifier id, Map v, Class keyClass, Class valueClass) { return setMap(firstIndexOf(id), v, keyClass, valueClass); @@ -446,7 +446,7 @@ default T setMap(CqlIdentifier id, Map v, Class keyClass, Class< *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setUdtValue(CqlIdentifier id, UdtValue v) { return setUdtValue(firstIndexOf(id), v); @@ -460,7 +460,7 @@ default T setUdtValue(CqlIdentifier id, UdtValue v) { *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the id is invalid. */ default T setTupleValue(CqlIdentifier id, TupleValue v) { return setTupleValue(firstIndexOf(id), v); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index 8ac4fefa412..9037a0e0507 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -49,7 +49,7 @@ public interface SettableByName> * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() * duplicate} it beforehand. If you change the buffer's index or its contents in any way, * further usage of this data will have unpredictable results. - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setBytesUnsafe(String name, ByteBuffer v) { return setBytesUnsafe(firstIndexOf(name), v); @@ -66,7 +66,7 @@ default DataType getType(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setToNull(String name) { return setToNull(firstIndexOf(name)); @@ -86,7 +86,7 @@ default T setToNull(String name) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T set(String name, V v, TypeCodec codec) { return set(firstIndexOf(name), v, codec); @@ -103,7 +103,7 @@ default T set(String name, V v, TypeCodec codec) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T set(String name, V v, GenericType targetType) { @@ -121,7 +121,7 @@ default T set(String name, V v, GenericType targetType) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ default T set(String name, V v, Class targetClass) { @@ -139,7 +139,7 @@ default T set(String name, V v, Class targetClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setBoolean(String name, boolean v) { return setBoolean(firstIndexOf(name), v); @@ -156,7 +156,7 @@ default T setBoolean(String name, boolean v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setByte(String name, byte v) { return setByte(firstIndexOf(name), v); @@ -173,7 +173,7 @@ default T setByte(String name, byte v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setDouble(String name, double v) { return setDouble(firstIndexOf(name), v); @@ -190,7 +190,7 @@ default T setDouble(String name, double v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setFloat(String name, float v) { return setFloat(firstIndexOf(name), v); @@ -207,7 +207,7 @@ default T setFloat(String name, float v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setInt(String name, int v) { return setInt(firstIndexOf(name), v); @@ -224,7 +224,7 @@ default T setInt(String name, int v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setLong(String name, long v) { return setLong(firstIndexOf(name), v); @@ -241,7 +241,7 @@ default T setLong(String name, long v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setShort(String name, short v) { return setShort(firstIndexOf(name), v); @@ -255,7 +255,7 @@ default T setShort(String name, short v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setInstant(String name, Instant v) { return setInstant(firstIndexOf(name), v); @@ -269,7 +269,7 @@ default T setInstant(String name, Instant v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setLocalDate(String name, LocalDate v) { return setLocalDate(firstIndexOf(name), v); @@ -283,7 +283,7 @@ default T setLocalDate(String name, LocalDate v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setLocalTime(String name, LocalTime v) { return setLocalTime(firstIndexOf(name), v); @@ -297,7 +297,7 @@ default T setLocalTime(String name, LocalTime v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setByteBuffer(String name, ByteBuffer v) { return setByteBuffer(firstIndexOf(name), v); @@ -311,7 +311,7 @@ default T setByteBuffer(String name, ByteBuffer v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setString(String name, String v) { return setString(firstIndexOf(name), v); @@ -325,7 +325,7 @@ default T setString(String name, String v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setBigInteger(String name, BigInteger v) { return setBigInteger(firstIndexOf(name), v); @@ -339,7 +339,7 @@ default T setBigInteger(String name, BigInteger v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setBigDecimal(String name, BigDecimal v) { return setBigDecimal(firstIndexOf(name), v); @@ -353,7 +353,7 @@ default T setBigDecimal(String name, BigDecimal v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setUuid(String name, UUID v) { return setUuid(firstIndexOf(name), v); @@ -367,7 +367,7 @@ default T setUuid(String name, UUID v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setInetAddress(String name, InetAddress v) { return setInetAddress(firstIndexOf(name), v); @@ -381,7 +381,7 @@ default T setInetAddress(String name, InetAddress v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setCqlDuration(String name, CqlDuration v) { return setCqlDuration(firstIndexOf(name), v); @@ -398,7 +398,7 @@ default T setCqlDuration(String name, CqlDuration v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setList(String name, List v, Class elementsClass) { return setList(firstIndexOf(name), v, elementsClass); @@ -415,7 +415,7 @@ default T setList(String name, List v, Class elementsClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setSet(String name, Set v, Class elementsClass) { return setSet(firstIndexOf(name), v, elementsClass); @@ -432,7 +432,7 @@ default T setSet(String name, Set v, Class elementsClass) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setMap(String name, Map v, Class keyClass, Class valueClass) { return setMap(firstIndexOf(name), v, keyClass, valueClass); @@ -447,7 +447,7 @@ default T setMap(String name, Map v, Class keyClass, Class va *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setUdtValue(String name, UdtValue v) { return setUdtValue(firstIndexOf(name), v); @@ -461,7 +461,7 @@ default T setUdtValue(String name, UdtValue v) { *

      This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IndexOutOfBoundsException if the name is invalid. */ default T setTupleValue(String name, TupleValue v) { return setTupleValue(firstIndexOf(name), v); From 27aecf1b8d63004238ea4d291fd4ec8fcfce613c Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 1 Aug 2017 15:06:38 -0700 Subject: [PATCH 180/742] JAVA-1591: Add programmatic way to get driver version --- changelog/README.md | 1 + core/pom.xml | 20 ++++++++++++++++++- .../datastax/oss/driver/api/core/Cluster.java | 12 +++++++++++ .../com/datastax/oss/driver/Driver.properties | 17 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/com/datastax/oss/driver/Driver.properties diff --git a/changelog/README.md b/changelog/README.md index e4d48118dc6..cb04608e21d 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1591: Add programmatic way to get driver version ### 4.0.0-alpha1 diff --git a/core/pom.xml b/core/pom.xml index bfc0c37d332..cc9e10e3396 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 @@ -89,6 +91,22 @@ + + + src/main/resources + + com/datastax/oss/driver/Driver.properties + + true + + + src/main/resources + + com/datastax/oss/driver/Driver.properties + + false + + maven-shade-plugin diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 043fa50220a..112d483d4c3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.ResourceBundle; import java.util.concurrent.CompletionStage; /** An instance of the driver, that connects to a Cassandra cluster. */ @@ -31,6 +32,17 @@ static ClusterBuilder builder() { return new ClusterBuilder(); } + /** + * The current version of the driver. + * + *

      This is intended for products that wrap or extend the driver, as a way to check + * compatibility if end-users override the driver version in their application. + */ + static String getDriverVersion() { + // Note: getBundle caches its result + return ResourceBundle.getBundle("com.datastax.oss.driver.Driver").getString("driver.version"); + } + /** * The unique name identifying this cluster. * diff --git a/core/src/main/resources/com/datastax/oss/driver/Driver.properties b/core/src/main/resources/com/datastax/oss/driver/Driver.properties new file mode 100644 index 00000000000..216fd66a110 --- /dev/null +++ b/core/src/main/resources/com/datastax/oss/driver/Driver.properties @@ -0,0 +1,17 @@ +# +# Copyright (C) 2017-2017 DataStax Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +driver.version=${project.version} \ No newline at end of file From 109ea0b6cdffad42bc4ef27aad3913a89619b4c3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 1 Aug 2017 16:07:35 -0700 Subject: [PATCH 181/742] JAVA-1576: Expose AsyncResultSet's iterator through a currentPage() method The goal is to make it more explicit that it does not return the whole result set. --- changelog/README.md | 1 + .../driver/api/core/cql/AsyncResultSet.java | 17 ++++++++++------- .../core/cql/DefaultAsyncResultSet.java | 19 ++++++++++--------- .../internal/core/cql/MultiPageResultSet.java | 4 ++-- .../core/cql/SinglePageResultSet.java | 2 +- .../core/cql/CqlRequestHandlerRetryTest.java | 8 ++++---- .../core/cql/CqlRequestHandlerTest.java | 2 +- .../internal/core/cql/ResultSetsTest.java | 2 +- .../driver/api/core/cql/AsyncResultSetIT.java | 4 ++-- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index cb04608e21d..d81209d1b49 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1576: Expose AsyncResultSet's iterator through a currentPage() method - [improvement] JAVA-1591: Add programmatic way to get driver version ### 4.0.0-alpha1 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 2e67cd981c7..0ee8c8cb2bc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -21,16 +21,10 @@ /** * The result of an asynchronous CQL query. * - *

      If the query is paged, the rows returned by {@link #iterator()} represent only the current - * page. To keep iterating beyond that, use {@link #fetchNextPage()}. - * - *

      Note that this object can only be iterated once: rows are "consumed" as they are read, - * subsequent calls to {@code iterator()} will return an empty iterator. - * * @see Session#executeAsync(Statement) * @see Session#executeAsync(String) */ -public interface AsyncResultSet extends Iterable { +public interface AsyncResultSet { ColumnDefinitions getColumnDefinitions(); @@ -39,6 +33,15 @@ public interface AsyncResultSet extends Iterable { /** How many rows are left before the current page is exhausted. */ int remaining(); + /** + * The rows in the current page. To keep iterating beyond that, use {@link #hasMorePages()} and + * {@link #fetchNextPage()}. + * + *

      Note that this method always returns the same object, and that that object can only be + * iterated once: rows are "consumed" as they are read. + */ + Iterable currentPage(); + /** * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next * one asynchronously. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 5b0d2d7c7be..20b901db346 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -26,7 +26,6 @@ import com.datastax.oss.driver.internal.core.util.CountingIterator; import java.nio.ByteBuffer; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Queue; import java.util.concurrent.CompletionStage; @@ -41,6 +40,7 @@ public class DefaultAsyncResultSet implements AsyncResultSet { private final ExecutionInfo executionInfo; private final Session session; private final CountingIterator iterator; + private final Iterable currentPage; public DefaultAsyncResultSet( ColumnDefinitions definitions, @@ -59,6 +59,7 @@ protected Row computeNext() { return (rowData == null) ? endOfData() : new DefaultRow(definitions, rowData, context); } }; + this.currentPage = () -> iterator; } @Override @@ -72,8 +73,8 @@ public ExecutionInfo getExecutionInfo() { } @Override - public Iterator iterator() { - return iterator; + public Iterable currentPage() { + return currentPage; } @Override @@ -104,7 +105,7 @@ public boolean wasApplied() { if (!definitions.contains("[applied]") || !definitions.get("[applied]").getType().equals(DataTypes.BOOLEAN)) { return true; - } else if (iterator().hasNext()) { + } else if (iterator.hasNext()) { // Note that [applied] has the same value for all rows, so as long as we have a row we don't // care which one it is. return iterator.peek().getBoolean("[applied]"); @@ -128,6 +129,11 @@ public ExecutionInfo getExecutionInfo() { return executionInfo; } + @Override + public Iterable currentPage() { + return Collections.emptyList(); + } + @Override public int remaining() { return 0; @@ -144,11 +150,6 @@ public CompletionStage fetchNextPage() throws IllegalStateExcept "No next page. Use #hasMorePages before calling this method to avoid this error."); } - @Override - public Iterator iterator() { - return Collections.emptyList().iterator(); - } - @Override public boolean wasApplied() { return true; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index 4359081594c..8c6f0c83b91 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -88,7 +88,7 @@ private class RowIterator extends CountingIterator { private RowIterator(AsyncResultSet firstPage) { super(firstPage.remaining()); this.pages.add(firstPage); - this.currentRows = firstPage.iterator(); + this.currentRows = firstPage.currentPage().iterator(); } @Override @@ -103,7 +103,7 @@ private void maybeMoveToNextPage() { // We've just finished iterating the current page, remove it pages.removeFirst(); if (!pages.isEmpty()) { - currentRows = pages.getFirst().iterator(); + currentRows = pages.getFirst().currentPage().iterator(); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java index 4ac2a26c2fb..8823c3e0a91 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java @@ -65,7 +65,7 @@ public void fetchNextPage() { @Override public Iterator iterator() { - return onlyPage.iterator(); + return onlyPage.currentPage().iterator(); } @Override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 22070ad6786..94e13944e80 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -71,7 +71,7 @@ public void should_always_try_next_node_if_bootstrapping( assertThat(resultSetFuture) .isSuccess( resultSet -> { - Iterator rows = resultSet.iterator(); + Iterator rows = resultSet.currentPage().iterator(); assertThat(rows.hasNext()).isTrue(); assertThat(rows.next().getString("message")).isEqualTo("hello, world"); @@ -139,7 +139,7 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( assertThat(resultSetFuture) .isSuccess( resultSet -> { - Iterator rows = resultSet.iterator(); + Iterator rows = resultSet.currentPage().iterator(); assertThat(rows.hasNext()).isTrue(); assertThat(rows.next().getString("message")).isEqualTo("hello, world"); @@ -171,7 +171,7 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( assertThat(resultSetFuture) .isSuccess( resultSet -> { - Iterator rows = resultSet.iterator(); + Iterator rows = resultSet.currentPage().iterator(); assertThat(rows.hasNext()).isTrue(); assertThat(rows.next().getString("message")).isEqualTo("hello, world"); @@ -202,7 +202,7 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( assertThat(resultSetFuture) .isSuccess( resultSet -> { - Iterator rows = resultSet.iterator(); + Iterator rows = resultSet.currentPage().iterator(); assertThat(rows.hasNext()).isFalse(); ExecutionInfo executionInfo = resultSet.getExecutionInfo(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 7a52b42fc9d..aaa1dc588bb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -64,7 +64,7 @@ public void should_complete_result_if_first_node_replies_immediately() { assertThat(resultSetFuture) .isSuccess( resultSet -> { - Iterator rows = resultSet.iterator(); + Iterator rows = resultSet.currentPage().iterator(); assertThat(rows.hasNext()).isTrue(); assertThat(rows.next().getString("message")).isEqualTo("hello, world"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index a1d3e08090d..a0cf30360ca 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -219,7 +219,7 @@ protected Row computeNext() { return (index == null) ? endOfData() : mockRow(index); } }; - Mockito.when(page.iterator()).thenReturn(iterator); + Mockito.when(page.currentPage()).thenReturn(() -> iterator); Mockito.when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); return page; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index cd982d6a84b..23a2d35cdeb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -82,7 +82,7 @@ public void should_only_iterate_over_rows_in_current_page() throws Exception { assertThat(rs.remaining()).isEqualTo(PAGE_SIZE); assertThat(rs.hasMorePages()).isTrue(); - Iterator rowIt = rs.iterator(); + Iterator rowIt = rs.currentPage().iterator(); for (int i = 0; i < PAGE_SIZE; i++) { Row row = rowIt.next(); assertThat(row.getString("k0")).isEqualTo(PARTITION_KEY1); @@ -160,7 +160,7 @@ public CompletionStage apply(AsyncResultSet result) { int pages = result.remaining() == 0 ? pagesSoFar : pagesSoFar + 1; // iterate over page and ensure data is in order. - for (Row row : result) { + for (Row row : result.currentPage()) { int v = row.getInt("v"); if (v != consumedRows) { CompletableFuture next = new CompletableFuture<>(); From d774781e18d2f088cb34c62b46eae3f357b60ae1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 1 Aug 2017 16:28:23 -0700 Subject: [PATCH 182/742] JAVA-1590: Properly skip deployment of integration-tests module --- integration-tests/pom.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5868c23d6d1..1de3a2b74cf 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 @@ -65,6 +67,13 @@ true + + org.sonatype.plugins + nexus-staging-maven-plugin + + true + + org.apache.maven.plugins maven-failsafe-plugin From 629a0a08aa7ccf811c4d8564df6b208182a6e206 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 2 Aug 2017 12:02:12 -0700 Subject: [PATCH 183/742] Update changelog for 1590 --- changelog/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog/README.md b/changelog/README.md index d81209d1b49..cfa28410dc3 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1590: Properly skip deployment of integration-tests module - [improvement] JAVA-1576: Expose AsyncResultSet's iterator through a currentPage() method - [improvement] JAVA-1591: Add programmatic way to get driver version From 0ba327287dd8eff9dddddd0fd8f8fd76ab5222eb Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 4 Aug 2017 17:05:48 -0700 Subject: [PATCH 184/742] Improve NodeDistance javadoc --- .../oss/driver/internal/core/metadata/DistanceEvent.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java index d758a09fdc7..3f1faed3467 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java @@ -16,9 +16,15 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; import java.util.Objects; -/** Fired when the load balancing policy assigns a new distance to a host. */ +/** + * Indicates that the load balancing policy has assigned a new distance to a host. + * + *

      This is informational only: firing this event manually does not change the distance. + * For that, see {@link LoadBalancingPolicyWrapper#setDistance(Node, NodeDistance)}. + */ public class DistanceEvent { public final NodeDistance distance; public final DefaultNode node; From c5e8de6f87708d0054ca772bec07260d5cf01110 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 4 Aug 2017 17:36:26 -0700 Subject: [PATCH 185/742] Improve EventBus javadoc --- .../datastax/oss/driver/internal/core/context/EventBus.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java index eb641df05d1..d15bd020d84 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java @@ -47,6 +47,10 @@ public EventBus(String logPrefix) { /** * Registers a listener for an event type. * + *

      If the listener has a shorter lifecycle than the {@code Cluster} instance, it is recommended + * to save the key returned by this method, and use it later to unregister and therefore avoid a + * leak. + * * @return a key that is needed to unregister later. */ public Object register(Class eventClass, Consumer listener) { From 17bbcfd7368cf0121d1d2863600d19bf7b1fca25 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 31 Jul 2017 12:46:48 -0500 Subject: [PATCH 186/742] Enable travis builds Also disable jenkins commit status updates Since this runs on an internal server the status updates are not useful for community. --- .travis.yml | 7 +++++++ README.md | 4 +++- build.yaml | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..9163d2152e5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: +- oraclejdk8 +sudo: false +cache: + directories: + - $HOME/.m2 diff --git a/README.md b/README.md index 2e69fdb0a48..04edbbc62b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Datastax Java Driver for Apache Cassandra® +[![Build Status](https://travis-ci.org/datastax/java-driver.svg?branch=4.x)](https://travis-ci.org/datastax/java-driver) + *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. @@ -65,4 +67,4 @@ and/or other countries. Apache Cassandra, Apache, Tomcat, Lucene, Solr, Hadoop, Spark, TinkerPop, and Cassandra are trademarks of the [Apache Software Foundation](http://www.apache.org/) or its subsidiaries in -Canada, the United States and/or other countries. \ No newline at end of file +Canada, the United States and/or other countries. diff --git a/build.yaml b/build.yaml index 571582df2d0..e367e41e085 100644 --- a/build.yaml +++ b/build.yaml @@ -16,3 +16,4 @@ build: - xunit: - "**/target/surefire-reports/TEST-*.xml" - "**/target/failsafe-reports/TEST-*.xml" +disable_commit_status: true From 9ecc00432c1c03c2ea4e9c8a96994f01d1af7901 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 2 Aug 2017 12:08:03 -0700 Subject: [PATCH 187/742] JAVA-1585: Add GenericType#where --- changelog/README.md | 1 + .../api/core/type/reflect/GenericType.java | 62 ++++++++++++++++--- .../type/reflect/GenericTypeParameter.java | 42 +++++++++++++ .../core/type/reflect/GenericTypeTest.java | 18 ++++++ 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java diff --git a/changelog/README.md b/changelog/README.md index cfa28410dc3..fe78df08a6b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1585: Add GenericType#where - [improvement] JAVA-1590: Properly skip deployment of integration-tests module - [improvement] JAVA-1576: Expose AsyncResultSet's iterator through a currentPage() method - [improvement] JAVA-1591: Add programmatic way to get driver version diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index 83897cb6870..3b9277e5f7f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -16,10 +16,14 @@ package com.datastax.oss.driver.api.core.type.reflect; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.GettableByIndex; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.google.common.reflect.TypeParameter; +import com.google.common.reflect.TypeResolver; import com.google.common.reflect.TypeToken; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -35,17 +39,41 @@ /** * Runtime representation of a generic Java type. * - *

      This is used by type codecs to indicate which Java types they accept, and by generic getters - * and setters in the driver's query API. + *

      This is used by type codecs to indicate which Java types they accept ({@link + * TypeCodec#accepts(GenericType)}), and by generic getters and setters (such as {@link + * GettableByIndex#get(int, GenericType)} in the driver's query API. * - *

      To get an instance, use one of the constants or static factory methods, or create an anonymous - * class: + *

      There are various ways to build instances of this class: + * + *

      By using one of the static factory methods: + * + *

      {@code
      + * GenericType> stringListType = GenericType.listOf(String.class);
      + * }
      + * + * By using an anonymous class: * *
      {@code
        * GenericType> fooBarType = new GenericType>(){};
        * }
      * - * You are encouraged to store and reuse these objects. + * In a generic method, by using {@link #where(GenericTypeParameter, GenericType)} to substitute + * free type variables with runtime types: + * + *
      {@code
      + *  GenericType> optionalOf(GenericType elementType) {
      + *   return new GenericType>() {}
      + *     .where(new GenericTypeParameter() {}, elementType);
      + * }
      + * ...
      + * GenericType>> optionalStringListType = optionalOf(GenericType.listOf(String.class));
      + * }
      + * + *

      You are encouraged to store and reuse these instances. + * + *

      Note that this class is a thin wrapper around Guava's {@code TypeToken}. The only reason why + * {@code TypeToken} is not used directly is because Guava is not exposed in the driver's public API + * (it's used internally, but shaded). */ public class GenericType { @@ -116,8 +144,6 @@ public static GenericType> mapOf( return new GenericType<>(token); } - // This wraps -- and delegates most of the work to -- a Guava type token. The reason we don't - // expose that type directly is because we shade Guava. private final TypeToken token; private GenericType(TypeToken token) { @@ -128,6 +154,28 @@ protected GenericType() { this.token = new TypeToken(getClass()) {}; } + /** + * Substitutes a free type variable with an actual type. See {@link GenericType this class's + * javadoc} for an example. + */ + public final GenericType where( + GenericTypeParameter freeVariable, GenericType actualType) { + TypeResolver resolver = + new TypeResolver().where(freeVariable.getTypeVariable(), actualType.__getToken().getType()); + Type resolvedType = resolver.resolveType(this.token.getType()); + @SuppressWarnings("unchecked") + TypeToken resolvedToken = (TypeToken) TypeToken.of(resolvedType); + return new GenericType<>(resolvedToken); + } + + /** + * Substitutes a free type variable with an actual type. See {@link GenericType this class's + * javadoc} for an example. + */ + public final GenericType where(GenericTypeParameter freeVariable, Class actualType) { + return where(freeVariable, GenericType.of(actualType)); + } + /** * This method is for internal use, DO NOT use it from client code. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java new file mode 100644 index 00000000000..8ba19ac50bf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.type.reflect; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Captures a free type variable that can be used in {@link GenericType#where(GenericTypeParameter, + * GenericType)}. + */ +@SuppressWarnings("unused") // for T (unfortunately has to cover the whole class) +public class GenericTypeParameter { + private final TypeVariable typeVariable; + + protected GenericTypeParameter() { + Type superclass = getClass().getGenericSuperclass(); + checkArgument(superclass instanceof ParameterizedType, "%s isn't parameterized", superclass); + this.typeVariable = + (TypeVariable) ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + public TypeVariable getTypeVariable() { + return typeVariable; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java index 65d8b08b8b8..5405e2c2d6a 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java @@ -18,6 +18,7 @@ import com.google.common.reflect.TypeToken; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -49,4 +50,21 @@ public void should_wrap_types_in_collection() { GenericType.mapOf(GenericType.of(String.class), GenericType.listOf(Integer.class)); assertThat(mapType.__getToken()).isEqualTo(new TypeToken>>() {}); } + + @Test + public void should_substitute_type_parameters() { + assertThat(optionalOf(GenericType.listOf(String.class)).__getToken()) + .isEqualTo(new TypeToken>>() {}); + assertThat(mapOf(String.class, Integer.class).__getToken()) + .isEqualTo(new TypeToken>() {}); + } + + private GenericType> optionalOf(GenericType elementType) { + return new GenericType>() {}.where(new GenericTypeParameter() {}, elementType); + } + + private GenericType> mapOf(Class keyClass, Class valueClass) { + return new GenericType>() {}.where(new GenericTypeParameter() {}, keyClass) + .where(new GenericTypeParameter() {}, valueClass); + } } From 15e8b27d1a4be13888bb9e5735d44e161341177b Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 3 Aug 2017 13:47:56 -0500 Subject: [PATCH 188/742] Add generic codec integration test --- .../type/codec/registry/CodecRegistryIT.java | 188 +++++++++++++++++- 1 file changed, 185 insertions(+), 3 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index cc65ac1c2cf..17860b3a52a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core.type.codec.registry; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -28,11 +27,19 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.api.core.type.reflect.GenericTypeParameter; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.internal.core.type.codec.IntCodec; import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import org.assertj.core.util.Maps; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; @@ -61,6 +68,14 @@ public static void createSchema() { SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k text primary key, v int)") .withConfigProfile(cluster.slowProfile()) .build()); + // table with map value + cluster + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test2 (k0 text, k1 int, v map, primary key (k0, k1))") + .withConfigProfile(cluster.slowProfile()) + .build()); } // A simple codec that allows float values to be used for cassandra int column type. @@ -121,7 +136,7 @@ public void should_throw_exception_if_no_codec_registered_for_type_get() { cluster .session() .execute( - SimpleStatement.builder("SELECT v from TEST where k = ?") + SimpleStatement.builder("SELECT v from test where k = ?") .addPositionalValue(name.getMethodName()) .build()); @@ -158,7 +173,7 @@ public void should_be_able_to_register_and_use_custom_codec() { ResultSet result = session.execute( - SimpleStatement.builder("SELECT v from TEST where k = ?") + SimpleStatement.builder("SELECT v from test where k = ?") .addPositionalValue(name.getMethodName()) .build()); @@ -170,4 +185,171 @@ public void should_be_able_to_register_and_use_custom_codec() { assertThat(row.getFloat(0)).isEqualTo(3.0f); } } + + // TODO: consider moving this into source as it could be generally useful. + private abstract static class MappingCodec implements TypeCodec { + + private final GenericType javaType; + private final TypeCodec innerCodec; + + MappingCodec(TypeCodec innerCodec, GenericType javaType) { + this.innerCodec = innerCodec; + this.javaType = javaType; + } + + @Override + public GenericType getJavaType() { + return javaType; + } + + @Override + public DataType getCqlType() { + return innerCodec.getCqlType(); + } + + @Override + public ByteBuffer encode(O value, ProtocolVersion protocolVersion) { + return innerCodec.encode(encode(value), protocolVersion); + } + + @Override + public O decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + return decode(innerCodec.decode(bytes, protocolVersion)); + } + + @Override + public String format(O value) { + return value == null ? null : innerCodec.format(encode(value)); + } + + @Override + public O parse(String value) { + return value == null || value.isEmpty() || value.equalsIgnoreCase("NULL") + ? null + : decode(innerCodec.parse(value)); + } + + protected abstract O decode(I value); + + protected abstract I encode(O value); + } + + private static class OptionalCodec extends MappingCodec, T> { + + // in cassandra, empty collections are considered null and vise versa. + Predicate isAbsent = + (i) -> + i == null + || (i instanceof Collection && ((Collection) i).isEmpty()) + || (i instanceof Map) && ((Map) i).isEmpty(); + + OptionalCodec(TypeCodec innerCodec) { + super( + innerCodec, + new GenericType>() {}.where( + new GenericTypeParameter() {}, innerCodec.getJavaType())); + } + + @Override + protected Optional decode(T value) { + return isAbsent.test(value) ? Optional.empty() : Optional.of(value); + } + + @Override + protected T encode(Optional value) { + return value.isPresent() ? value.get() : null; + } + } + + @Test + public void should_be_able_to_register_and_use_custom_codec_with_generic_type() { + // create a cluster with registered codecs using OptionalCodec + OptionalCodec> optionalMapCodec = + new OptionalCodec<>(TypeCodecs.mapOf(TypeCodecs.INT, TypeCodecs.TEXT)); + TypeCodec>> mapWithOptionalValueCodec = + TypeCodecs.mapOf(TypeCodecs.INT, new OptionalCodec<>(TypeCodecs.TEXT)); + + try (Cluster codecCluster = + Cluster.builder() + .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) + .addContactPoints(ccm.getContactPoints()) + .build()) { + Session session = codecCluster.connect(cluster.keyspace()); + + PreparedStatement prepared = + session.prepare("INSERT INTO test2 (k0, k1, v) values (?, ?, ?)"); + + // optional map should work. + Map v0 = Maps.newHashMap(0, "value"); + Optional> v0Opt = Optional.of(v0); + BoundStatement insert = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, 0) + .set( + 2, + v0Opt, + optionalMapCodec + .getJavaType()) // use java type so has to be looked up in registry. + .build(); + session.execute(insert); + + // optional absent map should work. + Optional> absentMap = Optional.empty(); + insert = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, 1) + .set(2, absentMap, optionalMapCodec.getJavaType()) + .build(); + session.execute(insert); + + // map with optional value should work - note that you can't have null values in collections, + // so this is not technically practical but want to validate that custom codec resolution works + // when it's composed in a collection codec. + Map> v2Map = Maps.newHashMap(1, Optional.of("hello")); + insert = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, 2) + .set(2, v2Map, mapWithOptionalValueCodec.getJavaType()) + .build(); + session.execute(insert); + + ResultSet result = + session.execute( + SimpleStatement.builder("SELECT v from test2 where k0 = ?") + .addPositionalValues(name.getMethodName()) + .build()); + + assertThat(result.getAvailableWithoutFetching()).isEqualTo(3); + + Iterator rows = result.iterator(); + // row (at key 0) should have v0 + Row row = rows.next(); + // should be able to retrieve value back as an optional map. + assertThat(row.get(0, optionalMapCodec.getJavaType())).isEqualTo(v0Opt); + // should be able to retrieve value back as map. + assertThat(row.getMap(0, Integer.class, String.class)).isEqualTo(v0); + + // next row (at key 1) should be absent (null value). + row = rows.next(); + // value should be null. + assertThat(row.isNull(0)); + // getting with codec should return Optional.empty() + assertThat(row.get(0, optionalMapCodec.getJavaType())).isEqualTo(absentMap); + // getting with map should return an empty map. + assertThat(row.getMap(0, Integer.class, String.class)).isEmpty(); + + // next row (at key 2) should have v2 + row = rows.next(); + // getting with codec should return with the correct type. + assertThat(row.get(0, mapWithOptionalValueCodec.getJavaType())).isEqualTo(v2Map); + // getting with map should return a map without optional value. + assertThat(row.getMap(0, Integer.class, String.class)).isEqualTo(Maps.newHashMap(1, "hello")); + } + } } From ce99768a0178e10243d4c310d45e22170aa55bee Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 2 Aug 2017 23:10:07 -0700 Subject: [PATCH 189/742] JAVA-1568: Handle Reconnection#reconnectNow/stop while the current attempt is still in progress --- changelog/README.md | 2 + .../core/control/ControlConnection.java | 4 +- .../internal/core/pool/ChannelPool.java | 17 +- .../core/util/concurrent/Reconnection.java | 95 ++++++++--- .../oss/driver/TestDataProviders.java | 6 + .../core/pool/ChannelPoolReconnectTest.java | 58 +++++++ .../util/concurrent/ReconnectionTest.java | 151 ++++++++++++++++-- 7 files changed, 284 insertions(+), 49 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index fe78df08a6b..68baea0bb95 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0-alpha2 (in progress) +- [bug] JAVA-1568: Handle Reconnection#reconnectNow/stop while the current attempt is still in + progress - [improvement] JAVA-1585: Add GenericType#where - [improvement] JAVA-1590: Properly skip deployment of integration-tests module - [improvement] JAVA-1576: Expose AsyncResultSet's iterator through a currentPage() method diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 2dbab12112c..e3da50a59aa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -333,9 +333,7 @@ private void onChannelClosed(DriverChannel channel, Node node) { if (!closeWasCalled) { LOG.debug("[{}] Lost channel {}", logPrefix, channel); context.eventBus().fire(ChannelEvent.channelClosed(node)); - if (!reconnection.isRunning()) { - reconnection.start(); - } + reconnection.start(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 4fb5930cc7c..e88682a2f09 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -340,9 +340,7 @@ private void onChannelCloseStarted(DriverChannel channel) { channels.remove(channel); closingChannels.add(channel); eventBus.fire(ChannelEvent.channelClosed(node)); - if (!reconnection.isRunning()) { - reconnection.start(); - } + reconnection.start(); } } @@ -354,9 +352,7 @@ private void onChannelClosed(DriverChannel channel) { if (channels.remove(channel)) { LOG.debug("[{}] Lost channel {}", logPrefix, channel); eventBus.fire(ChannelEvent.channelClosed(node)); - if (!reconnection.isRunning()) { - reconnection.start(); - } + reconnection.start(); } else { LOG.debug("[{}] Channel {} completed graceful shutdown", logPrefix, channel); closingChannels.remove(channel); @@ -371,9 +367,7 @@ private void resize(NodeDistance newDistance) { if (newChannelCount > wantedCount) { LOG.debug("[{}] Growing ({} => {} channels)", logPrefix, wantedCount, newChannelCount); wantedCount = newChannelCount; - if (!reconnection.isRunning()) { - reconnection.start(); - } + reconnection.start(); } else if (newChannelCount < wantedCount) { LOG.debug("[{}] Shrinking ({} => {} channels)", logPrefix, wantedCount, newChannelCount); wantedCount = newChannelCount; @@ -452,6 +446,8 @@ private CompletionStage setKeyspace(CqlIdentifier newKeyspaceName) { private void reconnectNow() { assert adminExecutor.inEventLoop(); + // Don't force because if the reconnection is stopped, it means either we have enough channels + // or the pool is shutting down. reconnection.reconnectNow(false); } @@ -462,7 +458,10 @@ private void close() { } isClosing = true; + // If an attempt was in progress right now, it might open new channels but they will be + // handled in onAllConnected reconnection.stop(); + eventBus.unregister(configListenerKey, ConfigChangeEvent.class); // Close all channels, the pool future completes when all the channels futures have completed diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index e6bd4d0dfc3..58780ae7eee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.util.concurrent; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; -import com.google.common.base.Preconditions; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ScheduledFuture; @@ -38,6 +37,14 @@ public class Reconnection { private static final Logger LOG = LoggerFactory.getLogger(Reconnection.class); + private enum State { + STOPPED, + SCHEDULED, // next attempt scheduled but not started yet + ATTEMPT_IN_PROGRESS, // current attempt started and not completed yet + STOP_AFTER_CURRENT, // stopped, but we're letting an in-progress attempt finish + ; + } + private final String logPrefix; private final EventExecutor executor; private final ReconnectionPolicy reconnectionPolicy; @@ -45,7 +52,7 @@ public class Reconnection { private final Runnable onStart; private final Runnable onStop; - private boolean isRunning; + private State state = State.STOPPED; private ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; private ScheduledFuture> nextAttempt; @@ -76,32 +83,57 @@ public Reconnection( this(logPrefix, executor, reconnectionPolicy, reconnectionTask, () -> {}, () -> {}); } + /** + * Note that if {@link #stop()} was called but we're still waiting for the last pending attempt to + * complete, this still returns {@code true}. + */ public boolean isRunning() { assert executor.inEventLoop(); - return isRunning; + return state != State.STOPPED; } - /** @throws IllegalStateException if the reconnection is already running */ + /** This is a no-op if the reconnection is already running. */ public void start() { assert executor.inEventLoop(); - Preconditions.checkState(!isRunning, "Already running"); - reconnectionSchedule = reconnectionPolicy.newSchedule(); - isRunning = true; - onStart.run(); - scheduleNextAttempt(); + switch (state) { + case SCHEDULED: + case ATTEMPT_IN_PROGRESS: + // nothing to do + break; + case STOP_AFTER_CURRENT: + // cancel the scheduled stop + state = State.ATTEMPT_IN_PROGRESS; + break; + case STOPPED: + reconnectionSchedule = reconnectionPolicy.newSchedule(); + onStart.run(); + scheduleNextAttempt(); + break; + } } /** * Forces a reconnection now, without waiting for the next scheduled attempt. * - * @param forceIfStopped if true and the reconnection is not running, it will get started. If - * false and the reconnection is not running, no attempt is scheduled. + * @param forceIfStopped if true and the reconnection is not running, it will get started (meaning + * subsequent reconnections will be scheduled if this attempt fails). If false and the + * reconnection is not running, no attempt is scheduled. */ public void reconnectNow(boolean forceIfStopped) { assert executor.inEventLoop(); - if (isRunning || forceIfStopped) { + if (state == State.ATTEMPT_IN_PROGRESS || state == State.STOP_AFTER_CURRENT) { + LOG.debug( + "[{}] reconnectNow and current attempt was still running, letting it complete", + logPrefix); + if (state == State.STOP_AFTER_CURRENT) { + // Make sure that we will schedule other attempts if this one fails. + state = State.ATTEMPT_IN_PROGRESS; + } + } else if (state == State.STOPPED && !forceIfStopped) { + LOG.debug("[{}] reconnectNow(false) while stopped, nothing to do", logPrefix); + } else { + assert state == State.SCHEDULED || (state == State.STOPPED && forceIfStopped); LOG.debug("[{}] Forcing next attempt now", logPrefix); - isRunning = true; if (nextAttempt != null) { nextAttempt.cancel(true); } @@ -116,20 +148,33 @@ public void reconnectNow(boolean forceIfStopped) { public void stop() { assert executor.inEventLoop(); - if (isRunning) { - isRunning = false; - LOG.debug("[{}] Stopping reconnection", logPrefix); - if (nextAttempt != null) { - nextAttempt.cancel(true); - } - onStop.run(); + switch (state) { + case STOPPED: + case STOP_AFTER_CURRENT: + break; + case ATTEMPT_IN_PROGRESS: + state = State.STOP_AFTER_CURRENT; + break; + case SCHEDULED: + reallyStop(); + break; + } + } + + private void reallyStop() { + LOG.debug("[{}] Stopping reconnection", logPrefix); + state = State.STOPPED; + if (nextAttempt != null) { + nextAttempt.cancel(true); nextAttempt = null; - reconnectionSchedule = null; } + onStop.run(); + reconnectionSchedule = null; } private void scheduleNextAttempt() { assert executor.inEventLoop(); + state = State.SCHEDULED; if (reconnectionSchedule == null) { // happens if reconnectNow() while we were stopped reconnectionSchedule = reconnectionPolicy.newSchedule(); } @@ -152,6 +197,7 @@ private void scheduleNextAttempt() { // the CompletableFuture to find out if that succeeded or not. private void onNextAttemptStarted(CompletionStage futureOutcome) { assert executor.inEventLoop(); + state = State.ATTEMPT_IN_PROGRESS; futureOutcome .whenCompleteAsync(this::onNextAttemptCompleted, executor) .exceptionally(UncaughtExceptions::log); @@ -161,12 +207,15 @@ private void onNextAttemptCompleted(Boolean success, Throwable error) { assert executor.inEventLoop(); if (success) { LOG.debug("[{}] Reconnection successful", logPrefix); - stop(); + reallyStop(); } else { if (error != null && !(error instanceof CancellationException)) { LOG.warn("[{}] Uncaught error while starting reconnection attempt", logPrefix, error); } - if (isRunning) { // can be false if stop() was called + if (state == State.STOP_AFTER_CURRENT) { + reallyStop(); + } else { + assert state == State.ATTEMPT_IN_PROGRESS; scheduleNextAttempt(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java index 016120a2212..398ce407107 100644 --- a/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java +++ b/core/src/test/java/com/datastax/oss/driver/TestDataProviders.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver; +import com.tngtech.java.junit.dataprovider.DataProvider; import java.util.Arrays; public class TestDataProviders { @@ -77,4 +78,9 @@ public static Object[][] combine(Object[][]... providers) { } return result; } + + @DataProvider + public static Object[][] booleans() { + return fromList(true, false); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 7194063b246..83d5bff5e43 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -24,11 +24,14 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; public class ChannelPoolReconnectTest extends ChannelPoolTestBase { @@ -133,4 +136,59 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce factoryHelper.verifyNoMoreCalls(); } + + @Test + public void should_let_current_attempt_complete_when_reconnecting_now() + throws ExecutionException, InterruptedException { + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + + Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(1); + + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS, channel1) + // reconnection + .pending(ADDRESS, channel2Future) + .build(); + + InOrder inOrder = Mockito.inOrder(eventBus); + + // Initial connection + CompletionStage poolFuture = + ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + factoryHelper.waitForCalls(ADDRESS, 1); + waitForPendingAdminTasks(); + assertThat(poolFuture).isSuccess(); + ChannelPool pool = poolFuture.toCompletableFuture().get(); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelOpened(NODE)); + + // Kill channel1, reconnection begins and starts initializing channel2, but the initialization + // is still pending (channel2Future not completed) + ((ChannelPromise) channel1.closeStartedFuture()).setSuccess(); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + Mockito.verify(reconnectionSchedule).nextDelay(); + factoryHelper.waitForCalls(ADDRESS, 1); + + // Force a reconnection, should not try to create a new channel since we have a pending one + pool.reconnectNow(); + waitForPendingAdminTasks(); + factoryHelper.verifyNoMoreCalls(); + inOrder.verify(eventBus, never()).fire(any()); + + // Complete the initialization of channel2, reconnection succeeds + channel2Future.complete(channel2); + waitForPendingAdminTasks(); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + + assertThat(pool.channels).containsOnly(channel2); + + factoryHelper.verifyNoMoreCalls(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index 850ea268927..9880f6803f7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -15,16 +15,21 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.util.concurrent.EventExecutor; import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -32,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; +@RunWith(DataProviderRunner.class) public class ReconnectionTest { @Mock private ReconnectionPolicy reconnectionPolicy; @@ -83,11 +89,19 @@ public void should_schedule_first_attempt_on_start() { Mockito.verify(onStartCallback).run(); } - @Test(expected = IllegalStateException.class) - public void should_fail_if_started_twice() { + @Test + public void should_ignore_start_if_already_started() { + // Given Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); reconnection.start(); + Mockito.verify(reconnectionSchedule).nextDelay(); + Mockito.verify(onStartCallback).run(); + + // When reconnection.start(); + + // Then + Mockito.verifyNoMoreInteractions(reconnectionSchedule, onStartCallback); } @Test @@ -100,7 +114,7 @@ public void should_stop_if_first_attempt_succeeds() { // When // the reconnection task is scheduled: runPendingTasks(); - assertThat(reconnectionTask.wasCalled()).isTrue(); + assertThat(reconnectionTask.callCount()).isEqualTo(1); // the reconnection task completes: reconnectionTask.complete(true); runPendingTasks(); @@ -120,7 +134,7 @@ public void should_reschedule_if_first_attempt_fails() { // When // the reconnection task is scheduled: runPendingTasks(); - assertThat(reconnectionTask.wasCalled()).isTrue(); + assertThat(reconnectionTask.callCount()).isEqualTo(1); // the reconnection task completes: reconnectionTask.complete(false); runPendingTasks(); @@ -130,7 +144,7 @@ public void should_reschedule_if_first_attempt_fails() { Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); runPendingTasks(); // task was called again - assertThat(reconnectionTask.wasCalled()).isTrue(); + assertThat(reconnectionTask.callCount()).isEqualTo(2); // still running assertThat(reconnection.isRunning()).isTrue(); @@ -145,7 +159,7 @@ public void should_reschedule_if_first_attempt_fails() { } @Test - public void should_reconnect_now_if_running() { + public void should_reconnect_now_if_next_attempt_not_started() { // Given Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); reconnection.start(); @@ -157,8 +171,8 @@ public void should_reconnect_now_if_running() { // Then // reconnection task was run immediately - assertThat(reconnectionTask.wasCalled()).isTrue(); - // if that attempt failed, another reconnection was scheduled + assertThat(reconnectionTask.callCount()).isEqualTo(1); + // if that attempt fails, another reconnection should be scheduled reconnectionTask.complete(false); runPendingTasks(); Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); @@ -176,13 +190,37 @@ public void should_reconnect_now_if_stopped_and_forced() { // Then // reconnection task was run immediately - assertThat(reconnectionTask.wasCalled()).isTrue(); + assertThat(reconnectionTask.callCount()).isEqualTo(1); // if that attempt failed, another reconnection was scheduled reconnectionTask.complete(false); runPendingTasks(); Mockito.verify(reconnectionSchedule).nextDelay(); } + @Test + @UseDataProvider(location = TestDataProviders.class, value = "booleans") + public void should_reconnect_now_when_attempt_in_progress(boolean force) { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + reconnection.start(); + runPendingTasks(); + // the next scheduled attempt has started, but not completed yet + assertThat(reconnectionTask.callCount()).isEqualTo(1); + + // When + reconnection.reconnectNow(force); + runPendingTasks(); + + // Then + // reconnection task should not have been called again + assertThat(reconnectionTask.callCount()).isEqualTo(1); + // should still run until current attempt completes + assertThat(reconnection.isRunning()).isTrue(); + reconnectionTask.complete(true); + runPendingTasks(); + assertThat(reconnection.isRunning()).isFalse(); + } + @Test public void should_not_reconnect_now_if_stopped_and_not_forced() { // Given @@ -193,22 +231,105 @@ public void should_not_reconnect_now_if_stopped_and_not_forced() { runPendingTasks(); // Then - // reconnection task was run immediately - assertThat(reconnectionTask.wasCalled()).isFalse(); + assertThat(reconnectionTask.callCount()).isEqualTo(0); } @Test - public void should_stop_between_attempts_if_requested() { + public void should_stop_between_attempts() { // Given Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); reconnection.start(); + runPendingTasks(); Mockito.verify(reconnectionSchedule).nextDelay(); // When reconnection.stop(); + runPendingTasks(); + + // Then + Mockito.verify(onStopCallback).run(); + assertThat(reconnection.isRunning()).isFalse(); + } + + @Test + public void should_restart_after_stopped_between_attempts() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); + reconnection.start(); + runPendingTasks(); + Mockito.verify(reconnectionSchedule).nextDelay(); + reconnection.stop(); + runPendingTasks(); + assertThat(reconnection.isRunning()).isFalse(); + + // When + reconnection.start(); + runPendingTasks(); + + // Then + Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + assertThat(reconnection.isRunning()).isTrue(); + } + + @Test + @UseDataProvider(location = TestDataProviders.class, value = "booleans") + public void should_stop_while_attempt_in_progress(boolean outcome) { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + reconnection.start(); + runPendingTasks(); + // the next scheduled attempt has started, but not completed yet + assertThat(reconnectionTask.callCount()).isEqualTo(1); + Mockito.verify(onStartCallback).run(); + + // When + reconnection.stop(); + runPendingTasks(); + + // Then + // should let the current attempt complete (whatever its outcome), and become stopped only then + assertThat(reconnection.isRunning()).isTrue(); + Mockito.verifyNoMoreInteractions(onStopCallback); + reconnectionTask.complete(outcome); + runPendingTasks(); + Mockito.verify(onStopCallback).run(); + assertThat(reconnection.isRunning()).isFalse(); + } + + @Test + public void should_restart_after_stopped_while_attempt_in_progress() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + reconnection.start(); + runPendingTasks(); + // the next scheduled attempt has started, but not completed yet + assertThat(reconnectionTask.callCount()).isEqualTo(1); + Mockito.verify(onStartCallback).run(); + // now stop + reconnection.stop(); + runPendingTasks(); + assertThat(reconnection.isRunning()).isTrue(); + + // When + reconnection.start(); + runPendingTasks(); + + // Then + assertThat(reconnection.isRunning()).isTrue(); + // still waiting on the same attempt, should not have called the task again + assertThat(reconnectionTask.callCount()).isEqualTo(1); + // because we were still in progress all the time, to the outside it's as if the stop/restart + // had never happened + Mockito.verifyNoMoreInteractions(onStartCallback); + Mockito.verifyNoMoreInteractions(onStopCallback); + + // When + reconnectionTask.complete(true); + runPendingTasks(); // Then assertThat(reconnection.isRunning()).isFalse(); + Mockito.verify(onStopCallback).run(); } private void runPendingTasks() { @@ -217,10 +338,12 @@ private void runPendingTasks() { private static class MockReconnectionTask implements Callable> { private volatile CompletableFuture nextResult; + private final AtomicInteger callCount = new AtomicInteger(); @Override public CompletionStage call() throws Exception { assertThat(nextResult == null || nextResult.isDone()).isTrue(); + callCount.incrementAndGet(); nextResult = new CompletableFuture<>(); return nextResult; } @@ -231,8 +354,8 @@ private void complete(boolean outcome) { nextResult = null; } - private boolean wasCalled() { - return nextResult != null; + private int callCount() { + return callCount.get(); } } } From a85074c0f344700464fdac7e31684c9af7d2f520 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 7 Aug 2017 14:51:15 -0700 Subject: [PATCH 190/742] Improve NodeState javadocs --- .../datastax/oss/driver/api/core/metadata/NodeState.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java index 9c1a9dab69b..6b43ee0dd82 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeState.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import java.net.InetSocketAddress; /** The state of a node, as viewed from the driver. */ public enum NodeState { @@ -55,7 +57,10 @@ public enum NodeState { * *

      This is used for edge error cases, for example when the driver detects that it's trying to * connect to a node that does not belong to the Cassandra cluster (e.g. a wrong address was - * provided in the contact points). + * provided in the contact points). It can also be {@link + * TopologyEvent#forceDown(InetSocketAddress) triggered explicitly} by components (for example a + * custom load balancing policy) that want to limit the number of nodes that the driver connects + * to. */ FORCED_DOWN, } From dbcf9fff2d6dca913823bf73f427239d16ace05c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 7 Aug 2017 11:30:14 -0700 Subject: [PATCH 191/742] Add an optional description to ConditionChecker --- .../api/testinfra/utils/ConditionChecker.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java index 0a1c9171580..c809d7eb7c1 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java @@ -43,6 +43,8 @@ public static class ConditionCheckerBuilder { private final BooleanSupplier predicate; + private String description; + ConditionCheckerBuilder(BooleanSupplier predicate) { this.predicate = predicate; } @@ -71,14 +73,17 @@ public ConditionCheckerBuilder before(long timeoutMillis) { return this; } - @SuppressWarnings("unchecked") + public ConditionCheckerBuilder as(String description) { + this.description = description; + return this; + } + public void becomesTrue() { - new ConditionChecker(predicate, period, periodUnit).await(timeout, timeoutUnit); + new ConditionChecker(predicate, period, periodUnit, description).await(timeout, timeoutUnit); } - @SuppressWarnings("unchecked") public void becomesFalse() { - new ConditionChecker(() -> !predicate.getAsBoolean(), period, periodUnit) + new ConditionChecker(() -> !predicate.getAsBoolean(), period, periodUnit, description) .await(timeout, timeoutUnit); } } @@ -88,13 +93,15 @@ public static ConditionCheckerBuilder checkThat(BooleanSupplier predicate) { } private final BooleanSupplier predicate; + private final String description; private final Lock lock; private final Condition condition; private final Timer timer; - @SuppressWarnings("unchecked") - public ConditionChecker(BooleanSupplier predicate, long period, TimeUnit periodUnit) { + public ConditionChecker( + BooleanSupplier predicate, long period, TimeUnit periodUnit, String description) { this.predicate = predicate; + this.description = (description != null) ? description : this.toString(); lock = new ReentrantLock(); condition = lock.newCondition(); timer = new Timer("condition-checker", true); @@ -119,8 +126,8 @@ public void await(long timeout, TimeUnit unit) { if (nanos <= 0L) fail( String.format( - "Timeout after %s %s while waiting for condition", - timeout, unit.toString().toLowerCase())); + "Timeout after %s %s while waiting for '%s'", + timeout, unit.toString().toLowerCase(), description)); try { nanos = condition.awaitNanos(nanos); } catch (InterruptedException e) { From 1024fd99b93176c0ccd8c7e1561b201fb8a78318 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 7 Aug 2017 16:29:40 -0700 Subject: [PATCH 192/742] JAVA-1595: Don't use system.local.rpc_address when refreshing node list --- changelog/README.md | 1 + .../core/metadata/DefaultTopologyMonitor.java | 10 +++- .../metadata/DefaultTopologyMonitorTest.java | 53 +++++++++++-------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 68baea0bb95..96a34f60e2d 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [bug] JAVA-1595: Don't use system.local.rpc_address when refreshing node list - [bug] JAVA-1568: Handle Reconnection#reconnectNow/stop while the current attempt is still in progress - [improvement] JAVA-1585: Add GenericType#where diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 5fbf2e61db0..8e35e7ae9f0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -120,6 +120,11 @@ public CompletionStage> refreshNodeList() { } LOG.debug("[{}] Refreshing node list", logPrefix); DriverChannel channel = controlConnection.channel(); + + // This cast always succeeds in production. The only way it could fail is in a test that uses a + // local channel, and we don't have such tests at the moment. + InetSocketAddress controlAddress = (InetSocketAddress) channel.address(); + savePort(channel); CompletionStage localQuery = query(channel, "SELECT * FROM system.local"); @@ -129,7 +134,10 @@ public CompletionStage> refreshNodeList() { peersQuery, (controlNodeResult, peersResult) -> { List nodeInfos = new ArrayList<>(); - nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next())); + // Don't rely on system.local.rpc_address for the control row, because it mistakenly + // reports the normal RPC address instead of the broadcast one (CASSANDRA-11181). We + // already know the address since we've just used it to query. + nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next(), controlAddress)); for (AdminResult.Row row : peersResult) { nodeInfos.add(buildNodeInfo(row)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 4ce35a8b2b5..efe5a0cf298 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -54,6 +54,7 @@ public class DefaultTopologyMonitorTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); @Mock private InternalDriverContext context; @Mock private DriverConfig config; @@ -62,6 +63,7 @@ public class DefaultTopologyMonitorTest { @Mock private DriverChannel channel; private AddressTranslator addressTranslator; private DefaultNode node1; + private DefaultNode node2; private TestTopologyMonitor topologyMonitor; @@ -79,10 +81,12 @@ public void setup() { new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + Mockito.when(channel.address()).thenReturn(ADDRESS1); Mockito.when(controlConnection.channel()).thenReturn(channel); Mockito.when(context.controlConnection()).thenReturn(controlConnection); node1 = new DefaultNode(ADDRESS1); + node2 = new DefaultNode(ADDRESS2); topologyMonitor = new TestTopologyMonitor(context); } @@ -98,9 +102,6 @@ public void should_initialize_control_connection() { @Test public void should_not_refresh_control_node() { - // Given - Mockito.when(channel.address()).thenReturn(ADDRESS1); - // When CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); @@ -111,16 +112,16 @@ public void should_not_refresh_control_node() { @Test public void should_refresh_node_from_peers_if_broadcast_address_is_present() { // Given - InetAddress broadcastAddress = ADDRESS1.getAddress(); - node1.broadcastAddress = Optional.of(broadcastAddress); + InetAddress broadcastAddress = ADDRESS2.getAddress(); + node2.broadcastAddress = Optional.of(broadcastAddress); topologyMonitor.stubQueries( new StubbedQuery( "SELECT * FROM system.peers WHERE peer = :address", ImmutableMap.of("address", broadcastAddress), - mockResult(mockPeersRow(1)))); + mockResult(mockPeersRow(2)))); // When - CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); + CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then assertThat(futureInfo) @@ -128,22 +129,21 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present() { maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); NodeInfo info = maybeInfo.get(); - assertThat(info.getDatacenter()).isEqualTo("dc1"); + assertThat(info.getDatacenter()).isEqualTo("dc2"); }); } @Test public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() { // Given - node1.broadcastAddress = Optional.empty(); + node2.broadcastAddress = Optional.empty(); AdminResult.Row peer3 = mockPeersRow(3); AdminResult.Row peer2 = mockPeersRow(2); - AdminResult.Row peer1 = mockPeersRow(1); topologyMonitor.stubQueries( - new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2, peer1))); + new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2))); // When - CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); + CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then assertThat(futureInfo) @@ -151,7 +151,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); NodeInfo info = maybeInfo.get(); - assertThat(info.getDatacenter()).isEqualTo("dc1"); + assertThat(info.getDatacenter()).isEqualTo("dc2"); }); // The rpc_address in each row should have been tried, only the last row should have been // converted @@ -161,11 +161,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() Mockito.verify(peer2).getInetAddress("rpc_address"); Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2, never()).getString(anyString()); - - Mockito.verify(peer1).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); - Mockito.verify(peer1).getString("data_center"); + Mockito.verify(peer2).getString("data_center"); } @Test @@ -220,9 +216,17 @@ public void should_refresh_node_list_from_local_and_peers() { .isSuccess( infos -> { Iterator iterator = infos.iterator(); - assertThat(iterator.next().getDatacenter()).isEqualTo("dc1"); - assertThat(iterator.next().getDatacenter()).isEqualTo("dc3"); - assertThat(iterator.next().getDatacenter()).isEqualTo("dc2"); + NodeInfo info1 = iterator.next(); + assertThat(info1.getConnectAddress()).isEqualTo(ADDRESS1); + assertThat(info1.getDatacenter()).isEqualTo("dc1"); + NodeInfo info3 = iterator.next(); + assertThat(info3.getConnectAddress()) + .isEqualTo(new InetSocketAddress("127.0.0.3", 9042)); + assertThat(info3.getDatacenter()).isEqualTo("dc3"); + NodeInfo info2 = iterator.next(); + assertThat(info2.getConnectAddress()) + .isEqualTo(new InetSocketAddress("127.0.0.2", 9042)); + assertThat(info2.getDatacenter()).isEqualTo("dc2"); }); } @@ -290,8 +294,11 @@ private AdminResult.Row mockLocalRow(int i) { .thenReturn(InetAddress.getByName("127.0.0." + i)); Mockito.when(row.getString("rack")).thenReturn("rack" + i); Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); - Mockito.when(row.getInetAddress("rpc_address")) - .thenReturn(InetAddress.getByName("127.0.0." + i)); + + // The driver should not use this column for the local row, because it can contain the + // non-broadcast RPC address. Simulate the bug to ensure it's handled correctly. + Mockito.when(row.getInetAddress("rpc_address")).thenReturn(InetAddress.getByName("0.0.0.0")); + Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); return row; } catch (UnknownHostException e) { From eb037bb8ce99e47450a3e73e104abaa8d4c09eca Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 4 Aug 2017 11:57:35 -0700 Subject: [PATCH 193/742] JAVA-1593: Reconnect control connection if current node is removed, forced down or ignored --- changelog/README.md | 1 + .../core/control/ControlConnection.java | 68 +++++- .../internal/core/session/DefaultSession.java | 5 +- .../core/control/ControlConnectionTest.java | 209 +++++++++++++++++- .../control/ControlConnectionTestBase.java | 29 +-- 5 files changed, 291 insertions(+), 21 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 96a34f60e2d..c33fce26c05 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [bug] JAVA-1593: Reconnect control connection if current node is removed, forced down or ignored - [bug] JAVA-1595: Don't use system.local.rpc_address when refreshing node list - [bug] JAVA-1568: Handle Reconnection#reconnectNow/stop while the current attempt is still in progress diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index e3da50a59aa..5cbb243f4e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -17,12 +17,16 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.channel.EventCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; @@ -41,6 +45,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Queue; +import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; @@ -66,7 +71,7 @@ public class ControlConnection implements EventCallback, AsyncAutoCloseable { private final EventExecutor adminExecutor; private final SingleThreaded singleThreaded; - // The single channel used by this connection. This field is accessed currently, but only + // The single channel used by this connection. This field is accessed concurrently, but only // mutated on adminExecutor (by SingleThreaded methods) private volatile DriverChannel channel; @@ -195,11 +200,21 @@ private class SingleThreaded { private boolean closeWasCalled; private final Reconnection reconnection; private DriverChannelOptions channelOptions; + // The last events received for each node + private final Map lastDistanceEvents = new WeakHashMap<>(); + private final Map lastStateEvents = new WeakHashMap<>(); private SingleThreaded(InternalDriverContext context) { this.context = context; this.reconnection = new Reconnection(logPrefix, adminExecutor, context.reconnectionPolicy(), this::reconnect); + + context + .eventBus() + .register(DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); + context + .eventBus() + .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); } private void init(boolean listenToClusterEvents) { @@ -253,6 +268,8 @@ private void connect( .whenCompleteAsync( (channel, error) -> { try { + DistanceEvent lastDistanceEvent = lastDistanceEvents.get(node); + NodeStateEvent lastStateEvent = lastStateEvents.get(node); if (error != null) { if (closeWasCalled) { onSuccess.run(); // abort, we don't really care about the result @@ -274,6 +291,25 @@ private void connect( channel); channel.forceClose(); onSuccess.run(); + } else if (lastDistanceEvent != null + && lastDistanceEvent.distance == NodeDistance.IGNORED) { + LOG.debug( + "[{}] New channel opened ({}) but node became ignored, " + + "closing and trying next node", + logPrefix, + channel); + channel.forceClose(); + connect(nodes, errors, onSuccess, onFailure); + } else if (lastStateEvent != null + && (lastStateEvent.newState == null /*(removed)*/ + || lastStateEvent.newState == NodeState.FORCED_DOWN)) { + LOG.debug( + "[{}] New channel opened ({}) but node was removed or forced down, " + + "closing and trying next node", + logPrefix, + channel); + channel.forceClose(); + connect(nodes, errors, onSuccess, onFailure); } else { LOG.debug("[{}] Connection established to {}", logPrefix, node); // Make sure previous channel gets closed (it may still be open if reconnection was forced) @@ -344,6 +380,36 @@ private void reconnectNow() { } } + private void onDistanceEvent(DistanceEvent event) { + assert adminExecutor.inEventLoop(); + this.lastDistanceEvents.put(event.node, event); + if (event.distance == NodeDistance.IGNORED + && channel != null + && !channel.closeFuture().isDone() + && event.node.getConnectAddress().equals(channel.address())) { + LOG.debug( + "[{}] Control node {} became IGNORED, reconnecting to a different node", + logPrefix, + event.node); + reconnectNow(); + } + } + + private void onStateEvent(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + this.lastStateEvents.put(event.node, event); + if ((event.newState == null /*(removed)*/ || event.newState == NodeState.FORCED_DOWN) + && channel != null + && !channel.closeFuture().isDone() + && event.node.getConnectAddress().equals(channel.address())) { + LOG.debug( + "[{}] Control node {} was removed or forced down, reconnecting to a different node", + logPrefix, + event.node); + reconnectNow(); + } + } + private void forceClose() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 545396f270a..dc31e39e00e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; @@ -210,8 +211,8 @@ private class SingleThreaded { // The pools that we have opened but have not finished initializing yet private final Map> pending = new HashMap<>(); // If we receive events while a pool is initializing, the last one is stored here - private final Map pendingDistanceEvents = new HashMap<>(); - private final Map pendingStateEvents = new HashMap<>(); + private final Map pendingDistanceEvents = new WeakHashMap<>(); + private final Map pendingStateEvents = new WeakHashMap<>(); private SingleThreaded(InternalDriverContext context) { this.context = context; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index b1039406ecf..29e3597a35b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -15,19 +15,30 @@ */ package com.datastax.oss.driver.internal.core.control; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; +import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.google.common.collect.ImmutableList; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.time.Duration; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +@RunWith(DataProviderRunner.class) public class ControlConnectionTest extends ControlConnectionTestBase { @Test @@ -149,7 +160,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // When - failChannel(channel1, "mock channel failure"); + channel1.close(); waitForPendingAdminTasks(); // Then @@ -167,6 +178,182 @@ public void should_reconnect_if_channel_goes_down() throws Exception { factoryHelper.verifyNoMoreCalls(); } + @Test + public void should_reconnect_if_node_becomes_ignored() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + + // When + mockQueryPlan(NODE2); + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, NODE1)); + waitForPendingAdminTasks(); + + // Then + // an immediate reconnection was started + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + assertThat(controlConnection.channel()).isEqualTo(channel2); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(metadataManager).refreshNodes(); + Mockito.verify(loadBalancingPolicyWrapper).init(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + @UseDataProvider("node1RemovedOrForcedDown") + public void should_reconnect_if_node_is_removed_or_forced_down(NodeStateEvent event) { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + .success(ADDRESS1, channel1) + .success(ADDRESS2, channel2) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + + // When + mockQueryPlan(NODE2); + eventBus.fire(event); + waitForPendingAdminTasks(); + + // Then + // an immediate reconnection was started + Mockito.verify(reconnectionSchedule, never()).nextDelay(); + factoryHelper.waitForCall(ADDRESS2); + waitForPendingAdminTasks(); + assertThat(controlConnection.channel()).isEqualTo(channel2); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(metadataManager).refreshNodes(); + Mockito.verify(loadBalancingPolicyWrapper).init(); + + factoryHelper.verifyNoMoreCalls(); + } + + @Test + public void should_reconnect_if_node_became_ignored_during_reconnection_attempt() { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS1, channel1) + // reconnection + .pending(ADDRESS2, channel2Future) + .success(ADDRESS1, channel3) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + + mockQueryPlan(NODE2, NODE1); + // channel1 goes down, triggering a reconnection + channel1.close(); + waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(reconnectionSchedule).nextDelay(); + // the reconnection to node2 is in progress + factoryHelper.waitForCall(ADDRESS2); + + // When + // node2 becomes ignored + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, NODE2)); + // the reconnection to node2 completes + channel2Future.complete(channel2); + waitForPendingAdminTasks(); + + // Then + // The channel should get closed and we should try the next node + Mockito.verify(channel2).forceClose(); + factoryHelper.waitForCall(ADDRESS1); + } + + @Test + @UseDataProvider("node2RemovedOrForcedDown") + public void + should_reconnect_if_node_was_removed_or_forced_down_ignored_during_reconnection_attempt( + NodeStateEvent event) { + // Given + Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + DriverChannel channel1 = newMockDriverChannel(1); + DriverChannel channel2 = newMockDriverChannel(2); + CompletableFuture channel2Future = new CompletableFuture<>(); + DriverChannel channel3 = newMockDriverChannel(3); + MockChannelFactoryHelper factoryHelper = + MockChannelFactoryHelper.builder(channelFactory) + // init + .success(ADDRESS1, channel1) + // reconnection + .pending(ADDRESS2, channel2Future) + .success(ADDRESS1, channel3) + .build(); + + CompletionStage initFuture = controlConnection.init(false); + factoryHelper.waitForCall(ADDRESS1); + + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + assertThat(controlConnection.channel()).isEqualTo(channel1); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + + mockQueryPlan(NODE2, NODE1); + // channel1 goes down, triggering a reconnection + channel1.close(); + waitForPendingAdminTasks(); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(reconnectionSchedule).nextDelay(); + // the reconnection to node2 is in progress + factoryHelper.waitForCall(ADDRESS2); + + // When + // node2 goes into the new state + eventBus.fire(event); + // the reconnection to node2 completes + channel2Future.complete(channel2); + waitForPendingAdminTasks(); + + // Then + // The channel should get closed and we should try the next node + Mockito.verify(channel2).forceClose(); + factoryHelper.waitForCall(ADDRESS1); + } + @Test public void should_force_reconnection_if_pending() { // Given @@ -189,7 +376,7 @@ public void should_force_reconnection_if_pending() { Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled for later - failChannel(channel1, "mock channel failure"); + channel1.close(); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); @@ -320,7 +507,7 @@ public void should_close_channel_if_closed_during_reconnection() { Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled - failChannel(channel1, "mock channel failure"); + channel1.close(); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); @@ -367,7 +554,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); // the channel fails and a reconnection is scheduled - failChannel(channel1, "mock channel failure"); + channel1.close(); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); Mockito.verify(reconnectionSchedule).nextDelay(); @@ -385,4 +572,18 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { // first failure factoryHelper.verifyNoMoreCalls(); } + + @DataProvider + public static List> node1RemovedOrForcedDown() { + return ImmutableList.of( + ImmutableList.of(NodeStateEvent.removed(NODE1)), + ImmutableList.of(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, NODE1))); + } + + @DataProvider + public static List> node2RemovedOrForcedDown() { + return ImmutableList.of( + ImmutableList.of(NodeStateEvent.removed(NODE2)), + ImmutableList.of(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, NODE2))); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index b0c49a89cdf..946412441d8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -65,7 +65,7 @@ abstract class ControlConnectionTestBase { @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; @Mock protected NettyOptions nettyOptions; protected DefaultEventLoopGroup adminEventLoopGroup; - @Mock protected EventBus eventBus; + protected EventBus eventBus; @Mock protected ChannelFactory channelFactory; protected Exchanger> channelFactoryFuture; @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @@ -82,6 +82,7 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.channelFactory()).thenReturn(channelFactory); @@ -101,14 +102,7 @@ public void setup() { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()) - .thenAnswer( - i -> { - ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); - queryPlan.offer(NODE1); - queryPlan.offer(NODE2); - return queryPlan; - }); + mockQueryPlan(NODE1, NODE2); Mockito.when(metadataManager.refreshNodes()) .thenReturn(CompletableFuture.completedFuture(null)); @@ -122,6 +116,18 @@ public void setup() { controlConnection = new ControlConnection(context); } + protected void mockQueryPlan(Node... nodes) { + Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()) + .thenAnswer( + i -> { + ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); + for (Node node : nodes) { + queryPlan.offer(node); + } + return queryPlan; + }); + } + @After public void teardown() { adminEventLoopGroup.shutdownGracefully(100, 200, TimeUnit.MILLISECONDS); @@ -149,11 +155,6 @@ protected DriverChannel newMockDriverChannel(int id) { return channel; } - protected static void failChannel(DriverChannel channel, String message) { - assertThat(MockUtil.isMock(channel)).isTrue(); - ((DefaultChannelPromise) channel.closeFuture()).setFailure(new Exception(message)); - } - // Wait for all the tasks on the admin executor to complete. protected void waitForPendingAdminTasks() { // This works because the event loop group is single-threaded From 3e72da22bc1ba4532926caf8c749f2f3f96c598b Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 4 Aug 2017 16:58:41 -0700 Subject: [PATCH 194/742] JAVA-1594: Don't create pool if node comes back up but is ignored --- changelog/README.md | 1 + .../internal/core/session/DefaultSession.java | 2 +- .../core/session/DefaultSessionTest.java | 30 +++++++++++++++++++ .../session/MockChannelPoolFactoryHelper.java | 18 +++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index c33fce26c05..f9b6be48215 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [bug] JAVA-1594: Don't create pool if node comes back up but is ignored - [bug] JAVA-1593: Reconnect control connection if current node is removed, forced down or ignored - [bug] JAVA-1595: Don't use system.local.rpc_address when refreshing node list - [bug] JAVA-1568: Handle Reconnection#reconnectNow/stop while the current attempt is still in diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index dc31e39e00e..4c9cc1c7600 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -354,7 +354,7 @@ private void processStateEvent(NodeStateEvent event) { return null; }); } - } else if (newState == NodeState.UP) { + } else if (newState == NodeState.UP && node.getDistance() != NodeDistance.IGNORED) { ChannelPool pool = pools.get(node); if (pool == null) { LOG.debug("[{}] {} came back UP and no pool found, initializing it", logPrefix, node); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index c3ea4790c9a..f2be478b7a4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -454,6 +454,36 @@ public void should_recreate_pool_if_node_is_forced_back_up() { assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool2, pool3); } + @Test + public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { + Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + // init + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); + + eventBus.fire(NodeStateEvent.changed(NodeState.FORCED_DOWN, NodeState.UP, node2)); + waitForPendingAdminTasks(); + factoryHelper.verifyNoMoreCalls(); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); + } + @Test public void should_adjust_distance_if_changed_while_recreating() { Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index b009f967e9b..83e4e5423d5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -24,10 +24,12 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Sets; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.mockito.ArgumentCaptor; @@ -94,6 +96,22 @@ public void waitForCalls(Node node, CqlIdentifier keyspace, NodeDistance distanc } } + public void verifyNoMoreCalls() { + inOrder + .verify(channelPoolFactory, timeout(100).times(0)) + .init( + any(Node.class), + any(CqlIdentifier.class), + any(NodeDistance.class), + any(InternalDriverContext.class), + any(String.class)); + + Set counts = Sets.newHashSet(previous.values()); + if (!counts.isEmpty()) { + assertThat(counts).containsExactly(0); + } + } + public static class Builder { private final ChannelPoolFactory channelPoolFactory; private final ListMultimap invocations = From fcff8e6906700e9a516fcaecdfeab5bfa46ac58b Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 6 Aug 2017 12:03:17 -0700 Subject: [PATCH 195/742] JAVA-1565: Mark node down when it loses its last connection and was already reconnecting --- changelog/README.md | 1 + .../internal/core/metadata/NodeStateManager.java | 3 +++ .../core/metadata/NodeStateManagerTest.java | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index f9b6be48215..bf286ce6760 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [bug] JAVA-1565: Mark node down when it loses its last connection and was already reconnecting - [bug] JAVA-1594: Don't create pool if node comes back up but is ignored - [bug] JAVA-1593: Reconnect control connection if current node is removed, forced down or ignored - [bug] JAVA-1595: Don't use system.local.rpc_address when refreshing node list diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 3239988280c..9fad825772b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -117,6 +117,9 @@ private void onChannelEvent(ChannelEvent event) { break; case CLOSED: node.openConnections -= 1; + if (node.openConnections == 0 && node.reconnections > 0) { + setState(node, NodeState.DOWN, "it was reconnecting and lost its last connection"); + } break; case RECONNECTION_STARTED: node.reconnections += 1; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index f0db03686c5..e7cd9d4e0b4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -482,6 +482,21 @@ public void should_mark_node_down_if_reconnection_starts_with_no_connections() { Mockito.verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); } + @Test + public void should_mark_node_down_if_no_connections_and_reconnection_already_started() { + new NodeStateManager(context); + + node1.state = NodeState.UP; + node1.openConnections = 1; + + eventBus.fire(ChannelEvent.reconnectionStarted(node1)); + eventBus.fire(ChannelEvent.channelClosed(node1)); + waitForPendingAdminTasks(); + + assertThat(node1.state).isEqualTo(NodeState.DOWN); + Mockito.verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); + } + @Test public void should_keep_node_up_if_reconnection_starts_with_some_connections() { new NodeStateManager(context); From d555cfd90c9cccb40cb6a690a928a948c1045518 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 6 Aug 2017 16:49:51 -0700 Subject: [PATCH 196/742] Rename DriverChannel#address to remoteAddress and add localAddress --- .../oss/driver/internal/core/channel/DriverChannel.java | 6 +++++- .../driver/internal/core/control/ControlConnection.java | 4 ++-- .../internal/core/metadata/DefaultTopologyMonitor.java | 8 ++++---- .../internal/core/control/ControlConnectionTestBase.java | 3 +-- .../core/metadata/DefaultTopologyMonitorTest.java | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 77e33b67c1e..42ed6a8f879 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -142,10 +142,14 @@ public ProtocolVersion protocolVersion() { return protocolVersion; } - public SocketAddress address() { + public SocketAddress remoteAddress() { return channel.remoteAddress(); } + public SocketAddress localAddress() { + return channel.localAddress(); + } + /** * Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will * be allowed to complete before the underlying channel is closed. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 5cbb243f4e3..38c0ecc50e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -386,7 +386,7 @@ private void onDistanceEvent(DistanceEvent event) { if (event.distance == NodeDistance.IGNORED && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.address())) { + && event.node.getConnectAddress().equals(channel.remoteAddress())) { LOG.debug( "[{}] Control node {} became IGNORED, reconnecting to a different node", logPrefix, @@ -401,7 +401,7 @@ private void onStateEvent(NodeStateEvent event) { if ((event.newState == null /*(removed)*/ || event.newState == NodeState.FORCED_DOWN) && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.address())) { + && event.node.getConnectAddress().equals(channel.remoteAddress())) { LOG.debug( "[{}] Control node {} was removed or forced down, reconnecting to a different node", logPrefix, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 8e35e7ae9f0..60877cdc4e2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -84,7 +84,7 @@ public CompletionStage> refreshNode(Node node) { } LOG.debug("[{}] Refreshing info for {}", logPrefix, node); DriverChannel channel = controlConnection.channel(); - if (node.getConnectAddress().equals(channel.address())) { + if (node.getConnectAddress().equals(channel.remoteAddress())) { // refreshNode is called for nodes that just came up. If the control node just came up, it // means the control connection just reconnected, which means we did a full node refresh. So // we don't need to process this call. @@ -123,7 +123,7 @@ public CompletionStage> refreshNodeList() { // This cast always succeeds in production. The only way it could fail is in a test that uses a // local channel, and we don't have such tests at the moment. - InetSocketAddress controlAddress = (InetSocketAddress) channel.address(); + InetSocketAddress controlAddress = (InetSocketAddress) channel.remoteAddress(); savePort(channel); @@ -230,8 +230,8 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con // nodes. As a consequence, the port is not stored in system tables. // We save it the first time we get a control connection channel. private void savePort(DriverChannel channel) { - if (port < 0 && channel.address() instanceof InetSocketAddress) { - port = ((InetSocketAddress) channel.address()).getPort(); + if (port < 0 && channel.remoteAddress() instanceof InetSocketAddress) { + port = ((InetSocketAddress) channel.remoteAddress()).getPort(); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 946412441d8..cb293642d0f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -48,7 +48,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.internal.util.MockUtil; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -151,7 +150,7 @@ protected DriverChannel newMockDriverChannel(int id) { }); Mockito.when(channel.closeFuture()).thenReturn(closeFuture); Mockito.when(channel.toString()).thenReturn("channel" + id); - Mockito.when(channel.address()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); + Mockito.when(channel.remoteAddress()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); return channel; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index efe5a0cf298..0eb0fcc2259 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -81,7 +81,7 @@ public void setup() { new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); - Mockito.when(channel.address()).thenReturn(ADDRESS1); + Mockito.when(channel.remoteAddress()).thenReturn(ADDRESS1); Mockito.when(controlConnection.channel()).thenReturn(channel); Mockito.when(context.controlConnection()).thenReturn(controlConnection); From 43605b5dd046a3e0cca44232856ed0a1775060a0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 8 Aug 2017 14:26:21 -0700 Subject: [PATCH 197/742] Allow usage of a Runnable in ConditionChecker The condition is considered true if the runnable doesn't throw. --- .../api/testinfra/utils/ConditionChecker.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java index c809d7eb7c1..9acec1650ee 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java @@ -41,7 +41,7 @@ public static class ConditionCheckerBuilder { private TimeUnit periodUnit = TimeUnit.MILLISECONDS; - private final BooleanSupplier predicate; + private final Object predicate; private String description; @@ -49,6 +49,10 @@ public static class ConditionCheckerBuilder { this.predicate = predicate; } + public ConditionCheckerBuilder(Runnable predicate) { + this.predicate = predicate; + } + public ConditionCheckerBuilder every(long period, TimeUnit unit) { this.period = period; periodUnit = unit; @@ -79,11 +83,12 @@ public ConditionCheckerBuilder as(String description) { } public void becomesTrue() { - new ConditionChecker(predicate, period, periodUnit, description).await(timeout, timeoutUnit); + new ConditionChecker(predicate, true, period, periodUnit, description) + .await(timeout, timeoutUnit); } public void becomesFalse() { - new ConditionChecker(() -> !predicate.getAsBoolean(), period, periodUnit, description) + new ConditionChecker(predicate, false, period, periodUnit, description) .await(timeout, timeoutUnit); } } @@ -92,15 +97,25 @@ public static ConditionCheckerBuilder checkThat(BooleanSupplier predicate) { return new ConditionCheckerBuilder(predicate); } - private final BooleanSupplier predicate; + public static ConditionCheckerBuilder checkThat(Runnable predicate) { + return new ConditionCheckerBuilder(predicate); + } + + private final Object predicate; + private final boolean expectedOutcome; private final String description; private final Lock lock; private final Condition condition; private final Timer timer; public ConditionChecker( - BooleanSupplier predicate, long period, TimeUnit periodUnit, String description) { + Object predicate, + boolean expectedOutcome, + long period, + TimeUnit periodUnit, + String description) { this.predicate = predicate; + this.expectedOutcome = expectedOutcome; this.description = (description != null) ? description : this.toString(); lock = new ReentrantLock(); condition = lock.newCondition(); @@ -152,6 +167,18 @@ private void checkCondition() { } private boolean evalCondition() { - return predicate.getAsBoolean(); + if (predicate instanceof BooleanSupplier) { + return ((BooleanSupplier) predicate).getAsBoolean() == expectedOutcome; + } else if (predicate instanceof Runnable) { + boolean succeeded = true; + try { + ((Runnable) predicate).run(); + } catch (Throwable t) { + succeeded = false; + } + return succeeded == expectedOutcome; + } else { + throw new AssertionError("Unsupported predicate type " + predicate.getClass()); + } } } From 87621115f0cd4e03f32f148629aa26da191fe0d1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 7 Aug 2017 10:50:50 -0700 Subject: [PATCH 198/742] Add integration state for node state changes --- .../internal/core/context/EventBus.java | 1 + .../core/metadata/NodeStateManager.java | 9 +- .../internal/core/session/DefaultSession.java | 56 ++- .../driver/api/core/metadata/NodeStateIT.java | 369 ++++++++++++++++++ .../oss/driver/assertions/Assertions.java | 24 ++ .../driver/assertions/NodeMetadataAssert.java | 75 ++++ 6 files changed, 515 insertions(+), 19 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/assertions/Assertions.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java index d15bd020d84..df75f1dbf59 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java @@ -82,6 +82,7 @@ public boolean unregister(Object key, Class eventClass) { * processing asynchronously if needed. */ public void fire(Object event) { + LOG.trace("[{}] Firing an instance of {}: {}", logPrefix, event.getClass(), event); // if the exact match thing gets too cumbersome, we can reconsider, but I'd like to avoid // scanning all the keys with instanceof checks. Class eventClass = event.getClass(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 9fad825772b..63a22232c3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -148,8 +149,8 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { metadataManager.addNode(event.address); } else if (node.state == NodeState.FORCED_DOWN) { LOG.debug("[{}] Not setting {} UP because it is FORCED_DOWN", logPrefix, node); - } else { - setState(node, NodeState.UP, "an UP topology event was received"); + } else if (node.distance == NodeDistance.IGNORED) { + setState(node, NodeState.UP, "it is IGNORED and an UP topology event was received"); } break; case SUGGEST_DOWN: @@ -165,8 +166,8 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { node); } else if (node.state == NodeState.FORCED_DOWN) { LOG.debug("[{}] Not setting {} DOWN because it is FORCED_DOWN", logPrefix, node); - } else { - setState(node, NodeState.DOWN, "a DOWN topology event was received"); + } else if (node.distance == NodeDistance.IGNORED) { + setState(node, NodeState.DOWN, "it is IGNORED and a DOWN topology event was received"); } break; case FORCE_UP: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 4c9cc1c7600..0946d6e430d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -208,6 +209,7 @@ private class SingleThreaded { private final Object stateListenerKey; private final ReplayingEventFilter stateEventFilter = new ReplayingEventFilter<>(this::processStateEvent); + private final Object topologyListenerKey; // The pools that we have opened but have not finished initializing yet private final Map> pending = new HashMap<>(); // If we receive events while a pool is initializing, the last one is stored here @@ -226,6 +228,11 @@ private SingleThreaded(InternalDriverContext context) { context .eventBus() .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); + this.topologyListenerKey = + context + .eventBus() + .register( + TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); } private void init() { @@ -338,15 +345,16 @@ private void processDistanceEvent(DistanceEvent event) { private void processStateEvent(NodeStateEvent event) { assert adminExecutor.inEventLoop(); - // no need to check closeWasCalled, because we stop listening for events one closed + // no need to check closeWasCalled, because we stop listening for events once closed DefaultNode node = event.node; + NodeState oldState = event.oldState; NodeState newState = event.newState; if (pending.containsKey(node)) { pendingStateEvents.put(node, event); } else if (newState == NodeState.FORCED_DOWN) { ChannelPool pool = pools.remove(node); if (pool != null) { - LOG.debug("[{}] {} became FORCED_DOWN, destroying pool", logPrefix, node); + LOG.debug("[{}] {} was FORCED_DOWN, destroying pool", logPrefix, node); pool.closeAsync() .exceptionally( error -> { @@ -354,23 +362,40 @@ private void processStateEvent(NodeStateEvent event) { return null; }); } - } else if (newState == NodeState.UP && node.getDistance() != NodeDistance.IGNORED) { - ChannelPool pool = pools.get(node); - if (pool == null) { - LOG.debug("[{}] {} came back UP and no pool found, initializing it", logPrefix, node); - CompletionStage poolFuture = - channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); - pending.put(node, poolFuture); - poolFuture - .thenAcceptAsync(this::onPoolInitialized, adminExecutor) - .exceptionally(UncaughtExceptions::log); - } else { - LOG.debug("[{}] {} came back UP, triggering pool reconnection", logPrefix, node); - pool.reconnectNow(); + } else if (oldState == NodeState.FORCED_DOWN + && newState == NodeState.UP + && node.getDistance() != NodeDistance.IGNORED) { + LOG.debug("[{}] {} was forced back UP, initializing pool", logPrefix, node); + createOrReconnectPool(node); + } + } + + private void onTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + if (event.type == TopologyEvent.Type.SUGGEST_UP) { + Node node = context.metadataManager().getMetadata().getNodes().get(event.address); + if (node.getDistance() != NodeDistance.IGNORED) { + LOG.debug( + "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); + createOrReconnectPool(node); } } } + private void createOrReconnectPool(Node node) { + ChannelPool pool = pools.get(node); + if (pool == null) { + CompletionStage poolFuture = + channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); + pending.put(node, poolFuture); + poolFuture + .thenAcceptAsync(this::onPoolInitialized, adminExecutor) + .exceptionally(UncaughtExceptions::log); + } else { + pool.reconnectNow(); + } + } + private void onPoolInitialized(ChannelPool pool) { assert adminExecutor.inEventLoop(); Node node = pool.getNode(); @@ -458,6 +483,7 @@ private void close() { // Stop listening for events context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); context.eventBus().unregister(stateListenerKey, NodeStateEvent.class); + context.eventBus().unregister(topologyListenerKey, TopologyEvent.class); List> closePoolStages = new ArrayList<>(pools.size()); for (ChannelPool pool : pools.values()) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java new file mode 100644 index 00000000000..364ae852126 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.cluster.NodeConnectionReport; +import com.datastax.oss.simulacron.common.stubbing.CloseType; +import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.RejectScope; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import static com.datastax.oss.driver.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class NodeStateIT { + + public @Rule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + + public @Rule ClusterRule cluster = + ClusterRule.builder(simulacron) + .withOptions( + "connection.pool.local.size = 2", + "connection.reconnection-policy.max-delay = 1 second") + .build(); + + private InternalDriverContext driverContext; + private final BlockingQueue stateEvents = new LinkedBlockingDeque<>(); + + private BoundNode simulacronControlNode; + private BoundNode simulacronRegularNode; + private DefaultNode metadataControlNode; + private DefaultNode metadataRegularNode; + + @Before + public void setup() { + driverContext = (InternalDriverContext) cluster.cluster().getContext(); + driverContext.eventBus().register(NodeStateEvent.class, stateEvents::add); + + // Sanity check: the driver should have connected to simulacron + ConditionChecker.checkThat( + () -> + // 1 control connection + 2 pooled connections per node + simulacron.cluster().getActiveConnections() == 5) + .as("Connections established") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + // Find out which node is the control node, and identify the corresponding Simulacron and driver + // metadata objects. + simulacronControlNode = simulacronRegularNode = null; + for (BoundNode boundNode : simulacron.cluster().getNodes()) { + if (boundNode.getActiveConnections() == 3) { + simulacronControlNode = boundNode; + } else { + simulacronRegularNode = boundNode; + } + } + assertThat(simulacronControlNode).isNotNull(); + assertThat(simulacronRegularNode).isNotNull(); + + Map nodesMetadata = cluster.cluster().getMetadata().getNodes(); + metadataControlNode = + (DefaultNode) nodesMetadata.get(simulacronControlNode.inetSocketAddress()); + metadataRegularNode = + (DefaultNode) nodesMetadata.get(simulacronRegularNode.inetSocketAddress()); + } + + @Test + public void should_report_connections_for_healthy_nodes() { + ConditionChecker.checkThat( + () -> { + assertThat(metadataControlNode).isUp().hasOpenConnections(3).isNotReconnecting(); + assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting(); + }) + .as("Node metadata up-to-date") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + } + + @Test + public void should_keep_regular_node_up_when_still_one_connection() { + simulacronRegularNode.rejectConnections(0, RejectScope.UNBIND); + NodeConnectionReport report = simulacronRegularNode.getConnections(); + simulacron.cluster().closeConnection(report.getConnections().get(0), CloseType.DISCONNECT); + + ConditionChecker.checkThat( + () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(1).isReconnecting()) + .as("Reconnection started") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + } + + @Test + public void should_mark_regular_node_down_when_no_more_connections() { + simulacronRegularNode.stop(); + + ConditionChecker.checkThat( + () -> assertThat(metadataRegularNode).isDown().hasOpenConnections(0).isReconnecting()) + .as("Node going down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode)); + } + + @Test + public void should_mark_control_node_down_when_control_connection_is_last_connection_and_dies() { + simulacronControlNode.rejectConnections(0, RejectScope.UNBIND); + + // Identify the control connection and close the two other ones + SocketAddress controlAddress = driverContext.controlConnection().channel().localAddress(); + NodeConnectionReport report = simulacronControlNode.getConnections(); + for (SocketAddress address : report.getConnections()) { + if (!address.equals(controlAddress)) { + simulacron.cluster().closeConnection(address, CloseType.DISCONNECT); + } + } + ConditionChecker.checkThat( + () -> assertThat(metadataControlNode).isUp().hasOpenConnections(1).isReconnecting()) + .as("Control node lost its non-control connections") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + simulacron.cluster().closeConnection(controlAddress, CloseType.DISCONNECT); + ConditionChecker.checkThat( + () -> assertThat(metadataControlNode).isDown().hasOpenConnections(0).isReconnecting()) + .as("Control node going down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataControlNode)); + } + + @Test + public void should_bring_node_back_up_when_reconnection_succeeds() { + simulacronRegularNode.stop(); + + ConditionChecker.checkThat( + () -> assertThat(metadataRegularNode).isDown().hasOpenConnections(0).isReconnecting()) + .as("Node going down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + simulacronRegularNode.acceptConnections(); + + ConditionChecker.checkThat( + () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting()) + .as("Connections re-established") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + expect( + NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode), + NodeStateEvent.changed(NodeState.DOWN, NodeState.UP, metadataRegularNode)); + } + + @Test + public void should_apply_up_and_down_topology_events_when_ignored() { + driverContext + .loadBalancingPolicyWrapper() + .setDistance(metadataRegularNode, NodeDistance.IGNORED); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isUp() + .isIgnored() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("Driver closed all connections to ignored node") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + driverContext + .eventBus() + .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isDown() + .isIgnored() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("SUGGEST_DOWN event applied") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isUp() + .isIgnored() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("SUGGEST_UP event applied") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + } + + @Test + public void should_ignore_down_topology_event_when_still_connected() throws InterruptedException { + driverContext + .eventBus() + .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + TimeUnit.MILLISECONDS.sleep(200); + assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting(); + } + + @Test + public void should_force_immediate_reconnection_when_up_topology_event() + throws InterruptedException { + // This test requires a longer reconnection interval, so create a separate driver instance + try (Cluster localCluster = + ClusterUtils.newCluster( + simulacron, + "connection.reconnection-policy.base-delay = 1 hour", + "connection.reconnection-policy.max-delay = 1 hour")) { + localCluster.connect(); + + BoundNode localSimulacronNode = simulacron.cluster().getNodes().iterator().next(); + assertThat(localSimulacronNode).isNotNull(); + + DefaultNode localMetadataNode = + (DefaultNode) + localCluster.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); + + localSimulacronNode.stop(); + + ConditionChecker.checkThat( + () -> assertThat(localMetadataNode).isDown().hasOpenConnections(0).isReconnecting()) + .as("Node going down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, localMetadataNode)); + + localSimulacronNode.acceptConnections(); + ((InternalDriverContext) localCluster.getContext()) + .eventBus() + .fire(TopologyEvent.suggestUp(localMetadataNode.getConnectAddress())); + + ConditionChecker.checkThat(() -> assertThat(localMetadataNode).isUp().isNotReconnecting()) + .as("Node coming back up") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + expect(NodeStateEvent.changed(NodeState.DOWN, NodeState.UP, localMetadataNode)); + } + } + + @Test + public void should_force_down_when_not_ignored() throws InterruptedException { + driverContext.eventBus().fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isForcedDown() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("Node forced down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + // Should ignore up/down topology events while forced down + driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + TimeUnit.MILLISECONDS.sleep(200); + assertThat(metadataRegularNode).isForcedDown(); + + driverContext + .eventBus() + .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + TimeUnit.MILLISECONDS.sleep(200); + assertThat(metadataRegularNode).isForcedDown(); + + // Should only come back up on a FORCE_UP event + driverContext.eventBus().fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting()) + .as("Node forced back up") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + } + + @Test + public void should_force_down_when_ignored() throws InterruptedException { + driverContext + .loadBalancingPolicyWrapper() + .setDistance(metadataRegularNode, NodeDistance.IGNORED); + driverContext.eventBus().fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isForcedDown() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("Node forced down") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + // Should ignore up/down topology events while forced down + driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + TimeUnit.MILLISECONDS.sleep(200); + assertThat(metadataRegularNode).isForcedDown(); + + driverContext + .eventBus() + .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + TimeUnit.MILLISECONDS.sleep(200); + assertThat(metadataRegularNode).isForcedDown(); + + // Should only come back up on a FORCE_UP event, will not reopen connections since it is still + // ignored + driverContext.eventBus().fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + ConditionChecker.checkThat( + () -> + assertThat(metadataRegularNode) + .isUp() + .isIgnored() + .hasOpenConnections(0) + .isNotReconnecting()) + .as("Node forced back up") + .before(10, TimeUnit.SECONDS) + .becomesTrue(); + + driverContext.loadBalancingPolicyWrapper().setDistance(metadataRegularNode, NodeDistance.LOCAL); + } + + private void expect(NodeStateEvent... expectedEvents) { + for (NodeStateEvent expected : expectedEvents) { + try { + NodeStateEvent actual = stateEvents.poll(10, TimeUnit.SECONDS); + assertThat(actual).isEqualTo(expected); + } catch (InterruptedException e) { + fail("Interrupted while waiting for event"); + } + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/assertions/Assertions.java b/test-infra/src/main/java/com/datastax/oss/driver/assertions/Assertions.java new file mode 100644 index 00000000000..81f9afa42a9 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/assertions/Assertions.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.assertions; + +import com.datastax.oss.driver.api.core.metadata.Node; + +public class Assertions extends org.assertj.core.api.Assertions { + public static NodeMetadataAssert assertThat(Node actual) { + return new NodeMetadataAssert(actual); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java new file mode 100644 index 00000000000..afc963484c9 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.assertions; + +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NodeMetadataAssert extends AbstractAssert { + + public NodeMetadataAssert(Node actual) { + super(actual, NodeMetadataAssert.class); + } + + public NodeMetadataAssert isUp() { + assertThat(actual.getState()).isSameAs(NodeState.UP); + return this; + } + + public NodeMetadataAssert isDown() { + assertThat(actual.getState()).isSameAs(NodeState.DOWN); + return this; + } + + public NodeMetadataAssert isForcedDown() { + assertThat(actual.getState()).isSameAs(NodeState.FORCED_DOWN); + return this; + } + + public NodeMetadataAssert hasOpenConnections(int expected) { + assertThat(actual.getOpenConnections()).isEqualTo(expected); + return this; + } + + public NodeMetadataAssert isReconnecting() { + assertThat(actual.isReconnecting()).isTrue(); + return this; + } + + public NodeMetadataAssert isNotReconnecting() { + assertThat(actual.isReconnecting()).isFalse(); + return this; + } + + public NodeMetadataAssert isLocal() { + assertThat(actual.getDistance()).isSameAs(NodeDistance.LOCAL); + return this; + } + + public NodeMetadataAssert isRemote() { + assertThat(actual.getDistance()).isSameAs(NodeDistance.REMOTE); + return this; + } + + public NodeMetadataAssert isIgnored() { + assertThat(actual.getDistance()).isSameAs(NodeDistance.IGNORED); + return this; + } +} From edbf8f9facd56b68c3777d76897a2ad5f5145048 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 31 Jul 2017 10:59:04 -0500 Subject: [PATCH 199/742] Add integration test around config reloading Also sets failsafe plugin to use testFailureIgnore so the build is marked as unstable instead of failed when integration tests fail. Unit tests failing will still fail the build (as desired). --- integration-tests/pom.xml | 4 + .../config/DriverConfigProfileReloadIT.java | 219 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 1de3a2b74cf..df8befee4c4 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -92,6 +92,8 @@ short classes 8 + + true @@ -129,6 +131,7 @@ com.datastax.oss.driver.categories.LongTests long + true @@ -144,6 +147,7 @@ 1 false + true diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java new file mode 100644 index 00000000000..64987864ff6 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.config; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; +import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.typesafe.config.ConfigFactory; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; + +import static com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +@Category(LongTests.class) +public class DriverConfigProfileReloadIT { + + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_periodically_reload_configuration() throws Exception { + String query = "mockquery"; + // Define a loader which configures a reload interval of 2s and current value of configSource. + AtomicReference configSource = new AtomicReference<>(""); + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( + () -> + ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) + .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), + CoreDriverOption.values()); + try (Cluster configCluster = + Cluster.builder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { + simulacron.cluster().prime(when(query).then(noRows()).delay(2, TimeUnit.SECONDS)); + + Session session = configCluster.connect(); + + // Expect timeout since default timeout is .5 s + try { + session.execute(query); + fail("DriverTimeoutException expected"); + } catch (DriverTimeoutException e) { + // expected. + } + + // Bump up request timeout to 10 seconds and wait for config to reload. + configSource.set("request.timeout = 10s"); + waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + + // Execute again, should not timeout. + session.execute(query); + } + } + + @Test + public void should_reload_configuration_when_event_fired() throws Exception { + String query = "mockquery"; + // Define a loader which configures no automatic reloads and current value of configSource. + AtomicReference configSource = new AtomicReference<>(""); + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( + () -> + ConfigFactory.parseString("config-reload-interval = 0\n" + configSource.get()) + .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), + CoreDriverOption.values()); + try (Cluster configCluster = + Cluster.builder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { + simulacron.cluster().prime(when(query).then(noRows()).delay(2, TimeUnit.SECONDS)); + + Session session = configCluster.connect(); + + // Expect timeout since default timeout is .5 s + try { + session.execute(query); + fail("DriverTimeoutException expected"); + } catch (DriverTimeoutException e) { + // expected. + } + + // Bump up request timeout to 10 seconds and trigger a manual reload. + configSource.set("request.timeout = 10s"); + ((InternalDriverContext) configCluster.getContext()) + .eventBus() + .fire(ForceReloadConfigEvent.INSTANCE); + waitForConfigChange(configCluster, 500, TimeUnit.MILLISECONDS); + + // Execute again, should not timeout. + session.execute(query); + } + } + + @Test + public void should_not_allow_dynamically_adding_profile() throws Exception { + String query = "mockquery"; + // Define a loader which configures a reload interval of 2s and current value of configSource. + AtomicReference configSource = new AtomicReference<>(""); + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( + () -> + ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) + .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), + CoreDriverOption.values()); + try (Cluster configCluster = + Cluster.builder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { + simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); + + Session session = configCluster.connect(); + + // Expect failure because profile doesn't exist. + try { + session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected. + } + + // Bump up request timeout to 10 seconds on profile and wait for config to reload. + configSource.set("profiles.slow.request.timeout = 2s"); + waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + + // Execute again, should expect to fail again because doesn't allow to dynamically define profile. + thrown.expect(IllegalArgumentException.class); + session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + } + } + + @Test + public void should_reload_profile_config_when_reloading_config() throws Exception { + String query = "mockquery"; + // Define a loader which configures a reload interval of 2s and current value of configSource. + // Define initial profile settings so it initially exists. + AtomicReference configSource = new AtomicReference<>(""); + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader( + () -> + ConfigFactory.parseString( + "profiles.slow.request.consistency = ONE\nconfig-reload-interval = 2s\n" + + configSource.get()) + .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), + CoreDriverOption.values()); + try (Cluster configCluster = + Cluster.builder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { + simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); + + Session session = configCluster.connect(); + + // Expect failure because profile doesn't exist. + try { + session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + fail("Expected DriverTimeoutException"); + } catch (DriverTimeoutException e) { + // expected. + } + + // Bump up request timeout to 10 seconds on profile and wait for config to reload. + configSource.set("profiles.slow.request.timeout = 10s"); + waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + + // Execute again, should succeed because profile timeout was increased. + session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + } + } + + private void waitForConfigChange(Cluster cluster, long timeout, TimeUnit unit) { + CountDownLatch latch = new CountDownLatch(1); + ((InternalDriverContext) cluster.getContext()) + .eventBus() + .register(ConfigChangeEvent.class, (e) -> latch.countDown()); + try { + boolean success = latch.await(timeout, unit); + assertThat(success).isTrue(); + } catch (InterruptedException e) { + fail("Interrupted while waiting for config change event"); + } + } +} From 1eee96814f51ebb47eaa374b694c4101aea740da Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 31 Jul 2017 12:26:09 -0500 Subject: [PATCH 200/742] Prevent duplicate initialization of SimulacronRule Because of hack in ClusterRule to ensure it's dependent rule has before called on it (via setUp()) before starting itself, before() can get called multiple times on SimulacronRule, protected duplicate set up of registration in this case. --- .../driver/api/testinfra/simulacron/SimulacronRule.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index f41c3135201..156c4d90c21 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -25,6 +25,7 @@ import com.datastax.oss.simulacron.server.Server; import java.net.InetSocketAddress; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; public class SimulacronRule extends CassandraResourceRule { @@ -35,6 +36,8 @@ public class SimulacronRule extends CassandraResourceRule { private final ClusterSpec clusterSpec; private BoundCluster boundCluster; + private final AtomicBoolean started = new AtomicBoolean(); + public SimulacronRule(ClusterSpec clusterSpec) { this.clusterSpec = clusterSpec; } @@ -58,7 +61,10 @@ public BoundCluster getBoundCluster() { @Override protected void before() { - boundCluster = server.register(clusterSpec); + // prevent duplicate initialization of rule + if (started.compareAndSet(false, true)) { + boundCluster = server.register(clusterSpec); + } } @Override From 5e597444ecf40b7c797500dab75296f50611379d Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 9 Aug 2017 10:08:46 -0700 Subject: [PATCH 201/742] Simplify signature of RequestProcessor#canProcess --- .../oss/driver/internal/core/cql/CqlPrepareProcessor.java | 2 +- .../oss/driver/internal/core/cql/CqlRequestProcessor.java | 2 +- .../oss/driver/internal/core/session/RequestProcessor.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java index b0360c1c32f..9eca6beaa3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java @@ -45,7 +45,7 @@ public class CqlPrepareProcessor new MapMaker().weakValues().makeMap(); @Override - public > boolean canProcess(T request) { + public boolean canProcess(Request request) { return request instanceof PrepareRequest; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java index 7bf44e8cc79..a4c2dd5d3b6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java @@ -29,7 +29,7 @@ public class CqlRequestProcessor implements RequestProcessor> { @Override - public > boolean canProcess(T request) { + public boolean canProcess(Request request) { return request instanceof Statement; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index 98d0957eccd..62ec0aab5dc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -37,7 +37,7 @@ public interface RequestProcessor { *

      Processors will be tried in the order they were registered. The first processor for which * this method returns true will be used. */ - > boolean canProcess(T request); + boolean canProcess(Request request); /** Builds a new handler to process a given request. */ RequestHandler newHandler( From 886668616c92e04e2255ebc06cc209136101ff3a Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 10 Aug 2017 14:27:13 -0700 Subject: [PATCH 202/742] Extract ProtocolVersionRegistry interface, add method to find cluster's optimal version --- .../UnsupportedProtocolVersionException.java | 2 +- .../CassandraProtocolVersionRegistry.java | 188 ++++++++++++++++++ .../core/ProtocolVersionRegistry.java | 108 ++++------ .../core/context/DefaultDriverContext.java | 3 +- ...tocolVersionRegistryHighestCommonTest.java | 106 ++++++++++ ...CassandraProtocolVersionRegistryTest.java} | 25 ++- .../core/channel/ProtocolInitHandlerTest.java | 4 +- 7 files changed, 355 insertions(+), 81 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java rename core/src/test/java/com/datastax/oss/driver/internal/core/{ProtocolVersionRegistryTest.java => CassandraProtocolVersionRegistryTest.java} (78%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 512cdfe6b9b..cfd5616590a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -52,7 +52,7 @@ public static UnsupportedProtocolVersionException forNegotiation( address, message, ImmutableList.copyOf(attemptedVersions)); } - private UnsupportedProtocolVersionException( + public UnsupportedProtocolVersionException( SocketAddress address, String message, List attemptedVersions) { super(message, null, true); this.address = address; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java new file mode 100644 index 00000000000..aa8e98f0a14 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Built-in implementation of the protocol version registry, that supports the protocol versions of + * Apache Cassandra. + * + *

      + * + *

      This can be overridden with a custom implementation by subclassing {@link + * DefaultDriverContext}. + * + * @see CoreProtocolVersion + */ +public class CassandraProtocolVersionRegistry implements ProtocolVersionRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(CassandraProtocolVersionRegistry.class); + + private static final CassandraVersion CASSANDRA_210 = CassandraVersion.parse("2.1.0"); + private static final CassandraVersion CASSANDRA_220 = CassandraVersion.parse("2.2.0"); + + private final String logPrefix; + private final NavigableMap versionsByCode; + + public CassandraProtocolVersionRegistry(String logPrefix) { + this(logPrefix, CoreProtocolVersion.values()); + } + + protected CassandraProtocolVersionRegistry(String logPrefix, ProtocolVersion[]... versionRanges) { + this.logPrefix = logPrefix; + this.versionsByCode = byCode(versionRanges); + } + + @Override + public ProtocolVersion fromCode(int code) { + ProtocolVersion protocolVersion = versionsByCode.get(code); + if (protocolVersion == null) { + throw new IllegalArgumentException("Unknown protocol version code: " + code); + } + return protocolVersion; + } + + @Override + public ProtocolVersion fromName(String name) { + for (ProtocolVersion version : versionsByCode.values()) { + if (version.name().equals(name)) { + return version; + } + } + throw new IllegalArgumentException("Unknown protocol version name: " + name); + } + + @Override + public ProtocolVersion highestNonBeta() { + ProtocolVersion highest = versionsByCode.lastEntry().getValue(); + if (!highest.isBeta()) { + return highest; + } else { + return downgrade(highest) + .orElseThrow(() -> new AssertionError("There should be at least one non-beta version")); + } + } + + @Override + public Optional downgrade(ProtocolVersion version) { + Map.Entry previousEntry = + versionsByCode.lowerEntry(version.getCode()); + if (previousEntry == null) { + return Optional.empty(); + } else { + ProtocolVersion previousVersion = previousEntry.getValue(); + // Beta versions are skipped during negotiation + return (previousVersion.isBeta()) ? downgrade(previousVersion) : Optional.of(previousVersion); + } + } + + @Override + public ProtocolVersion highestCommon(Collection nodes) { + if (nodes == null || nodes.isEmpty()) { + throw new IllegalArgumentException("Expected at least one node"); + } + + SortedSet candidates = new TreeSet<>(); + + for (CoreProtocolVersion version : CoreProtocolVersion.values()) { + // Beta versions always need to be forced, and we only call this method if the version + // wasn't forced + if (!version.isBeta()) { + candidates.add(version); + } + } + + // The C*<=>protocol mapping is hardcoded in the code below, I don't see a need to be more + // sophisticated right now. + for (Node node : nodes) { + CassandraVersion cassandraVersion = node.getCassandraVersion(); + if (cassandraVersion == null) { + LOG.warn( + "[{}] Node {} reports null Cassandra version, " + + "ignoring it from optimal protocol version computation", + logPrefix, + node.getConnectAddress()); + continue; + } + cassandraVersion = cassandraVersion.nextStable(); + if (cassandraVersion.compareTo(CASSANDRA_210) < 0) { + throw new UnsupportedProtocolVersionException( + node.getConnectAddress(), + String.format( + "Node %s reports Cassandra version %s, " + + "but the driver only supports 2.1.0 and above", + node.getConnectAddress(), cassandraVersion), + ImmutableList.of(CoreProtocolVersion.V3, CoreProtocolVersion.V4)); + } + + LOG.debug( + "[{}] Node {} reports Cassandra version {}", + logPrefix, + node.getConnectAddress(), + cassandraVersion); + if (cassandraVersion.compareTo(CASSANDRA_220) < 0 + && candidates.remove(CoreProtocolVersion.V4)) { + LOG.debug("[{}] Excluding protocol V4", logPrefix); + } + } + + if (candidates.isEmpty()) { + // Note: with the current algorithm, this never happens + throw new UnsupportedProtocolVersionException( + null, + String.format( + "Could not determine a common protocol version, " + + "enable DEBUG logs for '%s' for more details", + LOG.getName()), + ImmutableList.of(CoreProtocolVersion.V3, CoreProtocolVersion.V4)); + } else { + return candidates.last(); + } + } + + private NavigableMap byCode(ProtocolVersion[][] versionRanges) { + NavigableMap map = new TreeMap<>(); + for (ProtocolVersion[] versionRange : versionRanges) { + for (ProtocolVersion version : versionRange) { + ProtocolVersion previous = map.put(version.getCode(), version); + Preconditions.checkArgument( + previous == null, + "Duplicate version code: %s in %s and %s", + version.getCode(), + previous, + version); + } + } + return map; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java index 486974f4497..d55120f7045 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java @@ -15,85 +15,55 @@ */ package com.datastax.oss.driver.internal.core; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.google.common.base.Preconditions; -import java.util.Map; -import java.util.NavigableMap; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import java.util.Collection; import java.util.Optional; -import java.util.TreeMap; -/** Manages all the native protocol versions supported by the driver. */ -public class ProtocolVersionRegistry { - private final NavigableMap versionsByCode; +/** Defines which native protocol versions are supported by a driver instance. */ +public interface ProtocolVersionRegistry { - public ProtocolVersionRegistry(ProtocolVersion[]... versionRanges) { - this.versionsByCode = byCode(versionRanges); - } - - /** Default implementation, initialized with the core OSS versions. */ - public ProtocolVersionRegistry() { - this(CoreProtocolVersion.values()); - } - - public ProtocolVersion fromCode(int code) { - ProtocolVersion protocolVersion = versionsByCode.get(code); - if (protocolVersion == null) { - throw new IllegalArgumentException("Unknown protocol version code: " + code); - } - return protocolVersion; - } + /** + * Look up a version by its {@link ProtocolVersion#getCode()} code}. + * + * @throws IllegalArgumentException if there is no known version with this code. + */ + ProtocolVersion fromCode(int code); - public ProtocolVersion fromName(String name) { - for (ProtocolVersion version : versionsByCode.values()) { - if (version.name().equals(name)) { - return version; - } - } - throw new IllegalArgumentException("Unknown protocol version name: " + name); - } + /** + * Look up a version by its {@link ProtocolVersion#name() name}. This is used when a version was + * forced in the configuration. + * + * @throws IllegalArgumentException if there is no known version with this name. + * @see CoreDriverOption#PROTOCOL_VERSION + */ + ProtocolVersion fromName(String name); - public ProtocolVersion highestNonBeta() { - ProtocolVersion highest = versionsByCode.lastEntry().getValue(); - if (!highest.isBeta()) { - return highest; - } else { - return downgrade(highest) - .orElseThrow(() -> new AssertionError("There should be at least one non-beta version")); - } - } + /** + * The highest, non-beta version supported by the driver. This is used as the starting point for + * the negotiation process for the initial connection (if the version wasn't forced). + */ + ProtocolVersion highestNonBeta(); /** * Downgrade to a lower version if the current version is not supported by the server. This is - * used during the protocol negotiation process. + * used during the negotiation process for the initial connection (if the version wasn't forced). * - * @return an empty optional if there is no version to downgrade to. + * @return empty if there is no version to downgrade to. */ - public Optional downgrade(ProtocolVersion version) { - Map.Entry previousEntry = - versionsByCode.lowerEntry(version.getCode()); - if (previousEntry == null) { - return Optional.empty(); - } else { - ProtocolVersion previousVersion = previousEntry.getValue(); - // Beta versions are skipped during negotiation - return (previousVersion.isBeta()) ? downgrade(previousVersion) : Optional.of(previousVersion); - } - } + Optional downgrade(ProtocolVersion version); - private NavigableMap byCode(ProtocolVersion[][] versionRanges) { - NavigableMap map = new TreeMap<>(); - for (ProtocolVersion[] versionRange : versionRanges) { - for (ProtocolVersion version : versionRange) { - ProtocolVersion previous = map.put(version.getCode(), version); - Preconditions.checkArgument( - previous == null, - "Duplicate version code: %s in %s and %s", - version.getCode(), - previous, - version); - } - } - return map; - } + /** + * Computes the highest common version supported by the given nodes. This is called after the + * initial {@link TopologyMonitor#refreshNodeList()} node refresh} (provided that the version was + * not forced), to ensure that we proceed with a version that will work with all the nodes. + * + * @throws UnsupportedProtocolVersionException if no such version exists (the nodes support + * non-intersecting ranges), or if there was an error during the computation. This will cause + * the driver initialization to fail. + */ + ProtocolVersion highestCommon(Collection nodes); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 8e82103cc18..4aa9677ab0b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; @@ -219,7 +220,7 @@ protected FrameCodec buildFrameCodec() { } protected ProtocolVersionRegistry buildProtocolVersionRegistry() { - return new ProtocolVersionRegistry(); + return new CassandraProtocolVersionRegistry(clusterName()); } protected NettyOptions buildNettyOptions() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java new file mode 100644 index 00000000000..62c01081ad8 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Collections; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +/** + * Covers {@link CassandraProtocolVersionRegistry#highestCommon(Collection)} separately, because it + * relies explicitly on {@link CoreProtocolVersion} as the version implementation. + */ +public class CassandraProtocolVersionRegistryHighestCommonTest { + + private CassandraProtocolVersionRegistry registry = new CassandraProtocolVersionRegistry("test"); + + @Test + public void should_pick_v3_when_at_least_one_node_is_2_1() { + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode("2.2.1"), mockNode("2.1.0"), mockNode("3.1.9")))) + .isEqualTo(CoreProtocolVersion.V3); + } + + @Test + public void should_pick_v4_when_all_nodes_are_2_2_or_more() { + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode("2.2.0"), mockNode("2.2.1"), mockNode("3.1.9")))) + .isEqualTo(CoreProtocolVersion.V4); + } + + @Test + public void should_treat_rcs_as_next_stable_versions() { + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode("2.2.1"), mockNode("2.1.0-rc1"), mockNode("3.1.9")))) + .isEqualTo(CoreProtocolVersion.V3); + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode("2.2.0-rc2"), mockNode("2.2.1"), mockNode("3.1.9")))) + .isEqualTo(CoreProtocolVersion.V4); + } + + @Test + public void should_skip_nodes_that_report_null_version() { + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode(null), mockNode("2.1.0"), mockNode("3.1.9")))) + .isEqualTo(CoreProtocolVersion.V3); + + // Edge case: if all do, go with the latest version + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode(null), mockNode(null), mockNode(null)))) + .isEqualTo(CoreProtocolVersion.V4); + } + + @Test + public void should_use_v4_for_future_cassandra_versions() { + // That might change in the future when some C* versions drop v4 support + assertThat( + registry.highestCommon( + ImmutableList.of(mockNode("3.0.0"), mockNode("12.1.5"), mockNode("98.7.22")))) + .isEqualTo(CoreProtocolVersion.V4); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_if_no_nodes() { + registry.highestCommon(Collections.emptyList()); + } + + private Node mockNode(String cassandraVersion) { + Node node = Mockito.mock(Node.class); + if (cassandraVersion != null) { + Mockito.when(node.getCassandraVersion()).thenReturn(CassandraVersion.parse(cassandraVersion)); + } + return node; + } + + @Test(expected = UnsupportedProtocolVersionException.class) + public void should_fail_if_pre_2_1_node() { + registry.highestCommon(ImmutableList.of(mockNode("3.0.0"), mockNode("2.0.9"))); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java similarity index 78% rename from core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java index bf915821c0c..17f23aa5200 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java @@ -23,7 +23,11 @@ import static com.datastax.oss.driver.Assertions.assertThat; -public class ProtocolVersionRegistryTest { +/** + * Covers the method that are agnostic to the actual {@link ProtocolVersion} implementation (using a + * mock implementation). + */ +public class CassandraProtocolVersionRegistryTest { private static ProtocolVersion V3 = new MockProtocolVersion(3, false); private static ProtocolVersion V4 = new MockProtocolVersion(4, false); @@ -38,26 +42,29 @@ public class ProtocolVersionRegistryTest { public void should_fail_if_duplicate_version_code() { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Duplicate version code: 5 in V5 and V5_BETA"); - new ProtocolVersionRegistry(new ProtocolVersion[] {V5, V5_BETA}); + new CassandraProtocolVersionRegistry("test", new ProtocolVersion[] {V5, V5_BETA}); } @Test public void should_find_version_by_name() { - ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + ProtocolVersionRegistry versions = + new CassandraProtocolVersionRegistry("test", new ProtocolVersion[] {V3, V4}); assertThat(versions.fromName("V3")).isEqualTo(V3); assertThat(versions.fromName("V4")).isEqualTo(V4); } @Test public void should_downgrade_if_lower_version_available() { - ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + ProtocolVersionRegistry versions = + new CassandraProtocolVersionRegistry("test", new ProtocolVersion[] {V3, V4}); Optional downgraded = versions.downgrade(V4); downgraded.map(version -> assertThat(version).isEqualTo(V3)).orElseThrow(AssertionError::new); } @Test public void should_not_downgrade_if_no_lower_version() { - ProtocolVersionRegistry versions = new ProtocolVersionRegistry(new ProtocolVersion[] {V3, V4}); + ProtocolVersionRegistry versions = + new CassandraProtocolVersionRegistry("test", new ProtocolVersion[] {V3, V4}); Optional downgraded = versions.downgrade(V3); assertThat(downgraded.isPresent()).isFalse(); } @@ -65,8 +72,8 @@ public void should_not_downgrade_if_no_lower_version() { @Test public void should_downgrade_across_version_range() { ProtocolVersionRegistry versions = - new ProtocolVersionRegistry( - new ProtocolVersion[] {V3, V4}, new ProtocolVersion[] {V10, V11}); + new CassandraProtocolVersionRegistry( + "test", new ProtocolVersion[] {V3, V4}, new ProtocolVersion[] {V10, V11}); Optional downgraded = versions.downgrade(V10); downgraded.map(version -> assertThat(version).isEqualTo(V4)).orElseThrow(AssertionError::new); } @@ -74,8 +81,8 @@ public void should_downgrade_across_version_range() { @Test public void should_downgrade_skipping_beta_version() { ProtocolVersionRegistry versions = - new ProtocolVersionRegistry( - new ProtocolVersion[] {V4, V5_BETA}, new ProtocolVersion[] {V10, V11}); + new CassandraProtocolVersionRegistry( + "test", new ProtocolVersion[] {V4, V5_BETA}, new ProtocolVersion[] {V10, V11}); Optional downgraded = versions.downgrade(V10); downgraded.map(version -> assertThat(version).isEqualTo(V4)).orElseThrow(AssertionError::new); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index cba85aae095..660fc8db55c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -61,7 +62,8 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; - private ProtocolVersionRegistry protocolVersionRegistry = new ProtocolVersionRegistry(); + private ProtocolVersionRegistry protocolVersionRegistry = + new CassandraProtocolVersionRegistry("test"); private HeartbeatHandler heartbeatHandler; @Before From c9225c7f66aed151b4fdf6cab068c3d8bedf5295 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 10 Aug 2017 15:28:44 -0700 Subject: [PATCH 203/742] JAVA-1295: Auto-detect best protocol version in mixed cluster --- changelog/README.md | 1 + .../driver/internal/core/DefaultCluster.java | 66 +++++-- .../internal/core/channel/ChannelFactory.java | 14 +- .../core/context/DefaultDriverContext.java | 2 +- .../core/control/ControlConnection.java | 16 +- .../core/metadata/MetadataManager.java | 12 +- .../core/metadata/SchemaElementKind.java | 3 + core/src/main/resources/reference.conf | 23 ++- ... ProtocolVersionInitialNegotiationIT.java} | 5 +- .../core/ProtocolVersionMixedClusterIT.java | 172 ++++++++++++++++++ pom.xml | 2 +- .../testinfra/simulacron/SimulacronRule.java | 2 +- 12 files changed, 285 insertions(+), 33 deletions(-) rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/{ProtocolVersionIT.java => ProtocolVersionInitialNegotiationIT.java} (93%) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java diff --git a/changelog/README.md b/changelog/README.md index bf286ce6760..fc55b03ac75 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1295: Auto-detect best protocol version in mixed cluster - [bug] JAVA-1565: Mark node down when it loses its last connection and was already reconnecting - [bug] JAVA-1594: Don't create pool if node comes back up but is ignored - [bug] JAVA-1593: Reconnect control connection if current node is removed, forced down or ignored diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 2bd6ea2808b..13c036f9517 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -18,12 +18,16 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; +import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; @@ -145,18 +149,7 @@ private void init() { .addContactPoints(initialContactPoints) .thenCompose(v -> context.topologyMonitor().init()) .thenCompose(v -> metadataManager.refreshNodes()) - .thenAccept( - v -> { - try { - context.loadBalancingPolicyWrapper().init(); - context.configLoader().onDriverInit(context); - LOG.debug("[{}] Initialization complete, ready", logPrefix); - initFuture.complete(DefaultCluster.this); - // TODO schedule full schema refresh asynchronously (does not block init) - } catch (Throwable throwable) { - initFuture.completeExceptionally(throwable); - } - }) + .thenAccept(this::afterInitialNodeListRefresh) .exceptionally( error -> { initFuture.completeExceptionally(error); @@ -164,6 +157,55 @@ private void init() { }); } + private void afterInitialNodeListRefresh(@SuppressWarnings("unused") Void ignored) { + try { + boolean protocolWasForced = + context.config().getDefaultProfile().isDefined(CoreDriverOption.PROTOCOL_VERSION); + boolean needSchemaRefresh = true; + if (!protocolWasForced) { + ProtocolVersion currentVersion = context.protocolVersion(); + ProtocolVersion bestVersion = + context + .protocolVersionRegistry() + .highestCommon(metadataManager.getMetadata().getNodes().values()); + if (!currentVersion.equals(bestVersion)) { + LOG.info( + "[{}] Negotiated protocol version {} for the initial contact point, " + + "but other nodes only support {}, downgrading", + logPrefix, + currentVersion, + bestVersion); + context.channelFactory().setProtocolVersion(bestVersion); + ControlConnection controlConnection = context.controlConnection(); + // Might not have initialized yet if there is a custom TopologyMonitor + if (controlConnection.isInit()) { + controlConnection.reconnectNow(); + // Reconnection already triggers a full schema refresh + needSchemaRefresh = false; + } + } + } + if (needSchemaRefresh) { + metadataManager.refreshSchema(SchemaElementKind.WHOLE_SCHEMA, null, null, null); + } + metadataManager.firstSchemaRefreshFuture().thenAccept(this::afterInitialSchemaRefresh); + + } catch (Throwable throwable) { + initFuture.completeExceptionally(throwable); + } + } + + private void afterInitialSchemaRefresh(@SuppressWarnings("unused") Void ignored) { + try { + context.loadBalancingPolicyWrapper().init(); + context.configLoader().onDriverInit(context); + LOG.debug("[{}] Initialization complete, ready", logPrefix); + initFuture.complete(DefaultCluster.this); + } catch (Throwable throwable) { + initFuture.completeExceptionally(throwable); + } + } + private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index b43c73baac4..4fece48eb59 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -62,13 +62,25 @@ public ChannelFactory(InternalDriverContext context) { } // else it will be negotiated with the first opened connection } - public ProtocolVersion protocolVersion() { + public ProtocolVersion getProtocolVersion() { ProtocolVersion result = this.protocolVersion; Preconditions.checkState( result != null, "Protocol version not known yet, this should only be called after init"); return result; } + /** + * WARNING: this is only used at the very beginning of the init process (when we just refreshed + * the list of nodes for the first time, and found out that one of them requires a lower version + * than was negotiated with the first contact point); it's safe at this time because we are in a + * controlled state (only the control connection is open, it's not executing queries and we're + * going to reconnect immediately after). Calling this method at any other time will likely wreak + * havoc. + */ + public void setProtocolVersion(ProtocolVersion newVersion) { + this.protocolVersion = newVersion; + } + public CompletionStage connect( final SocketAddress address, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 4aa9677ab0b..8758d0072d8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -412,6 +412,6 @@ public CodecRegistry codecRegistry() { @Override public ProtocolVersion protocolVersion() { - return channelFactory().protocolVersion(); + return channelFactory().getProtocolVersion(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 38c0ecc50e6..07007d53948 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -56,10 +56,14 @@ * Maintains a dedicated connection to a Cassandra node for administrative queries: schema * refreshes, and cluster topology queries and events. * + *

      + * *

      If the control node goes down, a reconnection is triggered. The control node is chosen * randomly among the contact points at startup, or according to the load balancing policy for later * reconnections. * + *

      + * *

      If a custom {@link TopologyMonitor} is used, the control connection is used only for schema * refreshes; if schema metadata is also disabled, the control connection never initializes. */ @@ -92,6 +96,10 @@ public CompletionStage init(boolean listenToClusterEvents) { return singleThreaded.initFuture; } + public boolean isInit() { + return singleThreaded.initFuture.isDone(); + } + /** * The channel currently used by this control connection. This is modified concurrently in the * event of a reconnection, so it may occasionally return a closed channel (clients should be @@ -352,16 +360,12 @@ private void onSuccessfulReconnect() { try { // This does nothing if the LBP is initialized already context.loadBalancingPolicyWrapper().init(); + context.metadataManager().refreshSchema(null, null, null, null); } catch (Throwable t) { - LOG.warn( - "[{}] Unexpected error while initializing load balancing policy", - logPrefix, - t); + LOG.warn("[{}] Unexpected error on control connection reconnect", logPrefix, t); } } }); - - // TODO refresh schema metadata } private void onChannelClosed(DriverChannel channel, Node node) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 5639eac903d..ba7e282ab4e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -117,7 +117,16 @@ public void removeNode(InetSocketAddress address) { public void refreshSchema( SchemaElementKind kind, String keyspace, String object, List arguments) { - // TODO refresh schema metadata + // TODO refresh schema metadata, only complete the future once it's done + singleThreaded.firstSchemaRefreshFuture.complete(null); + } + + /** + * Returns a future that completes after the first schema refresh attempt, whether that attempt + * succeeded or not (we wait for that refresh at init, but if it fails it's not fatal). + */ + public CompletionStage firstSchemaRefreshFuture() { + return singleThreaded.firstSchemaRefreshFuture; } // TODO user-controlled refresh? @@ -141,6 +150,7 @@ public CompletionStage forceCloseAsync() { private class SingleThreaded { private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; + private final CompletableFuture firstSchemaRefreshFuture = new CompletableFuture<>(); private void initNodes( Set addresses, CompletableFuture initNodesFuture) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java index 3ed9ee396d4..acff6cbaaab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java @@ -21,6 +21,9 @@ /** The different kinds of objects in a schema. */ public enum SchemaElementKind { + WHOLE_SCHEMA( + // Dummy placeholder, this kind never comes from the server, only internally + "WHOLE_SCHEMA"), KEYSPACE(ProtocolConstants.SchemaChangeTarget.KEYSPACE), TABLE(ProtocolConstants.SchemaChangeTarget.TABLE), TYPE(ProtocolConstants.SchemaChangeTarget.TYPE), diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 2d487d475d1..93b2ce73a32 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -32,14 +32,21 @@ datastax-java-driver { protocol { # The native protocol version to use. # - # This option is not required. If it is absent, the driver will negotiate it with the *first* - # node it tries to connect to. More precisely, it will try with the highest supported version, - # and if not supported fallback to the second highest and so on. - # Once the version is set, it will be used for the lifetime of the driver instance. - # Auto detection can be problematic with mixed-version clusters: if the driver connects first - # to one of the higher-version nodes, it will negotiate a version that might not work when - # connecting to lower-version nodes later. You should force the lowest common protocol version - # in that case. + # This option is not required. If it is absent, the driver looks up the versions of the nodes at + # startup (by default in system.peers.release_version), and chooses the highest common protocol + # version. For example, if you have a mixed cluster with Apache Cassandra 2.1 nodes (protocol + # v3) and Apache Cassandra 3.0 nodes (protocol v3 and v4), then protocol v3 is chosen. If the + # nodes don't have a common protocol version, initialization fails. + # + # If this option is set, then the given version will be used for all connections, without any + # negotiation or downgrading. If any of the contact points doesn't support it, it will be + # skipped. + # + # Once the protocol version is set, it can't change for the rest of the driver's lifetime; if an + # incompatible node joins the cluster later, connection will fail and the driver will force it + # down (i.e. never try to connect to it again). + # + # You can check the actual version at runtime with Cluster.getContext().protocolVersion(). // version = V4 # The maximum length of the frames supported by the driver. Beyond that limit, requests will diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java similarity index 93% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 28352d59af8..b9fb40627d4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -24,7 +24,8 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ProtocolVersionIT { +/** Covers protocol negotiation for the initial connection to the first contact point. */ +public class ProtocolVersionInitialNegotiationIT { @Rule public CcmRule ccm = CcmRule.getInstance(); @@ -66,7 +67,7 @@ public void should_fail_if_provided_version_isnt_supported() { @CassandraRequirement(min = "2.2", description = "required to meet default protocol version") @Test - public void should_not_downgrade() { + public void should_not_downgrade_if_server_supports_latest_version() { try (Cluster v4cluster = ClusterUtils.newCluster(ccm)) { assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(4); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java new file mode 100644 index 00000000000..7ba204e94e6 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.cluster.DataCenterSpec; +import com.datastax.oss.simulacron.common.cluster.QueryLog; +import com.datastax.oss.simulacron.server.BoundCluster; +import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.BoundTopic; +import java.net.InetSocketAddress; +import java.util.stream.Stream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.datastax.oss.driver.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * Covers protocol re-negotiation with a mixed cluster: if, after the initial connection and the + * first node list refresh, we find out that some nodes only support a lower version, reconnect the + * control connection immediately. + */ +public class ProtocolVersionMixedClusterIT { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_downgrade_if_peer_does_not_support_negotiated_version() { + try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.1.0"); + BoundNode contactPoint = simulacron.node(0); + Cluster cluster = + Cluster.builder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(new TestConfigLoader()) + .build()) { + + InternalDriverContext context = (InternalDriverContext) cluster.getContext(); + assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V3); + + // Find out which node became the control node after the reconnection (not necessarily node 0) + InetSocketAddress controlAddress = + (InetSocketAddress) context.controlConnection().channel().remoteAddress(); + BoundNode currentControlNode = null; + for (BoundNode node : simulacron.getNodes()) { + if (node.inetSocketAddress().equals(controlAddress)) { + currentControlNode = node; + } + } + assertThat(currentControlNode).isNotNull(); + assertThat(queries(simulacron)).hasSize(6); + + assertThat(protocolQueries(contactPoint, 4)) + .containsExactly( + // Initial connection with protocol v4 + "SELECT cluster_name FROM system.local", + "SELECT * FROM system.local", + "SELECT * FROM system.peers"); + assertThat(protocolQueries(currentControlNode, 3)) + .containsExactly( + // Reconnection with protocol v3 + "SELECT cluster_name FROM system.local", + "SELECT * FROM system.local", + "SELECT * FROM system.peers"); + } + } + + @Test + public void should_keep_current_if_supported_by_all_peers() { + try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "3.11"); + BoundNode contactPoint = simulacron.node(0); + Cluster cluster = + Cluster.builder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(new TestConfigLoader()) + .build()) { + + InternalDriverContext context = (InternalDriverContext) cluster.getContext(); + assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V4); + assertThat(queries(simulacron)).hasSize(3); + assertThat(protocolQueries(contactPoint, 4)) + .containsExactly( + // Initial connection with protocol v4 + "SELECT cluster_name FROM system.local", + "SELECT * FROM system.local", + "SELECT * FROM system.peers"); + } + } + + @Test + public void should_fail_if_peer_does_not_support_v3() { + thrown.expect(UnsupportedProtocolVersionException.class); + thrown.expectMessage( + "reports Cassandra version 2.0.9, but the driver only supports 2.1.0 and above"); + + try (BoundCluster simulacron = mixedVersions("3.0.0", "2.0.9", "3.11"); + BoundNode contactPoint = simulacron.node(0); + Cluster ignored = + Cluster.builder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(new TestConfigLoader()) + .build()) { + fail("Cluster init should have failed"); + } + } + + @Test + public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { + try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.0.0"); + BoundNode contactPoint = simulacron.node(0); + Cluster cluster = + Cluster.builder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(new TestConfigLoader("protocol.version = V4")) + .build()) { + assertThat(cluster.getContext().protocolVersion()).isEqualTo(CoreProtocolVersion.V4); + + assertThat(queries(simulacron)).hasSize(3); + assertThat(protocolQueries(contactPoint, 4)) + .containsExactly( + // Initial connection with protocol v4 + "SELECT cluster_name FROM system.local", + "SELECT * FROM system.local", + "SELECT * FROM system.peers"); + + // Note: the 2.0.0 would be forced down if we try to open a connection to it. We can't check + // that here because Simulacron can't prime STARTUP requests. + } + } + + private BoundCluster mixedVersions(String... versions) { + ClusterSpec clusterSpec = ClusterSpec.builder().withCassandraVersion(versions[0]).build(); + DataCenterSpec dc0 = clusterSpec.addDataCenter().build(); + // inherits versions[0] + dc0.addNode().build(); + for (int i = 1; i < versions.length; i++) { + dc0.addNode().withCassandraVersion(versions[i]).build(); + } + return SimulacronRule.server.register(clusterSpec); + } + + private Stream queries(BoundTopic topic) { + return topic + .getLogs() + .getQueryLogs() + .stream() + .filter(q -> q.getFrame().message instanceof Query); + } + + private Stream protocolQueries(BoundTopic topic, int protocolVersion) { + return queries(topic) + .filter(q -> q.getFrame().protocolVersion == protocolVersion) + .map(QueryLog::getQuery); + } +} diff --git a/pom.xml b/pom.xml index 6767534a7b8..8fcc2826106 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ com.datastax.oss.simulacron simulacron-native-server - 0.5.0 + 0.5.2 org.apache.commons diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index 156c4d90c21..a563b655304 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -30,7 +30,7 @@ public class SimulacronRule extends CassandraResourceRule { // TODO perhaps share server some other way - private static final Server server = + public static final Server server = Server.builder().withAddressResolver(new AddressResolver.Inet4Resolver(9043)).build(); private final ClusterSpec clusterSpec; From e2d07e99265c903dcfc7b3a3c85ba63585f9cafe Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 8 Aug 2017 15:07:13 -0500 Subject: [PATCH 204/742] JAVA-1542: Enable JaCoCo code coverage --- build.yaml | 1 + changelog/README.md | 1 + pom.xml | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/build.yaml b/build.yaml index e367e41e085..4b2cc90c249 100644 --- a/build.yaml +++ b/build.yaml @@ -16,4 +16,5 @@ build: - xunit: - "**/target/surefire-reports/TEST-*.xml" - "**/target/failsafe-reports/TEST-*.xml" + - jacoco: true disable_commit_status: true diff --git a/changelog/README.md b/changelog/README.md index fc55b03ac75..c94caeb2318 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1542: Enable JaCoCo code coverage - [improvement] JAVA-1295: Auto-detect best protocol version in mixed cluster - [bug] JAVA-1565: Mark node down when it loses its last connection and was already reconnecting - [bug] JAVA-1594: Don't create pool if node comes back up but is ignored diff --git a/pom.xml b/pom.xml index 8fcc2826106..defd5ddb8b6 100644 --- a/pom.xml +++ b/pom.xml @@ -186,6 +186,11 @@ maven-deploy-plugin 2.7 + + org.jacoco + jacoco-maven-plugin + 0.7.9 + @@ -259,6 +264,24 @@ limitations under the License.]]> + + org.jacoco + jacoco-maven-plugin + + + + prepare-agent + + + + report + prepare-package + + report + + + + maven-surefire-plugin From 864fe0b2cf31ada7338df6ee363e97fc88267005 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 17 Aug 2017 16:17:15 -0700 Subject: [PATCH 205/742] Fix minor formatting issues --- .../internal/core/CassandraProtocolVersionRegistry.java | 2 -- .../oss/driver/internal/core/control/ControlConnection.java | 4 ---- 2 files changed, 6 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index aa8e98f0a14..7fd8637a916 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -37,8 +37,6 @@ * Built-in implementation of the protocol version registry, that supports the protocol versions of * Apache Cassandra. * - *

      - * *

      This can be overridden with a custom implementation by subclassing {@link * DefaultDriverContext}. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 07007d53948..29eabe4d39a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -56,14 +56,10 @@ * Maintains a dedicated connection to a Cassandra node for administrative queries: schema * refreshes, and cluster topology queries and events. * - *

      - * *

      If the control node goes down, a reconnection is triggered. The control node is chosen * randomly among the contact points at startup, or according to the load balancing policy for later * reconnections. * - *

      - * *

      If a custom {@link TopologyMonitor} is used, the control connection is used only for schema * refreshes; if schema metadata is also disabled, the control connection never initializes. */ From 0f05296b41e3b9d6afa78356d08311aeb35df0a6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 28 Sep 2017 08:15:49 -0700 Subject: [PATCH 206/742] JAVA-1597: Fix raw usages of Statement --- changelog/README.md | 1 + .../datastax/oss/driver/api/core/cql/ExecutionInfo.java | 2 +- .../oss/driver/internal/core/cql/Conversions.java | 4 ++-- .../oss/driver/internal/core/cql/CqlRequestHandler.java | 4 ++-- .../oss/driver/internal/core/cql/CqlRequestProcessor.java | 2 +- .../driver/internal/core/cql/DefaultAsyncResultSet.java | 4 ++-- .../driver/internal/core/cql/DefaultExecutionInfo.java | 6 +++--- .../internal/core/cql/DefaultAsyncResultSetTest.java | 8 ++++---- .../oss/driver/api/core/cql/BatchStatementIT.java | 4 ++-- .../oss/driver/api/core/cql/SimpleStatementIT.java | 6 +++--- .../com/datastax/oss/driver/api/core/data/DataTypeIT.java | 2 +- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index c94caeb2318..313075cb3d7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1597: Fix raw usages of Statement - [improvement] JAVA-1542: Enable JaCoCo code coverage - [improvement] JAVA-1295: Auto-detect best protocol version in mixed cluster - [bug] JAVA-1565: Mark node down when it loses its last connection and was already reconnecting diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 86e51dbab7a..40702dc804f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -26,7 +26,7 @@ public interface ExecutionInfo { /** The statement that was executed. */ - Statement getStatement(); + Statement getStatement(); /** The node that was used as a coordinator to successfully complete the query. */ Node getCoordinator(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index e528f99c625..a978744b0f9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -87,7 +87,7 @@ class Conversions { static Message toMessage( - Statement statement, DriverConfigProfile config, InternalDriverContext context) { + Statement statement, DriverConfigProfile config, InternalDriverContext context) { int consistency = config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); @@ -202,7 +202,7 @@ static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, Session session, InternalDriverContext context) { if (result instanceof Rows) { Rows rows = (Rows) result; - Statement statement = executionInfo.getStatement(); + Statement statement = executionInfo.getStatement(); ColumnDefinitions columnDefinitions = (statement instanceof BoundStatement) ? ((BoundStatement) statement).getPreparedStatement().getResultSetDefinitions() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index cc55fc8d69c..6f11976f101 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -111,7 +111,7 @@ public class CqlRequestHandler private volatile List> errors; CqlRequestHandler( - Statement statement, + Statement statement, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { @@ -301,7 +301,7 @@ private ExecutionInfo buildExecutionInfo( ByteBuffer pagingState = (resultMessage instanceof Rows) ? ((Rows) resultMessage).getMetadata().pagingState : null; return new DefaultExecutionInfo( - (Statement) request, + (Statement) request, callback.node, startedSpeculativeExecutionsCount.get(), callback.execution, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java index a4c2dd5d3b6..fcf82ca1320 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java @@ -39,6 +39,6 @@ public RequestHandler> newHandler( DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestHandler((Statement) request, session, context, sessionLogPrefix); + return new CqlRequestHandler((Statement) request, session, context, sessionLogPrefix); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 20b901db346..721dd72382f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -94,9 +94,9 @@ public CompletionStage fetchNextPage() throws IllegalStateExcept throw new IllegalStateException( "No next page. Use #hasMorePages before calling this method to avoid this error."); } - Statement statement = executionInfo.getStatement(); + Statement statement = executionInfo.getStatement(); LOG.debug("Fetching next page for {}", statement); - Statement nextStatement = statement.copy(nextState); + Statement nextStatement = statement.copy(nextState); return session.executeAsync(nextStatement); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index 68a58cb2a8c..4c2ecebe328 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -27,7 +27,7 @@ public class DefaultExecutionInfo implements ExecutionInfo { - private final Statement statement; + private final Statement statement; private final Node coordinator; private final int speculativeExecutionCount; private final int successfulExecutionIndex; @@ -38,7 +38,7 @@ public class DefaultExecutionInfo implements ExecutionInfo { private final Map customPayload; public DefaultExecutionInfo( - Statement statement, + Statement statement, Node coordinator, int speculativeExecutionCount, int successfulExecutionIndex, @@ -59,7 +59,7 @@ public DefaultExecutionInfo( } @Override - public Statement getStatement() { + public Statement getStatement() { return statement; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index bd08c8afd01..12e13ff18e7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -46,7 +46,7 @@ public class DefaultAsyncResultSetTest { @Mock private ColumnDefinitions columnDefinitions; @Mock private ExecutionInfo executionInfo; - @Mock private Statement statement; + @Mock private Statement statement; @Mock private Session session; @Mock private InternalDriverContext context; @@ -54,7 +54,7 @@ public class DefaultAsyncResultSetTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(executionInfo.getStatement()).thenReturn(statement); + Mockito.when(executionInfo.getStatement()).thenReturn((Statement) statement); Mockito.when(context.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); Mockito.when(context.protocolVersion()).thenReturn(CoreProtocolVersion.DEFAULT); } @@ -80,8 +80,8 @@ public void should_invoke_session_to_fetch_next_page() { ByteBuffer mockPagingState = ByteBuffer.allocate(0); Mockito.when(executionInfo.getPagingState()).thenReturn(mockPagingState); - Statement mockNextStatement = Mockito.mock(Statement.class); - Mockito.when(statement.copy(mockPagingState)).thenReturn(mockNextStatement); + Statement mockNextStatement = Mockito.mock(Statement.class); + Mockito.when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); CompletableFuture mockResultFuture = new CompletableFuture<>(); Mockito.when(session.executeAsync(Mockito.any(Statement.class))).thenReturn(mockResultFuture); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 710cf28f5f5..71cf327b2be 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -129,7 +129,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { cluster.session().execute(builder2.build()); - Statement select = + Statement select = SimpleStatement.builder("SELECT * from test where k0 = ?") .addPositionalValue(name.getMethodName()) .build(); @@ -313,7 +313,7 @@ public void should_fail_counter_batch_with_non_counter_increment() { private void verifyBatchInsert() { // validate data inserted by the batch. - Statement select = + Statement select = SimpleStatement.builder("SELECT * from test where k0 = ?") .addPositionalValue(name.getMethodName()) .build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 86b17a6821b..d80c2a63cea 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -68,7 +68,7 @@ public static void setupSchema() { @Test public void should_use_paging_state_when_copied() { - Statement st = + Statement st = SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); ResultSet result = cluster.session().execute(st); @@ -84,7 +84,7 @@ public void should_use_paging_state_when_copied() { @Test public void should_use_paging_state_when_provided_to_new_statement() { - Statement st = + Statement st = SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); ResultSet result = cluster.session().execute(st); @@ -104,7 +104,7 @@ public void should_use_paging_state_when_provided_to_new_statement() { @Test @Ignore public void should_fail_if_using_paging_state_from_different_query() { - Statement st = + Statement st = SimpleStatement.builder("SELECT v FROM test WHERE k=:k").addNamedValue("k", KEY).build(); ResultSet result = cluster.session().execute(st); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 0c79c1a9e7c..5bada76e8d7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -606,7 +606,7 @@ private static > S setValue( } private void readValue( - Statement select, DataType dataType, K value, K expectedPrimitiveValue) { + Statement select, DataType dataType, K value, K expectedPrimitiveValue) { TypeCodec codec = cluster.cluster().getContext().codecRegistry().codecFor(dataType); ResultSet result = cluster.session().execute(select); From edbcf78ae4885ab528b00b1411f211f5b7ecc386 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 9 Oct 2017 11:46:41 -0700 Subject: [PATCH 207/742] Remove spurious warning when session creation fails The error is already handled in whenCompleteAsync, no need to warn. --- .../com/datastax/oss/driver/internal/core/DefaultCluster.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 13c036f9517..945d9d8c572 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -229,8 +229,7 @@ private void connect(CqlIdentifier keyspace, CompletableFuture connectF connectFuture.complete(session); } }, - adminExecutor) - .exceptionally(UncaughtExceptions::log); + adminExecutor); } } From 743e583792f4ec42ed98804532838a4df16231ac Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 28 Sep 2017 13:39:50 -0700 Subject: [PATCH 208/742] JAVA-1605: Refactor request execution model Motivation: Request types encapsulate their sync and async result types. This is too restrictive: not all requests will have exactly those two types of results. For example, if we add a CQL reactive processor, it would be nice to reuse the Statement classes, not create new ones. Modifications: Make Request non generic. Change the generic Session execution method to: execute(RequestT request, GenericType resultType) Result: Each request processor now handles exactly one (RequestT, ResponseT) pair, so it is possible to add new response types for existing requests. As a consequence, the number of built-in processor has doubled (sync/async variants for each). --- changelog/README.md | 1 + core/console.scala | 4 +- .../datastax/oss/driver/api/core/Cluster.java | 23 ++- .../oss/driver/api/core/ClusterBuilder.java | 75 +++++++--- .../api/core/DefaultClusterBuilder.java | 28 ++++ .../driver/api/core/cql/PrepareRequest.java | 23 ++- .../oss/driver/api/core/cql/Statement.java | 24 ++- .../driver/api/core/session/CqlSession.java | 93 ++++++++++++ .../oss/driver/api/core/session/Request.java | 5 +- .../oss/driver/api/core/session/Session.java | 138 +++--------------- .../driver/internal/core/ClusterWrapper.java | 71 +++++++++ .../driver/internal/core/DefaultCluster.java | 15 +- .../driver/internal/core/cql/Conversions.java | 7 +- .../core/cql/CqlPrepareAsyncHandler.java | 44 ++++++ .../core/cql/CqlPrepareAsyncProcessor.java | 54 +++++++ ...andler.java => CqlPrepareHandlerBase.java} | 86 +++++++---- .../core/cql/CqlPrepareProcessor.java | 80 ---------- .../core/cql/CqlPrepareSyncHandler.java | 45 ++++++ .../core/cql/CqlPrepareSyncProcessor.java | 53 +++++++ .../core/cql/CqlRequestAsyncHandler.java | 41 ++++++ .../core/cql/CqlRequestAsyncProcessor.java | 45 ++++++ ...andler.java => CqlRequestHandlerBase.java} | 85 ++++++----- .../core/cql/CqlRequestSyncHandler.java | 44 ++++++ ...ssor.java => CqlRequestSyncProcessor.java} | 16 +- .../core/cql/DefaultAsyncResultSet.java | 6 +- .../internal/core/session/DefaultSession.java | 53 ++++--- .../internal/core/session/ReprepareOnUp.java | 4 +- .../internal/core/session/RequestHandler.java | 13 +- .../core/session/RequestHandlerBase.java | 29 +--- .../core/session/RequestProcessor.java | 15 +- .../session/RequestProcessorRegistry.java | 30 +++- .../internal/core/session/SessionWrapper.java | 70 +++++++++ ...onstantSpeculativeExecutionPolicyTest.java | 2 +- .../core/cql/CqlPrepareHandlerTest.java | 82 +++++++---- .../core/cql/CqlRequestHandlerRetryTest.java | 28 ++-- ...equestHandlerSpeculativeExecutionTest.java | 32 ++-- .../core/cql/CqlRequestHandlerTest.java | 21 +-- .../core/cql/DefaultAsyncResultSetTest.java | 4 +- .../core/cql/RequestHandlerTestHarness.java | 8 +- .../core/session/DefaultSessionTest.java | 41 +++--- .../ProtocolVersionInitialNegotiationIT.java | 18 +-- .../core/ProtocolVersionMixedClusterIT.java | 9 +- .../core/auth/PlainTextAuthProviderIT.java | 14 +- .../core/config/DriverConfigProfileIT.java | 22 +-- .../config/DriverConfigProfileReloadIT.java | 21 ++- .../driver/api/core/cql/BoundStatementIT.java | 6 +- .../api/core/heartbeat/HeartbeatIT.java | 8 +- .../driver/api/core/metadata/NodeStateIT.java | 3 +- .../core/specex/SpeculativeExecutionIT.java | 40 ++--- .../core/ssl/DefaultSslEngineFactoryIT.java | 6 +- ...faultSslEngineFactoryWithClientAuthIT.java | 6 +- ...ineFactoryWithClientAuthNotProvidedIT.java | 6 +- ...ineFactoryWithTruststoreNotProvidedIT.java | 6 +- .../type/codec/registry/CodecRegistryIT.java | 10 +- .../api/testinfra/cluster/ClusterRule.java | 10 +- .../api/testinfra/cluster/ClusterUtils.java | 19 +-- 56 files changed, 1157 insertions(+), 585 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{CqlPrepareHandler.java => CqlPrepareHandlerBase.java} (84%) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{CqlRequestHandler.java => CqlRequestHandlerBase.java} (90%) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{CqlRequestProcessor.java => CqlRequestSyncProcessor.java} (69%) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java diff --git a/changelog/README.md b/changelog/README.md index 313075cb3d7..190270b406c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [improvement] JAVA-1605: Refactor request execution model - [improvement] JAVA-1597: Fix raw usages of Statement - [improvement] JAVA-1542: Enable JaCoCo code coverage - [improvement] JAVA-1295: Auto-detect best protocol version in mixed cluster diff --git a/core/console.scala b/core/console.scala index a156b319dfc..1490675b19f 100644 --- a/core/console.scala +++ b/core/console.scala @@ -11,10 +11,10 @@ * Use Ctrl+C instead. */ import com.datastax.oss.driver.api.core._ +import com.datastax.oss.driver.api.core.session.CqlSession import com.datastax.oss.driver.internal.core.metadata.TopologyEvent import com.datastax.oss.driver.internal.core.context.InternalDriverContext import java.net.InetSocketAddress -import scala.collection.JavaConversions._ // Heartbeat logs every 30 seconds are annoying in the console, raise the interval System.setProperty("datastax-java-driver.connection.heartbeat.interval", "1 hour") @@ -33,6 +33,6 @@ println("* To start a driver instance, run: *") println("* implicit val cluster = builder.build *") println("********************************************") -def fire(event: AnyRef)(implicit cluster: Cluster): Unit = { +def fire(event: AnyRef)(implicit cluster: Cluster[CqlSession]): Unit = { cluster.getContext.asInstanceOf[InternalDriverContext].eventBus().fire(event) } \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 112d483d4c3..2aa37118cfa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -19,17 +19,24 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.ResourceBundle; import java.util.concurrent.CompletionStage; -/** An instance of the driver, that connects to a Cassandra cluster. */ -public interface Cluster extends AsyncAutoCloseable { +/** + * An instance of the driver, that connects to a Cassandra cluster. + * + * @param the type of session returned by this cluster. By default, this is {@link + * CqlSession}. + */ +public interface Cluster extends AsyncAutoCloseable { + /** Returns a builder to create a new instance of the default implementation. */ - static ClusterBuilder builder() { - return new ClusterBuilder(); + static DefaultClusterBuilder builder() { + return new DefaultClusterBuilder(); } /** @@ -72,14 +79,14 @@ static String getDriverVersion() { DriverContext getContext(); /** Creates a new session to execute requests against a given keyspace. */ - CompletionStage connectAsync(CqlIdentifier keyspace); + CompletionStage connectAsync(CqlIdentifier keyspace); /** * Creates a new session not tied to any keyspace. * *

      This is equivalent to {@code this.connectAsync(null)}. */ - default CompletionStage connectAsync() { + default CompletionStage connectAsync() { return connectAsync(null); } @@ -88,7 +95,7 @@ default CompletionStage connectAsync() { * *

      This must not be called on a driver thread. */ - default Session connect(CqlIdentifier keyspace) { + default SessionT connect(CqlIdentifier keyspace) { BlockingOperation.checkNotDriverThread(); return CompletableFutures.getUninterruptibly(connectAsync(keyspace)); } @@ -98,7 +105,7 @@ default Session connect(CqlIdentifier keyspace) { * *

      This must not be called on a driver thread. */ - default Session connect() { + default SessionT connect() { return connect(null); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index c37f0c6a693..8a12d8add62 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; @@ -36,11 +38,20 @@ import java.util.concurrent.CompletionStage; import java.util.function.Supplier; -/** Helper class to build an instance of the default {@link Cluster} implementation. */ -public class ClusterBuilder { - private DriverConfigLoader configLoader; - private Set programmaticContactPoints = new HashSet<>(); - private List> typeCodecs = new ArrayList<>(); +/** + * Base implementation to build cluster instances. + * + *

      You only need to deal with this directly if you use custom driver extensions. For the default + * cluster implementation, see {@link Cluster#builder()}. + */ +public abstract class ClusterBuilder { + + @SuppressWarnings("unchecked") + protected final SelfT self = (SelfT) this; + + protected DriverConfigLoader configLoader; + protected Set programmaticContactPoints = new HashSet<>(); + protected List> typeCodecs = new ArrayList<>(); /** * Sets the configuration loader to use. @@ -70,12 +81,12 @@ public class ClusterBuilder { * @see Typesafe config's * standard loading behavior */ - public ClusterBuilder withConfigLoader(DriverConfigLoader configLoader) { + public SelfT withConfigLoader(DriverConfigLoader configLoader) { this.configLoader = configLoader; - return this; + return self; } - private static DriverConfigLoader defaultConfigLoader() { + protected DriverConfigLoader defaultConfigLoader() { return new DefaultDriverConfigLoader(); } @@ -95,9 +106,9 @@ private static DriverConfigLoader defaultConfigLoader() { * If you need that, call {@link java.net.InetAddress#getAllByName(String)} before calling this * method. */ - public ClusterBuilder addContactPoints(Collection contactPoints) { + public SelfT addContactPoints(Collection contactPoints) { this.programmaticContactPoints.addAll(contactPoints); - return this; + return self; } /** @@ -105,15 +116,15 @@ public ClusterBuilder addContactPoints(Collection contactPoin * * @see #addContactPoints(Collection) */ - public ClusterBuilder addContactPoint(InetSocketAddress contactPoint) { + public SelfT addContactPoint(InetSocketAddress contactPoint) { this.programmaticContactPoints.add(contactPoint); - return this; + return self; } /** Registers additional codecs for custom type mappings. */ - public ClusterBuilder addTypeCodecs(TypeCodec... typeCodecs) { + public SelfT addTypeCodecs(TypeCodec... typeCodecs) { Collections.addAll(this.typeCodecs, typeCodecs); - return this; + return self; } /** @@ -121,9 +132,24 @@ public ClusterBuilder addTypeCodecs(TypeCodec... typeCodecs) { * * @return a completion stage that completes with the cluster when it is fully initialized. */ - public CompletionStage buildAsync() { - DriverConfigLoader configLoader = - buildIfNull(this.configLoader, ClusterBuilder::defaultConfigLoader); + public CompletionStage buildAsync() { + return buildDefaultClusterAsync().thenApply(this::wrap); + } + + /** + * Convenience method to call {@link #buildAsync()} and block on the result. + * + *

      This must not be called on a driver thread. + */ + public ClusterT build() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(buildAsync()); + } + + protected abstract ClusterT wrap(Cluster defaultCluster); + + protected final CompletionStage> buildDefaultClusterAsync() { + DriverConfigLoader configLoader = buildIfNull(this.configLoader, this::defaultConfigLoader); DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = @@ -134,14 +160,17 @@ public CompletionStage buildAsync() { Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); - InternalDriverContext context = new DefaultDriverContext(configLoader, typeCodecs); - return DefaultCluster.init(context, contactPoints); + return DefaultCluster.init( + (InternalDriverContext) buildContext(configLoader, typeCodecs), contactPoints); } - /** Convenience method to call {@link #buildAsync()} and block on the result. */ - public Cluster build() { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(buildAsync()); + /** + * This must return an instance of {@code InternalDriverContext} (it's not expressed + * directly in the signature to avoid leaking that type through the protected API). + */ + protected DriverContext buildContext( + DriverConfigLoader configLoader, List> typeCodecs) { + return new DefaultDriverContext(configLoader, typeCodecs); } private static T buildIfNull(T value, Supplier builder) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java new file mode 100644 index 00000000000..c842bb8ecc3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.session.CqlSession; + +/** Helper class to build an instance of the default {@link Cluster} implementation. */ +public class DefaultClusterBuilder + extends ClusterBuilder> { + + @Override + protected Cluster wrap(Cluster defaultCluster) { + return defaultCluster; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 5883cf696bd..569b5eadbd1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -30,8 +31,26 @@ * {@link Session}'s prepare methods. However a {@link RetryPolicy} implementation might use it if * it needs a custom behavior for prepare requests. */ -public interface PrepareRequest - extends Request> { +public interface PrepareRequest extends Request { + + /** + * The type returned when a CQL statement is prepared synchronously. + * + *

      Most users won't use this explicitly. It is needed for the generic execute method ({@link + * Session#execute(Request, GenericType)}), but CQL statements will generally be prepared with one + * of the driver's built-in helper methods (such as {@link Session#prepare(SimpleStatement)}). + */ + GenericType SYNC = GenericType.of(PreparedStatement.class); + + /** + * The type returned when a CQL statement is prepared asynchronously. + * + *

      Most users won't use this explicitly. It is needed for the generic execute method ({@link + * Session#execute(Request, GenericType)}), but CQL statements will generally be prepared with one + * of the driver's built-in helper methods (such as {@link Session#prepareAsync(SimpleStatement)}. + */ + GenericType> ASYNC = + new GenericType>() {}; /** The CQL query to prepare. */ String getQuery(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 01bb0a22b23..ad6cd1271e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -17,7 +17,9 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -27,11 +29,29 @@ * * @param the "self type" used for covariant returns in subtypes. */ -public interface Statement> - extends Request> { +public interface Statement> extends Request { // Implementation note: "CqlRequest" would be a better name, but we keep "Statement" to match // previous driver versions. + /** + * The type returned when a CQL statement is executed synchronously. + * + *

      Most users won't use this explicitly. It is needed for the generic execute method ({@link + * Session#execute(Request, GenericType)}), but CQL statements will generally be run with one of + * the driver's built-in helper methods (such as {@link Session#execute(Statement)}). + */ + GenericType SYNC = GenericType.of(ResultSet.class); + + /** + * The type returned when a CQL statement is executed asynchronously. + * + *

      Most users won't use this explicitly. It is needed for the generic execute method ({@link + * Session#execute(Request, GenericType)}), but CQL statements will generally be run with one of + * the driver's built-in helper methods (such as {@link Session#executeAsync(Statement)}). + */ + GenericType> ASYNC = + new GenericType>() {}; + /** * Sets the name of the driver configuration profile that will be used for execution. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java new file mode 100644 index 00000000000..f278849361c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; +import java.util.concurrent.CompletionStage; + +/** A specialized session with convenience methods to execute CQL statements. */ +public interface CqlSession extends Session { + + /** + * Executes a CQL statement synchronously (the calling thread blocks until the result becomes + * available). + */ + default ResultSet execute(Statement statement) { + return execute(statement, Statement.SYNC); + } + + /** + * Executes a CQL statement synchronously (the calling thread blocks until the result becomes + * available). + */ + default ResultSet execute(String query) { + return execute(SimpleStatement.newInstance(query)); + } + + /** + * Executes a CQL statement asynchronously (the call returns as soon as the statement was sent, + * generally before the result is available). + */ + default CompletionStage executeAsync(Statement statement) { + return execute(statement, Statement.ASYNC); + } + + /** + * Executes a CQL statement asynchronously (the call returns as soon as the statement was sent, + * generally before the result is available). + */ + default CompletionStage executeAsync(String query) { + return executeAsync(SimpleStatement.newInstance(query)); + } + + /** + * Prepares a CQL statement synchronously (the calling thread blocks until the statement is + * prepared). + */ + default PreparedStatement prepare(SimpleStatement query) { + return execute(new DefaultPrepareRequest(query), PrepareRequest.SYNC); + } + + /** + * Prepares a CQL statement synchronously (the calling thread blocks until the statement is + * prepared). + */ + default PreparedStatement prepare(String query) { + return execute(new DefaultPrepareRequest(query), PrepareRequest.SYNC); + } + + /** + * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was + * sent, generally before the statement is prepared). + */ + default CompletionStage prepareAsync(String query) { + return execute(new DefaultPrepareRequest(query), PrepareRequest.ASYNC); + } + + /** + * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was + * sent, generally before the statement is prepared). + */ + default CompletionStage prepareAsync(SimpleStatement query) { + return execute(new DefaultPrepareRequest(query), PrepareRequest.ASYNC); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 0959d7b77f3..ff669317876 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -27,11 +27,8 @@ *

      This is a high-level abstraction, agnostic to the actual language (e.g. CQL). A request is * anything that can be converted to a protocol message, provided that you register a request * processor with the driver to do that conversion. - * - * @param the type of response when this request is executed synchronously. - * @param the type of response when this request is executed asynchronously. */ -public interface Request { +public interface Request { /** * The name of the driver configuration profile that will be used for execution. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index c228c83445f..77d8879dd34 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -18,137 +18,41 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; -import java.util.concurrent.CompletionStage; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; /** * A nexus to send requests to a Cassandra cluster. * - *

      This is a high-level abstraction that can handle any kind of request (provided that you have - * registered a custom request processor with the driver). However, for user-friendliness, we also - * expose overrides for the standard CQL requests that are supported out of the box. + *

      This is a high-level abstraction capable of handling arbitrary request and result types. For + * CQL statements, {@link CqlSession} provides convenience methods with more familiar signatures. + * + *

      The driver's request execution logic is pluggable (see {@code RequestProcessor} in the + * internal API). This is intended for future extensions, for example a reactive API for CQL + * statements, or graph requests in the Datastax Enterprise driver. Hence the generic {@link + * #execute(Request, GenericType)} method in this interface, that makes no assumptions about the + * request or result type. */ public interface Session extends AsyncAutoCloseable { /** * The keyspace that this session is currently connected to. * - *

      There are two ways that this can be set: - * - *

        - *
      • during initialization, if the session was created with {@link - * Cluster#connect(CqlIdentifier)} or {@link Cluster#connectAsync(CqlIdentifier)}; - *
      • at runtime, if the client issues a request that changes the keyspace (such as a CQL - * {@code USE} query). Note that this second method is inherently unsafe, since other - * requests expecting the old keyspace might be executing concurrently. Therefore it is - * highly discouraged, aside from trivial cases (such as a cqlsh-style program where - * requests are never concurrent). - *
      + *

      There are two ways that this can be set: during initialization, if the session was created + * with {@link Cluster#connect(CqlIdentifier)} or {@link Cluster#connectAsync(CqlIdentifier)}; at + * runtime, if the client issues a request that changes the keyspace (such as a CQL {@code USE} + * query). Note that this second method is inherently unsafe, since other requests expecting the + * old keyspace might be executing concurrently. Therefore it is highly discouraged, aside from + * trivial cases (such as a cqlsh-style program where requests are never concurrent). */ CqlIdentifier getKeyspace(); /** - * Executes a request, and blocks until the result is available. - * - * @return a synchronous result, that provides immediate access to the data as soon as the method - * returns. - */ - SyncResultT execute(Request request); - - /** - * Executes a request, returning as soon as it has been scheduled, but generally before the result - * is available. - * - * @return an asynchronous result, that represents the future completion of the request. The - * client either wait, or schedule a callback to be executed on completion (this is - * implementation-specific). - */ - AsyncResultT executeAsync(Request request); - - /** - * Executes a CQL statement synchronously. - * - *

      This is a convenience method that does the exact same thing as {@link #execute(Request)}, - * but exposes a more user-friendly signature reminiscent of the 3.x API. - */ - default ResultSet execute(Statement statement) { - return execute((Request>) statement); - } - - /** - * Executes a CQL statement synchronously. - * - *

      This is a convenience method that builds a {@link SimpleStatement#newInstance(String) - * SimpleStatement} and passes it to {@link #execute(Request)}. - */ - default ResultSet execute(String query) { - return execute(SimpleStatement.newInstance(query)); - } - - /** - * Executes a CQL statement asynchronously. - * - *

      This is a convenience method that does the exact same thing as {@link - * #executeAsync(Statement)}, but exposes a more user-friendly signature reminiscent of the 3.x - * API. - */ - default CompletionStage executeAsync(Statement statement) { - return executeAsync((Request>) statement); - } - - /** - * Executes a CQL statement asynchronously. - * - *

      This is a convenience method that builds a {@link SimpleStatement#newInstance(String) - * SimpleStatement} and passes it to {@link #executeAsync(Statement)}. - */ - default CompletionStage executeAsync(String query) { - return executeAsync(SimpleStatement.newInstance(query)); - } - - /** - * Prepares a CQL statement synchronously. - * - *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link - * #execute(Request)}. - */ - default PreparedStatement prepare(SimpleStatement query) { - return execute(new DefaultPrepareRequest(query)); - } - - /** - * Prepares a CQL statement synchronously. - * - *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link - * #execute(Request)}. - */ - default PreparedStatement prepare(String query) { - return execute(new DefaultPrepareRequest(query)); - } - - /** - * Prepares a CQL statement asynchronously. - * - *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link - * #executeAsync(Request)}. - */ - default CompletionStage prepareAsync(String query) { - return executeAsync(new DefaultPrepareRequest(query)); - } - - /** - * Prepares a CQL statement asynchronously. + * Executes an arbitrary request. * - *

      This is a convenience method that builds a {@link PrepareRequest} and passes it to {@link - * #executeAsync(Request)}. + * @param resultType the type of the result, which determines the internal request processor + * (built-in or custom) that will be used to handle the request. + * @see Session */ - default CompletionStage prepareAsync(SimpleStatement query) { - return executeAsync(new DefaultPrepareRequest(query)); - } + ResultT execute( + RequestT request, GenericType resultType); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java new file mode 100644 index 00000000000..ba8abf283a4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.session.Session; +import java.util.concurrent.CompletionStage; + +/** Utility class to wrap a cluster and make it return a different session type. */ +public abstract class ClusterWrapper + implements Cluster { + + private final Cluster delegate; + + public ClusterWrapper(Cluster delegate) { + this.delegate = delegate; + } + + protected abstract TargetSessionT wrap(SourceSessionT session); + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Metadata getMetadata() { + return delegate.getMetadata(); + } + + @Override + public DriverContext getContext() { + return delegate.getContext(); + } + + @Override + public CompletionStage connectAsync(CqlIdentifier keyspace) { + return delegate.connectAsync(keyspace).thenApply(this::wrap); + } + + @Override + public CompletionStage closeFuture() { + return delegate.closeFuture(); + } + + @Override + public CompletionStage closeAsync() { + return delegate.closeAsync(); + } + + @Override + public CompletionStage forceCloseAsync() { + return delegate.forceCloseAsync(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 945d9d8c572..90c607bb1d5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; @@ -43,11 +44,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class DefaultCluster implements Cluster { +public class DefaultCluster implements Cluster { private static final Logger LOG = LoggerFactory.getLogger(DefaultCluster.class); - public static CompletableFuture init( + public static CompletableFuture> init( InternalDriverContext context, Set contactPoints) { DefaultCluster cluster = new DefaultCluster(context, contactPoints); return cluster.init(); @@ -68,7 +69,7 @@ private DefaultCluster(InternalDriverContext context, Set con this.logPrefix = context.clusterName(); } - private CompletableFuture init() { + private CompletableFuture> init() { RunOrSchedule.on(adminExecutor, singleThreaded::init); return singleThreaded.initFuture; } @@ -89,8 +90,8 @@ public DriverContext getContext() { } @Override - public CompletionStage connectAsync(CqlIdentifier keyspace) { - CompletableFuture connectFuture = new CompletableFuture<>(); + public CompletionStage connectAsync(CqlIdentifier keyspace) { + CompletableFuture connectFuture = new CompletableFuture<>(); RunOrSchedule.on(adminExecutor, () -> singleThreaded.connect(keyspace, connectFuture)); return connectFuture; } @@ -117,7 +118,7 @@ private class SingleThreaded { private final InternalDriverContext context; private final Set initialContactPoints; private final NodeStateManager nodeStateManager; - private final CompletableFuture initFuture = new CompletableFuture<>(); + private final CompletableFuture> initFuture = new CompletableFuture<>(); private boolean initWasCalled; private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; @@ -206,7 +207,7 @@ private void afterInitialSchemaRefresh(@SuppressWarnings("unused") Void ignored) } } - private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { + private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { connectFuture.completeExceptionally(new IllegalStateException("Cluster was closed")); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index a978744b0f9..c34d28ffc72 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -49,7 +49,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.protocol.internal.Message; @@ -199,7 +199,10 @@ private static Map encode( } static AsyncResultSet toResultSet( - Result result, ExecutionInfo executionInfo, Session session, InternalDriverContext context) { + Result result, + ExecutionInfo executionInfo, + CqlSession session, + InternalDriverContext context) { if (result instanceof Rows) { Rows rows = (Rows) result; Statement statement = executionInfo.getStatement(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java new file mode 100644 index 00000000000..ec7f514cc3e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentMap; + +public class CqlPrepareAsyncHandler extends CqlPrepareHandlerBase + implements RequestHandler> { + + CqlPrepareAsyncHandler( + PrepareRequest request, + ConcurrentMap preparedStatementsCache, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + super(request, preparedStatementsCache, session, context, sessionLogPrefix); + } + + @Override + public CompletableFuture handle() { + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java new file mode 100644 index 00000000000..9eb056d3857 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentMap; + +public class CqlPrepareAsyncProcessor + implements RequestProcessor> { + + private final ConcurrentMap preparedStatementsCache; + + public CqlPrepareAsyncProcessor( + ConcurrentMap preparedStatementsCache) { + this.preparedStatementsCache = preparedStatementsCache; + } + + @Override + public boolean canProcess(Request request, GenericType resultType) { + return request instanceof PrepareRequest && resultType.equals(PrepareRequest.ASYNC); + } + + @Override + public RequestHandler> newHandler( + PrepareRequest request, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + return new CqlPrepareAsyncHandler( + request, preparedStatementsCache, session, context, sessionLogPrefix); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java similarity index 84% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 89a64ab1e71..8f795006ee7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -33,8 +35,6 @@ import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; -import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -46,50 +46,74 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.ScheduledFuture; +import java.nio.ByteBuffer; import java.time.Duration; import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Handles the lifecycle of the preparation of a CQL statement. */ -public class CqlPrepareHandler - extends RequestHandlerBase> { +public abstract class CqlPrepareHandlerBase { - private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandlerBase.class); private final String logPrefix; - private final CompletableFuture result; + private final PrepareRequest request; + private final ConcurrentMap preparedStatementsCache; + private final DefaultSession session; + private final InternalDriverContext context; + private final Queue queryPlan; + protected final CompletableFuture result; private final Message message; private final EventExecutor scheduler; private final Duration timeout; private final ScheduledFuture timeoutFuture; private final RetryPolicy retryPolicy; private final Boolean prepareOnAllNodes; - private final CqlPrepareProcessor processor; private volatile InitialPrepareCallback initialCallback; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. private volatile List> errors; - CqlPrepareHandler( + protected CqlPrepareHandlerBase( PrepareRequest request, - CqlPrepareProcessor processor, + ConcurrentMap preparedStatementsCache, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - super(request, session, context); + this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); LOG.debug("[{}] Creating new handler for prepare request {}", logPrefix, request); - this.processor = processor; + + this.request = request; + this.preparedStatementsCache = preparedStatementsCache; + this.session = session; + this.context = context; + this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); + + DriverConfigProfile configProfile; + if (request.getConfigProfile() != null) { + configProfile = request.getConfigProfile(); + } else { + DriverConfig config = context.config(); + String profileName = request.getConfigProfileName(); + configProfile = + (profileName == null || profileName.isEmpty()) + ? config.getDefaultProfile() + : config.getNamedProfile(profileName); + } + this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -112,17 +136,6 @@ public class CqlPrepareHandler sendRequest(null, 0); } - @Override - public CompletionStage asyncResult() { - return result; - } - - @Override - public PreparedStatement syncResult() { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(asyncResult()); - } - private ScheduledFuture scheduleTimeout(Duration timeout) { if (timeout.toNanos() > 0) { return scheduler.schedule( @@ -150,9 +163,9 @@ private void sendRequest(Node node, int retryCount) { return; } DriverChannel channel = null; - if (node == null || (channel = getChannel(node, logPrefix)) == null) { + if (node == null || (channel = session.getChannel(node, logPrefix)) == null) { while (!result.isDone() && (node = queryPlan.poll()) != null) { - channel = getChannel(node, logPrefix); + channel = session.getChannel(node, logPrefix); if (channel != null) { break; } @@ -173,7 +186,7 @@ private void recordError(Node node, Throwable error) { // Use a local variable to do only a single single volatile read in the nominal case List> errorsSnapshot = this.errors; if (errorsSnapshot == null) { - synchronized (CqlPrepareHandler.this) { + synchronized (CqlPrepareHandlerBase.this) { errorsSnapshot = this.errors; if (errorsSnapshot == null) { this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); @@ -187,7 +200,7 @@ private void setFinalResult(Prepared prepared) { DefaultPreparedStatement newStatement = Conversions.toPreparedStatement(prepared, (PrepareRequest) request, context); - DefaultPreparedStatement cachedStatement = processor.cache(newStatement); + DefaultPreparedStatement cachedStatement = cache(newStatement); if (cachedStatement != newStatement) { // The statement already existed in the cache, assume it's because the client called @@ -217,6 +230,25 @@ private void setFinalResult(Prepared prepared) { } } + private DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatement) { + DefaultPreparedStatement previous = + preparedStatementsCache.putIfAbsent(preparedStatement.getId(), preparedStatement); + if (previous != null) { + LOG.warn( + "Re-preparing already prepared query. " + + "This is generally an anti-pattern and will likely affect performance. " + + "Consider preparing the statement only once. Query='{}'", + preparedStatement.getQuery()); + + // The one object in the cache will get GCed once it's not referenced by the client anymore + // since we use a weak reference. So we need to make sure that the instance we do return to + // the user is the one that is in the cache. + return previous; + } else { + return preparedStatement; + } + } + private CompletionStage prepareOnOtherNodes() { List> otherNodesFutures = new ArrayList<>(); // Only process the rest of the query plan. Any node before that is either the coordinator, or @@ -231,7 +263,7 @@ private CompletionStage prepareOnOtherNodes() { // blocking, the preparation will be retried later on that node. Simply warn and move on. private CompletionStage prepareOnOtherNode(Node node) { LOG.debug("[{}] Repreparing on {}", logPrefix, node); - DriverChannel channel = getChannel(node, logPrefix); + DriverChannel channel = session.getChannel(node, logPrefix); if (channel == null) { LOG.debug("[{}] Could not get a channel to reprepare on {}, skipping", logPrefix, node); return CompletableFuture.completedFuture(null); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java deleted file mode 100644 index 9eca6beaa3f..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareProcessor.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.cql; - -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; -import com.datastax.oss.driver.internal.core.session.RequestProcessor; -import com.google.common.collect.MapMaker; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Handles the preparation of CQL queries. - * - *

      This class is stateful, you can't reuse the same instance across multiple {@link Cluster} - * instances. - */ -public class CqlPrepareProcessor - implements RequestProcessor> { - - private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareProcessor.class); - - private final ConcurrentMap preparedStatements = - new MapMaker().weakValues().makeMap(); - - @Override - public boolean canProcess(Request request) { - return request instanceof PrepareRequest; - } - - @Override - public RequestHandler> newHandler( - Request> request, - DefaultSession session, - InternalDriverContext context, - String sessionLogPrefix) { - return new CqlPrepareHandler( - (PrepareRequest) request, this, session, context, sessionLogPrefix); - } - - DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatement) { - DefaultPreparedStatement previous = - preparedStatements.putIfAbsent(preparedStatement.getId(), preparedStatement); - if (previous != null) { - LOG.warn( - "Re-preparing already prepared query. " - + "This is generally an anti-pattern and will likely affect performance. " - + "Consider preparing the statement only once. Query='{}'", - preparedStatement.getQuery()); - - // The one object in the cache will get GCed once it's not referenced by the client anymore - // since we use a weak reference. So we need to make sure that the instance we do return to - // the user is the one that is in the cache. - return previous; - } else { - return preparedStatement; - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java new file mode 100644 index 00000000000..52beaf8a9cf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentMap; + +public class CqlPrepareSyncHandler extends CqlPrepareHandlerBase + implements RequestHandler { + + CqlPrepareSyncHandler( + PrepareRequest request, + ConcurrentMap preparedStatementsCache, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + super(request, preparedStatementsCache, session, context, sessionLogPrefix); + } + + @Override + public PreparedStatement handle() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(result); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java new file mode 100644 index 00000000000..251b3504eb9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentMap; + +public class CqlPrepareSyncProcessor + implements RequestProcessor { + + private final ConcurrentMap preparedStatementsCache; + + public CqlPrepareSyncProcessor( + ConcurrentMap preparedStatementsCache) { + this.preparedStatementsCache = preparedStatementsCache; + } + + @Override + public boolean canProcess(Request request, GenericType resultType) { + return request instanceof PrepareRequest && resultType.equals(PrepareRequest.SYNC); + } + + @Override + public RequestHandler newHandler( + PrepareRequest request, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + return new CqlPrepareSyncHandler( + request, preparedStatementsCache, session, context, sessionLogPrefix); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java new file mode 100644 index 00000000000..1d8848aab7f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +public class CqlRequestAsyncHandler extends CqlRequestHandlerBase + implements RequestHandler, CompletionStage> { + + CqlRequestAsyncHandler( + Statement statement, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + super(statement, session, context, sessionLogPrefix); + } + + @Override + public CompletionStage handle() { + return result; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java new file mode 100644 index 00000000000..9d6d8be4b9e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +public class CqlRequestAsyncProcessor + implements RequestProcessor, CompletionStage> { + + @Override + public boolean canProcess(Request request, GenericType resultType) { + return request instanceof Statement && resultType.equals(Statement.ASYNC); + } + + @Override + public RequestHandler, CompletionStage> newHandler( + Statement request, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + return new CqlRequestAsyncHandler(request, session, context, sessionLogPrefix); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java similarity index 90% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 6f11976f101..0e654173f4f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -19,10 +19,11 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; -import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; @@ -42,9 +43,6 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; -import com.datastax.oss.driver.internal.core.session.RequestHandlerBase; -import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -67,23 +65,27 @@ import java.util.AbstractMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Handles execution of a {@link Statement}. */ -public class CqlRequestHandler - extends RequestHandlerBase> { +public abstract class CqlRequestHandlerBase { - private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandlerBase.class); private final String logPrefix; - private final CompletableFuture result; + private final Statement statement; + private final DefaultSession session; + private final CqlIdentifier keyspace; + private final InternalDriverContext context; + private final Queue queryPlan; + private final boolean isIdempotent; + protected final CompletableFuture result; private final Message message; private final EventExecutor scheduler; /** @@ -110,14 +112,36 @@ public class CqlRequestHandler // We don't use a map because nodes can appear multiple times. private volatile List> errors; - CqlRequestHandler( + protected CqlRequestHandlerBase( Statement statement, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - super(statement, session, context); + this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); LOG.debug("[{}] Creating new handler for request {}", logPrefix, statement); + + this.statement = statement; + this.session = session; + this.keyspace = session.getKeyspace(); + this.context = context; + this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); + + DriverConfigProfile configProfile; + if (statement.getConfigProfile() != null) { + configProfile = statement.getConfigProfile(); + } else { + DriverConfig config = context.config(); + String profileName = statement.getConfigProfileName(); + configProfile = + (profileName == null || profileName.isEmpty()) + ? config.getDefaultProfile() + : config.getNamedProfile(profileName); + } + this.isIdempotent = + (statement.isIdempotent() == null) + ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) + : statement.isIdempotent(); this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -143,7 +167,8 @@ public class CqlRequestHandler if (isIdempotent) { // Schedule the first speculative execution if applicable - long nextDelay = context.speculativeExecutionPolicy().nextExecution(keyspace, request, 1); + long nextDelay = + context.speculativeExecutionPolicy().nextExecution(keyspace, this.statement, 1); if (nextDelay >= 0) { LOG.debug("[{}] Scheduling speculative execution 1 in {} ms", logPrefix, nextDelay); this.scheduledExecutions = new CopyOnWriteArrayList<>(); @@ -165,18 +190,6 @@ public class CqlRequestHandler sendRequest(null, 0, 0); } - @Override - public CompletionStage asyncResult() { - return result; - } - - @Override - public ResultSet syncResult() { - BlockingOperation.checkNotDriverThread(); - AsyncResultSet firstPage = CompletableFutures.getUninterruptibly(asyncResult()); - return ResultSets.newInstance(firstPage); - } - private ScheduledFuture scheduleTimeout(Duration timeout) { if (timeout.toNanos() > 0) { return scheduler.schedule( @@ -194,7 +207,7 @@ private void startExecution(int currentExecutionIndex) { activeExecutionsCount.incrementAndGet(); startedSpeculativeExecutionsCount.incrementAndGet(); long nextDelay = - speculativeExecutionPolicy.nextExecution(keyspace, request, currentExecutionIndex + 1); + speculativeExecutionPolicy.nextExecution(keyspace, statement, currentExecutionIndex + 1); if (nextDelay >= 0) { LOG.trace( "[{}] Scheduling speculative execution {} in {} ms", @@ -225,9 +238,9 @@ private void sendRequest(Node node, int currentExecutionIndex, int retryCount) { return; } DriverChannel channel = null; - if (node == null || (channel = getChannel(node, logPrefix)) == null) { + if (node == null || (channel = session.getChannel(node, logPrefix)) == null) { while (!result.isDone() && (node = queryPlan.poll()) != null) { - channel = getChannel(node, logPrefix); + channel = session.getChannel(node, logPrefix); if (channel != null) { break; } @@ -243,7 +256,7 @@ private void sendRequest(Node node, int currentExecutionIndex, int retryCount) { NodeResponseCallback nodeResponseCallback = new NodeResponseCallback(node, channel, currentExecutionIndex, retryCount, logPrefix); channel - .write(message, request.isTracing(), request.getCustomPayload(), nodeResponseCallback) + .write(message, statement.isTracing(), statement.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); } } @@ -252,7 +265,7 @@ private void recordError(Node node, Throwable error) { // Use a local variable to do only a single single volatile read in the nominal case List> errorsSnapshot = this.errors; if (errorsSnapshot == null) { - synchronized (CqlRequestHandler.this) { + synchronized (CqlRequestHandlerBase.this) { errorsSnapshot = this.errors; if (errorsSnapshot == null) { this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); @@ -301,7 +314,7 @@ private ExecutionInfo buildExecutionInfo( ByteBuffer pagingState = (resultMessage instanceof Rows) ? ((Rows) resultMessage).getMetadata().pagingState : null; return new DefaultExecutionInfo( - (Statement) request, + statement, callback.node, startedSpeculativeExecutionsCount.get(), callback.execution, @@ -448,7 +461,7 @@ private void processErrorResponse(Error errorMessage) { ReadTimeoutException readTimeout = (ReadTimeoutException) error; decision = retryPolicy.onReadTimeout( - request, + statement, readTimeout.getConsistencyLevel(), readTimeout.getBlockFor(), readTimeout.getReceived(), @@ -459,7 +472,7 @@ private void processErrorResponse(Error errorMessage) { decision = isIdempotent ? retryPolicy.onWriteTimeout( - request, + statement, writeTimeout.getConsistencyLevel(), writeTimeout.getWriteType(), writeTimeout.getBlockFor(), @@ -470,7 +483,7 @@ private void processErrorResponse(Error errorMessage) { UnavailableException unavailable = (UnavailableException) error; decision = retryPolicy.onUnavailable( - request, + statement, unavailable.getConsistencyLevel(), unavailable.getRequired(), unavailable.getAlive(), @@ -478,7 +491,7 @@ private void processErrorResponse(Error errorMessage) { } else { decision = isIdempotent - ? retryPolicy.onErrorResponse(request, error, retryCount) + ? retryPolicy.onErrorResponse(statement, error, retryCount) : RetryDecision.RETHROW; } processRetryDecision(decision, error); @@ -516,7 +529,7 @@ public void onFailure(Throwable error) { if (!isIdempotent || error instanceof FrameTooLongException) { decision = RetryDecision.RETHROW; } else { - decision = retryPolicy.onRequestAborted(request, error, retryCount); + decision = retryPolicy.onRequestAborted(statement, error, retryCount); } processRetryDecision(decision, error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java new file mode 100644 index 00000000000..a82853cfa17 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; + +public class CqlRequestSyncHandler extends CqlRequestHandlerBase + implements RequestHandler, ResultSet> { + + CqlRequestSyncHandler( + Statement statement, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + super(statement, session, context, sessionLogPrefix); + } + + @Override + public ResultSet handle() { + BlockingOperation.checkNotDriverThread(); + AsyncResultSet firstPage = CompletableFutures.getUninterruptibly(result); + return ResultSets.newInstance(firstPage); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java similarity index 69% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java index fcf82ca1320..3317999325e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java @@ -15,30 +15,28 @@ */ package com.datastax.oss.driver.internal.core.cql; -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; -import java.util.concurrent.CompletionStage; -public class CqlRequestProcessor - implements RequestProcessor> { +public class CqlRequestSyncProcessor implements RequestProcessor, ResultSet> { @Override - public boolean canProcess(Request request) { - return request instanceof Statement; + public boolean canProcess(Request request, GenericType resultType) { + return request instanceof Statement && resultType.equals(Statement.SYNC); } @Override - public RequestHandler> newHandler( - Request> request, + public RequestHandler, ResultSet> newHandler( + Statement request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestHandler((Statement) request, session, context, sessionLogPrefix); + return new CqlRequestSyncHandler(request, session, context, sessionLogPrefix); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 721dd72382f..b154895e086 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; @@ -38,7 +38,7 @@ public class DefaultAsyncResultSet implements AsyncResultSet { private final ColumnDefinitions definitions; private final ExecutionInfo executionInfo; - private final Session session; + private final CqlSession session; private final CountingIterator iterator; private final Iterable currentPage; @@ -46,7 +46,7 @@ public DefaultAsyncResultSet( ColumnDefinitions definitions, ExecutionInfo executionInfo, Queue> data, - Session session, + CqlSession session, InternalDriverContext context) { this.definitions = definitions; this.executionInfo = executionInfo; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 0946d6e430d..1b67bc92749 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -23,8 +23,10 @@ import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; @@ -68,11 +70,11 @@ *

    • trying to send the message on each pool, in the order of the query plan * */ -public class DefaultSession implements Session { +public class DefaultSession implements CqlSession { private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); - public static CompletionStage init( + public static CompletionStage init( InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { return new DefaultSession(context, keyspace, logPrefix).init(); } @@ -98,7 +100,7 @@ public static CompletionStage init( // its cache. // This is raw protocol-level data, as opposed to the actual instances returned to the client // (e.g. DefaultPreparedStatement) which are handled at the protocol level (e.g. - // CqlPrepareProcessor). We keep the two separate to avoid introducing a dependency from the + // CqlPrepareAsyncProcessor). We keep the two separate to avoid introducing a dependency from the // session to a particular processor implementation. private ConcurrentMap repreparePayloads = new MapMaker().weakValues().makeMap(); @@ -113,7 +115,7 @@ private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace, St this.logPrefix = logPrefix; } - private CompletionStage init() { + private CompletionStage init() { RunOrSchedule.on(adminExecutor, singleThreaded::init); return singleThreaded.initFuture; } @@ -153,24 +155,35 @@ public Map getPools() { } @Override - public SyncResultT execute( - Request request) { - return newHandler(request).syncResult(); - } - - @Override - public AsyncResultT executeAsync( - Request request) { - return newHandler(request).asyncResult(); - } - - private RequestHandler newHandler( - Request request) { + public ResultT execute( + RequestT request, GenericType resultType) { if (request.getKeyspace() != null) { // TODO CASSANDRA-10145 throw new UnsupportedOperationException("Per-request keyspaces are not supported yet"); } - return processorRegistry.processorFor(request).newHandler(request, this, context, logPrefix); + return processorRegistry + .processorFor(request, resultType) + .newHandler(request, this, context, logPrefix) + .handle(); + } + + public DriverChannel getChannel(Node node, String logPrefix) { + ChannelPool pool = pools.get(node); + if (pool == null) { + LOG.debug("[{}] No pool to {}, skipping", logPrefix, node); + return null; + } else { + DriverChannel channel = pool.next(); + if (channel == null) { + LOG.trace("[{}] Pool returned no channel for {}, skipping", logPrefix, node); + return null; + } else if (channel.closeFuture().isDone()) { + LOG.trace("[{}] Pool returned closed connection to {}, skipping", logPrefix, node); + return null; + } else { + return channel; + } + } } public ConcurrentMap getRepreparePayloads() { @@ -198,7 +211,7 @@ private class SingleThreaded { private final InternalDriverContext context; private final ChannelPoolFactory channelPoolFactory; - private final CompletableFuture initFuture = new CompletableFuture<>(); + private final CompletableFuture initFuture = new CompletableFuture<>(); private boolean initWasCalled; private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index d8bf0e6e6ea..880f9f2e5e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; -import com.datastax.oss.driver.internal.core.cql.CqlRequestHandler; +import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Prepare; @@ -45,7 +45,7 @@ * *

      See the comments in {@code reference.conf} for more explanations about this process. If any * prepare request fail, we ignore the error because it will be retried on the fly (see {@link - * CqlRequestHandler}). + * CqlRequestHandlerBase}). * *

      Logically this code belongs to {@link DefaultSession}, but it was extracted for modularity and * testability. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java index d5f1b55fe94..5bbec2d2afd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java @@ -15,14 +15,9 @@ */ package com.datastax.oss.driver.internal.core.session; -/** Manages the execution of a given request. */ -public interface RequestHandler { - /** - * Immediately returns an object that represents the pending request; that object will complete - * when the request is done. - */ - AsyncResultT asyncResult(); +import com.datastax.oss.driver.api.core.session.Request; - /** Blocks until the request is done, and returns an object representing the completed result. */ - SyncResultT syncResult(); +/** Manages the execution of a given request. */ +public interface RequestHandler { + ResultT handle(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index fa73140bdd7..fd1936e40dc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -29,12 +29,11 @@ import org.slf4j.LoggerFactory; /** Factors code that should be common to most request handler implementations. */ -public abstract class RequestHandlerBase - implements RequestHandler { +public abstract class RequestHandlerBase + implements RequestHandler { private static final Logger LOG = LoggerFactory.getLogger(RequestHandlerBase.class); - protected final Request request; protected final boolean isIdempotent; protected final DefaultSession session; protected final CqlIdentifier keyspace; @@ -43,10 +42,7 @@ public abstract class RequestHandlerBase protected final DriverConfigProfile configProfile; protected RequestHandlerBase( - Request request, - DefaultSession session, - InternalDriverContext context) { - this.request = request; + RequestT request, DefaultSession session, InternalDriverContext context) { this.session = session; this.keyspace = session.getKeyspace(); this.context = context; @@ -67,23 +63,4 @@ protected RequestHandlerBase( ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : request.isIdempotent(); } - - protected DriverChannel getChannel(Node node, String logPrefix) { - ChannelPool pool = session.getPools().get(node); - if (pool == null) { - LOG.debug("[{}] No pool to {}, skipping", logPrefix, node); - return null; - } else { - DriverChannel channel = pool.next(); - if (channel == null) { - LOG.trace("[{}] Pool returned no channel for {}, skipping", logPrefix, node); - return null; - } else if (channel.closeFuture().isDone()) { - LOG.trace("[{}] Pool returned closed connection to {}, skipping", logPrefix, node); - return null; - } else { - return channel; - } - } - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index 62ec0aab5dc..5c415e21e7d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; /** @@ -26,22 +27,22 @@ *

      By default, the driver supports CQL {@link Statement queries} and {@link PrepareRequest * preparation requests}. New processors can be plugged in to handle new types of requests. * - * @param the type of result when a request is executed synchronously. - * @param the type of result when a request is executed asynchronously. + * @param the type of request accepted. + * @param the type of result when a request is processed. */ -public interface RequestProcessor { +public interface RequestProcessor { /** - * Whether the processor can handle a given request. + * Whether the processor can produce the given result from the given request. * *

      Processors will be tried in the order they were registered. The first processor for which * this method returns true will be used. */ - boolean canProcess(Request request); + boolean canProcess(Request request, GenericType resultType); /** Builds a new handler to process a given request. */ - RequestHandler newHandler( - Request request, + RequestHandler newHandler( + RequestT request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java index e5aac867772..83e9f6cfa9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -16,8 +16,15 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.internal.core.cql.CqlPrepareProcessor; -import com.datastax.oss.driver.internal.core.cql.CqlRequestProcessor; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; +import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; +import com.google.common.collect.MapMaker; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +33,15 @@ public class RequestProcessorRegistry { private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); public static RequestProcessorRegistry defaultCqlProcessors(String logPrefix) { + ConcurrentMap preparedStatementsCache = + new MapMaker().weakValues().makeMap(); + return new RequestProcessorRegistry( - logPrefix, new CqlRequestProcessor(), new CqlPrepareProcessor()); + logPrefix, + new CqlRequestSyncProcessor(), + new CqlRequestAsyncProcessor(), + new CqlPrepareSyncProcessor(preparedStatementsCache), + new CqlPrepareAsyncProcessor(preparedStatementsCache)); } private final String logPrefix; @@ -39,16 +53,16 @@ public RequestProcessorRegistry(String logPrefix, RequestProcessor... proc this.processors = processors; } - public RequestProcessor processorFor( - Request request) { + public RequestProcessor processorFor( + RequestT request, GenericType resultType) { for (RequestProcessor processor : processors) { - if (processor.canProcess(request)) { + if (processor.canProcess(request, resultType)) { LOG.trace("[{}] Using {} to process {}", logPrefix, processor, request); // The cast is safe provided that the processor implements canProcess correctly @SuppressWarnings("unchecked") - RequestProcessor result = - (RequestProcessor) processor; + RequestProcessor result = + (RequestProcessor) processor; return result; } else { LOG.trace("[{}] {} cannot process {}, trying next", logPrefix, processor, request); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java new file mode 100644 index 00000000000..618a97bc284 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import java.util.concurrent.CompletionStage; + +/** + * Utility class to wrap a session. + * + *

      This will typically be used to mix in a convenience interface from a 3rd-party extension: + * + *

      {@code
      + * class ReactiveSessionWrapper extends SessionWrapper implements ReactiveSession {
      + *   public ReactiveSessionWrapper(Session delegate) {
      + *     super(delegate);
      + *   }
      + * }
      + * }
      + */ +public class SessionWrapper implements Session { + + private final Session delegate; + + public SessionWrapper(Session delegate) { + this.delegate = delegate; + } + + @Override + public CqlIdentifier getKeyspace() { + return delegate.getKeyspace(); + } + + @Override + public ResultT execute( + RequestT request, GenericType resultType) { + return delegate.execute(request, resultType); + } + + @Override + public CompletionStage closeFuture() { + return delegate.closeFuture(); + } + + @Override + public CompletionStage closeAsync() { + return delegate.closeAsync(); + } + + @Override + public CompletionStage forceCloseAsync() { + return delegate.forceCloseAsync(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index 90d7c859ddd..1fe346058c9 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -35,7 +35,7 @@ public class ConstantSpeculativeExecutionPolicyTest { @Mock private DriverContext context; @Mock private DriverConfig config; @Mock private DriverConfigProfile defaultProfile; - @Mock private Request request; + @Mock private Request request; @Before public void setup() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 21549030a77..5b87e0aeedf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -33,8 +33,11 @@ import com.datastax.oss.protocol.internal.response.result.RowsMetadata; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -54,15 +57,15 @@ public class CqlPrepareHandlerTest { @Mock private Node node2; @Mock private Node node3; - @Mock private CqlPrepareProcessor processor; + private ConcurrentMap preparedStatementsCache = + new ConcurrentHashMap<>(); @Before public void setup() { MockitoAnnotations.initMocks(this); // By default, simulate that the prepared statement is not already in the driver's cache - Mockito.when(processor.cache(any(DefaultPreparedStatement.class))) - .thenAnswer(invocation -> invocation.getArgument(0)); + preparedStatementsCache.clear(); } @Test @@ -75,9 +78,13 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -112,9 +119,13 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { Mockito.when(config.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -133,8 +144,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { public void should_not_reprepare_on_other_nodes_if_already_cached() { // Simulate an existing entry in the driver's cache: DefaultPreparedStatement mockExistingStatement = Mockito.mock(DefaultPreparedStatement.class); - Mockito.when(processor.cache(any(DefaultPreparedStatement.class))) - .thenAnswer(invocation -> mockExistingStatement); + preparedStatementsCache.put(Bytes.fromHexString("0xffff"), mockExistingStatement); RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -144,9 +154,13 @@ public void should_not_reprepare_on_other_nodes_if_already_cached() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -174,9 +188,13 @@ public void should_ignore_errors_while_repreparing_on_other_nodes() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); assertThat(prepareFuture).isNotDone(); @@ -214,9 +232,13 @@ public void should_retry_initial_prepare_if_recoverable_error() { .thenReturn(RetryDecision.RETRY_NEXT); CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); // Success on node2, reprepare on node3 assertThat(prepareFuture).isNotDone(); @@ -249,9 +271,13 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { .thenReturn(RetryDecision.RETHROW); CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); // Success on node2, reprepare on node3 assertThat(prepareFuture) @@ -285,9 +311,13 @@ public void should_fail_if_retry_policy_ignores_error() { .thenReturn(RetryDecision.IGNORE); CompletionStage prepareFuture = - new CqlPrepareHandler( - PREPARE_REQUEST, processor, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlPrepareAsyncHandler( + PREPARE_REQUEST, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); // Success on node2, reprepare on node3 assertThat(prepareFuture) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 94e13944e80..d13c56a8777 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -65,8 +65,8 @@ public void should_always_try_next_node_if_bootstrapping( .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -105,8 +105,8 @@ public void should_always_rethrow_query_validation_error( .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isFailed( @@ -133,8 +133,8 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -165,8 +165,8 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -196,8 +196,8 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.IGNORE); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -226,8 +226,8 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().retryPolicy(), RetryDecision.RETHROW); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isFailed( @@ -259,8 +259,8 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re } CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); assertThat(resultSetFuture) .isFailed( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 059400cf6bd..bfb4d96651a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -48,8 +48,8 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); @@ -81,8 +81,8 @@ public void should_schedule_speculative_executions( .thenReturn(secondExecutionDelay); Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(-1L); - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); @@ -127,8 +127,8 @@ public void should_not_start_execution_if_result_complete( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); harness.nextScheduledTask(); // Discard the timeout task @@ -170,8 +170,8 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); harness.nextScheduledTask(); // Discard the timeout task @@ -208,8 +208,8 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); // do not simulate a response from node1 yet @@ -259,8 +259,8 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); // do not simulate a response from node1 yet @@ -314,8 +314,8 @@ public void should_retry_in_speculative_executions( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); // do not simulate a response from node1. The request will stay hanging for the rest of this test @@ -360,8 +360,8 @@ public void should_stop_retrying_other_executions_if_result_complete( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); node1Behavior.verifyWrite(); harness.nextScheduledTask(); // Discard the timeout task diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index aaa1dc588bb..93ae9721796 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -54,12 +54,12 @@ public void should_complete_result_if_first_node_replies_immediately() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler( + new CqlRequestAsyncHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -88,12 +88,12 @@ public void should_fail_if_no_node_available() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler( + new CqlRequestAsyncHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + .handle(); assertThat(resultSetFuture) .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); @@ -109,12 +109,12 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestHandler( + new CqlRequestAsyncHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + .handle(); // First scheduled task is the timeout, run it before node1 has responded ScheduledTaskCapturingEventLoop.CapturedTask scheduledTask = harness.nextScheduledTask(); @@ -141,12 +141,12 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { .build()) { CompletionStage resultSetFuture = - new CqlRequestHandler( + new CqlRequestAsyncHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + .handle(); assertThat(resultSetFuture) .isSuccess( @@ -181,8 +181,9 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce Mockito.when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); CompletionStage resultSetFuture = - new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext(), "test") - .asyncResult(); + new CqlRequestAsyncHandler( + boundStatement, harness.getSession(), harness.getContext(), "test") + .handle(); // Before we proceed, mock the PREPARE exchange that will occur as soon as we complete the // first response. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 12e13ff18e7..23d4aa9e085 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -47,7 +47,7 @@ public class DefaultAsyncResultSetTest { @Mock private ColumnDefinitions columnDefinitions; @Mock private ExecutionInfo executionInfo; @Mock private Statement statement; - @Mock private Session session; + @Mock private CqlSession session; @Mock private InternalDriverContext context; @Before diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 22d6b328234..f0337519fc7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; /** * Provides the environment to test a request handler, where a query plan can be defined, and the @@ -117,7 +118,12 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.timestampGenerator()).thenReturn(timestampGenerator); Map pools = builder.buildMockPools(); - Mockito.when(session.getPools()).thenReturn(pools); + Mockito.when(session.getChannel(any(Node.class), anyString())) + .thenAnswer( + invocation -> { + Node node = invocation.getArgument(0); + return pools.get(node).next(); + }); Mockito.when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index f2be478b7a4..3a8c5d025a7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -123,7 +124,7 @@ public void should_initialize_pools_with_distances() { .pending(node3, KEYSPACE, NodeDistance.REMOTE, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -157,7 +158,7 @@ public void should_not_connect_to_ignored_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -181,7 +182,7 @@ public void should_not_connect_to_forced_down_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -207,7 +208,7 @@ public void should_adjust_distance_if_changed_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -248,7 +249,7 @@ public void should_remove_pool_if_ignored_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -288,7 +289,7 @@ public void should_remove_pool_if_forced_down_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -325,7 +326,7 @@ public void should_resize_pool_if_distance_changes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -349,7 +350,7 @@ public void should_remove_pool_if_node_becomes_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -380,7 +381,7 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -408,7 +409,7 @@ public void should_remove_pool_if_node_is_forced_down() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -439,7 +440,7 @@ public void should_recreate_pool_if_node_is_forced_back_up() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -469,7 +470,7 @@ public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -501,7 +502,7 @@ public void should_adjust_distance_if_changed_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -544,7 +545,7 @@ public void should_remove_pool_if_ignored_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -587,7 +588,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -625,7 +626,7 @@ public void should_close_all_pools_when_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -655,7 +656,7 @@ public void should_force_close_all_pools_when_force_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -690,7 +691,7 @@ public void should_close_pool_if_recreated_while_closing() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -728,7 +729,7 @@ public void should_set_keyspace_on_all_pools() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -763,7 +764,7 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index b9fb40627d4..77bac43401b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; @@ -36,10 +36,10 @@ public class ProtocolVersionInitialNegotiationIT { ) @Test public void should_downgrade_to_v3() { - try (Cluster v3cluster = ClusterUtils.newCluster(ccm)) { + try (Cluster v3cluster = ClusterUtils.newCluster(ccm)) { assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - Session session = v3cluster.connect(); + CqlSession session = v3cluster.connect(); session.execute("select * from system.local"); } } @@ -51,10 +51,10 @@ public void should_downgrade_to_v3() { ) @Test public void should_fail_if_provided_version_isnt_supported() { - try (Cluster v4cluster = ClusterUtils.newCluster(ccm, "protocol.version = V4")) { + try (Cluster v4cluster = ClusterUtils.newCluster(ccm, "protocol.version = V4")) { assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - Session session = v4cluster.connect(); + CqlSession session = v4cluster.connect(); session.execute("select * from system.local"); } catch (AllNodesFailedException anfe) { Throwable cause = anfe.getErrors().values().iterator().next(); @@ -68,10 +68,10 @@ public void should_fail_if_provided_version_isnt_supported() { @CassandraRequirement(min = "2.2", description = "required to meet default protocol version") @Test public void should_not_downgrade_if_server_supports_latest_version() { - try (Cluster v4cluster = ClusterUtils.newCluster(ccm)) { + try (Cluster v4cluster = ClusterUtils.newCluster(ccm)) { assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(4); - Session session = v4cluster.connect(); + CqlSession session = v4cluster.connect(); session.execute("select * from system.local"); } } @@ -79,10 +79,10 @@ public void should_not_downgrade_if_server_supports_latest_version() { @CassandraRequirement(min = "2.2", description = "required to use an older protocol version") @Test public void should_use_explicitly_provided_protocol_version() { - try (Cluster v3cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { + try (Cluster v3cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - Session session = v3cluster.connect(); + CqlSession session = v3cluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 7ba204e94e6..2338ab4e4db 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; @@ -46,7 +47,7 @@ public class ProtocolVersionMixedClusterIT { public void should_downgrade_if_peer_does_not_support_negotiated_version() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.1.0"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = + Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader()) @@ -86,7 +87,7 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { public void should_keep_current_if_supported_by_all_peers() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "3.11"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = + Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader()) @@ -112,7 +113,7 @@ public void should_fail_if_peer_does_not_support_v3() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.0.9", "3.11"); BoundNode contactPoint = simulacron.node(0); - Cluster ignored = + Cluster ignored = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader()) @@ -125,7 +126,7 @@ public void should_fail_if_peer_does_not_support_v3() { public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.0.0"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = + Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader("protocol.version = V4")) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index 30a20eeab6a..384385d706f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.LongTests; @@ -37,34 +37,34 @@ public class PlainTextAuthProviderIT { @Test public void should_connect_with_credentials() { - try (Cluster authCluster = + try (Cluster authCluster = ClusterUtils.newCluster( ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = cassandra", "protocol.auth-provider.password = cassandra")) { - Session session = authCluster.connect(); + CqlSession session = authCluster.connect(); session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_invalid_credentials() { - try (Cluster authCluster = + try (Cluster authCluster = ClusterUtils.newCluster( ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = baduser", "protocol.auth-provider.password = badpass")) { - Session session = authCluster.connect(); + CqlSession session = authCluster.connect(); session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_without_credentials() { - try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { - Session session = plainCluster.connect(); + try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { + CqlSession session = plainCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 20411504ec7..f42532f4b99 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -56,8 +56,8 @@ public class DriverConfigProfileIT { @Test public void should_fail_if_config_profile_specified_doesnt_exist() { - try (Cluster profileCluster = ClusterUtils.newCluster(simulacron)) { - Session session = profileCluster.connect(); + try (Cluster profileCluster = ClusterUtils.newCluster(simulacron)) { + CqlSession session = profileCluster.connect(); SimpleStatement statement = SimpleStatement.builder("select * from system.local") @@ -72,12 +72,12 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { @Test public void should_use_profile_request_timeout() { - try (Cluster profileCluster = + try (Cluster profileCluster = ClusterUtils.newCluster(simulacron, "profiles.olap.request.timeout = 10s")) { String query = "mockquery"; // configure query with delay of 2 seconds. simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); - Session session = profileCluster.connect(); + CqlSession session = profileCluster.connect(); // Execute query without profile, should timeout with default (0.5s). try { @@ -94,13 +94,13 @@ public void should_use_profile_request_timeout() { @Test public void should_use_profile_default_idempotence() { - try (Cluster profileCluster = + try (Cluster profileCluster = ClusterUtils.newCluster(simulacron, "profiles.idem.request.default-idempotence = true")) { String query = "mockquery"; // configure query with server error which should invoke onRequestError in retry policy. simulacron.cluster().prime(when(query).then(serverError("fail"))); - Session session = profileCluster.connect(); + CqlSession session = profileCluster.connect(); // Execute query without profile, should fail because couldn't be retried. try { @@ -118,14 +118,14 @@ public void should_use_profile_default_idempotence() { @Test public void should_use_profile_consistency() { - try (Cluster profileCluster = + try (Cluster profileCluster = ClusterUtils.newCluster( simulacron, "profiles.cl.request.consistency = LOCAL_QUORUM", "profiles.cl.request.serial-consistency = LOCAL_SERIAL")) { String query = "mockquery"; - Session session = profileCluster.connect(); + CqlSession session = profileCluster.connect(); // Execute query without profile, should use default CLs (LOCAL_ONE, SERIAL). session.execute(query); @@ -173,7 +173,7 @@ public void should_use_profile_consistency() { @Test public void should_use_profile_page_size() { - try (Cluster profileCluster = + try (Cluster profileCluster = ClusterUtils.newCluster( ccm, "request.page-size = 100", "profiles.smallpages.request.page-size = 10")) { @@ -181,7 +181,7 @@ public void should_use_profile_page_size() { DriverConfigProfile slowProfile = ClusterUtils.slowProfile(profileCluster); ClusterUtils.createKeyspace(profileCluster, keyspace, slowProfile); - Session session = profileCluster.connect(keyspace); + CqlSession session = profileCluster.connect(keyspace); // load 500 rows (value beyond page size). session.execute( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index 64987864ff6..7e340f2f93a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -29,7 +29,6 @@ import com.typesafe.config.ConfigFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.Rule; import org.junit.Test; @@ -60,14 +59,14 @@ public void should_periodically_reload_configuration() throws Exception { ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = + try (Cluster configCluster = Cluster.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(2, TimeUnit.SECONDS)); - Session session = configCluster.connect(); + CqlSession session = configCluster.connect(); // Expect timeout since default timeout is .5 s try { @@ -97,14 +96,14 @@ public void should_reload_configuration_when_event_fired() throws Exception { ConfigFactory.parseString("config-reload-interval = 0\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = + try (Cluster configCluster = Cluster.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(2, TimeUnit.SECONDS)); - Session session = configCluster.connect(); + CqlSession session = configCluster.connect(); // Expect timeout since default timeout is .5 s try { @@ -137,14 +136,14 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = + try (Cluster configCluster = Cluster.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); - Session session = configCluster.connect(); + CqlSession session = configCluster.connect(); // Expect failure because profile doesn't exist. try { @@ -178,14 +177,14 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = + try (Cluster configCluster = Cluster.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(1, TimeUnit.SECONDS)); - Session session = configCluster.connect(); + CqlSession session = configCluster.connect(); // Expect failure because profile doesn't exist. try { @@ -204,7 +203,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio } } - private void waitForConfigChange(Cluster cluster, long timeout, TimeUnit unit) { + private void waitForConfigChange(Cluster cluster, long timeout, TimeUnit unit) { CountDownLatch latch = new CountDownLatch(1); ((InternalDriverContext) cluster.getContext()) .eventBus() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index f6c3897613b..962d02e3fff 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; @@ -56,11 +56,11 @@ public void setupSchema() { @Ignore public void should_not_allow_unset_value_on_bound_statement_when_protocol_less_than_v4() { // TODO reenable this if JAVA-1584 is fixed. - try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { + try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); DriverConfigProfile slowProfile = ClusterUtils.slowProfile(v3Cluster); ClusterUtils.createKeyspace(v3Cluster, keyspace, slowProfile); - Session session = v3Cluster.connect(keyspace); + CqlSession session = v3Cluster.connect(keyspace); PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0, v1) values (?, ?, ?)"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index c7067a1dd41..fadc5535289 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -187,7 +187,7 @@ public void should_send_heartbeat_on_interval() throws InterruptedException { checkThat(() -> controlNodeHeartbeats.get() > 0).becomesTrue(); // Create session to initialize pools. - Session session = cluster.cluster().connect(); + CqlSession session = cluster.cluster().connect(); // Ensure we get a heartbeat after a second. AtomicInteger heartbeats = new AtomicInteger(); @@ -229,7 +229,7 @@ public void should_send_heartbeat_when_requests_being_written_but_nothing_receiv AtomicInteger heartbeats = registerHeartbeatListener(); // Send requests over 2.5 seconds. - Session session = cluster.cluster().connect(); + CqlSession session = cluster.cluster().connect(); for (int i = 0; i < 25; i++) { session.executeAsync(noResponseQueryStr); session.executeAsync(noResponseQueryStr); @@ -281,7 +281,7 @@ public void should_close_connection_when_heartbeat_times_out() { @Category(LongTests.class) public void should_not_send_heartbeat_when_disabled() throws InterruptedException { // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any - try (Cluster cluster = + try (Cluster cluster = ClusterUtils.newCluster(simulacron, "connection.heartbeat.interval = 0 second")) { cluster.connect(); AtomicInteger heartbeats = registerHeartbeatListener(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 364ae852126..33755508fa7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -241,7 +242,7 @@ public void should_ignore_down_topology_event_when_still_connected() throws Inte public void should_force_immediate_reconnection_when_up_topology_event() throws InterruptedException { // This test requires a longer reconnection interval, so create a separate driver instance - try (Cluster localCluster = + try (Cluster localCluster = ClusterUtils.newCluster( simulacron, "connection.reconnection-policy.base-delay = 1 hour", diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index d5a0e947e82..d68186ded97 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -57,8 +57,8 @@ public void should_not_start_speculative_executions_if_not_idempotent() { primeNode( 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY.setIdempotent(false)); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -76,8 +76,8 @@ public void should_complete_from_first_speculative_execution_if_faster() { 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); primeNode(1, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); @@ -96,8 +96,8 @@ public void should_complete_from_initial_execution_if_speculative_is_started_but primeNode( 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -117,8 +117,8 @@ public void should_complete_from_second_speculative_execution_if_faster() { 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); primeNode(2, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); @@ -136,8 +136,8 @@ public void should_retry_within_initial_execution() { primeNode(0, when(QUERY_STRING).then(isBootstrapping())); primeNode(1, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -157,8 +157,8 @@ public void should_retry_within_speculative_execution() { primeNode(1, when(QUERY_STRING).then(isBootstrapping())); primeNode(2, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); @@ -179,8 +179,8 @@ public void should_wait_for_last_execution_to_complete() { primeNode(1, when(QUERY_STRING).then(isBootstrapping())); primeNode(2, when(QUERY_STRING).then(isBootstrapping())); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -203,8 +203,8 @@ public void should_fail_if_all_executions_reach_end_of_query_plan() { .then(isBootstrapping()) .delay((3 - i) * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); } - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { + CqlSession session = cluster.connect(); session.execute(QUERY); } finally { assertQueryCount(0, 1); @@ -222,8 +222,8 @@ public void should_allow_zero_delay() { } primeNode(2, when(QUERY_STRING).then(noRows()).delay(SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(3, 0)) { - Session session = cluster.connect(); + try (Cluster cluster = buildCluster(3, 0)) { + CqlSession session = cluster.connect(); ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); @@ -236,7 +236,7 @@ public void should_allow_zero_delay() { } // Build a new Cluster instance for each test, because we need different configurations - private Cluster buildCluster(int maxSpeculativeExecutions, long speculativeDelayMs) { + private Cluster buildCluster(int maxSpeculativeExecutions, long speculativeDelayMs) { return ClusterUtils.newCluster( simulacron, String.format("request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index 0ed630390b4..9c42b1e5f95 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; @@ -36,11 +36,11 @@ public void should_connect_with_ssl() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = + try (Cluster sslCluster = ClusterUtils.newCluster( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - Session session = sslCluster.connect(); + CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index 87a8e238983..f424bbe1466 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; @@ -40,11 +40,11 @@ public void should_connect_with_ssl_using_client_auth() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = + try (Cluster sslCluster = ClusterUtils.newCluster( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - Session session = sslCluster.connect(); + CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java index 5ed97192356..d04b763dee9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; @@ -37,11 +37,11 @@ public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = + try (Cluster sslCluster = ClusterUtils.newCluster( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - Session session = sslCluster.connect(); + CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java index bfecd7dad9a..4a924bd6a6e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; @@ -32,8 +32,8 @@ public class DefaultSslEngineFactoryWithTruststoreNotProvidedIT { @Test(expected = AllNodesFailedException.class) public void should_not_connect_if_not_using_ssl() { - try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { - Session session = plainCluster.connect(); + try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { + CqlSession session = plainCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 17860b3a52a..142baf1ae1e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; @@ -153,12 +153,12 @@ public void should_throw_exception_if_no_codec_registered_for_type_get() { @Test public void should_be_able_to_register_and_use_custom_codec() { // create a cluster with a registered codec from Float <-> cql int. - try (Cluster codecCluster = + try (Cluster codecCluster = Cluster.builder() .addTypeCodecs(new FloatCIntCodec()) .addContactPoints(ccm.getContactPoints()) .build()) { - Session session = codecCluster.connect(cluster.keyspace()); + CqlSession session = codecCluster.connect(cluster.keyspace()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); @@ -269,12 +269,12 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() TypeCodec>> mapWithOptionalValueCodec = TypeCodecs.mapOf(TypeCodecs.INT, new OptionalCodec<>(TypeCodecs.TEXT)); - try (Cluster codecCluster = + try (Cluster codecCluster = Cluster.builder() .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) .addContactPoints(ccm.getContactPoints()) .build()) { - Session session = codecCluster.connect(cluster.keyspace()); + CqlSession session = codecCluster.connect(cluster.keyspace()); PreparedStatement prepared = session.prepare("INSERT INTO test2 (k0, k1, v) values (?, ?, ?)"); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 411a6011577..f5693a8a0ed 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import org.junit.rules.ExternalResource; @@ -57,10 +57,10 @@ public class ClusterRule extends ExternalResource { private final String[] defaultClusterOptions; // the default cluster that is auto created for this rule. - private Cluster cluster; + private Cluster cluster; // the default session that is auto created for this rule and is tied to the given keyspace. - private Session defaultSession; + private CqlSession defaultSession; private DriverConfigProfile slowProfile; @@ -118,7 +118,7 @@ protected void after() { } /** @return the cluster created with this rule. */ - public Cluster cluster() { + public Cluster cluster() { return cluster; } @@ -126,7 +126,7 @@ public Cluster cluster() { * @return the default session created with this rule, or {@code null} if no default session was * created. */ - public Session session() { + public CqlSession session() { return defaultSession; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index 1fb1bde2c1e..f31696ba52e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import java.util.concurrent.atomic.AtomicInteger; @@ -59,7 +59,8 @@ public class ClusterUtils { * in the 0th DC of the provided Cassandra resource as contact points, and the default * configuration augmented with the provided options. */ - public static Cluster newCluster(CassandraResourceRule cassandraResource, String... options) { + public static Cluster newCluster( + CassandraResourceRule cassandraResource, String... options) { return Cluster.builder() .addContactPoints(cassandraResource.getContactPoints()) .withConfigLoader(new TestConfigLoader(options)) @@ -77,8 +78,8 @@ public static CqlIdentifier uniqueKeyspaceId() { /** Creates a keyspace through the given cluster instance, with the given profile. */ public static void createKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { - try (Session session = cluster.connect()) { + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + try (CqlSession session = cluster.connect()) { SimpleStatement createKeyspace = SimpleStatement.builder( String.format( @@ -98,14 +99,14 @@ public static void createKeyspace( * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, * and storing it in a local variable so it can be reused. */ - public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { + public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { createKeyspace(cluster, keyspace, slowProfile(cluster)); } /** Drops a keyspace through the given cluster instance, with the given profile. */ public static void dropKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { - try (Session session = cluster.connect()) { + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + try (CqlSession session = cluster.connect()) { session.execute( SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql())) .withConfigProfile(profile) @@ -121,7 +122,7 @@ public static void dropKeyspace( * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, * and storing it in a local variable so it can be reused. */ - public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { + public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { dropKeyspace(cluster, keyspace, slowProfile(cluster)); } @@ -129,7 +130,7 @@ public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { * Builds a profile derived from the given cluster's default profile, with a higher request * timeout (30 seconds) that is appropriate for DML operations. */ - public static DriverConfigProfile slowProfile(Cluster cluster) { + public static DriverConfigProfile slowProfile(Cluster cluster) { return cluster .getContext() .config() From 18d298632fe2b32c780c7b92066252c5da3c933b Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 10 Oct 2017 16:10:31 -0500 Subject: [PATCH 209/742] JAVA-1605: Add integration tests for custom request processors --- integration-tests/pom.xml | 5 + .../driver/api/core/session/GuavaCluster.java | 36 +++++ .../api/core/session/GuavaClusterBuilder.java | 37 +++++ .../api/core/session/GuavaDriverContext.java | 67 ++++++++ .../session/GuavaRequestAsyncProcessor.java | 91 +++++++++++ .../driver/api/core/session/GuavaSession.java | 54 +++++++ .../driver/api/core/session/KeyRequest.java | 64 ++++++++ .../api/core/session/KeyRequestProcessor.java | 82 ++++++++++ .../api/core/session/RequestProcessorIT.java | 146 ++++++++++++++++++ 9 files changed, 582 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index df8befee4c4..966148afdb6 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -48,6 +48,11 @@ logback-classic test + + com.google.guava + guava + test + diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java new file mode 100644 index 00000000000..0bd0212bd7a --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java @@ -0,0 +1,36 @@ + +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.internal.core.ClusterWrapper; + +public class GuavaCluster extends ClusterWrapper { + + GuavaCluster(Cluster delegate) { + super(delegate); + } + + @Override + protected GuavaSession wrap(CqlSession session) { + return new GuavaSession(session); + } + + public static GuavaClusterBuilder builder() { + return new GuavaClusterBuilder(); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java new file mode 100644 index 00000000000..5378193aba4 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.ClusterBuilder; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import java.util.List; + +public class GuavaClusterBuilder extends ClusterBuilder { + + @Override + protected DriverContext buildContext( + DriverConfigLoader configLoader, List> typeCodecs) { + return new GuavaDriverContext(configLoader, typeCodecs); + } + + @Override + protected GuavaCluster wrap(Cluster defaultCluster) { + return new GuavaCluster(defaultCluster); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java new file mode 100644 index 00000000000..4c45055cd24 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; +import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; +import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; +import com.google.common.collect.MapMaker; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +/** + * A Custom {@link DefaultDriverContext} that overrides {@link #requestProcessorRegistry()} to + * return a {@link RequestProcessorRegistry} that includes processors for returning guava futures. + */ +public class GuavaDriverContext extends DefaultDriverContext { + + GuavaDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { + super(configLoader, typeCodecs); + } + + @Override + public RequestProcessorRegistry requestProcessorRegistry() { + // Register the typical request processors, except instead of the normal async processors, + // use GuavaRequestAsyncProcessor to return ListenableFutures in async methods. + ConcurrentMap preparedStatementsCache = + new MapMaker().weakValues().makeMap(); + + CqlRequestAsyncProcessor cqlRequestAsyncProcessor = new CqlRequestAsyncProcessor(); + CqlPrepareAsyncProcessor cqlPrepareAsyncProcessor = + new CqlPrepareAsyncProcessor(preparedStatementsCache); + CqlRequestSyncProcessor cqlRequestSyncProcessor = new CqlRequestSyncProcessor(); + + return new RequestProcessorRegistry( + clusterName(), + cqlRequestSyncProcessor, + new CqlPrepareSyncProcessor(preparedStatementsCache), + new GuavaRequestAsyncProcessor<>( + cqlRequestAsyncProcessor, Statement.class, GuavaSession.ASYNC), + new GuavaRequestAsyncProcessor<>( + cqlPrepareAsyncProcessor, PrepareRequest.class, GuavaSession.ASYNC_PREPARED), + // Register KeyRequestProcessor for handling KeyRequest and returning Integer. + new KeyRequestProcessor(cqlRequestSyncProcessor)); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java new file mode 100644 index 00000000000..3ce154f93d1 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import java.util.concurrent.CompletionStage; + +/** + * Wraps a {@link RequestProcessor} that returns {@link CompletionStage}s and converts them to a + * {@link ListenableFuture}s. + * + * @param The type of request + * @param The type of responses enclosed in the future response. + */ +public class GuavaRequestAsyncProcessor + implements RequestProcessor> { + + private final RequestProcessor> subProcessor; + + private final GenericType resultType; + + private final Class requestClass; + + GuavaRequestAsyncProcessor( + RequestProcessor> subProcessor, + Class requestClass, + GenericType resultType) { + this.subProcessor = subProcessor; + this.requestClass = requestClass; + this.resultType = resultType; + } + + @Override + public boolean canProcess(Request request, GenericType resultType) { + return requestClass.isInstance(request) && resultType.equals(this.resultType); + } + + @Override + public RequestHandler> newHandler( + T request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { + return new GuavaRequestHandler( + subProcessor.newHandler(request, session, context, sessionLogPrefix)); + } + + class GuavaRequestHandler implements RequestHandler> { + + private final RequestHandler> subHandler; + + GuavaRequestHandler(RequestHandler> subHandler) { + this.subHandler = subHandler; + } + + @Override + public ListenableFuture handle() { + // convert CompletionStage to ListenableFuture by adding a whenComplete listener that sets the + // listenable future's result. + SettableFuture future = SettableFuture.create(); + subHandler + .handle() + .whenComplete( + (r, ex) -> { + if (ex != null) { + future.setException(ex); + } else { + future.set(r); + } + }); + return future; + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java new file mode 100644 index 00000000000..dac155976cb --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; +import com.datastax.oss.driver.internal.core.session.SessionWrapper; +import com.google.common.util.concurrent.ListenableFuture; + +public class GuavaSession extends SessionWrapper { + + static final GenericType> ASYNC = + new GenericType>() {}; + + static final GenericType> ASYNC_PREPARED = + new GenericType>() {}; + + GuavaSession(Session delegate) { + super(delegate); + } + + ListenableFuture executeAsync(Statement statement) { + return this.execute(statement, ASYNC); + } + + ListenableFuture executeAsync(String statement) { + return this.executeAsync(SimpleStatement.newInstance(statement)); + } + + ListenableFuture prepareAsync(SimpleStatement statement) { + return this.execute(new DefaultPrepareRequest(statement), ASYNC_PREPARED); + } + + ListenableFuture prepareAsync(String statement) { + return this.prepareAsync(SimpleStatement.newInstance(statement)); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java new file mode 100644 index 00000000000..c5022e09690 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import java.nio.ByteBuffer; +import java.util.Map; + +/** A custom request that simply wraps an integer key and uses it as a parameter for a query. */ +public class KeyRequest implements Request { + + private final int key; + + public KeyRequest(int key) { + this.key = key; + } + + public int getKey() { + return key; + } + + @Override + public String getConfigProfileName() { + return null; + } + + @Override + public DriverConfigProfile getConfigProfile() { + return null; + } + + @Override + public String getKeyspace() { + return null; + } + + @Override + public Map getCustomPayload() { + return null; + } + + @Override + public Boolean isIdempotent() { + return true; + } + + @Override + public boolean isTracing() { + return false; + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java new file mode 100644 index 00000000000..d08e8dd2772 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.RequestHandler; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; + +/** + * A request processor that takes a given {@link KeyRequest#getKey} and generates a query, delegates + * it to {@link CqlRequestSyncProcessor} to get the integer value of a row and return it as a + * result. + */ +public class KeyRequestProcessor implements RequestProcessor { + + static final GenericType INT_TYPE = GenericType.of(Integer.class); + + private final CqlRequestSyncProcessor subProcessor; + + KeyRequestProcessor(CqlRequestSyncProcessor subProcessor) { + this.subProcessor = subProcessor; + } + + @Override + public boolean canProcess(Request request, GenericType resultType) { + return request instanceof KeyRequest && resultType.equals(INT_TYPE); + } + + @Override + public RequestHandler newHandler( + KeyRequest request, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + // Create statement from key and delegate it to CqlRequestSyncProcessor + SimpleStatement statement = + SimpleStatement.newInstance( + "select v1 from test where k = ? and v0 = ?", RequestProcessorIT.KEY, request.getKey()); + RequestHandler, ResultSet> subHandler = + subProcessor.newHandler(statement, session, context, sessionLogPrefix); + return new KeyRequestHandler(subHandler); + } + + class KeyRequestHandler implements RequestHandler { + + private final RequestHandler, ResultSet> subHandler; + + KeyRequestHandler(RequestHandler, ResultSet> subHandler) { + this.subHandler = subHandler; + } + + @Override + public Integer handle() { + ResultSet result = subHandler.handle(); + // If not exactly 1 rows were found, return Integer.MIN_VALUE, otherwise return the value. + if (result.getAvailableWithoutFetching() != 1) { + return Integer.MIN_VALUE; + } else { + return result.iterator().next().getInt("v1"); + } + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java new file mode 100644 index 00000000000..0cf3aff2e6f --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.Uninterruptibles; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A suite of tests for exercising registration of custom {@link + * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations to add-in + * additional request handling and response types. + * + *

      Uses {@link GuavaCluster} which is a specialized cluster implementation that uses {@link + * GuavaDriverContext} which overrides {@link DefaultDriverContext#requestProcessorRegistry()} to + * provide its own {@link com.datastax.oss.driver.internal.core.session.RequestProcessor} + * implementations for returning {@link ListenableFuture}s rather than {@link + * java.util.concurrent.CompletionStage}s in async method responses. + * + *

      {@link GuavaSession} provides execute method implementation shortcuts that mimics {@link + * CqlSession}'s async methods. + * + *

      {@link KeyRequestProcessor} is also registered for handling {@link KeyRequest}s which + * simplifies a certain query down to 1 parameter. + */ +public class RequestProcessorIT { + + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + static final String KEY = "test"; + + @BeforeClass + public static void setupSchema() { + // table with clustering key where v1 == v0 * 2. + cluster + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test (k text, v0 int, v1 int, PRIMARY KEY(k, v0))") + .withConfigProfile(cluster.slowProfile()) + .build()); + for (int i = 0; i < 100; i++) { + cluster + .session() + .execute( + SimpleStatement.builder("INSERT INTO test (k, v0, v1) VALUES (?, ?, ?)") + .addPositionalValues(KEY, i, i * 2) + .build()); + } + } + + private GuavaCluster newCluster(String... options) { + return GuavaCluster.builder() + .addContactPoints(ccm.getContactPoints()) + .withConfigLoader(new TestConfigLoader(options)) + .build(); + } + + @Test + public void should_use_custom_request_processor_for_prepareAsync() throws Exception { + try (GuavaCluster gCluster = newCluster()) { + GuavaSession session = gCluster.connect(cluster.keyspace()); + ListenableFuture preparedFuture = + session.prepareAsync("select * from test"); + + PreparedStatement prepared = Uninterruptibles.getUninterruptibly(preparedFuture); + + assertThat(prepared.getResultSetDefinitions().contains("k")).isTrue(); + assertThat(prepared.getResultSetDefinitions().contains("v0")).isTrue(); + assertThat(prepared.getResultSetDefinitions().contains("v1")).isTrue(); + + ListenableFuture future = session.executeAsync(prepared.bind()); + AsyncResultSet result = Uninterruptibles.getUninterruptibly(future); + assertThat(Iterables.size(result.currentPage())).isEqualTo(100); + } + } + + @Test + public void should_use_custom_request_processor_for_handling_special_request_type() + throws Exception { + try (GuavaCluster gCluster = newCluster()) { + GuavaSession session = gCluster.connect(cluster.keyspace()); + + // RequestProcessor executes "select v from test where k = " and returns v as Integer. + int v1 = session.execute(new KeyRequest(5), KeyRequestProcessor.INT_TYPE); + assertThat(v1).isEqualTo(10); // v1 = v0 * 2 + + // RequestProcessor returns Integer.MIN_VALUE if key not found in data (no rows in result). + v1 = session.execute(new KeyRequest(200), KeyRequestProcessor.INT_TYPE); + assertThat(v1).isEqualTo(Integer.MIN_VALUE); + } + } + + @Test + public void should_use_custom_request_processor_for_executeAsync() throws Exception { + try (GuavaCluster gCluster = newCluster()) { + GuavaSession session = gCluster.connect(cluster.keyspace()); + + ListenableFuture future = session.executeAsync("select * from test"); + AsyncResultSet result = Uninterruptibles.getUninterruptibly(future); + assertThat(Iterables.size(result.currentPage())).isEqualTo(100); + } + } + + @Test + public void should_throw_illegal_argument_exception_if_no_matching_processor_found() + throws Exception { + // Since cluster does not have a processor registered for returning ListenableFuture, an IllegalArgumentException + // should be thrown. + thrown.expect(IllegalArgumentException.class); + cluster + .session() + .execute(SimpleStatement.newInstance("select * from test"), GuavaSession.ASYNC); + } +} From a927578784a4530baf5c8f485491301bfd5dcf83 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 20 Apr 2017 10:55:54 -0700 Subject: [PATCH 210/742] JAVA-1493: Handle schema metadata --- changelog/README.md | 1 + .../oss/driver/api/core/CassandraVersion.java | 4 + .../datastax/oss/driver/api/core/Cluster.java | 55 ++ .../oss/driver/api/core/CqlIdentifier.java | 25 +- .../api/core/config/CoreDriverOption.java | 6 + .../driver/api/core/metadata/Metadata.java | 17 +- .../metadata/schema/AggregateMetadata.java | 127 ++++ .../core/metadata/schema/ClusteringOrder.java | 22 + .../core/metadata/schema/ColumnMetadata.java | 37 ++ .../api/core/metadata/schema/Describable.java | 41 ++ .../metadata/schema/FunctionMetadata.java | 87 +++ .../metadata/schema/FunctionSignature.java | 67 +++ .../api/core/metadata/schema/IndexKind.java | 23 + .../core/metadata/schema/IndexMetadata.java | 104 ++++ .../metadata/schema/KeyspaceMetadata.java | 138 +++++ .../metadata/schema/RelationMetadata.java | 52 ++ .../metadata/schema/SchemaChangeListener.java | 77 +++ .../schema/SchemaChangeListenerBase.java | 88 +++ .../core/metadata/schema/TableMetadata.java | 112 ++++ .../core/metadata/schema/ViewMetadata.java | 101 ++++ .../oss/driver/api/core/type/CustomType.java | 5 + .../oss/driver/api/core/type/DataType.java | 13 + .../oss/driver/api/core/type/DataTypes.java | 26 +- .../oss/driver/api/core/type/ListType.java | 6 + .../oss/driver/api/core/type/MapType.java | 9 + .../oss/driver/api/core/type/SetType.java | 6 + .../oss/driver/api/core/type/TupleType.java | 20 + .../driver/api/core/type/UserDefinedType.java | 47 +- .../CassandraProtocolVersionRegistry.java | 7 +- .../driver/internal/core/ClusterWrapper.java | 26 + .../driver/internal/core/DefaultCluster.java | 61 +- .../internal/core/SchemaListenerNotifier.java | 180 ++++++ .../adminrequest/AdminRequestHandler.java | 2 +- .../core/adminrequest/AdminResult.java | 104 +--- .../internal/core/adminrequest/AdminRow.java | 105 ++++ .../core/channel/InFlightHandler.java | 2 +- .../core/channel/ProtocolInitHandler.java | 2 +- .../core/context/DefaultDriverContext.java | 26 + .../core/context/InternalDriverContext.java | 6 + .../core/control/ControlConnection.java | 34 +- .../core/cql/CqlRequestHandlerBase.java | 7 +- .../core/cql/DefaultColumnDefinition.java | 8 +- .../core/metadata/AddNodeRefresh.java | 21 +- .../core/metadata/DefaultMetadata.java | 38 +- .../core/metadata/DefaultTopologyMonitor.java | 13 +- .../core/metadata/FullNodeListRefresh.java | 19 +- .../metadata/InitContactPointsRefresh.java | 10 +- .../core/metadata/MetadataManager.java | 249 +++++++- .../core/metadata/MetadataRefresh.java | 30 +- .../internal/core/metadata/NodesRefresh.java | 17 +- .../core/metadata/RemoveNodeRefresh.java | 14 +- .../core/metadata/SchemaElementKind.java | 57 -- .../schema/DefaultAggregateMetadata.java | 136 +++++ .../schema/DefaultColumnMetadata.java | 88 +++ .../schema/DefaultFunctionMetadata.java | 116 ++++ .../metadata/schema/DefaultIndexMetadata.java | 98 +++ .../schema/DefaultKeyspaceMetadata.java | 123 ++++ .../metadata/schema/DefaultTableMetadata.java | 130 ++++ .../metadata/schema/DefaultViewMetadata.java | 150 +++++ .../metadata/schema/SchemaChangeType.java | 23 + .../core/metadata/schema/ScriptBuilder.java | 103 ++++ .../schema/ShallowUserDefinedType.java | 128 ++++ .../schema/events/AggregateChangeEvent.java | 84 +++ .../schema/events/FunctionChangeEvent.java | 84 +++ .../schema/events/KeyspaceChangeEvent.java | 83 +++ .../schema/events/TableChangeEvent.java | 82 +++ .../schema/events/TypeChangeEvent.java | 82 +++ .../schema/events/ViewChangeEvent.java | 81 +++ .../schema/parsing/AggregateParser.java | 121 ++++ .../DataTypeClassNameCompositeParser.java | 99 ++++ .../parsing/DataTypeClassNameParser.java | 369 ++++++++++++ .../schema/parsing/DataTypeCqlNameParser.java | 315 ++++++++++ .../schema/parsing/DataTypeParser.java | 61 ++ .../parsing/DefaultSchemaParserFactory.java | 33 ++ .../schema/parsing/FunctionParser.java | 106 ++++ .../metadata/schema/parsing/RawColumn.java | 145 +++++ .../schema/parsing/RelationParser.java | 143 +++++ .../metadata/schema/parsing/SchemaParser.java | 163 +++++ .../schema/parsing/SchemaParserFactory.java | 22 + .../schema/parsing/SimpleJsonParser.java | 179 ++++++ .../metadata/schema/parsing/TableParser.java | 314 ++++++++++ .../schema/parsing/UserDefinedTypeParser.java | 160 +++++ .../metadata/schema/parsing/ViewParser.java | 148 +++++ .../queries/Cassandra21SchemaQueries.java | 72 +++ .../queries/Cassandra22SchemaQueries.java | 72 +++ .../queries/Cassandra3SchemaQueries.java | 72 +++ .../queries/DefaultSchemaQueriesFactory.java | 75 +++ .../schema/queries/SchemaQueries.java | 192 ++++++ .../schema/queries/SchemaQueriesFactory.java | 23 + .../metadata/schema/queries/SchemaRows.java | 209 +++++++ .../schema/refresh/SchemaRefresh.java | 151 +++++ .../internal/core/session/DefaultSession.java | 2 +- .../internal/core/session/ReprepareOnUp.java | 3 +- .../internal/core/type/DataTypeHelper.java | 1 + .../core/type/DefaultUserDefinedType.java | 25 +- .../internal/core/type/PrimitiveType.java | 5 + .../core/type/UserDefinedTypeBuilder.java | 9 +- .../internal/core/type/codec/ParseUtils.java | 4 +- .../internal/core/type/codec/UdtCodec.java | 2 +- .../internal/core/util/DirectedGraph.java | 102 ++++ .../internal/core/util/ImmutableMaps.java | 32 + .../driver/internal/core/util/NanoTime.java | 43 ++ .../core/util/concurrent/Debouncer.java | 8 +- core/src/main/resources/reference.conf | 27 + .../driver/api/core/CqlIdentifierTest.java | 20 +- .../api/core/type/UserDefinedTypeTest.java | 65 ++ .../control/ControlConnectionEventsTest.java | 14 +- .../core/control/ControlConnectionTest.java | 32 +- .../core/metadata/AddNodeRefreshTest.java | 16 +- .../metadata/DefaultTopologyMonitorTest.java | 27 +- .../metadata/FullNodeListRefreshTest.java | 16 +- .../InitContactPointsRefreshTest.java | 9 +- .../core/metadata/MetadataManagerTest.java | 25 +- .../core/metadata/RemoveNodeRefreshTest.java | 16 +- .../schema/parsing/AggregateParserTest.java | 110 ++++ .../parsing/DataTypeClassNameParserTest.java | 180 ++++++ .../parsing/DataTypeCqlNameParserTest.java | 119 ++++ .../schema/parsing/FunctionParserTest.java | 83 +++ .../schema/parsing/SchemaParserTest.java | 144 +++++ .../schema/parsing/SchemaParserTestBase.java | 292 +++++++++ .../schema/parsing/TableParserTest.java | 188 ++++++ .../UserDefinedTypeListParserTest.java | 227 +++++++ .../schema/parsing/ViewParserTest.java | 93 +++ .../queries/Cassandra21SchemaQueriesTest.java | 130 ++++ .../queries/Cassandra22SchemaQueriesTest.java | 150 +++++ .../queries/Cassandra3SchemaQueriesTest.java | 342 +++++++++++ .../schema/queries/SchemaQueriesTest.java | 97 +++ .../schema/refresh/SchemaRefreshTest.java | 146 +++++ .../core/type/codec/UdtCodecTest.java | 1 + .../internal/core/util/DirectedGraphTest.java | 81 +++ integration-tests/pom.xml | 5 + .../oss/driver/api/core/data/DataTypeIT.java | 11 +- .../api/core/metadata/SchemaChangesIT.java | 559 ++++++++++++++++++ .../driver/api/core/metadata/SchemaIT.java | 149 +++++ .../driver/api/testinfra/ccm/CcmBridge.java | 3 + .../api/testinfra/cluster/ClusterUtils.java | 5 +- .../internal/testinfra/ccm/BaseCcmRule.java | 2 +- 137 files changed, 10414 insertions(+), 395 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/AggregateMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ClusteringOrder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ColumnMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/Describable.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexKind.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ViewMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRow.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/SchemaChangeType.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ScriptBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ShallowUserDefinedType.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/AggregateChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/FunctionChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/KeyspaceChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TableChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TypeChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/ViewChangeEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java diff --git a/changelog/README.md b/changelog/README.md index 190270b406c..f5e39be9b14 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1493: Handle schema metadata - [improvement] JAVA-1605: Refactor request execution model - [improvement] JAVA-1597: Fix raw usages of Statement - [improvement] JAVA-1542: Enable JaCoCo code coverage diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java index bc7659eea4a..5c198650745 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java @@ -38,6 +38,10 @@ public class CassandraVersion implements Comparable { "(\\d+)\\.(\\d+)(\\.\\d+)?(\\.\\d+)?([~\\-]\\w[.\\w]*(?:\\-\\w[.\\w]*)*)?(\\+[.\\w]+)?"; private static final Pattern pattern = Pattern.compile(VERSION_REGEXP); + public static final CassandraVersion V2_1_0 = parse("2.1.0"); + public static final CassandraVersion V2_2_0 = parse("2.2.0"); + public static final CassandraVersion V3_0_0 = parse("3.0.0"); + private final int major; private final int minor; private final int patch; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 2aa37118cfa..0aa93cf502e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; @@ -75,6 +76,46 @@ static String getDriverVersion() { */ Metadata getMetadata(); + /** Whether schema metadata is currently enabled. */ + boolean isSchemaMetadataEnabled(); + + /** + * Enable or disable schema metadata programmatically. + * + *

      Use this method to override the value defined in the driver's configuration; one typical use + * case is to temporarily disable schema metadata while the client issues a sequence of DDL + * statements. + * + *

      If calling this method re-enables the metadata (that is, {@link #isSchemaMetadataEnabled()} + * was false before, and becomes true as a result of the call), a refresh is also triggered. + * + * @param newValue a boolean value to enable or disable schema metadata programmatically, or + * {@code null} to use the driver's configuration. + * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @return if this call triggered a refresh, a future that will complete when that refresh is + * complete. Otherwise, a completed future with the current metadata. + */ + CompletionStage setSchemaMetadataEnabled(Boolean newValue); + + /** + * Force an immediate refresh of the schema metadata, even if it is currently disabled (either in + * the configuration or via {@link #setSchemaMetadataEnabled(Boolean)}). + * + *

      The new metadata is returned in the resulting future (and will also be reflected by {@link + * #getMetadata()} when that future completes). + */ + CompletionStage refreshSchemaAsync(); + + /** + * Convenience method to call {@link #refreshSchemaAsync()} and block for the result. + * + *

      This must not be called on a driver thread. + */ + default Metadata refreshSchema() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(refreshSchemaAsync()); + } + /** Returns a context that provides access to all the policies used by this driver instance. */ DriverContext getContext(); @@ -108,4 +149,18 @@ default SessionT connect(CqlIdentifier keyspace) { default SessionT connect() { return connect(null); } + + /** + * Registers the provided schema change listener. + * + *

      This is a no-op if the listener was registered already. + */ + Cluster register(SchemaChangeListener listener); + + /** + * Unregisters the provided schema change listener. + * + *

      This is a no-op if the listener was not registered. + */ + Cluster unregister(SchemaChangeListener listener); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 695664a89ad..f71131b9263 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -102,22 +102,17 @@ public String asInternal() { /** * Returns the identifier in a format appropriate for concatenation in a CQL query. * - * @return the double-quoted form, always. Note that this is not the most compact representation - * of case-insensitive identifiers (see {@link #asPrettyCql()}. + * @param pretty if {@code true}, use the shortest possible representation: if the identifier is + * case-insensitive, an unquoted, lower-case string, otherwise the double-quoted form. If + * {@code false}, always use the double-quoted form (this is slightly more efficient since we + * don't need to inspect the string). */ - public String asCql() { - return Strings.doubleQuote(internal); - } - - /** - * Returns the identifier in a format appropriate for concatenation in a CQL query, using the - * simplest possible representation. - * - * @return if the identifier is case-insensitive, an unquoted, lower-case string. Otherwise, the - * double-quoted form. - */ - public String asPrettyCql() { - return Strings.needsDoubleQuotes(internal) ? Strings.doubleQuote(internal) : internal; + public String asCql(boolean pretty) { + if (pretty) { + return Strings.needsDoubleQuotes(internal) ? Strings.doubleQuote(internal) : internal; + } else { + return Strings.doubleQuote(internal); + } } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index cdd7b5da10a..96feeb08da6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -83,6 +83,12 @@ public enum CoreDriverOption implements DriverOption { METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), + METADATA_SCHEMA_ENABLED("metadata.schema.enabled", true), + METADATA_SCHEMA_REQUEST_TIMEOUT("metadata.schema.request-timeout", true), + METADATA_SCHEMA_REQUEST_PAGE_SIZE("metadata.schema.request-page-size", true), + METADATA_SCHEMA_REFRESHED_KEYSPACES("metadata.schema.refreshed-keyspaces", false), + METADATA_SCHEMA_WINDOW("metadata.schema.debouncer.window", true), + METADATA_SCHEMA_MAX_EVENTS("metadata.schema.debouncer.max-events", true), TIMESTAMP_GENERATOR_ROOT("request.timestamp-generator", true), RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 8cd44ed816b..de249a0ed6b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -16,17 +16,20 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import java.net.InetSocketAddress; import java.util.Map; /** * The metadata of the Cassandra cluster that this driver instance is connected to. * + *

      Updates to this object are guaranteed to be atomic: the node list, schema, and token metadata + * are immutable, and will always be consistent for a given metadata instance. The node instances + * are the only mutable objects in the hierarchy, and some of their fields will be modified + * dynamically (in particular the node state). + * * @see Cluster#getMetadata() - *

      Updates to this object are guaranteed to be atomic: the node list, schema, and token - * metadata are immutable, and will always be consistent for a given metadata instance. The node - * instances are the only mutable objects in the hierarchy, and some of their fields will be - * modified dynamically (in particular the node state). */ public interface Metadata { /** @@ -34,4 +37,10 @@ public interface Metadata { * might include nodes that are currently viewed as down, or ignored by the load balancing policy. */ Map getNodes(); + + Map getKeyspaces(); + + default KeyspaceMetadata getKeyspace(CqlIdentifier keyspaceId) { + return getKeyspaces().get(keyspaceId); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/AggregateMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/AggregateMetadata.java new file mode 100644 index 00000000000..8bf7f0eedb6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/AggregateMetadata.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; + +/** A CQL aggregate in the schema metadata. */ +public interface AggregateMetadata extends Describable { + CqlIdentifier getKeyspace(); + + FunctionSignature getSignature(); + + /** + * The signature of the final function of this aggregate, or {@code null} if there is none. + * + *

      This is the function specified with {@code FINALFUNC} in the {@code CREATE AGGREGATE...} + * statement. It transforms the final value after the aggregation is complete. + */ + FunctionSignature getFinalFuncSignature(); + + /** + * The initial state value of this aggregate, or {@code null} if there is none. + * + *

      This is the value specified with {@code INITCOND} in the {@code CREATE AGGREGATE...} + * statement. It's passed to the initial invocation of the state function (if that function does + * not accept null arguments). + * + *

      The actual type of the returned object depends on the aggregate's {@link #getStateType() + * state type} and on the {@link TypeCodec codec} used to {@link TypeCodec#parse(String) parse} + * the {@code INITCOND} literal. + * + *

      If, for some reason, the {@code INITCOND} literal cannot be parsed, a warning will be logged + * and the returned object will be the original {@code INITCOND} literal in its textual, + * non-parsed form. + * + * @return the initial state, or {@code null} if there is none. + */ + Object getInitCond(); + + /** + * The return type of this aggregate. + * + *

      This is the final type of the value computed by this aggregate; in other words, the return + * type of the final function if it is defined, or the state type otherwise. + */ + DataType getReturnType(); + + /** + * The signature of the state function of this aggregate. + * + *

      This is the function specified with {@code SFUNC} in the {@code CREATE AGGREGATE...} + * statement. It aggregates the current state with each row to produce a new state. + */ + FunctionSignature getStateFuncSignature(); + + /** + * The state type of this aggregate. + * + *

      This is the type specified with {@code STYPE} in the {@code CREATE AGGREGATE...} statement. + * It defines the type of the value that is accumulated as the aggregate iterates through the + * rows. + */ + DataType getStateType(); + + @Override + default String describeWithChildren(boolean pretty) { + // An aggregate has no children + return describe(pretty); + } + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = new ScriptBuilder(pretty); + builder + .append("CREATE AGGREGATE ") + .append(getKeyspace()) + .append(".") + .append(getSignature().getName()) + .append("("); + boolean first = true; + for (int i = 0; i < getSignature().getParameterTypes().size(); i++) { + if (first) { + first = false; + } else { + builder.append(","); + } + DataType type = getSignature().getParameterTypes().get(i); + builder.append(type.asCql(false, pretty)); + } + builder + .increaseIndent() + .append(")") + .newLine() + .append("SFUNC ") + .append(getStateFuncSignature().getName()) + .newLine() + .append("STYPE ") + .append(getStateType().asCql(false, pretty)); + + if (getFinalFuncSignature() != null) { + builder.newLine().append("FINALFUNC ").append(getFinalFuncSignature().getName()); + } + if (getInitCond() != null) { + builder.newLine().append("INITCOND ").append(formatInitCond()); + } + return builder.append(";").build(); + } + + /** Formats the {@link #getInitCond() initial state value} for inclusion in a CQL statement. */ + String formatInitCond(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ClusteringOrder.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ClusteringOrder.java new file mode 100644 index 00000000000..93b0d4db3e0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ClusteringOrder.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +/** The order of a clustering column in a table or materialized view. */ +public enum ClusteringOrder { + ASC, + DESC +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ColumnMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ColumnMetadata.java new file mode 100644 index 00000000000..15fbdaa5ae7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ColumnMetadata.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; + +/** A column in the schema metadata. */ +public interface ColumnMetadata { + + CqlIdentifier getKeyspace(); + + /** + * The identifier of the {@link TableMetadata} or a {@link ViewMetadata} that this column belongs + * to. + */ + CqlIdentifier getParent(); + + CqlIdentifier getName(); + + DataType getType(); + + boolean isStatic(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/Describable.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/Describable.java new file mode 100644 index 00000000000..00da342d44a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/Describable.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +/** A schema element that can be described in terms of CQL {@code CREATE} statements. */ +public interface Describable { + + /** + * Returns a single CQL statement that creates the element. + * + * @param pretty if {@code true}, make the output more human-readable (line breaks, indents, and + * {@link CqlIdentifier#asCql(boolean) pretty identifiers}). If {@code false}, return the + * statement on a single line with minimal formatting. + */ + String describe(boolean pretty); + + /** + * Returns a CQL script that creates the element and all of its children. For example: a schema + * with its tables, materialized views, types, etc. A table with its indices. + * + * @param pretty if {@code true}, make the output more human-readable (line breaks, indents, and + * {@link CqlIdentifier#asCql(boolean) pretty identifiers}). If {@code false}, return each + * statement on a single line with minimal formatting. + */ + String describeWithChildren(boolean pretty); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionMetadata.java new file mode 100644 index 00000000000..94b72eb5d27 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionMetadata.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import java.util.List; + +/** A CQL function in the schema metadata. */ +public interface FunctionMetadata extends Describable { + + CqlIdentifier getKeyspace(); + + FunctionSignature getSignature(); + + /** + * The names of the parameters. This is in the same order as {@code + * getSignature().getParameterTypes()} + */ + List getParameterNames(); + + String getBody(); + + boolean isCalledOnNullInput(); + + String getLanguage(); + + DataType getReturnType(); + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = new ScriptBuilder(pretty); + builder + .append("CREATE FUNCTION ") + .append(getKeyspace()) + .append(".") + .append(getSignature().getName()) + .append("("); + boolean first = true; + for (int i = 0; i < getSignature().getParameterTypes().size(); i++) { + if (first) { + first = false; + } else { + builder.append(","); + } + DataType type = getSignature().getParameterTypes().get(i); + CqlIdentifier name = getParameterNames().get(i); + builder.append(name).append(" ").append(type.asCql(false, pretty)); + } + return builder + .append(")") + .increaseIndent() + .newLine() + .append(isCalledOnNullInput() ? "CALLED ON NULL INPUT" : "RETURNS NULL ON NULL INPUT") + .newLine() + .append("RETURNS ") + .append(getReturnType().asCql(false, true)) + .newLine() + .append("LANGUAGE ") + .append(getLanguage()) + .newLine() + .append("AS '") + .append(getBody()) + .append("';") + .build(); + } + + @Override + default String describeWithChildren(boolean pretty) { + // A function has no children + return describe(pretty); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java new file mode 100644 index 00000000000..8b311c31a96 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.Objects; + +/** + * The signature that uniquely identifies a CQL function or aggregate in a keyspace. + * + *

      It's composed of a name and a list of parameter types. Overloads (such as {@code sum(int)} and + * {@code sum(int, int)} are not equal. + */ +public class FunctionSignature { + private final CqlIdentifier name; + private final List parameterTypes; + + public FunctionSignature(CqlIdentifier name, Iterable parameterTypes) { + this.name = name; + this.parameterTypes = ImmutableList.copyOf(parameterTypes); + } + + public FunctionSignature(CqlIdentifier name, DataType... parameterTypes) { + this(name, ImmutableList.builder().add(parameterTypes).build()); + } + + public CqlIdentifier getName() { + return name; + } + + public List getParameterTypes() { + return parameterTypes; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof FunctionSignature) { + FunctionSignature that = (FunctionSignature) other; + return this.name.equals(that.name) && this.parameterTypes.equals(that.parameterTypes); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(name, parameterTypes); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexKind.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexKind.java new file mode 100644 index 00000000000..ad8ec59f5ff --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexKind.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +/** A kind of index in the schema. */ +public enum IndexKind { + KEYS, + CUSTOM, + COMPOSITES +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java new file mode 100644 index 00000000000..185d566354c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import com.google.common.collect.Maps; +import java.util.Map; + +/** A secondary index in the schema metadata. */ +public interface IndexMetadata extends Describable { + + CqlIdentifier getKeyspace(); + + CqlIdentifier getTable(); + + CqlIdentifier getName(); + + IndexKind getKind(); + + String getTarget(); + + /** + * If this index is custom, the name of the server-side implementation. Otherwise, {@code null}. + */ + default String getClassName() { + return getOptions().get("class_name"); + } + + /** + * The options of the index. + * + *

      This directly reflects the corresponding column of the system table ( {@code + * system.schema_columns.index_options} in Cassandra <= 2.2, or {@code + * system_schema.indexes.options} in later versions). + * + *

      Note that some of these options might also be exposed as standalone fields in this + * interface, namely {@link #getClassName()} and {{@link #getTarget()}}. + */ + Map getOptions(); + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = new ScriptBuilder(pretty); + if (getClassName() != null) { + builder + .append("CREATE CUSTOM INDEX ") + .append(getName()) + .append(" ON ") + .append(getKeyspace()) + .append(".") + .append(getTable()) + .append(String.format(" (%s)", getTarget())) + .newLine() + .append(String.format("USING '%s'", getClassName())); + + // Some options already appear in the CREATE statement, ignore them + Map describedOptions = + Maps.filterKeys(getOptions(), k -> !"target".equals(k) && !"class_name".equals(k)); + if (!describedOptions.isEmpty()) { + builder.newLine().append("WITH OPTIONS = {").newLine().increaseIndent(); + boolean first = true; + for (Map.Entry option : describedOptions.entrySet()) { + if (first) { + first = false; + } else { + builder.append(",").newLine(); + } + builder.append(String.format("'%s' : '%s'", option.getKey(), option.getValue())); + } + builder.decreaseIndent().append("}"); + } + } else { + builder + .append("CREATE INDEX ") + .append(getName()) + .append(" ON ") + .append(getKeyspace()) + .append(".") + .append(getTable()) + .append(String.format(" (%s);", getTarget())); + } + return builder.build(); + } + + @Override + default String describeWithChildren(boolean pretty) { + // An index has no children + return describe(pretty); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java new file mode 100644 index 00000000000..fbf08b7e08b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.util.Map; + +/** A keyspace in the schema metadata. */ +public interface KeyspaceMetadata extends Describable { + CqlIdentifier getName(); + + /** Whether durable writes are set on this keyspace. */ + boolean isDurableWrites(); + + /** The replication options defined for this keyspace. */ + Map getReplication(); + + Map getTables(); + + default TableMetadata getTable(CqlIdentifier tableId) { + return getTables().get(tableId); + } + + Map getViews(); + + /** Gets the views based on a given table. */ + default Map getViewsOnTable(CqlIdentifier tableId) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ViewMetadata view : getViews().values()) { + if (view.getBaseTable().equals(tableId)) { + builder.put(view.getName(), view); + } + } + return builder.build(); + } + + default ViewMetadata getView(CqlIdentifier viewId) { + return getViews().get(viewId); + } + + Map getUserDefinedTypes(); + + default UserDefinedType getUserDefinedType(CqlIdentifier typeId) { + return getUserDefinedTypes().get(typeId); + } + + Map getFunctions(); + + default FunctionMetadata getFunction(FunctionSignature functionSignature) { + return getFunctions().get(functionSignature); + } + + default FunctionMetadata getFunction( + CqlIdentifier functionId, Iterable parameterTypes) { + return getFunctions().get(new FunctionSignature(functionId, parameterTypes)); + } + + default FunctionMetadata getFunction(CqlIdentifier functionId, DataType... parameterTypes) { + return getFunctions().get(new FunctionSignature(functionId, parameterTypes)); + } + + Map getAggregates(); + + default AggregateMetadata getAggregate(FunctionSignature aggregateSignature) { + return getAggregates().get(aggregateSignature); + } + + default AggregateMetadata getAggregate( + CqlIdentifier aggregateId, Iterable parameterTypes) { + return getAggregates().get(new FunctionSignature(aggregateId, parameterTypes)); + } + + default AggregateMetadata getAggregate(CqlIdentifier aggregateId, DataType... parameterTypes) { + return getAggregates().get(new FunctionSignature(aggregateId, parameterTypes)); + } + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = + new ScriptBuilder(pretty) + .append("CREATE KEYSPACE ") + .append(getName()) + .append(" WITH replication = { 'class' : '") + .append(getReplication().get("class")) + .append("'"); + for (Map.Entry entry : getReplication().entrySet()) { + if (!entry.getKey().equals("class")) { + builder + .append(", '") + .append(entry.getKey()) + .append("': '") + .append(entry.getValue()) + .append("'"); + } + } + return builder + .append(" } AND durable_writes = ") + .append(Boolean.toString(isDurableWrites())) + .append(";") + .build(); + } + + @Override + default String describeWithChildren(boolean pretty) { + String createKeyspace = describe(pretty); + ScriptBuilder builder = new ScriptBuilder(pretty).append(createKeyspace); + + for (Describable element : + Iterables.concat( + getUserDefinedTypes().values(), + getTables().values(), + getViews().values(), + getFunctions().values(), + getAggregates().values())) { + builder.forceNewLine(2).append(element.describeWithChildren(pretty)); + } + + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java new file mode 100644 index 00000000000..6479277582d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** A table or materialized view in the schema metadata. */ +public interface RelationMetadata extends Describable { + + CqlIdentifier getKeyspace(); + + CqlIdentifier getName(); + + /** The unique id generated by the server for this element. */ + UUID getId(); + + List getPartitionKey(); + + Map getClusteringColumns(); + + Map getColumns(); + + default ColumnMetadata getColumn(CqlIdentifier columnName) { + return getColumns().get(columnName); + } + + /** + * The options of this table or materialized view. + * + *

      This corresponds to the {@code WITH} clauses in the {@code CREATE} statement that would + * recreate this element. The exact set of keys and the types of the values depend on the server + * version that this metadata was extracted from. For example, in Cassandra 2.2 and below, {@code + * WITH caching} takes a string argument, whereas starting with Cassandra 3.0 it is a map. + */ + Map getOptions(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java new file mode 100644 index 00000000000..59ac343bf49 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.type.UserDefinedType; + +/** + * Tracks schema changes. + * + *

      An implementation of this interface can be registered with {@link + * Cluster#register(SchemaChangeListener)}. + * + *

      Note that the methods defined by this interface will be executed by internal driver threads, + * and are therefore expected to have short execution times. If you need to perform long + * computations or blocking calls in response to schema change events, it is strongly recommended to + * schedule them asynchronously on a separate thread provided by your application code. + */ +public interface SchemaChangeListener { + + void onKeyspaceCreated(KeyspaceMetadata keyspace); + + void onKeyspaceDropped(KeyspaceMetadata keyspace); + + void onKeyspaceUpdated(KeyspaceMetadata current, KeyspaceMetadata previous); + + void onTableCreated(TableMetadata table); + + void onTableDropped(TableMetadata table); + + void onTableUpdated(TableMetadata current, TableMetadata previous); + + void onUserDefinedTypeCreated(UserDefinedType type); + + void onUserDefinedTypeDropped(UserDefinedType type); + + void onUserDefinedTypeUpdated(UserDefinedType current, UserDefinedType previous); + + void onFunctionCreated(FunctionMetadata function); + + void onFunctionDropped(FunctionMetadata function); + + void onFunctionUpdated(FunctionMetadata current, FunctionMetadata previous); + + void onAggregateCreated(AggregateMetadata aggregate); + + void onAggregateDropped(AggregateMetadata aggregate); + + void onAggregateUpdated(AggregateMetadata current, AggregateMetadata previous); + + void onViewCreated(ViewMetadata view); + + void onViewDropped(ViewMetadata view); + + void onViewUpdated(ViewMetadata current, ViewMetadata previous); + /** Invoked when the listener is registered with a cluster. */ + void onRegister(Cluster cluster); + + /** + * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever + * comes first. + */ + void onUnregister(Cluster cluster); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java new file mode 100644 index 00000000000..986c96a9098 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.type.UserDefinedType; + +/** + * Convenience schema change listener implementation that defines all methods as no-ops. + * + *

      Implementors that are only interested in a subset of events can extend this class and override + * the relevant methods. + */ +public class SchemaChangeListenerBase implements SchemaChangeListener { + + @Override + public void onKeyspaceCreated(KeyspaceMetadata keyspace) {} + + @Override + public void onKeyspaceDropped(KeyspaceMetadata keyspace) {} + + @Override + public void onKeyspaceUpdated(KeyspaceMetadata current, KeyspaceMetadata previous) {} + + @Override + public void onTableCreated(TableMetadata table) {} + + @Override + public void onTableDropped(TableMetadata table) {} + + @Override + public void onTableUpdated(TableMetadata current, TableMetadata previous) {} + + @Override + public void onUserDefinedTypeCreated(UserDefinedType type) {} + + @Override + public void onUserDefinedTypeDropped(UserDefinedType type) {} + + @Override + public void onUserDefinedTypeUpdated(UserDefinedType current, UserDefinedType previous) {} + + @Override + public void onFunctionCreated(FunctionMetadata function) {} + + @Override + public void onFunctionDropped(FunctionMetadata function) {} + + @Override + public void onFunctionUpdated(FunctionMetadata current, FunctionMetadata previous) {} + + @Override + public void onAggregateCreated(AggregateMetadata aggregate) {} + + @Override + public void onAggregateDropped(AggregateMetadata aggregate) {} + + @Override + public void onAggregateUpdated(AggregateMetadata current, AggregateMetadata previous) {} + + @Override + public void onViewCreated(ViewMetadata view) {} + + @Override + public void onViewDropped(ViewMetadata view) {} + + @Override + public void onViewUpdated(ViewMetadata current, ViewMetadata previous) {} + + @Override + public void onRegister(Cluster cluster) {} + + @Override + public void onUnregister(Cluster cluster) {} +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java new file mode 100644 index 00000000000..2dc5e25ebd5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.RelationParser; +import java.util.Map; + +/** A table in the schema metadata. */ +public interface TableMetadata extends RelationMetadata { + + boolean isCompactStorage(); + + Map getIndexes(); + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = + new ScriptBuilder(pretty) + .append("CREATE TABLE ") + .append(getKeyspace()) + .append(".") + .append(getName()) + .append(" (") + .newLine() + .increaseIndent(); + + for (ColumnMetadata column : getColumns().values()) { + builder.append(column.getName()).append(" ").append(column.getType().asCql(true, pretty)); + if (column.isStatic()) { + builder.append(" static"); + } + builder.append(",").newLine(); + } + + // PK + builder.append("PRIMARY KEY ("); + if (getPartitionKey().size() == 1) { // PRIMARY KEY (k + builder.append(getPartitionKey().get(0).getName()); + } else { // PRIMARY KEY ((k1, k2) + builder.append("("); + boolean first = true; + for (ColumnMetadata pkColumn : getPartitionKey()) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(pkColumn.getName()); + } + builder.append(")"); + } + // PRIMARY KEY (, cc1, cc2, cc3) + for (ColumnMetadata clusteringColumn : getClusteringColumns().keySet()) { + builder.append(", ").append(clusteringColumn.getName()); + } + builder.append(")"); + + builder.newLine().decreaseIndent().append(")"); + + builder.increaseIndent(); + if (isCompactStorage()) { + builder.andWith().append("COMPACT STORAGE"); + } + if (getClusteringColumns().containsValue(ClusteringOrder.DESC)) { + builder.andWith().append("CLUSTERING ORDER BY ("); + boolean first = true; + for (Map.Entry entry : getClusteringColumns().entrySet()) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(entry.getKey().getName()).append(" ").append(entry.getValue().name()); + } + builder.append(")"); + } + Map options = getOptions(); + RelationParser.appendOptions(options, builder); + return builder.append(";").build(); + } + + /** + * {@inheritDoc} + * + *

      This describes the table and all of its indices. Contrary to previous driver versions, views + * are not included. + */ + @Override + default String describeWithChildren(boolean pretty) { + String createTable = describe(pretty); + ScriptBuilder builder = new ScriptBuilder(pretty).append(createTable); + for (IndexMetadata indexMetadata : getIndexes().values()) { + builder.forceNewLine(2).append(indexMetadata.describeWithChildren(pretty)); + } + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ViewMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ViewMetadata.java new file mode 100644 index 00000000000..3de0d85d915 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/ViewMetadata.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.RelationParser; + +/** A materialized view in the schema metadata. */ +public interface ViewMetadata extends RelationMetadata { + + /** The table that this view is based on. */ + CqlIdentifier getBaseTable(); + + /** + * Whether this view does a {@code SELECT *} on its base table (this only affects the output of + * {@link #describe(boolean)}). + */ + boolean includesAllColumns(); + + String getWhereClause(); + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = + new ScriptBuilder(pretty) + .append("CREATE MATERIALIZED VIEW ") + .append(getKeyspace()) + .append(".") + .append(getName()) + .append(" AS") + .newLine(); + + builder.append("SELECT"); + if (includesAllColumns()) { + builder.append(" * "); + } else { + builder.newLine().increaseIndent(); + boolean first = true; + for (ColumnMetadata column : getColumns().values()) { + if (first) { + first = false; + } else { + builder.append(",").newLine(); + } + builder.append(column.getName()); + } + builder.newLine().decreaseIndent(); + } + + builder.append("FROM ").append(getKeyspace()).append(".").append(getBaseTable()); + + String whereClause = getWhereClause(); + if (whereClause != null && !whereClause.isEmpty()) { + builder.newLine().append("WHERE ").append(whereClause); + } + + builder.newLine().append("PRIMARY KEY ("); + if (getPartitionKey().size() == 1) { // PRIMARY KEY (k + builder.append(getPartitionKey().get(0).getName()); + } else { // PRIMARY KEY ((k1, k2) + builder.append("("); + boolean first = true; + for (ColumnMetadata pkColumn : getPartitionKey()) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(pkColumn.getName()); + } + builder.append(")"); + } + // PRIMARY KEY (, cc1, cc2, cc3) + for (ColumnMetadata clusteringColumn : getClusteringColumns().keySet()) { + builder.append(", ").append(clusteringColumn.getName()); + } + builder.append(")").increaseIndent(); + + RelationParser.appendOptions(getOptions(), builder); + return builder.append(";").build(); + } + + @Override + default String describeWithChildren(boolean pretty) { + return describe(pretty); // A view has no children + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java index 3bd3389ccbc..3d521b2c97b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java @@ -24,6 +24,11 @@ public interface CustomType extends DataType { */ String getClassName(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + return String.format("'%s'", getClassName()); + } + default int getProtocolCode() { return ProtocolConstants.DataType.CUSTOM; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java index 8f317e80ad4..81630aae327 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataType.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.type; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.detach.Detachable; import java.io.Serializable; @@ -26,4 +27,16 @@ public interface DataType extends Detachable, Serializable { /** The code of the data type in the native protocol specification. */ int getProtocolCode(); + + /** + * Builds an appropriate representation for use in a CQL query. + * + * @param includeFrozen whether to include the {@code frozen<...>} keyword if applicable. This + * will need to be set depending on where the result is used: for example, {@code CREATE + * TABLE} statements use the frozen keyword, whereas it should never appear in {@code CREATE + * FUNCTION}. + * @param pretty whether to pretty-print UDT names (as described in {@link + * CqlIdentifier#asCql(boolean)}. + */ + String asCql(boolean includeFrozen, boolean pretty); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java index 5a0aab753b0..d09907b9ef0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/DataTypes.java @@ -60,19 +60,41 @@ public static DataType custom(String className) { } public static ListType listOf(DataType elementType) { - // frozen == true is only used in column definitions, it's unlikely that end users will need to - // create such instances. return new DefaultListType(elementType, false); } + public static ListType listOf(DataType elementType, boolean frozen) { + return new DefaultListType(elementType, frozen); + } + + public static ListType frozenListOf(DataType elementType) { + return new DefaultListType(elementType, true); + } + public static SetType setOf(DataType elementType) { return new DefaultSetType(elementType, false); } + public static SetType setOf(DataType elementType, boolean frozen) { + return new DefaultSetType(elementType, frozen); + } + + public static SetType frozenSetOf(DataType elementType) { + return new DefaultSetType(elementType, true); + } + public static MapType mapOf(DataType keyType, DataType valueType) { return new DefaultMapType(keyType, valueType, false); } + public static MapType mapOf(DataType keyType, DataType valueType, boolean frozen) { + return new DefaultMapType(keyType, valueType, frozen); + } + + public static MapType frozenMapOf(DataType keyType, DataType valueType) { + return new DefaultMapType(keyType, valueType, true); + } + /** * Builds a new, detached tuple type. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java index abd8c552496..0a64ccded75 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java @@ -22,6 +22,12 @@ public interface ListType extends DataType { boolean isFrozen(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + String template = (isFrozen() && includeFrozen) ? "frozen>" : "list<%s>"; + return String.format(template, getElementType().asCql(includeFrozen, pretty)); + } + default int getProtocolCode() { return ProtocolConstants.DataType.LIST; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java index 09877917116..dd11a7ecedb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java @@ -24,6 +24,15 @@ public interface MapType extends DataType { boolean isFrozen(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + String template = (isFrozen() && includeFrozen) ? "frozen>" : "map<%s, %s>"; + return String.format( + template, + getKeyType().asCql(includeFrozen, pretty), + getValueType().asCql(includeFrozen, pretty)); + } + default int getProtocolCode() { return ProtocolConstants.DataType.MAP; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java index 3eea41176b1..48d8186b33f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java @@ -22,6 +22,12 @@ public interface SetType extends DataType { boolean isFrozen(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + String template = (isFrozen() && includeFrozen) ? "frozen>" : "set<%s>"; + return String.format(template, getElementType().asCql(includeFrozen, pretty)); + } + default int getProtocolCode() { return ProtocolConstants.DataType.SET; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java index 25e6d931175..4a576173cc6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java @@ -27,6 +27,26 @@ public interface TupleType extends DataType { AttachmentPoint getAttachmentPoint(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + StringBuilder builder = new StringBuilder(); + // Tuples are always frozen + if (includeFrozen) { + builder.append("frozen<"); + } + boolean first = true; + for (DataType type : getComponentTypes()) { + builder.append(first ? "tuple<" : ", "); + first = false; + builder.append(type.asCql(includeFrozen, pretty)); + } + builder.append('>'); + if (includeFrozen) { + builder.append('>'); + } + return builder.toString(); + } + default int getProtocolCode() { return ProtocolConstants.DataType.TUPLE; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java index 51ce25bccc9..9572b3cd1fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java @@ -18,14 +18,18 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.core.metadata.schema.Describable; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; import com.datastax.oss.protocol.internal.ProtocolConstants; import java.util.List; -public interface UserDefinedType extends DataType { +public interface UserDefinedType extends DataType, Describable { CqlIdentifier getKeyspace(); CqlIdentifier getName(); + boolean isFrozen(); + List getFieldNames(); int firstIndexOf(CqlIdentifier id); @@ -42,10 +46,51 @@ default boolean contains(String name) { List getFieldTypes(); + UserDefinedType copy(boolean newFrozen); + UdtValue newValue(); AttachmentPoint getAttachmentPoint(); + @Override + default String asCql(boolean includeFrozen, boolean pretty) { + String template = (isFrozen() && includeFrozen) ? "frozen<%s.%s>" : "%s.%s"; + return String.format(template, getKeyspace().asCql(pretty), getName().asCql(pretty)); + } + + @Override + default String describe(boolean pretty) { + ScriptBuilder builder = new ScriptBuilder(pretty); + + builder + .append("CREATE TYPE ") + .append(getKeyspace()) + .append(".") + .append(getName()) + .append(" (") + .newLine() + .increaseIndent(); + + List fieldNames = getFieldNames(); + List fieldTypes = getFieldTypes(); + int fieldCount = fieldNames.size(); + for (int i = 0; i < fieldCount; i++) { + builder.append(fieldNames.get(i)).append(" ").append(fieldTypes.get(i).asCql(true, pretty)); + if (i < fieldCount - 1) { + builder.append(","); + } + builder.newLine(); + } + builder.decreaseIndent().append(");"); + return builder.build(); + } + + @Override + default String describeWithChildren(boolean pretty) { + // No children (if it uses other types, they're considered dependencies, not sub-elements) + return describe(pretty); + } + default int getProtocolCode() { return ProtocolConstants.DataType.UDT; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 7fd8637a916..96a1e081bf4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -46,9 +46,6 @@ public class CassandraProtocolVersionRegistry implements ProtocolVersionRegistry private static final Logger LOG = LoggerFactory.getLogger(CassandraProtocolVersionRegistry.class); - private static final CassandraVersion CASSANDRA_210 = CassandraVersion.parse("2.1.0"); - private static final CassandraVersion CASSANDRA_220 = CassandraVersion.parse("2.2.0"); - private final String logPrefix; private final NavigableMap versionsByCode; @@ -133,7 +130,7 @@ public ProtocolVersion highestCommon(Collection nodes) { continue; } cassandraVersion = cassandraVersion.nextStable(); - if (cassandraVersion.compareTo(CASSANDRA_210) < 0) { + if (cassandraVersion.compareTo(CassandraVersion.V2_1_0) < 0) { throw new UnsupportedProtocolVersionException( node.getConnectAddress(), String.format( @@ -148,7 +145,7 @@ public ProtocolVersion highestCommon(Collection nodes) { logPrefix, node.getConnectAddress(), cassandraVersion); - if (cassandraVersion.compareTo(CASSANDRA_220) < 0 + if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) < 0 && candidates.remove(CoreProtocolVersion.V4)) { LOG.debug("[{}] Excluding protocol V4", logPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java index ba8abf283a4..aaa2d88ea43 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; @@ -44,6 +45,21 @@ public Metadata getMetadata() { return delegate.getMetadata(); } + @Override + public boolean isSchemaMetadataEnabled() { + return delegate.isSchemaMetadataEnabled(); + } + + @Override + public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { + return delegate.setSchemaMetadataEnabled(newValue); + } + + @Override + public CompletionStage refreshSchemaAsync() { + return delegate.refreshSchemaAsync(); + } + @Override public DriverContext getContext() { return delegate.getContext(); @@ -54,6 +70,16 @@ public CompletionStage connectAsync(CqlIdentifier keyspace) { return delegate.connectAsync(keyspace).thenApply(this::wrap); } + @Override + public Cluster register(SchemaChangeListener listener) { + return delegate.register(listener); + } + + @Override + public Cluster unregister(SchemaChangeListener listener) { + return delegate.unregister(listener); + } + @Override public CompletionStage closeFuture() { return delegate.closeFuture(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 90c607bb1d5..7b1caf72397 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -22,21 +22,21 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; -import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; -import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.google.common.collect.ImmutableList; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -84,6 +84,21 @@ public Metadata getMetadata() { return metadataManager.getMetadata(); } + @Override + public boolean isSchemaMetadataEnabled() { + return metadataManager.isSchemaEnabled(); + } + + @Override + public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { + return metadataManager.setSchemaEnabled(newValue); + } + + @Override + public CompletionStage refreshSchemaAsync() { + return metadataManager.refreshSchema(null, true, true); + } + @Override public DriverContext getContext() { return context; @@ -96,6 +111,18 @@ public CompletionStage connectAsync(CqlIdentifier keyspace) { return connectFuture; } + @Override + public Cluster register(SchemaChangeListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); + return this; + } + + @Override + public Cluster unregister(SchemaChangeListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); + return this; + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -127,12 +154,14 @@ private class SingleThreaded { // is something really wrong in the client program private List sessions; private int sessionCounter; + private Set schemaChangeListeners = new HashSet<>(); private SingleThreaded(InternalDriverContext context, Set contactPoints) { this.context = context; this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; this.sessions = new ArrayList<>(); + new SchemaListenerNotifier(schemaChangeListeners, context.eventBus(), adminExecutor); } private void init() { @@ -187,7 +216,7 @@ private void afterInitialNodeListRefresh(@SuppressWarnings("unused") Void ignore } } if (needSchemaRefresh) { - metadataManager.refreshSchema(SchemaElementKind.WHOLE_SCHEMA, null, null, null); + metadataManager.refreshSchema(null, false, true); } metadataManager.firstSchemaRefreshFuture().thenAccept(this::afterInitialSchemaRefresh); @@ -234,6 +263,28 @@ private void connect(CqlIdentifier keyspace, CompletableFuture conne } } + private void register(SchemaChangeListener listener) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + // We want onRegister to be called before any event. We can add the listener before, because + // schema events are processed on this same thread. + if (schemaChangeListeners.add(listener)) { + listener.onRegister(DefaultCluster.this); + } + } + + private void unregister(SchemaChangeListener listener) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + if (schemaChangeListeners.remove(listener)) { + listener.onUnregister(DefaultCluster.this); + } + } + private void close() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { @@ -242,6 +293,10 @@ private void close() { closeWasCalled = true; LOG.debug("[{}] Starting shutdown", logPrefix); + for (SchemaChangeListener listener : schemaChangeListeners) { + listener.onUnregister(DefaultCluster.this); + } + schemaChangeListeners.clear(); List> childrenCloseStages = new ArrayList<>(); closePolicies(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java b/core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java new file mode 100644 index 00000000000..755ce96c49e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.metadata.schema.events.AggregateChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.FunctionChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.KeyspaceChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.TableChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.TypeChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.ViewChangeEvent; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import io.netty.util.concurrent.EventExecutor; +import java.util.Set; + +class SchemaListenerNotifier { + + private final Set listeners; + private final EventExecutor adminExecutor; + + SchemaListenerNotifier( + Set listeners, EventBus eventBus, EventExecutor adminExecutor) { + this.listeners = listeners; + this.adminExecutor = adminExecutor; + + // No need to unregister at shutdown, this component has the same lifecycle as the cluster + eventBus.register( + AggregateChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onAggregateChangeEvent)); + eventBus.register( + FunctionChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onFunctionChangeEvent)); + eventBus.register( + KeyspaceChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onKeyspaceChangeEvent)); + eventBus.register( + TableChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onTableChangeEvent)); + eventBus.register( + TypeChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onTypeChangeEvent)); + eventBus.register( + ViewChangeEvent.class, RunOrSchedule.on(adminExecutor, this::onViewChangeEvent)); + } + + private void onAggregateChangeEvent(AggregateChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onAggregateCreated(event.newAggregate); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onAggregateUpdated(event.newAggregate, event.oldAggregate); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onAggregateDropped(event.oldAggregate); + } + break; + } + } + + private void onFunctionChangeEvent(FunctionChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onFunctionCreated(event.newFunction); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onFunctionUpdated(event.newFunction, event.oldFunction); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onFunctionDropped(event.oldFunction); + } + break; + } + } + + private void onKeyspaceChangeEvent(KeyspaceChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onKeyspaceCreated(event.newKeyspace); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onKeyspaceUpdated(event.newKeyspace, event.oldKeyspace); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onKeyspaceDropped(event.oldKeyspace); + } + break; + } + } + + private void onTableChangeEvent(TableChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onTableCreated(event.newTable); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onTableUpdated(event.newTable, event.oldTable); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onTableDropped(event.oldTable); + } + break; + } + } + + private void onTypeChangeEvent(TypeChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onUserDefinedTypeCreated(event.newType); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onUserDefinedTypeUpdated(event.newType, event.oldType); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onUserDefinedTypeDropped(event.oldType); + } + break; + } + } + + private void onViewChangeEvent(ViewChangeEvent event) { + assert adminExecutor.inEventLoop(); + switch (event.changeType) { + case CREATED: + for (SchemaChangeListener listener : listeners) { + listener.onViewCreated(event.newView); + } + break; + case UPDATED: + for (SchemaChangeListener listener : listeners) { + listener.onViewUpdated(event.newView, event.oldView); + } + break; + case DROPPED: + for (SchemaChangeListener listener : listeners) { + listener.onViewDropped(event.oldView); + } + break; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index cdf97f55c77..d8256648d4e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -195,7 +195,7 @@ private static ByteBuffer serialize(Object parameter, ProtocolVersion protocolVe } else if (parameter instanceof List && ((List) parameter).get(0) instanceof String) { @SuppressWarnings("unchecked") List l = (List) parameter; - return AdminResult.Row.LIST_OF_TEXT.encode(l, protocolVersion); + return AdminRow.LIST_OF_TEXT.encode(l, protocolVersion); } else { throw new IllegalArgumentException( "Unsupported variable type for admin query: " + parameter.getClass()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java index 63d216cc750..fc8ab0d1168 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminResult.java @@ -16,25 +16,19 @@ package com.datastax.oss.driver.internal.core.adminrequest; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.type.codec.TypeCodec; -import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.Rows; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableMap; -import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.Set; -import java.util.UUID; import java.util.concurrent.CompletionStage; -public class AdminResult implements Iterable { +public class AdminResult implements Iterable { private final Queue> data; private final Map columnSpecs; @@ -58,12 +52,14 @@ public AdminResult(Rows rows, AdminRequestHandler nextHandler, ProtocolVersion p /** This consumes the result's data and can be called only once. */ @Override - public Iterator iterator() { - return new AbstractIterator() { + public Iterator iterator() { + return new AbstractIterator() { @Override - protected Row computeNext() { + protected AdminRow computeNext() { List rowData = data.poll(); - return (rowData == null) ? endOfData() : new Row(columnSpecs, rowData, protocolVersion); + return (rowData == null) + ? endOfData() + : new AdminRow(columnSpecs, rowData, protocolVersion); } }; } @@ -78,90 +74,4 @@ public CompletionStage nextPage() { new AssertionError("No next page, use hasNextPage() before you call this method")) : nextHandler.start(); } - - public static class Row { - private static final TypeCodec> MAP_OF_TEXT_TO_TEXT = - TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.TEXT); - private static final TypeCodec> MAP_OF_TEXT_TO_BLOB = - TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.BLOB); - private static final TypeCodec> MAP_OF_UUID_TO_BLOB = - TypeCodecs.mapOf(TypeCodecs.UUID, TypeCodecs.BLOB); - - @VisibleForTesting - static final TypeCodec> LIST_OF_TEXT = TypeCodecs.listOf(TypeCodecs.TEXT); - - private static final TypeCodec> SET_OF_TEXT = TypeCodecs.setOf(TypeCodecs.TEXT); - - private final Map columnSpecs; - private final List data; - private final ProtocolVersion protocolVersion; - - private Row( - Map columnSpecs, - List data, - ProtocolVersion protocolVersion) { - this.columnSpecs = columnSpecs; - this.data = data; - this.protocolVersion = protocolVersion; - } - - public Boolean getBoolean(String columnName) { - return get(columnName, TypeCodecs.BOOLEAN); - } - - public Integer getInteger(String columnName) { - return get(columnName, TypeCodecs.INT); - } - - public Double getDouble(String columnName) { - return get(columnName, TypeCodecs.DOUBLE); - } - - public String getString(String columnName) { - return get(columnName, TypeCodecs.TEXT); - } - - public UUID getUuid(String columnName) { - return get(columnName, TypeCodecs.UUID); - } - - public ByteBuffer getByteBuffer(String columnName) { - return get(columnName, TypeCodecs.BLOB); - } - - public InetAddress getInetAddress(String columnName) { - return get(columnName, TypeCodecs.INET); - } - - public List getListOfString(String columnName) { - return get(columnName, LIST_OF_TEXT); - } - - public Set getSetOfString(String columnName) { - return get(columnName, SET_OF_TEXT); - } - - public Map getMapOfStringToString(String columnName) { - return get(columnName, MAP_OF_TEXT_TO_TEXT); - } - - public Map getMapOfStringToByteBuffer(String columnName) { - return get(columnName, MAP_OF_TEXT_TO_BLOB); - } - - public Map getMapOfUuidToByteBuffer(String columnName) { - return get(columnName, MAP_OF_UUID_TO_BLOB); - } - - private T get(String columnName, TypeCodec codec) { - // Minimal checks here: this is for internal use, so the caller should know what they're - // doing - if (!columnSpecs.containsKey(columnName)) { - return null; - } else { - int index = columnSpecs.get(columnName).index; - return codec.decode(data.get(index), protocolVersion); - } - } - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRow.java new file mode 100644 index 00000000000..86fdde64520 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRow.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.result.ColumnSpec; +import com.google.common.annotations.VisibleForTesting; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class AdminRow { + + @VisibleForTesting + static final TypeCodec> LIST_OF_TEXT = TypeCodecs.listOf(TypeCodecs.TEXT); + + private static final TypeCodec> SET_OF_TEXT = TypeCodecs.setOf(TypeCodecs.TEXT); + private static final TypeCodec> MAP_OF_STRING_TO_STRING = + TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.TEXT); + + private final Map columnSpecs; + private final List data; + private final ProtocolVersion protocolVersion; + + public AdminRow( + Map columnSpecs, List data, ProtocolVersion protocolVersion) { + this.columnSpecs = columnSpecs; + this.data = data; + this.protocolVersion = protocolVersion; + } + + public Boolean getBoolean(String columnName) { + return get(columnName, TypeCodecs.BOOLEAN); + } + + public Integer getInteger(String columnName) { + return get(columnName, TypeCodecs.INT); + } + + public boolean isString(String columnName) { + return columnSpecs.get(columnName).type.id == ProtocolConstants.DataType.VARCHAR; + } + + public String getString(String columnName) { + return get(columnName, TypeCodecs.TEXT); + } + + public UUID getUuid(String columnName) { + return get(columnName, TypeCodecs.UUID); + } + + public ByteBuffer getByteBuffer(String columnName) { + return get(columnName, TypeCodecs.BLOB); + } + + public InetAddress getInetAddress(String columnName) { + return get(columnName, TypeCodecs.INET); + } + + public List getListOfString(String columnName) { + return get(columnName, LIST_OF_TEXT); + } + + public Set getSetOfString(String columnName) { + return get(columnName, SET_OF_TEXT); + } + + public Map getMapOfStringToString(String columnName) { + return get(columnName, MAP_OF_STRING_TO_STRING); + } + + public boolean contains(String columnName) { + return columnSpecs.containsKey(columnName); + } + + public T get(String columnName, TypeCodec codec) { + // Minimal checks here: this is for internal use, so the caller should know what they're + // doing + if (!contains(columnName)) { + return null; + } else { + int index = columnSpecs.get(columnName).index; + return codec.decode(data.get(index), protocolVersion); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index e611a257e1c..031cc6c9018 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -359,7 +359,7 @@ String describe() { @Override Message getRequest() { - return new Query("USE " + keyspaceName.asCql()); + return new Query("USE " + keyspaceName.asCql(false)); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index d59dc662241..21cf1536224 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -144,7 +144,7 @@ Message getRequest() { case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: - return new Query("USE " + options.keyspace.asCql()); + return new Query("USE " + options.keyspace.asCql(false)); case AUTH_RESPONSE: return new AuthResponse(authReponseToken); case REGISTER: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 8758d0072d8..383553e0e52 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -40,6 +40,10 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.DefaultSchemaParserFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.DefaultSchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; @@ -124,6 +128,10 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); private final LazyReference timestampGeneratorRef = new LazyReference<>("timestampGenerator", this::buildTimestampGenerator, cycleDetector); + private final LazyReference schemaQueriesFactoryRef = + new LazyReference<>("schemaQueriesFactory", this::buildSchemaQueriesFactory, cycleDetector); + private final LazyReference schemaParserFactoryRef = + new LazyReference<>("schemaParserFactory", this::buildSchemaParserFactory, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -280,6 +288,14 @@ protected TimestampGenerator buildTimestampGenerator() { "Missing timestamp generator, check your configuration (%s)", rootOption))); } + protected SchemaQueriesFactory buildSchemaQueriesFactory() { + return new DefaultSchemaQueriesFactory(this); + } + + protected SchemaParserFactory buildSchemaParserFactory() { + return new DefaultSchemaParserFactory(this); + } + @Override public String clusterName() { return clusterName; @@ -405,6 +421,16 @@ public TimestampGenerator timestampGenerator() { return timestampGeneratorRef.get(); } + @Override + public SchemaQueriesFactory schemaQueriesFactory() { + return schemaQueriesFactoryRef.get(); + } + + @Override + public SchemaParserFactory schemaParserFactory() { + return schemaParserFactoryRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 6255b3d58ea..2f91e7cc460 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -24,6 +24,8 @@ import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -64,4 +66,8 @@ public interface InternalDriverContext extends DriverContext { RequestProcessorRegistry requestProcessorRegistry(); DriverConfigLoader configLoader(); + + SchemaQueriesFactory schemaQueriesFactory(); + + SchemaParserFactory schemaParserFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 29eabe4d39a..adca8c7f92f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -27,7 +28,6 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; -import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; @@ -86,9 +86,13 @@ public ControlConnection(InternalDriverContext context) { * @param listenToClusterEvents whether to register for TOPOLOGY_CHANGE and STATUS_CHANGE events. * If the control connection has already initialized with another value, this is ignored. * SCHEMA_CHANGE events are always registered. + * @param reconnectOnFailure whether to schedule a reconnection if the initial attempt fails (this + * does not affect the returned future, which always represent the outcome of the initial + * attempt only). */ - public CompletionStage init(boolean listenToClusterEvents) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.init(listenToClusterEvents)); + public CompletionStage init(boolean listenToClusterEvents, boolean reconnectOnFailure) { + RunOrSchedule.on( + adminExecutor, () -> singleThreaded.init(listenToClusterEvents, reconnectOnFailure)); return singleThreaded.initFuture; } @@ -187,13 +191,7 @@ private void processStatusChange(Event event) { private void processSchemaChange(Event event) { SchemaChangeEvent sce = (SchemaChangeEvent) event; - context - .metadataManager() - .refreshSchema( - SchemaElementKind.fromProtocolString(sce.target), - sce.keyspace, - sce.object, - sce.arguments); + context.metadataManager().refreshSchema(sce.keyspace, false, false); } private class SingleThreaded { @@ -221,7 +219,7 @@ private SingleThreaded(InternalDriverContext context) { .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); } - private void init(boolean listenToClusterEvents) { + private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { assert adminExecutor.inEventLoop(); if (initWasCalled) { return; @@ -237,7 +235,16 @@ private void init(boolean listenToClusterEvents) { Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); - connect(nodes, null, () -> initFuture.complete(null), initFuture::completeExceptionally); + connect( + nodes, + null, + () -> initFuture.complete(null), + error -> { + if (reconnectOnFailure && !closeWasCalled) { + reconnection.start(); + } + initFuture.completeExceptionally(error); + }); } private CompletionStage reconnect() { @@ -356,7 +363,8 @@ private void onSuccessfulReconnect() { try { // This does nothing if the LBP is initialized already context.loadBalancingPolicyWrapper().init(); - context.metadataManager().refreshSchema(null, null, null, null); + context.metadataManager().refreshSchema(null, false, true); + // TODO avoid refreshing the token map twice } catch (Throwable t) { LOG.warn("[{}] Unexpected error on control connection reconnect", logPrefix, t); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 0e654173f4f..0341f7bc875 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -395,7 +395,12 @@ public void onResponse(Frame responseFrame) { Message responseMessage = responseFrame.message; if (responseMessage instanceof SchemaChange) { // TODO schema agreement, and chain setFinalResult to the result - setFinalResult((Result) responseMessage, responseFrame, this); + SchemaChange schemaChange = (SchemaChange) responseMessage; + context + .metadataManager() + .refreshSchema(schemaChange.keyspace, false, false) + .whenComplete( + ((metadata, error) -> setFinalResult(schemaChange, responseFrame, this))); } else if (responseMessage instanceof Result) { LOG.debug("[{}] Got result, completing", logPrefix); setFinalResult((Result) responseMessage, responseFrame, this); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java index 8f94e11968f..1ab4b8cffad 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultColumnDefinition.java @@ -75,12 +75,6 @@ public void attach(AttachmentPoint attachmentPoint) { @Override public String toString() { - return keyspace.asPrettyCql() - + "." - + table.asPrettyCql() - + "." - + name.asPrettyCql() - + " " - + type; + return keyspace.asCql(true) + "." + table.asCql(true) + "." + name.asCql(true) + " " + type; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 0bc0c6d5d3b..9c10bff3f31 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; @@ -25,24 +26,26 @@ public class AddNodeRefresh extends NodesRefresh { @VisibleForTesting final NodeInfo newNodeInfo; - AddNodeRefresh(DefaultMetadata oldMetadata, NodeInfo newNodeInfo, String logPrefix) { - super(oldMetadata, logPrefix); + AddNodeRefresh(NodeInfo newNodeInfo, String logPrefix) { + super(logPrefix); this.newNodeInfo = newNodeInfo; } @Override - protected Map computeNewNodes() { + public Result compute(DefaultMetadata oldMetadata) { Map oldNodes = oldMetadata.getNodes(); if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { - return oldNodes; + return new Result(oldMetadata); } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); copyInfos(newNodeInfo, newNode, logPrefix); - events.add(NodeStateEvent.added(newNode)); - return ImmutableMap.builder() - .putAll(oldNodes) - .put(newNode.getConnectAddress(), newNode) - .build(); + Map newNodes = + ImmutableMap.builder() + .putAll(oldNodes) + .put(newNode.getConnectAddress(), newNode) + .build(); + return new Result( + oldMetadata.withNodes(newNodes), ImmutableList.of(NodeStateEvent.added(newNode))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 49879746cee..01161e53411 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -15,8 +15,10 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Collections; @@ -31,11 +33,17 @@ public class DefaultMetadata implements Metadata { public static final DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); private final Map nodes; - // TODO schema + private final Map keyspaces; // TODO token map public DefaultMetadata(Map nodes) { - this.nodes = ImmutableMap.copyOf(nodes); + this(ImmutableMap.copyOf(nodes), Collections.emptyMap()); + } + + private DefaultMetadata( + Map nodes, Map keyspaces) { + this.nodes = nodes; + this.keyspaces = keyspaces; } @Override @@ -43,18 +51,18 @@ public Map getNodes() { return nodes; } - public DefaultMetadata addNode(Node toAdd) { - Map newNodes; - if (nodes.containsKey(toAdd.getConnectAddress())) { - return this; - } else { - newNodes = - ImmutableMap.builder() - .putAll(nodes) - .put(toAdd.getConnectAddress(), toAdd) - .build(); - // TODO recompute token map - return new DefaultMetadata(newNodes); - } + @Override + public Map getKeyspaces() { + return keyspaces; + } + + public DefaultMetadata withNodes(Map newNodes) { + // TODO recompute token map + return new DefaultMetadata(ImmutableMap.copyOf(newNodes), this.keyspaces); + } + + public DefaultMetadata withKeyspaces(Map newKeyspaces) { + // TODO recompute token map + return new DefaultMetadata(this.nodes, ImmutableMap.copyOf(newKeyspaces)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 60877cdc4e2..52af8a1a833 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; @@ -74,7 +75,7 @@ public CompletionStage init() { if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - return controlConnection.init(true); + return controlConnection.init(true, false); } @Override @@ -138,7 +139,7 @@ public CompletionStage> refreshNodeList() { // reports the normal RPC address instead of the broadcast one (CASSANDRA-11181). We // already know the address since we've just used it to query. nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next(), controlAddress)); - for (AdminResult.Row row : peersResult) { + for (AdminRow row : peersResult) { nodeInfos.add(buildNodeInfo(row)); } return nodeInfos; @@ -173,7 +174,7 @@ private CompletionStage query(DriverChannel channel, String querySt return query(channel, queryString, Collections.emptyMap()); } - private NodeInfo buildNodeInfo(AdminResult.Row row) { + private NodeInfo buildNodeInfo(AdminRow row) { InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); if (broadcastRpcAddress == null) { throw new IllegalArgumentException("Missing rpc_address in system row, can't refresh node"); @@ -183,7 +184,7 @@ private NodeInfo buildNodeInfo(AdminResult.Row row) { return buildNodeInfo(row, connectAddress); } - private NodeInfo buildNodeInfo(AdminResult.Row row, InetSocketAddress connectAddress) { + private NodeInfo buildNodeInfo(AdminRow row, InetSocketAddress connectAddress) { DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder().withConnectAddress(connectAddress); InetAddress broadcastAddress = row.getInetAddress("broadcast_address"); // in system.local @@ -202,7 +203,7 @@ private NodeInfo buildNodeInfo(AdminResult.Row row, InetSocketAddress connectAdd } private Optional buildNodeInfoFromFirstRow(AdminResult result) { - Iterator iterator = result.iterator(); + Iterator iterator = result.iterator(); if (iterator.hasNext()) { return Optional.of(buildNodeInfo(iterator.next())); } else { @@ -213,7 +214,7 @@ private Optional buildNodeInfoFromFirstRow(AdminResult result) { private Optional findInPeers(AdminResult result, InetSocketAddress connectAddress) { // The peers table is keyed by broadcast_address, but we only have the translated // broadcast_rpc_address, so we have to traverse the whole table and check the rows one by one. - for (AdminResult.Row row : result) { + for (AdminRow row : result) { InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); if (broadcastRpcAddress != null && addressTranslator diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 1e6b50f8d16..e9a78af9f96 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import java.net.InetSocketAddress; @@ -33,13 +34,13 @@ class FullNodeListRefresh extends NodesRefresh { @VisibleForTesting final Iterable nodeInfos; - FullNodeListRefresh(DefaultMetadata current, Iterable nodeInfos, String logPrefix) { - super(current, logPrefix); + FullNodeListRefresh(Iterable nodeInfos, String logPrefix) { + super(logPrefix); this.nodeInfos = nodeInfos; } - protected Map computeNewNodes() { - + @Override + public Result compute(DefaultMetadata oldMetadata) { Map oldNodes = oldMetadata.getNodes(); Map added = new HashMap<>(); @@ -64,9 +65,11 @@ protected Map computeNewNodes() { Set removed = Sets.difference(oldNodes.keySet(), seen); if (added.isEmpty() && removed.isEmpty()) { - return oldNodes; + return new Result(oldMetadata); } else { ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + ImmutableList.Builder eventsBuilder = ImmutableList.builder(); + newNodesBuilder.putAll(added); for (Map.Entry entry : oldNodes.entrySet()) { if (!removed.contains(entry.getKey())) { @@ -75,14 +78,14 @@ protected Map computeNewNodes() { } for (Node node : added.values()) { - events.add(NodeStateEvent.added((DefaultNode) node)); + eventsBuilder.add(NodeStateEvent.added((DefaultNode) node)); } for (InetSocketAddress address : removed) { Node node = oldNodes.get(address); - events.add(NodeStateEvent.removed((DefaultNode) node)); + eventsBuilder.add(NodeStateEvent.removed((DefaultNode) node)); } - return newNodesBuilder.build(); + return new Result(oldMetadata.withNodes(newNodesBuilder.build()), eventsBuilder.build()); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index 851cd59edf7..35b54271b79 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -19,6 +19,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,14 +30,13 @@ class InitContactPointsRefresh extends MetadataRefresh { @VisibleForTesting final Set contactPoints; - InitContactPointsRefresh( - DefaultMetadata current, Set contactPoints, String logPrefix) { - super(current, logPrefix); + InitContactPointsRefresh(Set contactPoints, String logPrefix) { + super(logPrefix); this.contactPoints = contactPoints; } @Override - void compute() { + public Result compute(DefaultMetadata oldMetadata) { assert oldMetadata == DefaultMetadata.EMPTY; LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); @@ -44,7 +44,7 @@ void compute() { for (InetSocketAddress address : contactPoints) { newNodes.put(address, new DefaultNode(address)); } - newMetadata = new DefaultMetadata(newNodes.build()); + return new Result(new DefaultMetadata(newNodes.build())); // No token map refresh, because we don't have enough information yet } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index ba7e282ab4e..ad746fe773b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -16,13 +16,25 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.internal.core.util.concurrent.Debouncer; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -38,15 +50,45 @@ public class MetadataManager implements AsyncAutoCloseable { private final InternalDriverContext context; private final String logPrefix; private final EventExecutor adminExecutor; + private final DriverConfigProfile config; private final SingleThreaded singleThreaded; - private volatile DefaultMetadata metadata; // must be updated on adminExecutor only + private final ControlConnection controlConnection; + + private volatile DefaultMetadata metadata; // only updated from adminExecutor + private volatile boolean schemaEnabledInConfig; + private volatile List refreshedKeyspaces; + private volatile Boolean schemaEnabledProgrammatically; public MetadataManager(InternalDriverContext context) { this.context = context; this.logPrefix = context.clusterName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(); + this.config = context.config().getDefaultProfile(); + this.singleThreaded = new SingleThreaded(context, config); + this.controlConnection = context.controlConnection(); this.metadata = DefaultMetadata.EMPTY; + this.schemaEnabledInConfig = config.getBoolean(CoreDriverOption.METADATA_SCHEMA_ENABLED); + this.refreshedKeyspaces = + config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + : Collections.emptyList(); + + context.eventBus().register(ConfigChangeEvent.class, this::onConfigChanged); + } + + private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event) { + boolean wasEnabledBefore = isSchemaEnabled(); + List keyspacesBefore = this.refreshedKeyspaces; + + this.schemaEnabledInConfig = config.getBoolean(CoreDriverOption.METADATA_SCHEMA_ENABLED); + this.refreshedKeyspaces = + config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + : Collections.emptyList(); + + if ((!wasEnabledBefore || !keyspacesBefore.equals(refreshedKeyspaces)) && isSchemaEnabled()) { + refreshSchema(null, false, true); + } } public Metadata getMetadata() { @@ -115,10 +157,37 @@ public void removeNode(InetSocketAddress address) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.removeNode(address)); } - public void refreshSchema( - SchemaElementKind kind, String keyspace, String object, List arguments) { - // TODO refresh schema metadata, only complete the future once it's done - singleThreaded.firstSchemaRefreshFuture.complete(null); + /** + * @param keyspace if this refresh was triggered by an event, that event's keyspace, otherwise + * null (this is only used to discard the event if it targets a keyspace that we're ignoring) + * @param evenIfDisabled force the refresh even if schema is currently disabled (used for user + * request) + * @param flushNow bypass the debouncer and force an immediate refresh (used to avoid a delay at + * startup) + */ + public CompletionStage refreshSchema( + String keyspace, boolean evenIfDisabled, boolean flushNow) { + CompletableFuture future = new CompletableFuture<>(); + RunOrSchedule.on( + adminExecutor, + () -> singleThreaded.refreshSchema(keyspace, evenIfDisabled, flushNow, future)); + return future; + } + + public boolean isSchemaEnabled() { + return (schemaEnabledProgrammatically != null) + ? schemaEnabledProgrammatically + : schemaEnabledInConfig; + } + + public CompletionStage setSchemaEnabled(Boolean newValue) { + boolean wasEnabledBefore = isSchemaEnabled(); + schemaEnabledProgrammatically = newValue; + if (!wasEnabledBefore && isSchemaEnabled()) { + return refreshSchema(null, false, true); + } else { + return CompletableFuture.completedFuture(metadata); + } } /** @@ -129,8 +198,6 @@ public CompletionStage firstSchemaRefreshFuture() { return singleThreaded.firstSchemaRefreshFuture; } - // TODO user-controlled refresh? - @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -151,15 +218,39 @@ private class SingleThreaded { private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; private final CompletableFuture firstSchemaRefreshFuture = new CompletableFuture<>(); + private final Debouncer, CompletableFuture> + schemaRefreshDebouncer; + private final SchemaQueriesFactory schemaQueriesFactory; + private final SchemaParserFactory schemaParserFactory; + + // We don't allow concurrent schema refreshes. If one is already running, the next one is queued + // (and the ones after that are merged with the queued one). + private CompletableFuture currentSchemaRefresh; + private CompletableFuture queuedSchemaRefresh; + + private boolean didFirstNodeListRefresh; + + private SingleThreaded(InternalDriverContext context, DriverConfigProfile config) { + this.schemaRefreshDebouncer = + new Debouncer<>( + adminExecutor, + this::coalesceSchemaRequests, + this::startSchemaRequest, + config.getDuration(CoreDriverOption.METADATA_SCHEMA_WINDOW), + config.getInt(CoreDriverOption.METADATA_SCHEMA_MAX_EVENTS)); + this.schemaQueriesFactory = context.schemaQueriesFactory(); + this.schemaParserFactory = context.schemaParserFactory(); + } private void initNodes( Set addresses, CompletableFuture initNodesFuture) { - refresh(new InitContactPointsRefresh(metadata, addresses, logPrefix)); + apply(new InitContactPointsRefresh(addresses, logPrefix)); initNodesFuture.complete(null); } private Void refreshNodes(Iterable nodeInfos) { - return refresh(new FullNodeListRefresh(metadata, nodeInfos, logPrefix)); + didFirstNodeListRefresh = true; + return apply(new FullNodeListRefresh(nodeInfos, logPrefix)); } private void addNode(InetSocketAddress address, Optional maybeInfo) { @@ -175,7 +266,7 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { address, info.getConnectAddress()); } else { - refresh(new AddNodeRefresh(metadata, info, logPrefix)); + apply(new AddNodeRefresh(info, logPrefix)); } } else { LOG.debug( @@ -190,7 +281,125 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { } private void removeNode(InetSocketAddress address) { - refresh(new RemoveNodeRefresh(metadata, address, logPrefix)); + apply(new RemoveNodeRefresh(address, logPrefix)); + } + + private void refreshSchema( + String keyspace, + boolean evenIfDisabled, + boolean flushNow, + CompletableFuture future) { + + if (!didFirstNodeListRefresh) { + // This happen if the control connection receives a schema event during init. We can't + // refresh yet because we don't know the nodes' versions, simply ignore. + future.complete(metadata); + return; + } + + // If this is an event, make sure it's not targeting a keyspace that we're ignoring. + boolean isRefreshedKeyspace = + keyspace == null || refreshedKeyspaces.isEmpty() || refreshedKeyspaces.contains(keyspace); + + if (isRefreshedKeyspace && (evenIfDisabled || isSchemaEnabled())) { + acceptSchemaRequest(future, flushNow); + } else { + future.complete(metadata); + singleThreaded.firstSchemaRefreshFuture.complete(null); + } + } + + // An external component has requested a schema refresh, feed it to the debouncer. + private void acceptSchemaRequest(CompletableFuture future, boolean flushNow) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + future.complete(metadata); + } else { + schemaRefreshDebouncer.receive(future); + if (flushNow) { + schemaRefreshDebouncer.flushNow(); + } + } + } + + // Multiple requests have arrived within the debouncer window, coalesce them. + private CompletableFuture coalesceSchemaRequests( + List> futures) { + assert adminExecutor.inEventLoop(); + assert !futures.isEmpty(); + // Keep only one, but ensure that the discarded ones will still be completed when we're done + CompletableFuture result = null; + for (CompletableFuture future : futures) { + if (result == null) { + result = future; + } else { + CompletableFutures.completeFrom(result, future); + } + } + return result; + } + + // The debouncer has flushed, start the actual work. + private void startSchemaRequest(CompletableFuture future) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + future.complete(metadata); + return; + } + if (currentSchemaRefresh == null) { + currentSchemaRefresh = future; + LOG.debug("[{}] Starting schema refresh", logPrefix); + maybeInitControlConnection() + // 1. Query system tables + .thenCompose(v -> schemaQueriesFactory.newInstance(future).execute()) + // 2. Parse the rows into metadata objects, put them in a MetadataRefresh + // 3. Apply the MetadataRefresh + .thenApplyAsync(this::parseAndApplySchemaRows, adminExecutor) + .whenComplete( + (v, error) -> { + if (error != null) { + LOG.warn( + "[{}] Unexpected error while refreshing schema, skipping", + logPrefix, + error); + } + singleThreaded.firstSchemaRefreshFuture.complete(null); + }); + } else if (queuedSchemaRefresh == null) { + queuedSchemaRefresh = future; // wait for our turn + } else { + CompletableFutures.completeFrom(queuedSchemaRefresh, future); // join the queued request + } + } + + // The control connection may or may not have been initialized already by TopologyMonitor. + private CompletionStage maybeInitControlConnection() { + return firstSchemaRefreshFuture.isDone() + // Not the first schema refresh, so we know init was attempted already + ? firstSchemaRefreshFuture + : controlConnection.init(false, true); + } + + private Void parseAndApplySchemaRows(SchemaRows schemaRows) { + assert adminExecutor.inEventLoop(); + assert schemaRows.refreshFuture == currentSchemaRefresh; + try { + SchemaRefresh schemaRefresh = schemaParserFactory.newInstance(schemaRows).parse(); + long start = System.nanoTime(); + apply(schemaRefresh); + currentSchemaRefresh.complete(metadata); + LOG.debug( + "[{}] Applying schema refresh took {}", logPrefix, NanoTime.formatTimeSince(start)); + } catch (Throwable t) { + currentSchemaRefresh.completeExceptionally(t); + } + currentSchemaRefresh = null; + if (queuedSchemaRefresh != null) { + CompletableFuture tmp = this.queuedSchemaRefresh; + this.queuedSchemaRefresh = null; + startSchemaRequest(tmp); + } + return null; } private void close() { @@ -199,17 +408,23 @@ private void close() { } closeWasCalled = true; LOG.debug("[{}] Closing", logPrefix); + // The current schema refresh should fail when its channel gets closed. + if (queuedSchemaRefresh != null) { + queuedSchemaRefresh.completeExceptionally(new IllegalStateException("Cluster is closed")); + } closeFuture.complete(null); } } @VisibleForTesting - Void refresh(MetadataRefresh refresh) { + Void apply(MetadataRefresh refresh) { assert adminExecutor.inEventLoop(); - if (!singleThreaded.closeWasCalled) { - refresh.compute(); - metadata = refresh.newMetadata; - for (Object event : refresh.events) { + MetadataRefresh.Result result = refresh.compute(metadata); + metadata = result.newMetadata; + boolean isFirstSchemaRefresh = + refresh instanceof SchemaRefresh && !singleThreaded.firstSchemaRefreshFuture.isDone(); + if (!singleThreaded.closeWasCalled && !isFirstSchemaRefresh) { + for (Object event : result.events) { context.eventBus().fire(event); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java index fcfc8d955f6..483c5a16d61 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -27,22 +27,32 @@ * we are doing the refresh (by contract, the new copy of the metadata needs to be visible before * the events are sent). This also makes unit testing very easy. * - *

      This is only instantiated and called from the metadata manager's admin thread, therefore + *

      This is only instantiated and called from {@link MetadataManager}'s admin thread, therefore * implementations don't need to be thread-safe. * * @see Cluster#getMetadata() */ -abstract class MetadataRefresh { - final DefaultMetadata oldMetadata; - DefaultMetadata newMetadata; - final List events; +public abstract class MetadataRefresh { + protected final String logPrefix; - protected MetadataRefresh(DefaultMetadata current, String logPrefix) { - this.oldMetadata = current; + protected MetadataRefresh(String logPrefix) { this.logPrefix = logPrefix; - this.events = new ArrayList<>(); } - abstract void compute(); + public abstract Result compute(DefaultMetadata oldMetadata); + + public static class Result { + public final DefaultMetadata newMetadata; + public final List events; + + public Result(DefaultMetadata newMetadata, List events) { + this.newMetadata = newMetadata; + this.events = events; + } + + public Result(DefaultMetadata newMetadata) { + this(newMetadata, Collections.emptyList()); + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 2c54b48c2fd..99a9c35845d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -16,11 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableMap; -import java.net.InetSocketAddress; import java.util.Collections; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,18 +25,8 @@ abstract class NodesRefresh extends MetadataRefresh { private static final Logger LOG = LoggerFactory.getLogger(NodesRefresh.class); - protected NodesRefresh(DefaultMetadata current, String logPrefix) { - super(current, logPrefix); - } - - /** @return null if the nodes haven't changed */ - protected abstract Map computeNewNodes(); - - @Override - void compute() { - Map newNodes = computeNewNodes(); - newMetadata = (newNodes == null) ? oldMetadata : new DefaultMetadata(newNodes); - // TODO recompute token map (even if node list hasn't changed, b/c tokens might have changed) + protected NodesRefresh(String logPrefix) { + super(logPrefix); } protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node, String logPrefix) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 84741be5467..97137a3ff6e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; @@ -29,29 +30,30 @@ public class RemoveNodeRefresh extends NodesRefresh { @VisibleForTesting final InetSocketAddress toRemove; - RemoveNodeRefresh(DefaultMetadata current, InetSocketAddress toRemove, String logPrefix) { - super(current, logPrefix); + RemoveNodeRefresh(InetSocketAddress toRemove, String logPrefix) { + super(logPrefix); this.toRemove = toRemove; } @Override - protected Map computeNewNodes() { + public Result compute(DefaultMetadata oldMetadata) { Map oldNodes = oldMetadata.getNodes(); Node node = oldNodes.get(toRemove); if (node == null) { // Normally this should already be checked before calling MetadataManager, but it doesn't // hurt to fail gracefully just in case - return null; + return new Result(oldMetadata); } else { LOG.debug("[{}] Removing node {}", logPrefix, node); - events.add(NodeStateEvent.removed((DefaultNode) node)); ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); for (Map.Entry entry : oldNodes.entrySet()) { if (!entry.getKey().equals(toRemove)) { newNodesBuilder.put(entry.getKey(), entry.getValue()); } } - return newNodesBuilder.build(); + return new Result( + oldMetadata.withNodes(newNodesBuilder.build()), + ImmutableList.of(NodeStateEvent.removed((DefaultNode) node))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java deleted file mode 100644 index acff6cbaaab..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaElementKind.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.metadata; - -import com.datastax.oss.protocol.internal.ProtocolConstants; -import com.google.common.collect.ImmutableMap; -import java.util.Map; - -/** The different kinds of objects in a schema. */ -public enum SchemaElementKind { - WHOLE_SCHEMA( - // Dummy placeholder, this kind never comes from the server, only internally - "WHOLE_SCHEMA"), - KEYSPACE(ProtocolConstants.SchemaChangeTarget.KEYSPACE), - TABLE(ProtocolConstants.SchemaChangeTarget.TABLE), - TYPE(ProtocolConstants.SchemaChangeTarget.TYPE), - FUNCTION(ProtocolConstants.SchemaChangeTarget.FUNCTION), - AGGREGATE(ProtocolConstants.SchemaChangeTarget.AGGREGATE), - ; - - private final String protocolString; - - SchemaElementKind(String protocolString) { - this.protocolString = protocolString; - } - - private static final Map BY_PROTOCOL_STRING; - - static { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (SchemaElementKind kind : values()) { - builder.put(kind.protocolString, kind); - } - BY_PROTOCOL_STRING = builder.build(); - } - - public static SchemaElementKind fromProtocolString(String protocolString) { - SchemaElementKind kind = BY_PROTOCOL_STRING.get(protocolString); - if (kind == null) { - throw new IllegalArgumentException("Unsupported schema type: " + protocolString); - } - return kind; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java new file mode 100644 index 00000000000..7653819fe6a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultAggregateMetadata implements AggregateMetadata { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultAggregateMetadata.class); + + private final CqlIdentifier keyspace; + private final FunctionSignature signature; + private final FunctionSignature finalFuncSignature; + private final Object initCond; + private final DataType returnType; + private final FunctionSignature stateFuncSignature; + private final DataType stateType; + private final TypeCodec stateTypeCodec; + + public DefaultAggregateMetadata( + CqlIdentifier keyspace, + FunctionSignature signature, + FunctionSignature finalFuncSignature, + Object initCond, + DataType returnType, + FunctionSignature stateFuncSignature, + DataType stateType, + TypeCodec stateTypeCodec) { + this.keyspace = keyspace; + this.signature = signature; + this.finalFuncSignature = finalFuncSignature; + this.initCond = initCond; + this.returnType = returnType; + this.stateFuncSignature = stateFuncSignature; + this.stateType = stateType; + this.stateTypeCodec = stateTypeCodec; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public FunctionSignature getSignature() { + return signature; + } + + @Override + public FunctionSignature getFinalFuncSignature() { + return finalFuncSignature; + } + + @Override + public Object getInitCond() { + return initCond; + } + + @Override + public DataType getReturnType() { + return returnType; + } + + @Override + public FunctionSignature getStateFuncSignature() { + return stateFuncSignature; + } + + @Override + public DataType getStateType() { + return stateType; + } + + @Override + public String formatInitCond() { + try { + return stateTypeCodec.format(initCond); + } catch (Throwable t) { + LOG.warn( + String.format( + "Failed to format INITCOND for %s.%s, using toString instead", + keyspace.asInternal(), signature.getName().asInternal())); + return initCond.toString(); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof AggregateMetadata) { + AggregateMetadata that = (AggregateMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.signature, that.getSignature()) + && Objects.equals(this.finalFuncSignature, that.getFinalFuncSignature()) + && Objects.equals(this.initCond, that.getInitCond()) + && Objects.equals(this.returnType, that.getReturnType()) + && Objects.equals(this.stateFuncSignature, that.getStateFuncSignature()) + && Objects.equals(this.stateType, that.getStateType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + keyspace, + signature, + finalFuncSignature, + initCond, + returnType, + stateFuncSignature, + stateType); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java new file mode 100644 index 00000000000..87f82c7f345 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.type.DataType; +import java.util.Objects; + +public class DefaultColumnMetadata implements ColumnMetadata { + private final CqlIdentifier keyspace; + private final CqlIdentifier parent; + private final CqlIdentifier name; + private final DataType dataType; + private final boolean isStatic; + + public DefaultColumnMetadata( + CqlIdentifier keyspace, + CqlIdentifier parent, + CqlIdentifier name, + DataType dataType, + boolean isStatic) { + this.keyspace = keyspace; + this.parent = parent; + this.name = name; + this.dataType = dataType; + this.isStatic = isStatic; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getParent() { + return parent; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public DataType getType() { + return dataType; + } + + @Override + public boolean isStatic() { + return isStatic; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ColumnMetadata) { + ColumnMetadata that = (ColumnMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.parent, that.getParent()) + && Objects.equals(this.name, that.getName()) + && Objects.equals(this.dataType, that.getType()) + && this.isStatic == that.isStatic(); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(keyspace, parent, name, dataType, isStatic); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java new file mode 100644 index 00000000000..41b3390d2d5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.type.DataType; +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Objects; + +public class DefaultFunctionMetadata implements FunctionMetadata { + + private final CqlIdentifier keyspace; + private final FunctionSignature signature; + private final List parameterNames; + private final String body; + private final boolean calledOnNullInput; + private final String language; + private final DataType returnType; + + public DefaultFunctionMetadata( + CqlIdentifier keyspace, + FunctionSignature signature, + List parameterNames, + String body, + boolean calledOnNullInput, + String language, + DataType returnType) { + Preconditions.checkArgument( + signature.getParameterTypes().size() == parameterNames.size(), + "Number of parameter names should match number of types in the signature (got %s and %s)", + parameterNames.size(), + signature.getParameterTypes().size()); + this.keyspace = keyspace; + this.signature = signature; + this.parameterNames = parameterNames; + this.body = body; + this.calledOnNullInput = calledOnNullInput; + this.language = language; + this.returnType = returnType; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public FunctionSignature getSignature() { + return signature; + } + + @Override + public List getParameterNames() { + return parameterNames; + } + + @Override + public String getBody() { + return body; + } + + @Override + public boolean isCalledOnNullInput() { + return calledOnNullInput; + } + + @Override + public String getLanguage() { + return language; + } + + @Override + public DataType getReturnType() { + return returnType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof FunctionMetadata) { + FunctionMetadata that = (FunctionMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.signature, that.getSignature()) + && Objects.equals(this.parameterNames, that.getParameterNames()) + && Objects.equals(this.body, that.getBody()) + && this.calledOnNullInput == that.isCalledOnNullInput() + && Objects.equals(this.language, that.getLanguage()) + && Objects.equals(this.returnType, that.getReturnType()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + keyspace, signature, parameterNames, body, calledOnNullInput, language, returnType); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java new file mode 100644 index 00000000000..b48b2db5bb2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.IndexKind; +import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; +import java.util.Map; +import java.util.Objects; + +public class DefaultIndexMetadata implements IndexMetadata { + + private final CqlIdentifier keyspace; + private final CqlIdentifier table; + private final CqlIdentifier name; + private final IndexKind kind; + private final String target; + private final Map options; + + public DefaultIndexMetadata( + CqlIdentifier keyspace, + CqlIdentifier table, + CqlIdentifier name, + IndexKind kind, + String target, + Map options) { + this.keyspace = keyspace; + this.table = table; + this.name = name; + this.kind = kind; + this.target = target; + this.options = options; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getTable() { + return table; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public IndexKind getKind() { + return kind; + } + + @Override + public String getTarget() { + return target; + } + + public Map getOptions() { + return options; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof IndexMetadata) { + IndexMetadata that = (IndexMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.table, that.getTable()) + && Objects.equals(this.name, that.getName()) + && Objects.equals(this.kind, that.getKind()) + && Objects.equals(this.target, that.getTarget()) + && Objects.equals(this.options, that.getOptions()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(keyspace, table, name, kind, target, options); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java new file mode 100644 index 00000000000..4a1d8b37e9e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import java.util.Map; +import java.util.Objects; + +public class DefaultKeyspaceMetadata implements KeyspaceMetadata { + + private final CqlIdentifier name; + private final boolean durableWrites; + private final Map replication; + private final Map types; + private final Map tables; + private final Map views; + private final Map functions; + private final Map aggregates; + + public DefaultKeyspaceMetadata( + CqlIdentifier name, + boolean durableWrites, + Map replication, + Map types, + Map tables, + Map views, + Map functions, + Map aggregates) { + this.name = name; + this.durableWrites = durableWrites; + this.replication = replication; + this.types = types; + this.tables = tables; + this.views = views; + this.functions = functions; + this.aggregates = aggregates; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public boolean isDurableWrites() { + return durableWrites; + } + + @Override + public Map getReplication() { + return replication; + } + + @Override + public Map getUserDefinedTypes() { + return types; + } + + @Override + public Map getTables() { + return tables; + } + + @Override + public Map getViews() { + return views; + } + + @Override + public Map getFunctions() { + return functions; + } + + @Override + public Map getAggregates() { + return aggregates; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof KeyspaceMetadata) { + KeyspaceMetadata that = (KeyspaceMetadata) other; + return Objects.equals(this.name, that.getName()) + && this.durableWrites == that.isDurableWrites() + && Objects.equals(this.replication, that.getReplication()) + && Objects.equals(this.types, that.getUserDefinedTypes()) + && Objects.equals(this.tables, that.getTables()) + && Objects.equals(this.views, that.getViews()) + && Objects.equals(this.functions, that.getFunctions()) + && Objects.equals(this.aggregates, that.getAggregates()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + name, durableWrites, replication, types, tables, views, functions, aggregates); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java new file mode 100644 index 00000000000..2b21a3971cb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +public class DefaultTableMetadata implements TableMetadata { + + private final CqlIdentifier keyspace; + private final CqlIdentifier name; + private final UUID id; + private final boolean compactStorage; + private final List partitionKey; + private final Map clusteringColumns; + private final Map columns; + private final Map options; + private final Map indexes; + + public DefaultTableMetadata( + CqlIdentifier keyspace, + CqlIdentifier name, + UUID id, + boolean compactStorage, + List partitionKey, + Map clusteringColumns, + Map columns, + Map options, + Map indexes) { + this.keyspace = keyspace; + this.name = name; + this.id = id; + this.compactStorage = compactStorage; + this.partitionKey = partitionKey; + this.clusteringColumns = clusteringColumns; + this.columns = columns; + this.options = options; + this.indexes = indexes; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public boolean isCompactStorage() { + return compactStorage; + } + + @Override + public List getPartitionKey() { + return partitionKey; + } + + @Override + public Map getClusteringColumns() { + return clusteringColumns; + } + + @Override + public Map getColumns() { + return columns; + } + + @Override + public Map getOptions() { + return options; + } + + @Override + public Map getIndexes() { + return indexes; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TableMetadata) { + TableMetadata that = (TableMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.name, that.getName()) + && Objects.equals(this.id, that.getId()) + && this.compactStorage == that.isCompactStorage() + && Objects.equals(this.partitionKey, that.getPartitionKey()) + && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) + && Objects.equals(this.columns, that.getColumns()) + && Objects.equals(this.indexes, that.getIndexes()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + keyspace, name, id, compactStorage, partitionKey, clusteringColumns, columns, indexes); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java new file mode 100644 index 00000000000..bd2af6b97bf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +public class DefaultViewMetadata implements ViewMetadata { + + private final CqlIdentifier keyspace; + private final CqlIdentifier name; + private final CqlIdentifier baseTable; + private final boolean includesAllColumns; + private final String whereClause; + private final UUID id; + private final ImmutableList partitionKey; + private final ImmutableMap clusteringColumns; + private final ImmutableMap columns; + private final Map options; + + public DefaultViewMetadata( + CqlIdentifier keyspace, + CqlIdentifier name, + CqlIdentifier baseTable, + boolean includesAllColumns, + String whereClause, + UUID id, + ImmutableList partitionKey, + ImmutableMap clusteringColumns, + ImmutableMap columns, + Map options) { + this.keyspace = keyspace; + this.name = name; + this.baseTable = baseTable; + this.includesAllColumns = includesAllColumns; + this.whereClause = whereClause; + this.id = id; + this.partitionKey = partitionKey; + this.clusteringColumns = clusteringColumns; + this.columns = columns; + this.options = options; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public CqlIdentifier getBaseTable() { + return baseTable; + } + + @Override + public boolean includesAllColumns() { + return includesAllColumns; + } + + @Override + public String getWhereClause() { + return whereClause; + } + + @Override + public List getPartitionKey() { + return partitionKey; + } + + @Override + public Map getClusteringColumns() { + return clusteringColumns; + } + + @Override + public Map getColumns() { + return columns; + } + + @Override + public Map getOptions() { + return options; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ViewMetadata) { + ViewMetadata that = (ViewMetadata) other; + return Objects.equals(this.keyspace, that.getKeyspace()) + && Objects.equals(this.name, that.getName()) + && Objects.equals(this.baseTable, that.getBaseTable()) + && this.includesAllColumns == that.includesAllColumns() + && Objects.equals(this.whereClause, that.getWhereClause()) + && Objects.equals(this.id, that.getId()) + && Objects.equals(this.partitionKey, that.getPartitionKey()) + && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) + && Objects.equals(this.columns, that.getColumns()) + && Objects.equals(this.options, that.getOptions()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + keyspace, + name, + baseTable, + includesAllColumns, + whereClause, + id, + partitionKey, + clusteringColumns, + columns, + options); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/SchemaChangeType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/SchemaChangeType.java new file mode 100644 index 00000000000..4c8596fed11 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/SchemaChangeType.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +public enum SchemaChangeType { + CREATED, + UPDATED, + DROPPED, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ScriptBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ScriptBuilder.java new file mode 100644 index 00000000000..3888b33a0b4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ScriptBuilder.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.Describable; +import com.google.common.base.Strings; +import java.util.function.Consumer; + +/** + * A simple builder that is used internally for the queries of {@link Describable} schema elements. + */ +public class ScriptBuilder { + private static final int INDENT_SIZE = 4; + + private final boolean pretty; + private final StringBuilder builder = new StringBuilder(); + private int indent; + private boolean isAtLineStart; + private boolean isFirstOption = true; + + public ScriptBuilder(boolean pretty) { + this.pretty = pretty; + } + + public ScriptBuilder append(String s) { + if (pretty && isAtLineStart && indent > 0) { + builder.append(Strings.repeat(" ", indent * INDENT_SIZE)); + } + isAtLineStart = false; + builder.append(s); + return this; + } + + public ScriptBuilder append(CqlIdentifier id) { + append(id.asCql(pretty)); + return this; + } + + public ScriptBuilder newLine() { + if (pretty) { + builder.append('\n'); + } else { + builder.append(' '); + } + isAtLineStart = true; + return this; + } + + public ScriptBuilder forceNewLine(int count) { + builder.append(Strings.repeat("\n", count)); + isAtLineStart = true; + return this; + } + + public ScriptBuilder increaseIndent() { + indent += 1; + return this; + } + + public ScriptBuilder decreaseIndent() { + if (indent > 0) { + indent -= 1; + } + return this; + } + + /** Appends "WITH " the first time it's called, then "AND " the next times. */ + public ScriptBuilder andWith() { + if (isFirstOption) { + append(" WITH "); + isFirstOption = false; + } else { + newLine(); + append("AND "); + } + return this; + } + + public ScriptBuilder forEach(Iterable iterable, Consumer action) { + for (E e : iterable) { + action.accept(e); + } + return this; + } + + public String build() { + return builder.toString(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ShallowUserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ShallowUserDefinedType.java new file mode 100644 index 00000000000..ec46a34e528 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/ShallowUserDefinedType.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +/** + * A temporary UDT implementation that only contains the keyspace and name. + * + *

      When we process a schema refresh that spans multiple UDTs, we can't fully materialize them + * right away, because they might depend on each other and the system table query does not return + * them in topological order. So we do a first pass where UDT field that are also UDTs are resolved + * as instances of this class, then a topological sort, then a second pass to replace all shallow + * definitions by the actual instance (which will be a {@link DefaultUserDefinedType}). + */ +public class ShallowUserDefinedType implements UserDefinedType { + + private final CqlIdentifier keyspace; + private final CqlIdentifier name; + private final boolean frozen; + + public ShallowUserDefinedType(CqlIdentifier keyspace, CqlIdentifier name, boolean frozen) { + this.keyspace = keyspace; + this.name = name; + this.frozen = frozen; + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public boolean isFrozen() { + return frozen; + } + + @Override + public List getFieldNames() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public int firstIndexOf(String name) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public List getFieldTypes() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public UserDefinedType copy(boolean newFrozen) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public UdtValue newValue() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public AttachmentPoint getAttachmentPoint() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public boolean isDetached() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public void attach(AttachmentPoint attachmentPoint) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + private void readObject(ObjectInputStream s) throws IOException { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + private void writeObject(ObjectOutputStream s) throws IOException { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/AggregateChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/AggregateChangeEvent.java new file mode 100644 index 00000000000..d61e64f9e15 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/AggregateChangeEvent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class AggregateChangeEvent { + + public static AggregateChangeEvent dropped(AggregateMetadata oldAggregate) { + return new AggregateChangeEvent(SchemaChangeType.DROPPED, oldAggregate, null); + } + + public static AggregateChangeEvent created(AggregateMetadata newAggregate) { + return new AggregateChangeEvent(SchemaChangeType.CREATED, null, newAggregate); + } + + public static AggregateChangeEvent updated( + AggregateMetadata oldAggregate, AggregateMetadata newAggregate) { + return new AggregateChangeEvent(SchemaChangeType.UPDATED, oldAggregate, newAggregate); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final AggregateMetadata oldAggregate; + /** {@code null} if the event is a drop */ + public final AggregateMetadata newAggregate; + + private AggregateChangeEvent( + SchemaChangeType changeType, AggregateMetadata oldAggregate, AggregateMetadata newAggregate) { + this.changeType = changeType; + this.oldAggregate = oldAggregate; + this.newAggregate = newAggregate; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof AggregateChangeEvent) { + AggregateChangeEvent that = (AggregateChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldAggregate, that.oldAggregate) + && Objects.equals(this.newAggregate, that.newAggregate); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldAggregate, newAggregate); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("AggregateChangeEvent(CREATED %s)", newAggregate.getSignature()); + case UPDATED: + return String.format( + "AggregateChangeEvent(UPDATED %s=>%s)", + oldAggregate.getSignature(), newAggregate.getSignature()); + case DROPPED: + return String.format("AggregateChangeEvent(DROPPED %s)", oldAggregate.getSignature()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/FunctionChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/FunctionChangeEvent.java new file mode 100644 index 00000000000..7217d583b7d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/FunctionChangeEvent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class FunctionChangeEvent { + + public static FunctionChangeEvent dropped(FunctionMetadata oldFunction) { + return new FunctionChangeEvent(SchemaChangeType.DROPPED, oldFunction, null); + } + + public static FunctionChangeEvent created(FunctionMetadata newFunction) { + return new FunctionChangeEvent(SchemaChangeType.CREATED, null, newFunction); + } + + public static FunctionChangeEvent updated( + FunctionMetadata oldFunction, FunctionMetadata newFunction) { + return new FunctionChangeEvent(SchemaChangeType.UPDATED, oldFunction, newFunction); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final FunctionMetadata oldFunction; + /** {@code null} if the event is a drop */ + public final FunctionMetadata newFunction; + + private FunctionChangeEvent( + SchemaChangeType changeType, FunctionMetadata oldFunction, FunctionMetadata newFunction) { + this.changeType = changeType; + this.oldFunction = oldFunction; + this.newFunction = newFunction; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof FunctionChangeEvent) { + FunctionChangeEvent that = (FunctionChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldFunction, that.oldFunction) + && Objects.equals(this.newFunction, that.newFunction); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldFunction, newFunction); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("FunctionChangeEvent(CREATED %s)", newFunction.getSignature()); + case UPDATED: + return String.format( + "FunctionChangeEvent(UPDATED %s=>%s)", + oldFunction.getSignature(), newFunction.getSignature()); + case DROPPED: + return String.format("FunctionChangeEvent(DROPPED %s)", oldFunction.getSignature()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/KeyspaceChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/KeyspaceChangeEvent.java new file mode 100644 index 00000000000..a7e0afc445e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/KeyspaceChangeEvent.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class KeyspaceChangeEvent { + + public static KeyspaceChangeEvent dropped(KeyspaceMetadata oldKeyspace) { + return new KeyspaceChangeEvent(SchemaChangeType.DROPPED, oldKeyspace, null); + } + + public static KeyspaceChangeEvent created(KeyspaceMetadata newKeyspace) { + return new KeyspaceChangeEvent(SchemaChangeType.CREATED, null, newKeyspace); + } + + public static KeyspaceChangeEvent updated( + KeyspaceMetadata oldKeyspace, KeyspaceMetadata newKeyspace) { + return new KeyspaceChangeEvent(SchemaChangeType.UPDATED, oldKeyspace, newKeyspace); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final KeyspaceMetadata oldKeyspace; + /** {@code null} if the event is a drop */ + public final KeyspaceMetadata newKeyspace; + + private KeyspaceChangeEvent( + SchemaChangeType changeType, KeyspaceMetadata oldKeyspace, KeyspaceMetadata newKeyspace) { + this.changeType = changeType; + this.oldKeyspace = oldKeyspace; + this.newKeyspace = newKeyspace; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof KeyspaceChangeEvent) { + KeyspaceChangeEvent that = (KeyspaceChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldKeyspace, that.oldKeyspace) + && Objects.equals(this.newKeyspace, that.newKeyspace); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldKeyspace, newKeyspace); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("KeyspaceChangeEvent(CREATED %s)", newKeyspace.getName()); + case UPDATED: + return String.format( + "KeyspaceChangeEvent(UPDATED %s=>%s)", oldKeyspace.getName(), newKeyspace.getName()); + case DROPPED: + return String.format("KeyspaceChangeEvent(DROPPED %s)", oldKeyspace.getName()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TableChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TableChangeEvent.java new file mode 100644 index 00000000000..af58f7502c7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TableChangeEvent.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class TableChangeEvent { + + public static TableChangeEvent dropped(TableMetadata oldTable) { + return new TableChangeEvent(SchemaChangeType.DROPPED, oldTable, null); + } + + public static TableChangeEvent created(TableMetadata newTable) { + return new TableChangeEvent(SchemaChangeType.CREATED, null, newTable); + } + + public static TableChangeEvent updated(TableMetadata oldTable, TableMetadata newTable) { + return new TableChangeEvent(SchemaChangeType.UPDATED, oldTable, newTable); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final TableMetadata oldTable; + /** {@code null} if the event is a drop */ + public final TableMetadata newTable; + + private TableChangeEvent( + SchemaChangeType changeType, TableMetadata oldTable, TableMetadata newTable) { + this.changeType = changeType; + this.oldTable = oldTable; + this.newTable = newTable; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TableChangeEvent) { + TableChangeEvent that = (TableChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldTable, that.oldTable) + && Objects.equals(this.newTable, that.newTable); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldTable, newTable); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("TableChangeEvent(CREATED %s)", newTable.getName()); + case UPDATED: + return String.format( + "TableChangeEvent(UPDATED %s=>%s)", oldTable.getName(), newTable.getName()); + case DROPPED: + return String.format("TableChangeEvent(DROPPED %s)", oldTable.getName()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TypeChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TypeChangeEvent.java new file mode 100644 index 00000000000..ce736aefcd2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/TypeChangeEvent.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class TypeChangeEvent { + + public static TypeChangeEvent dropped(UserDefinedType oldType) { + return new TypeChangeEvent(SchemaChangeType.DROPPED, oldType, null); + } + + public static TypeChangeEvent created(UserDefinedType newType) { + return new TypeChangeEvent(SchemaChangeType.CREATED, null, newType); + } + + public static TypeChangeEvent updated(UserDefinedType oldType, UserDefinedType newType) { + return new TypeChangeEvent(SchemaChangeType.UPDATED, oldType, newType); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final UserDefinedType oldType; + /** {@code null} if the event is a drop */ + public final UserDefinedType newType; + + private TypeChangeEvent( + SchemaChangeType changeType, UserDefinedType oldType, UserDefinedType newType) { + this.changeType = changeType; + this.oldType = oldType; + this.newType = newType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TypeChangeEvent) { + TypeChangeEvent that = (TypeChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldType, that.oldType) + && Objects.equals(this.newType, that.newType); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldType, newType); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("TypeChangeEvent(CREATED %s)", newType.getName()); + case UPDATED: + return String.format( + "TypeChangeEvent(UPDATED %s=>%s)", oldType.getName(), newType.getName()); + case DROPPED: + return String.format("TypeChangeEvent(DROPPED %s)", oldType.getName()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/ViewChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/ViewChangeEvent.java new file mode 100644 index 00000000000..a136140ed60 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/events/ViewChangeEvent.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.events; + +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.SchemaChangeType; +import java.util.Objects; + +public class ViewChangeEvent { + + public static ViewChangeEvent dropped(ViewMetadata oldView) { + return new ViewChangeEvent(SchemaChangeType.DROPPED, oldView, null); + } + + public static ViewChangeEvent created(ViewMetadata newView) { + return new ViewChangeEvent(SchemaChangeType.CREATED, null, newView); + } + + public static ViewChangeEvent updated(ViewMetadata oldView, ViewMetadata newView) { + return new ViewChangeEvent(SchemaChangeType.UPDATED, oldView, newView); + } + + public final SchemaChangeType changeType; + /** {@code null} if the event is a creation */ + public final ViewMetadata oldView; + /** {@code null} if the event is a drop */ + public final ViewMetadata newView; + + private ViewChangeEvent(SchemaChangeType changeType, ViewMetadata oldView, ViewMetadata newView) { + this.changeType = changeType; + this.oldView = oldView; + this.newView = newView; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ViewChangeEvent) { + ViewChangeEvent that = (ViewChangeEvent) other; + return this.changeType == that.changeType + && Objects.equals(this.oldView, that.oldView) + && Objects.equals(this.newView, that.newView); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(changeType, oldView, newView); + } + + @Override + public String toString() { + switch (changeType) { + case CREATED: + return String.format("ViewChangeEvent(CREATED %s)", newView.getName()); + case UPDATED: + return String.format( + "ViewChangeEvent(UPDATED %s=>%s)", oldView.getName(), newView.getName()); + case DROPPED: + return String.format("ViewChangeEvent(DROPPED %s)", oldView.getName()); + default: + throw new IllegalStateException("Unsupported change type " + changeType); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java new file mode 100644 index 00000000000..8dce9c24bcc --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultAggregateMetadata; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +class AggregateParser { + private final DataTypeParser dataTypeParser; + private final InternalDriverContext context; + + AggregateParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + this.dataTypeParser = dataTypeParser; + this.context = context; + } + + AggregateMetadata parseAggregate( + AdminRow row, + CqlIdentifier keyspaceId, + Map userDefinedTypes) { + // Cassandra < 3.0: + // CREATE TABLE system.schema_aggregates ( + // keyspace_name text, + // aggregate_name text, + // signature frozen>, + // argument_types list, + // final_func text, + // initcond blob, + // return_type text, + // state_func text, + // state_type text, + // PRIMARY KEY (keyspace_name, aggregate_name, signature) + // ) WITH CLUSTERING ORDER BY (aggregate_name ASC, signature ASC) + // + // Cassandra >= 3.0: + // CREATE TABLE system.schema_aggregates ( + // keyspace_name text, + // aggregate_name text, + // argument_types frozen>, + // final_func text, + // initcond text, + // return_type text, + // state_func text, + // state_type text, + // PRIMARY KEY (keyspace_name, aggregate_name, argument_types) + // ) WITH CLUSTERING ORDER BY (aggregate_name ASC, argument_types ASC) + String simpleName = row.getString("aggregate_name"); + List argumentTypes = row.getListOfString("argument_types"); + FunctionSignature signature = + new FunctionSignature( + CqlIdentifier.fromInternal(simpleName), + dataTypeParser.parse(keyspaceId, argumentTypes, userDefinedTypes, context)); + + DataType stateType = + dataTypeParser.parse(keyspaceId, row.getString("state_type"), userDefinedTypes, context); + TypeCodec stateTypeCodec = context.codecRegistry().codecFor(stateType); + + String stateFuncSimpleName = row.getString("state_func"); + FunctionSignature stateFuncSignature = + new FunctionSignature( + CqlIdentifier.fromInternal(stateFuncSimpleName), + ImmutableList.builder() + .add(stateType) + .addAll(signature.getParameterTypes()) + .build()); + + String finalFuncSimpleName = row.getString("final_func"); + FunctionSignature finalFuncSignature = + (finalFuncSimpleName == null) + ? null + : new FunctionSignature(CqlIdentifier.fromInternal(finalFuncSimpleName), stateType); + + DataType returnType = + dataTypeParser.parse(keyspaceId, row.getString("return_type"), userDefinedTypes, context); + + Object initCond; + if (row.isString("initcond")) { // Cassandra 3 + String initCondString = row.getString("initcond"); + initCond = (initCondString == null) ? null : stateTypeCodec.parse(initCondString); + } else { // Cassandra 2.2 + ByteBuffer initCondBlob = row.getByteBuffer("initcond"); + initCond = + (initCondBlob == null) + ? null + : stateTypeCodec.decode(initCondBlob, context.protocolVersion()); + } + return new DefaultAggregateMetadata( + keyspaceId, + signature, + finalFuncSignature, + initCond, + returnType, + stateFuncSignature, + stateType, + stateTypeCodec); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java new file mode 100644 index 00000000000..32c279c77bc --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class DataTypeClassNameCompositeParser extends DataTypeClassNameParser { + + ParseResult parseWithComposite( + String className, + CqlIdentifier keyspaceId, + Map userTypes, + InternalDriverContext context) { + Parser parser = new Parser(className, 0); + + String next = parser.parseNextName(); + if (!isComposite(next)) { + return new ParseResult(parse(keyspaceId, className, userTypes, context), isReversed(next)); + } + + List subClassNames = parser.getTypeParameters(); + int count = subClassNames.size(); + String last = subClassNames.get(count - 1); + Map collections = new HashMap<>(); + if (isCollection(last)) { + count--; + Parser collectionParser = new Parser(last, 0); + collectionParser.parseNextName(); // skips columnToCollectionType + Map params = collectionParser.getCollectionsParameters(); + for (Map.Entry entry : params.entrySet()) { + collections.put(entry.getKey(), parse(keyspaceId, entry.getValue(), userTypes, context)); + } + } + + List types = new ArrayList<>(count); + List reversed = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + types.add(parse(keyspaceId, subClassNames.get(i), userTypes, context)); + reversed.add(isReversed(subClassNames.get(i))); + } + + return new ParseResult(true, types, reversed, collections); + } + + static class ParseResult { + final boolean isComposite; + final List types; + final List reversed; + final Map collections; + + private ParseResult(DataType type, boolean reversed) { + this( + false, + Collections.singletonList(type), + Collections.singletonList(reversed), + Collections.emptyMap()); + } + + private ParseResult( + boolean isComposite, + List types, + List reversed, + Map collections) { + this.isComposite = isComposite; + this.types = types; + this.reversed = reversed; + this.collections = collections; + } + } + + private static boolean isComposite(String className) { + return className.startsWith("org.apache.cassandra.db.marshal.CompositeType"); + } + + private static boolean isCollection(String className) { + return className.startsWith("org.apache.cassandra.db.marshal.ColumnToCollectionType"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java new file mode 100644 index 00000000000..ecfde0ec103 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; +import com.datastax.oss.driver.internal.core.type.codec.ParseUtils; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses data types from schema tables, for Cassandra 2.2 and below. + * + *

      In these versions, data types appear as class names, like + * "org.apache.cassandra.db.marshal.AsciiType" or + * "org.apache.cassandra.db.marshal.TupleType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type)". + * + *

      This is modified (and simplified) from Cassandra's {@code TypeParser} class to suit our needs. + * In particular it's not very efficient, but it doesn't really matter since it's rarely used and + * never in a critical path. + */ +class DataTypeClassNameParser implements DataTypeParser { + + private static final Logger LOG = LoggerFactory.getLogger(DataTypeClassNameParser.class); + + @Override + public DataType parse( + CqlIdentifier keyspaceId, + String toParse, + Map userTypes, + InternalDriverContext context) { + boolean frozen = false; + if (isReversed(toParse)) { + // Just skip the ReversedType part, we don't care + toParse = getNestedClassName(toParse); + } else if (toParse.startsWith("org.apache.cassandra.db.marshal.FrozenType")) { + frozen = true; + toParse = getNestedClassName(toParse); + } + + Parser parser = new Parser(toParse, 0); + String next = parser.parseNextName(); + + if (next.startsWith("org.apache.cassandra.db.marshal.ListType")) { + DataType elementType = + parse(keyspaceId, parser.getTypeParameters().get(0), userTypes, context); + return DataTypes.listOf(elementType, frozen); + } + + if (next.startsWith("org.apache.cassandra.db.marshal.SetType")) { + DataType elementType = + parse(keyspaceId, parser.getTypeParameters().get(0), userTypes, context); + return DataTypes.setOf(elementType, frozen); + } + + if (next.startsWith("org.apache.cassandra.db.marshal.MapType")) { + List parameters = parser.getTypeParameters(); + DataType keyType = parse(keyspaceId, parameters.get(0), userTypes, context); + DataType valueType = parse(keyspaceId, parameters.get(1), userTypes, context); + return DataTypes.mapOf(keyType, valueType, frozen); + } + + if (frozen) + LOG.warn( + "[{}] Got o.a.c.db.marshal.FrozenType for something else than a collection, " + + "this driver version might be too old for your version of Cassandra", + context.clusterName()); + + if (next.startsWith("org.apache.cassandra.db.marshal.UserType")) { + ++parser.idx; // skipping '(' + + CqlIdentifier keyspace = CqlIdentifier.fromInternal(parser.readOne()); + parser.skipBlankAndComma(); + CqlIdentifier typeName = + CqlIdentifier.fromInternal( + TypeCodecs.TEXT.decode( + Bytes.fromHexString("0x" + parser.readOne()), context.protocolVersion())); + Map nameAndTypeParameters = parser.getNameAndTypeParameters(); + + // Avoid re-parsing if we already have the definition + if (userTypes != null && userTypes.containsKey(typeName)) { + // copy as frozen since C* 2.x UDTs are always frozen. + return userTypes.get(typeName).copy(true); + } else { + UserDefinedTypeBuilder builder = new UserDefinedTypeBuilder(keyspace, typeName); + parser.skipBlankAndComma(); + for (Map.Entry entry : nameAndTypeParameters.entrySet()) { + CqlIdentifier fieldName = CqlIdentifier.fromInternal(entry.getKey()); + DataType fieldType = parse(keyspaceId, entry.getValue(), userTypes, context); + builder.withField(fieldName, fieldType); + } + // create a frozen UserType since C* 2.x UDTs are always frozen. + return builder.frozen().build(); + } + } + + if (next.startsWith("org.apache.cassandra.db.marshal.TupleType")) { + List rawTypes = parser.getTypeParameters(); + ImmutableList.Builder componentTypesBuilder = ImmutableList.builder(); + for (String rawType : rawTypes) { + componentTypesBuilder.add(parse(keyspaceId, rawType, userTypes, context)); + } + return new DefaultTupleType(componentTypesBuilder.build(), context); + } + + DataType type = NATIVE_TYPES_BY_CLASS_NAME.get(next); + return type == null ? DataTypes.custom(toParse) : type; + } + + static boolean isReversed(String toParse) { + return toParse.startsWith("org.apache.cassandra.db.marshal.ReversedType"); + } + + private static String getNestedClassName(String className) { + Parser p = new Parser(className, 0); + p.parseNextName(); + List l = p.getTypeParameters(); + if (l.size() != 1) { + throw new IllegalStateException(); + } + className = l.get(0); + return className; + } + + static class Parser { + + private final String str; + private int idx; + + Parser(String str, int idx) { + this.str = str; + this.idx = idx; + } + + String parseNextName() { + skipBlank(); + return readNextIdentifier(); + } + + private String readOne() { + String name = parseNextName(); + String args = readRawArguments(); + return name + args; + } + + // Assumes we have just read a class name and read it's potential arguments + // blindly. I.e. it assume that either parsing is done or that we're on a '(' + // and this reads everything up until the corresponding closing ')'. It + // returns everything read, including the enclosing parenthesis. + private String readRawArguments() { + skipBlank(); + + if (isEOS() || str.charAt(idx) == ')' || str.charAt(idx) == ',') { + return ""; + } + + if (str.charAt(idx) != '(') { + throw new IllegalStateException( + String.format( + "Expecting char %d of %s to be '(' but '%c' found", idx, str, str.charAt(idx))); + } + + int i = idx; + int open = 1; + while (open > 0) { + ++idx; + + if (isEOS()) { + throw new IllegalStateException("Non closed parenthesis"); + } + + if (str.charAt(idx) == '(') { + open++; + } else if (str.charAt(idx) == ')') { + open--; + } + } + // we've stopped at the last closing ')' so move past that + ++idx; + return str.substring(i, idx); + } + + List getTypeParameters() { + List list = new ArrayList<>(); + + if (isEOS()) { + return list; + } + + if (str.charAt(idx) != '(') { + throw new IllegalStateException(); + } + + ++idx; // skipping '(' + + while (skipBlankAndComma()) { + if (str.charAt(idx) == ')') { + ++idx; + return list; + } + list.add(readOne()); + } + throw new IllegalArgumentException( + String.format( + "Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); + } + + Map getCollectionsParameters() { + if (isEOS()) { + return Collections.emptyMap(); + } + if (str.charAt(idx) != '(') { + throw new IllegalStateException(); + } + ++idx; // skipping '(' + return getNameAndTypeParameters(); + } + + // Must be at the start of the first parameter to read + private Map getNameAndTypeParameters() { + // The order of the hashmap matters for UDT + Map map = new LinkedHashMap<>(); + + while (skipBlankAndComma()) { + if (str.charAt(idx) == ')') { + ++idx; + return map; + } + + String bbHex = readNextIdentifier(); + String name = null; + try { + name = + TypeCodecs.TEXT.decode( + Bytes.fromHexString("0x" + bbHex), CoreProtocolVersion.DEFAULT); + } catch (NumberFormatException e) { + throwSyntaxError(e.getMessage()); + } + + skipBlank(); + if (str.charAt(idx) != ':') { + throwSyntaxError("expecting ':' token"); + } + + ++idx; + skipBlank(); + map.put(name, readOne()); + } + throw new IllegalArgumentException( + String.format( + "Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); + } + + private void throwSyntaxError(String msg) { + throw new IllegalArgumentException( + String.format("Syntax error parsing '%s' at char %d: %s", str, idx, msg)); + } + + private boolean isEOS() { + return isEOS(str, idx); + } + + private static boolean isEOS(String str, int i) { + return i >= str.length(); + } + + private void skipBlank() { + idx = skipBlank(str, idx); + } + + private static int skipBlank(String str, int i) { + while (!isEOS(str, i) && ParseUtils.isBlank(str.charAt(i))) { + ++i; + } + return i; + } + + // skip all blank and at best one comma, return true if there not EOS + private boolean skipBlankAndComma() { + boolean commaFound = false; + while (!isEOS()) { + int c = str.charAt(idx); + if (c == ',') { + if (commaFound) { + return true; + } else { + commaFound = true; + } + } else if (!ParseUtils.isBlank(c)) { + return true; + } + ++idx; + } + return false; + } + + // left idx positioned on the character stopping the read + private String readNextIdentifier() { + int i = idx; + while (!isEOS() && ParseUtils.isCqlIdentifierChar(str.charAt(idx))) { + ++idx; + } + return str.substring(i, idx); + } + + @Override + public String toString() { + return str.substring(0, idx) + + "[" + + (idx == str.length() ? "" : str.charAt(idx)) + + "]" + + str.substring(idx + 1); + } + } + + @VisibleForTesting + static ImmutableMap NATIVE_TYPES_BY_CLASS_NAME = + new ImmutableMap.Builder() + .put("org.apache.cassandra.db.marshal.AsciiType", DataTypes.ASCII) + .put("org.apache.cassandra.db.marshal.LongType", DataTypes.BIGINT) + .put("org.apache.cassandra.db.marshal.BytesType", DataTypes.BLOB) + .put("org.apache.cassandra.db.marshal.BooleanType", DataTypes.BOOLEAN) + .put("org.apache.cassandra.db.marshal.CounterColumnType", DataTypes.COUNTER) + .put("org.apache.cassandra.db.marshal.DecimalType", DataTypes.DECIMAL) + .put("org.apache.cassandra.db.marshal.DoubleType", DataTypes.DOUBLE) + .put("org.apache.cassandra.db.marshal.FloatType", DataTypes.FLOAT) + .put("org.apache.cassandra.db.marshal.InetAddressType", DataTypes.INET) + .put("org.apache.cassandra.db.marshal.Int32Type", DataTypes.INT) + .put("org.apache.cassandra.db.marshal.UTF8Type", DataTypes.TEXT) + .put("org.apache.cassandra.db.marshal.TimestampType", DataTypes.TIMESTAMP) + .put("org.apache.cassandra.db.marshal.SimpleDateType", DataTypes.DATE) + .put("org.apache.cassandra.db.marshal.TimeType", DataTypes.TIME) + .put("org.apache.cassandra.db.marshal.UUIDType", DataTypes.UUID) + .put("org.apache.cassandra.db.marshal.IntegerType", DataTypes.VARINT) + .put("org.apache.cassandra.db.marshal.TimeUUIDType", DataTypes.TIMEUUID) + .put("org.apache.cassandra.db.marshal.ByteType", DataTypes.TINYINT) + .put("org.apache.cassandra.db.marshal.ShortType", DataTypes.SMALLINT) + .put("org.apache.cassandra.db.marshal.DurationType", DataTypes.DURATION) + .build(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java new file mode 100644 index 00000000000..6d01507a59d --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.ShallowUserDefinedType; +import com.datastax.oss.driver.internal.core.type.DefaultTupleType; +import com.datastax.oss.driver.internal.core.type.codec.ParseUtils; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Parses data types from schema tables, for Cassandra 3.0 and above. + * + *

      In these versions, data types appear as string literals, like "ascii" or "tuple". + */ +public class DataTypeCqlNameParser implements DataTypeParser { + + @Override + public DataType parse( + CqlIdentifier keyspaceId, + String toParse, + Map userTypes, + InternalDriverContext context) { + // Top-level is never frozen, it is only set recursively when we encounter the frozen<> keyword + return parse(toParse, keyspaceId, false, userTypes, context); + } + + private DataType parse( + String toParse, + CqlIdentifier keyspaceId, + boolean frozen, + Map userTypes, + InternalDriverContext context) { + + if (toParse.startsWith("'")) { + return DataTypes.custom(toParse.substring(1, toParse.length() - 1)); + } + + Parser parser = new Parser(toParse, 0); + String type = parser.parseTypeName(); + + DataType nativeType = NATIVE_TYPES_BY_NAME.get(type.toLowerCase()); + if (nativeType != null) { + return nativeType; + } + + if (type.equalsIgnoreCase("list")) { + List parameters = parser.parseTypeParameters(); + if (parameters.size() != 1) { + throw new IllegalArgumentException( + String.format("Expecting single parameter for list, got %s", parameters)); + } + DataType elementType = parse(parameters.get(0), keyspaceId, false, userTypes, context); + return DataTypes.listOf(elementType, frozen); + } + + if (type.equalsIgnoreCase("set")) { + List parameters = parser.parseTypeParameters(); + if (parameters.size() != 1) { + throw new IllegalArgumentException( + String.format("Expecting single parameter for set, got %s", parameters)); + } + DataType elementType = parse(parameters.get(0), keyspaceId, false, userTypes, context); + return DataTypes.setOf(elementType, frozen); + } + + if (type.equalsIgnoreCase("map")) { + List parameters = parser.parseTypeParameters(); + if (parameters.size() != 2) { + throw new IllegalArgumentException( + String.format("Expecting two parameters for map, got %s", parameters)); + } + DataType keyType = parse(parameters.get(0), keyspaceId, false, userTypes, context); + DataType valueType = parse(parameters.get(1), keyspaceId, false, userTypes, context); + return DataTypes.mapOf(keyType, valueType, frozen); + } + + if (type.equalsIgnoreCase("frozen")) { + List parameters = parser.parseTypeParameters(); + if (parameters.size() != 1) { + throw new IllegalArgumentException( + String.format("Expecting single parameter for frozen keyword, got %s", parameters)); + } + return parse(parameters.get(0), keyspaceId, true, userTypes, context); + } + + if (type.equalsIgnoreCase("tuple")) { + List rawTypes = parser.parseTypeParameters(); + ImmutableList.Builder componentTypesBuilder = ImmutableList.builder(); + for (String rawType : rawTypes) { + componentTypesBuilder.add(parse(rawType, keyspaceId, false, userTypes, context)); + } + return new DefaultTupleType(componentTypesBuilder.build(), context); + } + + // Otherwise it's a UDT + CqlIdentifier name = CqlIdentifier.fromInternal(type); + if (userTypes != null) { + UserDefinedType userType = userTypes.get(name); + if (userType == null) { + throw new IllegalStateException(String.format("Can't find referenced user type %s", type)); + } + return userType.copy(frozen); + } else { + return new ShallowUserDefinedType(keyspaceId, name, frozen); + } + } + + private static class Parser { + + private final String str; + + private int idx; + + Parser(String str, int idx) { + this.str = str; + this.idx = idx; + } + + String parseTypeName() { + idx = ParseUtils.skipSpaces(str, idx); + return readNextIdentifier(); + } + + List parseTypeParameters() { + List list = new ArrayList<>(); + + if (isEOS()) { + return list; + } + + skipBlankAndComma(); + + if (str.charAt(idx) != '<') { + throw new IllegalStateException(); + } + + ++idx; // skipping '<' + + while (skipBlankAndComma()) { + if (str.charAt(idx) == '>') { + ++idx; + return list; + } + + String name = parseTypeName(); + String args = readRawTypeParameters(); + list.add(name + args); + } + throw new IllegalArgumentException( + String.format( + "Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); + } + + // left idx positioned on the character stopping the read + private String readNextIdentifier() { + int startIdx = idx; + if (str.charAt(startIdx) == '"') { // case-sensitive name included in double quotes + ++idx; + // read until closing quote. + while (!isEOS()) { + boolean atQuote = str.charAt(idx) == '"'; + ++idx; + if (atQuote) { + // if the next character is also a quote, this is an escaped quote, continue reading, + // otherwise stop. + if (!isEOS() && str.charAt(idx) == '"') { + ++idx; + } else { + break; + } + } + } + } else if (str.charAt(startIdx) == '\'') { // custom type name included in single quotes + ++idx; + // read until closing quote. + while (!isEOS() && str.charAt(idx++) != '\'') { + /* loop */ + } + } else { + while (!isEOS() + && (ParseUtils.isCqlIdentifierChar(str.charAt(idx)) || str.charAt(idx) == '"')) { + ++idx; + } + } + return str.substring(startIdx, idx); + } + + // Assumes we have just read a type name and read its potential arguments blindly. I.e. it + // assumes that either parsing is done or that we're on a '<' and this reads everything up until + // the corresponding closing '>'. It returns everything read, including the enclosing brackets. + private String readRawTypeParameters() { + idx = ParseUtils.skipSpaces(str, idx); + + if (isEOS() || str.charAt(idx) == '>' || str.charAt(idx) == ',') { + return ""; + } + + if (str.charAt(idx) != '<') { + throw new IllegalStateException( + String.format( + "Expecting char %d of %s to be '<' but '%c' found", idx, str, str.charAt(idx))); + } + + int i = idx; + int open = 1; + boolean inQuotes = false; + while (open > 0) { + ++idx; + + if (isEOS()) { + throw new IllegalStateException("Non closed angle brackets"); + } + + // Only parse for '<' and '>' characters if not within a quoted identifier. + // Note we don't need to handle escaped quotes ("") in type names here, because they just + // cause inQuotes to flip to false and immediately back to true + if (!inQuotes) { + if (str.charAt(idx) == '"') { + inQuotes = true; + } else if (str.charAt(idx) == '<') { + open++; + } else if (str.charAt(idx) == '>') { + open--; + } + } else if (str.charAt(idx) == '"') { + inQuotes = false; + } + } + // we've stopped at the last closing ')' so move past that + ++idx; + return str.substring(i, idx); + } + + // skip all blank and at best one comma, return true if there not EOS + private boolean skipBlankAndComma() { + boolean commaFound = false; + while (!isEOS()) { + int c = str.charAt(idx); + if (c == ',') { + if (commaFound) { + return true; + } else { + commaFound = true; + } + } else if (!ParseUtils.isBlank(c)) { + return true; + } + ++idx; + } + return false; + } + + private boolean isEOS() { + return idx >= str.length(); + } + + @Override + public String toString() { + return str.substring(0, idx) + + "[" + + (idx == str.length() ? "" : str.charAt(idx)) + + "]" + + str.substring(idx + 1); + } + } + + @VisibleForTesting + static final ImmutableMap NATIVE_TYPES_BY_NAME = + new ImmutableMap.Builder() + .put("ascii", DataTypes.ASCII) + .put("bigint", DataTypes.BIGINT) + .put("blob", DataTypes.BLOB) + .put("boolean", DataTypes.BOOLEAN) + .put("counter", DataTypes.COUNTER) + .put("decimal", DataTypes.DECIMAL) + .put("double", DataTypes.DOUBLE) + .put("float", DataTypes.FLOAT) + .put("inet", DataTypes.INET) + .put("int", DataTypes.INT) + .put("text", DataTypes.TEXT) + .put("varchar", DataTypes.TEXT) + .put("timestamp", DataTypes.TIMESTAMP) + .put("date", DataTypes.DATE) + .put("time", DataTypes.TIME) + .put("uuid", DataTypes.UUID) + .put("varint", DataTypes.VARINT) + .put("timeuuid", DataTypes.TIMEUUID) + .put("tinyint", DataTypes.TINYINT) + .put("smallint", DataTypes.SMALLINT) + .put("duration", DataTypes.DURATION) + .build(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java new file mode 100644 index 00000000000..ec29331f7cd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.ShallowUserDefinedType; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** Parses data types from their string representation in schema tables. */ +interface DataTypeParser { + + /** + * @param userTypes the UDTs in the current keyspace, if we know them already. This is used to + * resolve subtypes if the type to parse is complex (such as {@code list}). The only + * situation where we don't have them is when we refresh all the UDTs of a keyspace; in that + * case, the filed will be {@code null} and any UDT encountered by this method will always be + * re-created from scratch: for Cassandra < 2.2, this means parsing the whole definition; for + * > 3.0, this means materializing it as a {@link ShallowUserDefinedType} that will be + * resolved in a second pass. + */ + DataType parse( + CqlIdentifier keyspaceId, + String toParse, + Map userTypes, + InternalDriverContext context); + + default List parse( + CqlIdentifier keyspaceId, + List typeStrings, + Map userTypes, + InternalDriverContext context) { + if (typeStrings.isEmpty()) { + return Collections.emptyList(); + } else { + ImmutableList.Builder builder = ImmutableList.builder(); + for (String typeString : typeStrings) { + builder.add(parse(keyspaceId, typeString, userTypes, context)); + } + return builder.build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java new file mode 100644 index 00000000000..1e4b90768da --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; + +public class DefaultSchemaParserFactory implements SchemaParserFactory { + + private final InternalDriverContext context; + + public DefaultSchemaParserFactory(InternalDriverContext context) { + this.context = context; + } + + @Override + public SchemaParser newInstance(SchemaRows rows) { + return new SchemaParser(rows, context); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java new file mode 100644 index 00000000000..ebc1d8e737f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultFunctionMetadata; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class FunctionParser { + + private static final Logger LOG = LoggerFactory.getLogger(FunctionParser.class); + + private final DataTypeParser dataTypeParser; + private final InternalDriverContext context; + private final String logPrefix; + + FunctionParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + this.dataTypeParser = dataTypeParser; + this.context = context; + this.logPrefix = context.clusterName(); + } + + FunctionMetadata parseFunction( + AdminRow row, + CqlIdentifier keyspaceId, + Map userDefinedTypes) { + // Cassandra < 3.0: + // CREATE TABLE system.schema_functions ( + // keyspace_name text, + // function_name text, + // signature frozen>, + // argument_names list, + // argument_types list, + // body text, + // called_on_null_input boolean, + // language text, + // return_type text, + // PRIMARY KEY (keyspace_name, function_name, signature) + // ) WITH CLUSTERING ORDER BY (function_name ASC, signature ASC) + // + // Cassandra >= 3.0: + // CREATE TABLE system_schema.functions ( + // keyspace_name text, + // function_name text, + // argument_names frozen>, + // argument_types frozen>, + // body text, + // called_on_null_input boolean, + // language text, + // return_type text, + // PRIMARY KEY (keyspace_name, function_name, argument_types) + // ) WITH CLUSTERING ORDER BY (function_name ASC, argument_types ASC) + String simpleName = row.getString("function_name"); + List argumentNames = + ImmutableList.copyOf( + Lists.transform(row.getListOfString("argument_names"), CqlIdentifier::fromInternal)); + List argumentTypes = row.getListOfString("argument_types"); + if (argumentNames.size() != argumentTypes.size()) { + LOG.warn( + "[{}] Error parsing system row for function {}.{}, " + + "number of argument names and types don't match (got {} and {}).", + logPrefix, + keyspaceId.asInternal(), + simpleName, + argumentNames.size(), + argumentTypes.size()); + return null; + } + FunctionSignature signature = + new FunctionSignature( + CqlIdentifier.fromInternal(simpleName), + dataTypeParser.parse(keyspaceId, argumentTypes, userDefinedTypes, context)); + String body = row.getString("body"); + Boolean calledOnNullInput = row.getBoolean("called_on_null_input"); + String language = row.getString("language"); + DataType returnType = + dataTypeParser.parse(keyspaceId, row.getString("return_type"), userDefinedTypes, context); + + return new DefaultFunctionMetadata( + keyspaceId, signature, argumentNames, body, calledOnNullInput, language, returnType); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java new file mode 100644 index 00000000000..beed4c17061 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * An intermediary format to manipulate columns before we turn them into {@link ColumnMetadata} + * instances. + */ +class RawColumn implements Comparable { + + static List toRawColumns( + Collection rows, + CqlIdentifier keyspaceId, + Map userTypes) { + if (rows.isEmpty()) { + return Collections.emptyList(); + } else { + // Use a mutable list, we might remove some elements later + List result = Lists.newArrayListWithExpectedSize(rows.size()); + for (AdminRow row : rows) { + result.add(new RawColumn(row, keyspaceId, userTypes)); + } + return result; + } + } + + enum Kind { + PARTITION_KEY, + CLUSTERING_COLUMN, + REGULAR, + COMPACT_VALUE, + STATIC, + ; + + static Kind from(String s) { + if ("partition_key".equalsIgnoreCase(s)) { + return PARTITION_KEY; + } else if ("clustering_key".equalsIgnoreCase(s) || "clustering".equalsIgnoreCase(s)) { + return CLUSTERING_COLUMN; + } else if ("regular".equalsIgnoreCase(s)) { + return REGULAR; + } else if ("compact_value".equalsIgnoreCase(s)) { + return COMPACT_VALUE; + } else if ("static".equalsIgnoreCase(s)) { + return STATIC; + } + throw new IllegalArgumentException("Unknown column kind " + s); + } + } + + final CqlIdentifier name; + Kind kind; + final int position; + final String dataType; + final boolean reversed; + final String indexName; + final String indexType; + final Map indexOptions; + + private RawColumn( + AdminRow row, CqlIdentifier keyspaceId, Map userTypes) { + // Cassandra < 3.0: + // CREATE TABLE system.schema_columns ( + // keyspace_name text, + // columnfamily_name text, + // column_name text, + // component_index int, + // index_name text, + // index_options text, + // index_type text, + // type text, + // validator text, + // PRIMARY KEY (keyspace_name, columnfamily_name, column_name) + // ) WITH CLUSTERING ORDER BY (columnfamily_name ASC, column_name ASC) + // + // Cassandra >= 3.0: + // CREATE TABLE system_schema.columns ( + // keyspace_name text, + // table_name text, + // column_name text, + // clustering_order text, + // column_name_bytes blob, + // kind text, + // position int, + // type text, + // PRIMARY KEY (keyspace_name, table_name, column_name) + // ) WITH CLUSTERING ORDER BY (table_name ASC, column_name ASC) + this.name = CqlIdentifier.fromInternal(row.getString("column_name")); + this.kind = Kind.from((row.contains("kind") ? row.getString("kind") : row.getString("type"))); + + Integer rawPosition = + row.contains("position") ? row.getInteger("position") : row.getInteger("component_index"); + this.position = (rawPosition == null || rawPosition == -1) ? 0 : rawPosition; + + this.dataType = row.contains("validator") ? row.getString("validator") : row.getString("type"); + this.reversed = + row.contains("clustering_order") + ? "desc".equals(row.getString("clustering_order")) + : DataTypeClassNameParser.isReversed(dataType); + this.indexName = row.getString("index_name"); + this.indexType = row.getString("index_type"); + // index_options can apparently contain the string 'null' (JAVA-834) + String indexOptionsString = row.getString("index_options"); + this.indexOptions = + (indexOptionsString == null || indexOptionsString.equals("null")) + ? Collections.emptyMap() + : SimpleJsonParser.parseStringMap(indexOptionsString); + } + + @Override + public int compareTo(RawColumn that) { + // First, order by kind. Then order partition key and clustering columns by position. For + // other kinds, order by column name. + if (this.kind != that.kind) { + return this.kind.compareTo(that.kind); + } else if (kind == Kind.PARTITION_KEY || kind == Kind.CLUSTERING_COLUMN) { + return Integer.compare(this.position, that.position); + } else { + return this.name.asInternal().compareTo(that.name.asInternal()); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java new file mode 100644 index 00000000000..6e3a78a472f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.ScriptBuilder; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.google.common.collect.ImmutableMap; +import java.nio.ByteBuffer; +import java.util.Map; + +// Shared code for table and view parsing +public abstract class RelationParser { + + protected final SchemaRows rows; + protected final DataTypeParser dataTypeParser; + protected final InternalDriverContext context; + protected final String logPrefix; + + protected RelationParser( + SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { + this.rows = rows; + this.dataTypeParser = dataTypeParser; + this.context = context; + this.logPrefix = context.clusterName(); + } + + protected Map parseOptions(AdminRow row) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map.Entry> entry : OPTION_CODECS.entrySet()) { + String name = entry.getKey(); + CqlIdentifier id = CqlIdentifier.fromInternal(name); + TypeCodec codec = entry.getValue(); + + if (name.equals("caching") && row.isString("caching")) { + // C* <=2.2, caching is stored as a string, and also appears as a string in the WITH clause. + builder.put(id, row.getString(name)); + } else if (name.equals("compaction_strategy_class")) { + // C* <=2.2, compaction options split in two columns + String strategyClass = row.getString(name); + if (strategyClass != null) { + builder.put( + CqlIdentifier.fromInternal("compaction"), + ImmutableMap.builder() + .put("class", strategyClass) + .putAll( + SimpleJsonParser.parseStringMap(row.getString("compaction_strategy_options"))) + .build()); + } + } else if (name.equals("compression_parameters")) { + // C* <=2.2, compression stored as a string + String compressionParameters = row.getString(name); + if (compressionParameters != null) { + builder.put( + CqlIdentifier.fromInternal("compression"), + ImmutableMap.copyOf(SimpleJsonParser.parseStringMap(row.getString(name)))); + } + } else { + // Default case, read the value in a generic fashion + Object value = row.get(name, codec); + if (value != null) { + builder.put(id, value); + } + } + } + return builder.build(); + } + + public static void appendOptions(Map options, ScriptBuilder builder) { + for (Map.Entry entry : options.entrySet()) { + CqlIdentifier name = entry.getKey(); + Object value = entry.getValue(); + String formattedValue; + if (name.asInternal().equals("caching") && value instanceof String) { + formattedValue = TypeCodecs.TEXT.format((String) value); + } else { + @SuppressWarnings("unchecked") + TypeCodec codec = + (TypeCodec) RelationParser.OPTION_CODECS.get(name.asInternal()); + formattedValue = codec.format(value); + } + String optionName = name.asCql(true); + if ("local_read_repair_chance".equals(optionName)) { + // Another small quirk in C* <= 2.2 + optionName = "dclocal_read_repair_chance"; + } + builder.andWith().append(optionName).append(" = ").append(formattedValue); + } + } + + public static final TypeCodec> MAP_OF_TEXT_TO_TEXT = + TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.TEXT); + private static final TypeCodec> MAP_OF_TEXT_TO_BLOB = + TypeCodecs.mapOf(TypeCodecs.TEXT, TypeCodecs.BLOB); + /** + * The columns of the system table that are turned into entries in {@link + * RelationMetadata#getOptions()}. + */ + public static final Map> OPTION_CODECS = + ImmutableMap.>builder() + .put("bloom_filter_fp_chance", TypeCodecs.DOUBLE) + // In C* <= 2.2, this is a string, not a map (this is special-cased in parseOptions): + .put("caching", MAP_OF_TEXT_TO_TEXT) + .put("cdc", TypeCodecs.BOOLEAN) + .put("comment", TypeCodecs.TEXT) + .put("compaction", MAP_OF_TEXT_TO_TEXT) + // In C*<=2.2, must read from this column and another one called + // 'compaction_strategy_options' (this is special-cased in parseOptions): + .put("compaction_strategy_class", TypeCodecs.TEXT) + .put("compression", MAP_OF_TEXT_TO_TEXT) + // In C*<=2.2, must parse this column into a map (this is special-cased in parseOptions): + .put("compression_parameters", TypeCodecs.TEXT) + .put("crc_check_chance", TypeCodecs.DOUBLE) + .put("dclocal_read_repair_chance", TypeCodecs.DOUBLE) + .put("default_time_to_live", TypeCodecs.INT) + .put("extensions", MAP_OF_TEXT_TO_BLOB) + .put("gc_grace_seconds", TypeCodecs.INT) + .put("local_read_repair_chance", TypeCodecs.DOUBLE) + .put("max_index_interval", TypeCodecs.INT) + .put("memtable_flush_period_in_ms", TypeCodecs.INT) + .put("min_index_interval", TypeCodecs.INT) + .put("read_repair_chance", TypeCodecs.DOUBLE) + .put("speculative_retry", TypeCodecs.TEXT) + .build(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java new file mode 100644 index 00000000000..8ac0fab506b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultKeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The main entry point for system schema rows parsing. + * + *

      For modularity, the code for each element row is split into separate classes (schema stuff is + * not on the hot path, so creating a few extra objects doesn't matter). + */ +public class SchemaParser { + + private static final Logger LOG = LoggerFactory.getLogger(SchemaParser.class); + + private final SchemaRows rows; + private final UserDefinedTypeParser userDefinedTypeParser; + private final TableParser tableParser; + private final ViewParser viewParser; + private final FunctionParser functionParser; + private final AggregateParser aggregateParser; + private final String logPrefix; + private final long startTimeNs = System.nanoTime(); + + public SchemaParser(SchemaRows rows, InternalDriverContext context) { + this.rows = rows; + this.logPrefix = context.clusterName(); + + DataTypeParser dataTypeParser = + rows.isCassandraV3 ? new DataTypeCqlNameParser() : new DataTypeClassNameParser(); + + this.userDefinedTypeParser = new UserDefinedTypeParser(dataTypeParser, context); + this.tableParser = new TableParser(rows, dataTypeParser, context); + this.viewParser = new ViewParser(rows, dataTypeParser, context); + this.functionParser = new FunctionParser(dataTypeParser, context); + this.aggregateParser = new AggregateParser(dataTypeParser, context); + } + + public SchemaRefresh parse() { + ImmutableMap.Builder keyspacesBuilder = ImmutableMap.builder(); + for (AdminRow row : rows.keyspaces) { + KeyspaceMetadata keyspace = parseKeyspace(row); + keyspacesBuilder.put(keyspace.getName(), keyspace); + } + SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build(), logPrefix); + LOG.debug("[{}] Schema parsing took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); + return refresh; + } + + protected KeyspaceMetadata parseKeyspace(AdminRow keyspaceRow) { + + // Cassandra <= 2.2 + // CREATE TABLE system.schema_keyspaces ( + // keyspace_name text PRIMARY KEY, + // durable_writes boolean, + // strategy_class text, + // strategy_options text + // ) + // + // Cassandra >= 3.0: + // CREATE TABLE system_schema.keyspaces ( + // keyspace_name text PRIMARY KEY, + // durable_writes boolean, + // replication frozen> + // ) + CqlIdentifier keyspaceId = CqlIdentifier.fromInternal(keyspaceRow.getString("keyspace_name")); + boolean durableWrites = + MoreObjects.firstNonNull(keyspaceRow.getBoolean("durable_writes"), false); + + Map replicationOptions; + if (keyspaceRow.contains("strategy_class")) { + String strategyClass = keyspaceRow.getString("strategy_class"); + Map strategyOptions = + SimpleJsonParser.parseStringMap(keyspaceRow.getString("strategy_options")); + replicationOptions = + ImmutableMap.builder() + .putAll(strategyOptions) + .put("class", strategyClass) + .build(); + } else { + replicationOptions = keyspaceRow.getMapOfStringToString("replication"); + } + + Map types = + userDefinedTypeParser.parse(rows.types.get(keyspaceId), keyspaceId); + + ImmutableMap.Builder tablesBuilder = ImmutableMap.builder(); + for (AdminRow tableRow : rows.tables.get(keyspaceId)) { + TableMetadata table = tableParser.parseTable(tableRow, keyspaceId, types); + if (table != null) { + tablesBuilder.put(table.getName(), table); + } + } + + ImmutableMap.Builder viewsBuilder = ImmutableMap.builder(); + for (AdminRow viewRow : rows.views.get(keyspaceId)) { + ViewMetadata view = viewParser.parseView(viewRow, keyspaceId, types); + if (view != null) { + viewsBuilder.put(view.getName(), view); + } + } + + ImmutableMap.Builder functionsBuilder = + ImmutableMap.builder(); + for (AdminRow functionRow : rows.functions.get(keyspaceId)) { + FunctionMetadata function = functionParser.parseFunction(functionRow, keyspaceId, types); + if (function != null) { + functionsBuilder.put(function.getSignature(), function); + } + } + + ImmutableMap.Builder aggregatesBuilder = + ImmutableMap.builder(); + for (AdminRow aggregateRow : rows.aggregates.get(keyspaceId)) { + AggregateMetadata aggregate = aggregateParser.parseAggregate(aggregateRow, keyspaceId, types); + if (aggregate != null) { + aggregatesBuilder.put(aggregate.getSignature(), aggregate); + } + } + + return new DefaultKeyspaceMetadata( + keyspaceId, + durableWrites, + replicationOptions, + types, + tablesBuilder.build(), + viewsBuilder.build(), + functionsBuilder.build(), + aggregatesBuilder.build()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserFactory.java new file mode 100644 index 00000000000..e2e23d09545 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserFactory.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; + +public interface SchemaParserFactory { + SchemaParser newInstance(SchemaRows rows); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java new file mode 100644 index 00000000000..59b0ddd8ec9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A very simple json parser. The only reason we need to read json in the driver is because for + * historical reason Cassandra encodes a few properties using json in the schema and we need to + * decode them. + * + *

      We however don't need a full-blown JSON library because: 1) we know we only need to decode + * string lists and string maps 2) we can basically assume the input is valid, we don't particularly + * have to bother about decoding exactly JSON as long as we at least decode what we need. 3) we + * don't really care much about performance, none of this is done in performance sensitive parts. + * + *

      So instead of pulling a new dependency, we roll out our own very dumb parser. We should + * obviously not expose this publicly. + */ +class SimpleJsonParser { + + private final String input; + private int idx; + + private SimpleJsonParser(String input) { + this.input = input; + } + + static List parseStringList(String input) { + if (input == null || input.isEmpty()) { + return Collections.emptyList(); + } + + List output = new ArrayList<>(); + SimpleJsonParser parser = new SimpleJsonParser(input); + if (parser.nextCharSkipSpaces() != '[') { + throw new IllegalArgumentException("Not a JSON list: " + input); + } + + char c = parser.nextCharSkipSpaces(); + if (c == ']') { + return output; + } + + while (true) { + assert c == '"'; + output.add(parser.nextString()); + c = parser.nextCharSkipSpaces(); + if (c == ']') { + return output; + } + assert c == ','; + c = parser.nextCharSkipSpaces(); + } + } + + static Map parseStringMap(String input) { + if (input == null || input.isEmpty()) { + return Collections.emptyMap(); + } + + Map output = new HashMap<>(); + SimpleJsonParser parser = new SimpleJsonParser(input); + if (parser.nextCharSkipSpaces() != '{') { + throw new IllegalArgumentException("Not a JSON map: " + input); + } + + char c = parser.nextCharSkipSpaces(); + if (c == '}') { + return output; + } + + while (true) { + assert c == '"'; + String key = parser.nextString(); + c = parser.nextCharSkipSpaces(); + assert c == ':'; + c = parser.nextCharSkipSpaces(); + assert c == '"'; + String value = parser.nextString(); + output.put(key, value); + c = parser.nextCharSkipSpaces(); + if (c == '}') { + return output; + } + assert c == ','; + c = parser.nextCharSkipSpaces(); + } + } + + /** Read the next char, the one at position idx, and advance ix. */ + private char nextChar() { + if (idx >= input.length()) { + throw new IllegalArgumentException("Invalid json input: " + input); + } + return input.charAt(idx++); + } + + /** Same as nextChar, except that it skips space characters (' ', '\t' and '\n'). */ + private char nextCharSkipSpaces() { + char c = nextChar(); + while (c == ' ' || c == '\t' || c == '\n') { + c = nextChar(); + } + return c; + } + + /** + * Reads a String, assuming idx is on the first character of the string (i.e. the one after the + * opening double-quote character). After the string has been read, idx will be on the first + * character after the closing double-quote. + */ + private String nextString() { + assert input.charAt(idx - 1) == '"' : "Char is '" + input.charAt(idx - 1) + '\''; + StringBuilder sb = new StringBuilder(); + while (true) { + char c = nextChar(); + switch (c) { + case '\n': + case '\r': + throw new IllegalArgumentException("Unterminated string"); + case '\\': + c = nextChar(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(input.substring(idx, idx + 4), 16)); + idx += 4; + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw new IllegalArgumentException("Illegal escape"); + } + break; + default: + if (c == '"') { + return sb.toString(); + } + sb.append(c); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java new file mode 100644 index 00000000000..30e5ab30644 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.IndexKind; +import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultColumnMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultIndexMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultTableMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class TableParser extends RelationParser { + + private static final Logger LOG = LoggerFactory.getLogger(TableParser.class); + + TableParser(SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { + super(rows, dataTypeParser, context); + } + + TableMetadata parseTable( + AdminRow tableRow, CqlIdentifier keyspaceId, Map userTypes) { + // Cassandra <= 2.2: + // CREATE TABLE system.schema_columnfamilies ( + // keyspace_name text, + // columnfamily_name text, + // bloom_filter_fp_chance double, + // caching text, + // cf_id uuid, + // column_aliases text, (2.1 only) + // comment text, + // compaction_strategy_class text, + // compaction_strategy_options text, + // comparator text, + // compression_parameters text, + // default_time_to_live int, + // default_validator text, + // dropped_columns map, + // gc_grace_seconds int, + // index_interval int, + // is_dense boolean, (2.1 only) + // key_aliases text, (2.1 only) + // key_validator text, + // local_read_repair_chance double, + // max_compaction_threshold int, + // max_index_interval int, + // memtable_flush_period_in_ms int, + // min_compaction_threshold int, + // min_index_interval int, + // read_repair_chance double, + // speculative_retry text, + // subcomparator text, + // type text, + // value_alias text, (2.1 only) + // PRIMARY KEY (keyspace_name, columnfamily_name) + // ) WITH CLUSTERING ORDER BY (columnfamily_name ASC) + // + // Cassandra 3.0: + // CREATE TABLE system_schema.tables ( + // keyspace_name text, + // table_name text, + // bloom_filter_fp_chance double, + // caching frozen>, + // cdc boolean, + // comment text, + // compaction frozen>, + // compression frozen>, + // crc_check_chance double, + // dclocal_read_repair_chance double, + // default_time_to_live int, + // extensions frozen>, + // flags frozen>, + // gc_grace_seconds int, + // id uuid, + // max_index_interval int, + // memtable_flush_period_in_ms int, + // min_index_interval int, + // read_repair_chance double, + // speculative_retry text, + // PRIMARY KEY (keyspace_name, table_name) + // ) WITH CLUSTERING ORDER BY (table_name ASC) + CqlIdentifier tableId = + CqlIdentifier.fromInternal( + tableRow.getString(rows.isCassandraV3 ? "table_name" : "columnfamily_name")); + + UUID uuid = (tableRow.contains("id")) ? tableRow.getUuid("id") : tableRow.getUuid("cf_id"); + + List rawColumns = + RawColumn.toRawColumns( + rows.columns.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId), + keyspaceId, + userTypes); + if (rawColumns.isEmpty()) { + LOG.warn( + "[{}] Processing TABLE refresh for {}.{} but found no matching rows, skipping", + logPrefix, + keyspaceId, + tableId); + return null; + } + + boolean isCompactStorage; + if (tableRow.contains("flags")) { + Set flags = tableRow.getSetOfString("flags"); + boolean isDense = flags.contains("dense"); + boolean isSuper = flags.contains("super"); + boolean isCompound = flags.contains("compound"); + isCompactStorage = isSuper || isDense || !isCompound; + boolean isStaticCompact = !isSuper && !isDense && !isCompound; + if (isStaticCompact) { + pruneStaticCompactTableColumns(rawColumns); + } else if (isDense) { + pruneDenseTableColumnsV3(rawColumns); + } + } else { + boolean isDense = tableRow.getBoolean("is_dense"); + if (isDense) { + pruneDenseTableColumnsV2(rawColumns); + } + DataTypeClassNameCompositeParser.ParseResult comparator = + new DataTypeClassNameCompositeParser() + .parseWithComposite(tableRow.getString("comparator"), keyspaceId, userTypes, context); + isCompactStorage = isDense || !comparator.isComposite; + } + + Collections.sort(rawColumns); + ImmutableMap.Builder allColumnsBuilder = ImmutableMap.builder(); + ImmutableList.Builder partitionKeyBuilder = ImmutableList.builder(); + ImmutableMap.Builder clusteringColumnsBuilder = + ImmutableMap.builder(); + ImmutableMap.Builder indexesBuilder = ImmutableMap.builder(); + + for (RawColumn raw : rawColumns) { + DataType dataType = dataTypeParser.parse(keyspaceId, raw.dataType, userTypes, context); + ColumnMetadata column = + new DefaultColumnMetadata( + keyspaceId, tableId, raw.name, dataType, raw.kind == RawColumn.Kind.STATIC); + switch (raw.kind) { + case PARTITION_KEY: + partitionKeyBuilder.add(column); + break; + case CLUSTERING_COLUMN: + clusteringColumnsBuilder.put( + column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); + break; + } + allColumnsBuilder.put(column.getName(), column); + + IndexMetadata index = buildLegacyIndex(raw, column); + if (index != null) { + indexesBuilder.put(index.getName(), index); + } + } + + Map options; + try { + options = parseOptions(tableRow); + } catch (Exception e) { + // Options change the most often, so be especially lenient if anything goes wrong. + LOG.warn( + "[{}] Error while parsing options for {}.{}, getOptions() will be empty", + logPrefix, + keyspaceId, + tableId, + e); + options = Collections.emptyMap(); + } + + Collection indexRows = + rows.indexes.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId); + for (AdminRow indexRow : indexRows) { + IndexMetadata index = buildModernIndex(keyspaceId, tableId, indexRow); + indexesBuilder.put(index.getName(), index); + } + + return new DefaultTableMetadata( + keyspaceId, + tableId, + uuid, + isCompactStorage, + partitionKeyBuilder.build(), + clusteringColumnsBuilder.build(), + allColumnsBuilder.build(), + options, + indexesBuilder.build()); + } + + // Upon migration from thrift to CQL, we internally create a pair of surrogate clustering/regular + // columns for compact static tables. These columns shouldn't be exposed to the user but are + // currently returned by C*. We also need to remove the static keyword for all other columns in + // the table. + private void pruneStaticCompactTableColumns(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + switch (column.kind) { + case CLUSTERING_COLUMN: + case REGULAR: + iterator.remove(); + break; + case STATIC: + column.kind = RawColumn.Kind.REGULAR; + } + } + } + + // Upon migration from thrift to CQL, we internally create a surrogate column "value" of type + // EmptyType for dense tables. This column shouldn't be exposed to the user but is currently + // returned by C*. + private void pruneDenseTableColumnsV3(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + if (column.kind == RawColumn.Kind.REGULAR && "empty".equals(column.dataType)) { + iterator.remove(); + } + } + } + + private void pruneDenseTableColumnsV2(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + if (column.kind == RawColumn.Kind.COMPACT_VALUE && column.name.asInternal().isEmpty()) { + iterator.remove(); + } + } + } + + // In C*<=2.2, index information is stored alongside the column. + private IndexMetadata buildLegacyIndex(RawColumn raw, ColumnMetadata column) { + if (raw.indexName == null) { + return null; + } + return new DefaultIndexMetadata( + column.getKeyspace(), + column.getParent(), + CqlIdentifier.fromInternal(raw.indexName), + IndexKind.valueOf(raw.indexType), + buildLegacyIndexTarget(column, raw.indexOptions), + raw.indexOptions); + } + + private static String buildLegacyIndexTarget(ColumnMetadata column, Map options) { + String columnName = column.getName().asCql(true); + DataType columnType = column.getType(); + if (options.containsKey("index_keys")) { + return String.format("keys(%s)", columnName); + } + if (options.containsKey("index_keys_and_values")) { + return String.format("entries(%s)", columnName); + } + if (columnType instanceof ListType && ((ListType) columnType).isFrozen() + || columnType instanceof SetType && ((SetType) columnType).isFrozen() + || columnType instanceof MapType && ((MapType) columnType).isFrozen()) { + return String.format("full(%s)", columnName); + } + // Note: the keyword 'values' is not accepted as a valid index target function until 3.0 + return columnName; + } + + // In C*>=3.0, index information is stored in a dedicated table: + // CREATE TABLE system_schema.indexes ( + // keyspace_name text, + // table_name text, + // index_name text, + // kind text, + // options frozen>, + // PRIMARY KEY (keyspace_name, table_name, index_name) + // ) WITH CLUSTERING ORDER BY (table_name ASC, index_name ASC) + private IndexMetadata buildModernIndex( + CqlIdentifier keyspaceId, CqlIdentifier tableId, AdminRow row) { + CqlIdentifier name = CqlIdentifier.fromInternal(row.getString("index_name")); + IndexKind kind = IndexKind.valueOf(row.getString("kind")); + Map options = row.getMapOfStringToString("options"); + String target = options.get("target"); + return new DefaultIndexMetadata(keyspaceId, tableId, name, kind, target, options); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java new file mode 100644 index 00000000000..f4790c99439 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; +import com.datastax.oss.driver.internal.core.util.DirectedGraph; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +class UserDefinedTypeParser { + private final DataTypeParser dataTypeParser; + private final InternalDriverContext context; + + UserDefinedTypeParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + this.dataTypeParser = dataTypeParser; + this.context = context; + } + + /** + * Contrary to other element parsers, this one processes all the types of a keyspace in one go. + * UDTs can depend on each other, but the system table returns them in alphabetical order. In + * order to properly build the definitions, we need to do a topological sort of the rows first, so + * that each type is parsed after its dependencies. + */ + Map parse( + Collection typeRows, CqlIdentifier keyspaceId) { + if (typeRows.isEmpty()) { + return Collections.emptyMap(); + } else { + Map types = new LinkedHashMap<>(); + for (AdminRow row : topologicalSort(typeRows, keyspaceId)) { + UserDefinedType type = parseType(row, keyspaceId, types); + types.put(type.getName(), type); + } + return ImmutableMap.copyOf(types); + } + } + + @VisibleForTesting + Map parse(CqlIdentifier keyspaceId, AdminRow... typeRows) { + return parse(Arrays.asList(typeRows), keyspaceId); + } + + private List topologicalSort(Collection typeRows, CqlIdentifier keyspaceId) { + if (typeRows.size() == 1) { + AdminRow row = typeRows.iterator().next(); + return Collections.singletonList(row); + } else { + DirectedGraph graph = new DirectedGraph<>(typeRows); + for (AdminRow dependent : typeRows) { + for (AdminRow dependency : typeRows) { + if (dependent != dependency && dependsOn(dependent, dependency, keyspaceId)) { + // Edges mean "is depended upon by"; we want the types with no dependencies to come + // first in the sort. + graph.addEdge(dependency, dependent); + } + } + } + return graph.topologicalSort(); + } + } + + private boolean dependsOn(AdminRow dependent, AdminRow dependency, CqlIdentifier keyspaceId) { + CqlIdentifier dependencyId = CqlIdentifier.fromInternal(dependency.getString("type_name")); + for (String fieldTypeName : dependent.getListOfString("field_types")) { + DataType fieldType = dataTypeParser.parse(keyspaceId, fieldTypeName, null, context); + if (references(fieldType, dependencyId)) { + return true; + } + } + return false; + } + + private boolean references(DataType dependent, CqlIdentifier dependency) { + if (dependent instanceof UserDefinedType) { + UserDefinedType userType = (UserDefinedType) dependent; + return userType.getName().equals(dependency); + } else if (dependent instanceof ListType) { + ListType listType = (ListType) dependent; + return references(listType.getElementType(), dependency); + } else if (dependent instanceof SetType) { + SetType setType = (SetType) dependent; + return references(setType.getElementType(), dependency); + } else if (dependent instanceof MapType) { + MapType mapType = (MapType) dependent; + return references(mapType.getKeyType(), dependency) + || references(mapType.getValueType(), dependency); + } else if (dependent instanceof TupleType) { + TupleType tupleType = (TupleType) dependent; + for (DataType componentType : tupleType.getComponentTypes()) { + if (references(componentType, dependency)) { + return true; + } + } + } + return false; + } + + private UserDefinedType parseType( + AdminRow row, + CqlIdentifier keyspaceId, + Map userDefinedTypes) { + // Cassandra < 3.0: + // CREATE TABLE system.schema_usertypes ( + // keyspace_name text, + // type_name text, + // field_names list, + // field_types list, + // PRIMARY KEY (keyspace_name, type_name) + // ) WITH CLUSTERING ORDER BY (type_name ASC) + // + // Cassandra >= 3.0: + // CREATE TABLE system_schema.types ( + // keyspace_name text, + // type_name text, + // field_names frozen>, + // field_types frozen>, + // PRIMARY KEY (keyspace_name, type_name) + // ) WITH CLUSTERING ORDER BY (type_name ASC) + CqlIdentifier name = CqlIdentifier.fromInternal(row.getString("type_name")); + List fieldNames = + ImmutableList.copyOf( + Lists.transform(row.getListOfString("field_names"), CqlIdentifier::fromInternal)); + List fieldTypes = + dataTypeParser.parse( + keyspaceId, row.getListOfString("field_types"), userDefinedTypes, context); + + return new DefaultUserDefinedType(keyspaceId, name, false, fieldNames, fieldTypes, context); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java new file mode 100644 index 00000000000..52e1f0471c9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultColumnMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultViewMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ViewParser extends RelationParser { + + private static final Logger LOG = LoggerFactory.getLogger(ViewParser.class); + + ViewParser(SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { + super(rows, dataTypeParser, context); + } + + ViewMetadata parseView( + AdminRow viewRow, CqlIdentifier keyspaceId, Map userTypes) { + // Cassandra 3.0 (no views in earlier versions): + // CREATE TABLE system_schema.views ( + // keyspace_name text, + // view_name text, + // base_table_id uuid, + // base_table_name text, + // bloom_filter_fp_chance double, + // caching frozen>, + // cdc boolean, + // comment text, + // compaction frozen>, + // compression frozen>, + // crc_check_chance double, + // dclocal_read_repair_chance double, + // default_time_to_live int, + // extensions frozen>, + // gc_grace_seconds int, + // id uuid, + // include_all_columns boolean, + // max_index_interval int, + // memtable_flush_period_in_ms int, + // min_index_interval int, + // read_repair_chance double, + // speculative_retry text, + // where_clause text, + // PRIMARY KEY (keyspace_name, view_name) + // ) WITH CLUSTERING ORDER BY (view_name ASC) + CqlIdentifier viewId = CqlIdentifier.fromInternal(viewRow.getString("view_name")); + + UUID uuid = viewRow.getUuid("id"); + CqlIdentifier baseTableId = CqlIdentifier.fromInternal(viewRow.getString("base_table_name")); + boolean includesAllColumns = + MoreObjects.firstNonNull(viewRow.getBoolean("include_all_columns"), false); + String whereClause = viewRow.getString("where_clause"); + + List rawColumns = + RawColumn.toRawColumns( + rows.columns.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(viewId), + keyspaceId, + userTypes); + if (rawColumns.isEmpty()) { + LOG.warn( + "[{}] Processing VIEW refresh for {}.{} but found no matching rows, skipping", + logPrefix, + keyspaceId, + viewId); + return null; + } + + Collections.sort(rawColumns); + ImmutableMap.Builder allColumnsBuilder = ImmutableMap.builder(); + ImmutableList.Builder partitionKeyBuilder = ImmutableList.builder(); + ImmutableMap.Builder clusteringColumnsBuilder = + ImmutableMap.builder(); + + for (RawColumn raw : rawColumns) { + DataType dataType = dataTypeParser.parse(keyspaceId, raw.dataType, userTypes, context); + ColumnMetadata column = + new DefaultColumnMetadata( + keyspaceId, viewId, raw.name, dataType, raw.kind == RawColumn.Kind.STATIC); + switch (raw.kind) { + case PARTITION_KEY: + partitionKeyBuilder.add(column); + break; + case CLUSTERING_COLUMN: + clusteringColumnsBuilder.put( + column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); + break; + } + allColumnsBuilder.put(column.getName(), column); + } + + Map options; + try { + options = parseOptions(viewRow); + } catch (Exception e) { + // Options change the most often, so be especially lenient if anything goes wrong. + LOG.warn( + "[{}] Error while parsing options for {}.{}, getOptions() will be empty", + logPrefix, + keyspaceId, + viewId, + e); + options = Collections.emptyMap(); + } + + return new DefaultViewMetadata( + keyspaceId, + viewId, + baseTableId, + includesAllColumns, + whereClause, + uuid, + partitionKeyBuilder.build(), + clusteringColumnsBuilder.build(), + allColumnsBuilder.build(), + options); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java new file mode 100644 index 00000000000..e5d96e8d8d4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +class Cassandra21SchemaQueries extends SchemaQueries { + Cassandra21SchemaQueries( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, false, refreshFuture, config, logPrefix); + } + + @Override + protected String selectKeyspacesQuery() { + return "SELECT * FROM system.schema_keyspaces"; + } + + @Override + protected String selectTablesQuery() { + return "SELECT * FROM system.schema_columnfamilies"; + } + + @Override + protected Optional selectViewsQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectIndexesQuery() { + return Optional.empty(); + } + + @Override + protected String selectColumnsQuery() { + return "SELECT * FROM system.schema_columns"; + } + + @Override + protected String selectTypesQuery() { + return "SELECT * FROM system.schema_usertypes"; + } + + @Override + protected Optional selectFunctionsQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectAggregatesQuery() { + return Optional.empty(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java new file mode 100644 index 00000000000..6fbd30eed87 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +class Cassandra22SchemaQueries extends SchemaQueries { + Cassandra22SchemaQueries( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, false, refreshFuture, config, logPrefix); + } + + @Override + protected String selectKeyspacesQuery() { + return "SELECT * FROM system.schema_keyspaces"; + } + + @Override + protected String selectTablesQuery() { + return "SELECT * FROM system.schema_columnfamilies"; + } + + @Override + protected Optional selectViewsQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectIndexesQuery() { + return Optional.empty(); + } + + @Override + protected String selectColumnsQuery() { + return "SELECT * FROM system.schema_columns"; + } + + @Override + protected String selectTypesQuery() { + return "SELECT * FROM system.schema_usertypes"; + } + + @Override + protected Optional selectFunctionsQuery() { + return Optional.of("SELECT * FROM system.schema_functions"); + } + + @Override + protected Optional selectAggregatesQuery() { + return Optional.of("SELECT * FROM system.schema_aggregates"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java new file mode 100644 index 00000000000..d5ca85e9393 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +class Cassandra3SchemaQueries extends SchemaQueries { + Cassandra3SchemaQueries( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, true, refreshFuture, config, logPrefix); + } + + @Override + protected String selectKeyspacesQuery() { + return "SELECT * FROM system_schema.keyspaces"; + } + + @Override + protected String selectTablesQuery() { + return "SELECT * FROM system_schema.tables"; + } + + @Override + protected Optional selectViewsQuery() { + return Optional.of("SELECT * FROM system_schema.views"); + } + + @Override + protected Optional selectIndexesQuery() { + return Optional.of("SELECT * FROM system_schema.indexes"); + } + + @Override + protected String selectColumnsQuery() { + return "SELECT * FROM system_schema.columns"; + } + + @Override + protected String selectTypesQuery() { + return "SELECT * FROM system_schema.types"; + } + + @Override + protected Optional selectFunctionsQuery() { + return Optional.of("SELECT * FROM system_schema.functions"); + } + + @Override + protected Optional selectAggregatesQuery() { + return Optional.of("SELECT * FROM system_schema.aggregates"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java new file mode 100644 index 00000000000..3b42e73bf72 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultSchemaQueriesFactory implements SchemaQueriesFactory { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultSchemaQueriesFactory.class); + + private final InternalDriverContext context; + + public DefaultSchemaQueriesFactory(InternalDriverContext context) { + this.context = context; + } + + public SchemaQueries newInstance(CompletableFuture refreshFuture) { + String logPrefix = context.clusterName(); + + DriverChannel channel = context.controlConnection().channel(); + if (channel == null || channel.closeFuture().isDone()) { + throw new IllegalStateException("Control channel not available, aborting schema refresh"); + } + @SuppressWarnings("SuspiciousMethodCalls") + Node node = context.metadataManager().getMetadata().getNodes().get(channel.remoteAddress()); + if (node == null) { + throw new IllegalStateException( + "Could not find control node metadata " + + channel.remoteAddress() + + ", aborting schema refresh"); + } + CassandraVersion cassandraVersion = node.getCassandraVersion(); + if (cassandraVersion == null) { + LOG.warn( + "[{}] Cassandra version missing for {}, defaulting to {}", + logPrefix, + node, + CassandraVersion.V3_0_0); + cassandraVersion = CassandraVersion.V3_0_0; + } else { + cassandraVersion = cassandraVersion.nextStable(); + } + DriverConfigProfile config = context.config().getDefaultProfile(); + LOG.debug( + "[{}] Sending schema queries to {} with version {}", logPrefix, node, cassandraVersion); + if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) < 0) { + return new Cassandra21SchemaQueries(channel, refreshFuture, config, logPrefix); + } else if (cassandraVersion.compareTo(CassandraVersion.V3_0_0) < 0) { + return new Cassandra22SchemaQueries(channel, refreshFuture, config, logPrefix); + } else { + return new Cassandra3SchemaQueries(channel, refreshFuture, config, logPrefix); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java new file mode 100644 index 00000000000..27158508507 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.google.common.annotations.VisibleForTesting; +import io.netty.util.concurrent.EventExecutor; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the queries to system tables during a schema refresh. + * + *

      Depending on the kind of refresh, there is a variable number of queries. They are all + * asynchronous, and possibly paged. This class abstracts all the details and exposes a common + * result type. + */ +public abstract class SchemaQueries { + + private static final Logger LOG = LoggerFactory.getLogger(SchemaQueries.class); + + private final DriverChannel channel; + private final EventExecutor adminExecutor; + private final boolean isCassandraV3; + private final String logPrefix; + private final Duration timeout; + private final int pageSize; + private final String whereClause; + // The future we return from execute, completes when all the queries are done. + private final CompletableFuture schemaRowsFuture = new CompletableFuture<>(); + // A future that completes later, when the whole refresh is done. We just store it here to pass it + // down to the next step. + public final CompletableFuture refreshFuture; + private final long startTimeNs = System.nanoTime(); + + // All non-final fields are accessed exclusively on adminExecutor + private SchemaRows.Builder schemaRowsBuilder; + private int pendingQueries; + + protected SchemaQueries( + DriverChannel channel, + boolean isCassandraV3, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + this.channel = channel; + this.adminExecutor = channel.eventLoop(); + this.isCassandraV3 = isCassandraV3; + this.refreshFuture = refreshFuture; + this.logPrefix = logPrefix; + this.timeout = config.getDuration(CoreDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT); + this.pageSize = config.getInt(CoreDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); + + List refreshedKeyspaces = + config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + : Collections.emptyList(); + this.whereClause = buildWhereClause(refreshedKeyspaces); + } + + private static String buildWhereClause(List refreshedKeyspaces) { + if (refreshedKeyspaces.isEmpty()) { + return ""; + } else { + StringBuilder builder = new StringBuilder(" WHERE keyspace_name in ("); + boolean first = true; + for (String keyspace : refreshedKeyspaces) { + if (first) { + first = false; + } else { + builder.append(","); + } + builder.append('\'').append(keyspace).append('\''); + } + return builder.append(")").toString(); + } + } + + protected abstract String selectKeyspacesQuery(); + + protected abstract String selectTablesQuery(); + + protected abstract Optional selectViewsQuery(); + + protected abstract Optional selectIndexesQuery(); + + protected abstract String selectColumnsQuery(); + + protected abstract String selectTypesQuery(); + + protected abstract Optional selectFunctionsQuery(); + + protected abstract Optional selectAggregatesQuery(); + + public CompletionStage execute() { + RunOrSchedule.on(adminExecutor, this::executeOnAdminExecutor); + return schemaRowsFuture; + } + + private void executeOnAdminExecutor() { + assert adminExecutor.inEventLoop(); + + schemaRowsBuilder = new SchemaRows.Builder(isCassandraV3, refreshFuture, logPrefix); + + query(selectKeyspacesQuery() + whereClause, schemaRowsBuilder::withKeyspaces); + query(selectTypesQuery() + whereClause, schemaRowsBuilder::withTypes); + query(selectTablesQuery() + whereClause, schemaRowsBuilder::withTables); + query(selectColumnsQuery() + whereClause, schemaRowsBuilder::withColumns); + selectIndexesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withIndexes)); + selectViewsQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withViews)); + selectFunctionsQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions)); + selectAggregatesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates)); + } + + private void query( + String queryString, Function, SchemaRows.Builder> builderUpdater) { + assert adminExecutor.inEventLoop(); + + pendingQueries += 1; + query(queryString) + .whenCompleteAsync( + (result, error) -> handleResult(result, error, builderUpdater), adminExecutor); + } + + @VisibleForTesting + protected CompletionStage query(String query) { + return AdminRequestHandler.query(channel, query, timeout, pageSize, logPrefix).start(); + } + + private void handleResult( + AdminResult result, + Throwable error, + Function, SchemaRows.Builder> builderUpdater) { + if (schemaRowsFuture.isDone()) { // Another query failed already, ignore + return; + } + if (error != null) { + // Any error fails the whole refresh + schemaRowsFuture.completeExceptionally(error); + } else { + // Store the rows of the current page in the builder + schemaRowsBuilder = builderUpdater.apply(result); + // Move to the next page, or complete if we're the last query + if (result.hasNextPage()) { + result + .nextPage() + .whenCompleteAsync( + (nextResult, nextError) -> handleResult(nextResult, nextError, builderUpdater), + adminExecutor); + } else { + pendingQueries -= 1; + if (pendingQueries == 0) { + LOG.debug( + "[{}] Schema queries took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); + schemaRowsFuture.complete(schemaRowsBuilder.build()); + } + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesFactory.java new file mode 100644 index 00000000000..f572576d23a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesFactory.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.metadata.Metadata; +import java.util.concurrent.CompletableFuture; + +public interface SchemaQueriesFactory { + SchemaQueries newInstance(CompletableFuture refreshFuture); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java new file mode 100644 index 00000000000..21edb17cbdd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Gathers all the rows returned by the queries for a schema refresh, categorizing them by + * keyspace/table where relevant. + */ +public class SchemaRows { + + public final boolean isCassandraV3; + public final CompletableFuture refreshFuture; + public final List keyspaces; + public final Multimap tables; + public final Multimap views; + public final Multimap types; + public final Multimap functions; + public final Multimap aggregates; + public final Map> columns; + public final Map> indexes; + + private SchemaRows( + boolean isCassandraV3, + CompletableFuture refreshFuture, + List keyspaces, + Multimap tables, + Multimap views, + Map> columns, + Map> indexes, + Multimap types, + Multimap functions, + Multimap aggregates) { + this.isCassandraV3 = isCassandraV3; + this.refreshFuture = refreshFuture; + this.keyspaces = keyspaces; + this.tables = tables; + this.views = views; + this.columns = columns; + this.indexes = indexes; + this.types = types; + this.functions = functions; + this.aggregates = aggregates; + } + + public static class Builder { + private static final Logger LOG = LoggerFactory.getLogger(Builder.class); + + private final boolean isCassandraV3; + private final CompletableFuture refreshFuture; + private final String tableNameColumn; + private final String logPrefix; + private final ImmutableList.Builder keyspacesBuilder = ImmutableList.builder(); + private final ImmutableMultimap.Builder tablesBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder viewsBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder typesBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder functionsBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder aggregatesBuilder = + ImmutableListMultimap.builder(); + private final Map> + columnsBuilders = new LinkedHashMap<>(); + private final Map> + indexesBuilders = new LinkedHashMap<>(); + + public Builder( + boolean isCassandraV3, CompletableFuture refreshFuture, String logPrefix) { + this.isCassandraV3 = isCassandraV3; + this.refreshFuture = refreshFuture; + this.logPrefix = logPrefix; + this.tableNameColumn = isCassandraV3 ? "table_name" : "columnfamily_name"; + } + + public Builder withKeyspaces(Iterable rows) { + keyspacesBuilder.addAll(rows); + return this; + } + + public Builder withTables(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, tablesBuilder); + } + return this; + } + + public Builder withViews(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, viewsBuilder); + } + return this; + } + + public Builder withTypes(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, typesBuilder); + } + return this; + } + + public Builder withFunctions(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, functionsBuilder); + } + return this; + } + + public Builder withAggregates(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, aggregatesBuilder); + } + return this; + } + + public Builder withColumns(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspaceAndTable(row, columnsBuilders); + } + return this; + } + + public Builder withIndexes(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspaceAndTable(row, indexesBuilders); + } + return this; + } + + private void putByKeyspace( + AdminRow row, ImmutableMultimap.Builder builder) { + String keyspace = row.getString("keyspace_name"); + if (keyspace == null) { + LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); + } else { + builder.put(CqlIdentifier.fromInternal(keyspace), row); + } + } + + private void putByKeyspaceAndTable( + AdminRow row, + Map> builders) { + String keyspace = row.getString("keyspace_name"); + String table = row.getString(tableNameColumn); + if (keyspace == null) { + LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); + } else if (table == null) { + LOG.warn("[{}] Skipping system row with missing table name", logPrefix); + } else { + ImmutableMultimap.Builder builder = + builders.computeIfAbsent( + CqlIdentifier.fromInternal(keyspace), s -> ImmutableListMultimap.builder()); + builder.put(CqlIdentifier.fromInternal(table), row); + } + } + + public SchemaRows build() { + return new SchemaRows( + isCassandraV3, + refreshFuture, + keyspacesBuilder.build(), + tablesBuilder.build(), + viewsBuilder.build(), + build(columnsBuilders), + build(indexesBuilders), + typesBuilder.build(), + functionsBuilder.build(), + aggregatesBuilder.build()); + } + + private static Map> build( + Map> builders) { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (Map.Entry> entry : builders.entrySet()) { + builder.put(entry.getKey(), entry.getValue().build()); + } + return builder.build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java new file mode 100644 index 00000000000..45d80608995 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.refresh; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; +import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; +import com.datastax.oss.driver.internal.core.metadata.schema.events.AggregateChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.FunctionChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.KeyspaceChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.TableChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.TypeChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.ViewChangeEvent; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class SchemaRefresh extends MetadataRefresh { + + @VisibleForTesting public final Map newKeyspaces; + + public SchemaRefresh(Map newKeyspaces, String logPrefix) { + super(logPrefix); + this.newKeyspaces = newKeyspaces; + } + + @Override + public Result compute(DefaultMetadata oldMetadata) { + ImmutableList.Builder events = ImmutableList.builder(); + + Map oldKeyspaces = oldMetadata.getKeyspaces(); + for (CqlIdentifier removedKey : Sets.difference(oldKeyspaces.keySet(), newKeyspaces.keySet())) { + events.add(KeyspaceChangeEvent.dropped(oldKeyspaces.get(removedKey))); + } + for (Map.Entry entry : newKeyspaces.entrySet()) { + CqlIdentifier key = entry.getKey(); + computeEvents(oldKeyspaces.get(key), entry.getValue(), events); + } + + return new Result(oldMetadata.withKeyspaces(this.newKeyspaces), events.build()); + } + + private static boolean shallowEquals(KeyspaceMetadata keyspace1, KeyspaceMetadata keyspace2) { + return Objects.equals(keyspace1.getName(), keyspace2.getName()) + && keyspace1.isDurableWrites() == keyspace2.isDurableWrites() + && Objects.equals(keyspace1.getReplication(), keyspace2.getReplication()); + } + + /** + * Computes the exact set of events to emit when a keyspace has changed. + * + *

      We can't simply emit {@link KeyspaceChangeEvent#updated(KeyspaceMetadata, KeyspaceMetadata)} + * because this method might be called as part of a full schema refresh, or a keyspace refresh + * initiated by coalesced child element refreshes. We need to traverse all children to check what + * has exactly changed. + */ + private void computeEvents( + KeyspaceMetadata oldKeyspace, + KeyspaceMetadata newKeyspace, + ImmutableList.Builder events) { + if (oldKeyspace == null) { + events.add(KeyspaceChangeEvent.created(newKeyspace)); + } else { + if (!shallowEquals(oldKeyspace, newKeyspace)) { + events.add(KeyspaceChangeEvent.updated(oldKeyspace, newKeyspace)); + } + computeChildEvents(oldKeyspace, newKeyspace, events); + } + } + + private void computeChildEvents( + KeyspaceMetadata oldKeyspace, + KeyspaceMetadata newKeyspace, + ImmutableList.Builder events) { + computeChildEvents( + oldKeyspace.getTables(), + newKeyspace.getTables(), + TableChangeEvent::dropped, + TableChangeEvent::created, + TableChangeEvent::updated, + events); + computeChildEvents( + oldKeyspace.getViews(), + newKeyspace.getViews(), + ViewChangeEvent::dropped, + ViewChangeEvent::created, + ViewChangeEvent::updated, + events); + computeChildEvents( + oldKeyspace.getUserDefinedTypes(), + newKeyspace.getUserDefinedTypes(), + TypeChangeEvent::dropped, + TypeChangeEvent::created, + TypeChangeEvent::updated, + events); + computeChildEvents( + oldKeyspace.getFunctions(), + newKeyspace.getFunctions(), + FunctionChangeEvent::dropped, + FunctionChangeEvent::created, + FunctionChangeEvent::updated, + events); + computeChildEvents( + oldKeyspace.getAggregates(), + newKeyspace.getAggregates(), + AggregateChangeEvent::dropped, + AggregateChangeEvent::created, + AggregateChangeEvent::updated, + events); + } + + private void computeChildEvents( + Map oldChildren, + Map newChildren, + Function newDroppedEvent, + Function newCreatedEvent, + BiFunction newUpdatedEvent, + ImmutableList.Builder events) { + for (K removedKey : Sets.difference(oldChildren.keySet(), newChildren.keySet())) { + events.add(newDroppedEvent.apply(oldChildren.get(removedKey))); + } + for (Map.Entry entry : newChildren.entrySet()) { + K key = entry.getKey(); + V newChild = entry.getValue(); + V oldChild = oldChildren.get(key); + if (oldChild == null) { + events.add(newCreatedEvent.apply(newChild)); + } else if (!oldChild.equals(newChild)) { + events.add(newUpdatedEvent.apply(oldChild, newChild)); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 1b67bc92749..25f44b88313 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -294,7 +294,7 @@ private void onPoolsInit(List> poolStages) { } if (allInvalidKeyspaces) { initFuture.completeExceptionally( - new InvalidKeyspaceException("Invalid keyspace " + keyspace.asPrettyCql())); + new InvalidKeyspaceException("Invalid keyspace " + keyspace.asCql(true))); forceClose(); } else { LOG.debug("[{}] Initialization complete, ready", logPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 880f9f2e5e3..25124fd99c6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; import com.datastax.oss.driver.internal.core.pool.ChannelPool; @@ -134,7 +135,7 @@ private void gatherServerIds(AdminResult rows, Throwable error) { error.toString()); gatherPayloadsToReprepare(); } else { - for (AdminResult.Row row : rows) { + for (AdminRow row : rows) { serverKnownIds.add(row.getByteBuffer("prepared_id")); } if (rows.hasNextPage()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java index 1ee63a4c5d1..b2d766a35b3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DataTypeHelper.java @@ -67,6 +67,7 @@ public static DataType fromProtocolSpec(RawType rawType, AttachmentPoint attachm return new DefaultUserDefinedType( CqlIdentifier.fromInternal(rawUdt.keyspace), CqlIdentifier.fromInternal(rawUdt.typeName), + false, fieldNames.build(), fieldTypes.build(), attachmentPoint); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java index 28931fd6245..1ed9935bf9f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/DefaultUserDefinedType.java @@ -37,6 +37,10 @@ public class DefaultUserDefinedType implements UserDefinedType { private final CqlIdentifier keyspace; /** @serial */ private final CqlIdentifier name; + + // Data types are only [de]serialized as part of a row, frozenness doesn't matter in that context + private final transient boolean frozen; + /** @serial */ private final List fieldNames; /** @serial */ @@ -48,6 +52,7 @@ public class DefaultUserDefinedType implements UserDefinedType { public DefaultUserDefinedType( CqlIdentifier keyspace, CqlIdentifier name, + boolean frozen, List fieldNames, List fieldTypes, AttachmentPoint attachmentPoint) { @@ -60,6 +65,7 @@ public DefaultUserDefinedType( "There should be the same number of field names and types"); this.keyspace = keyspace; this.name = name; + this.frozen = frozen; this.fieldNames = ImmutableList.copyOf(fieldNames); this.fieldTypes = ImmutableList.copyOf(fieldTypes); this.index = new IdentifierIndex(this.fieldNames); @@ -69,9 +75,10 @@ public DefaultUserDefinedType( public DefaultUserDefinedType( CqlIdentifier keyspace, CqlIdentifier name, + boolean frozen, List fieldNames, List fieldTypes) { - this(keyspace, name, fieldNames, fieldTypes, AttachmentPoint.NONE); + this(keyspace, name, frozen, fieldNames, fieldTypes, AttachmentPoint.NONE); } @Override @@ -84,6 +91,11 @@ public CqlIdentifier getName() { return name; } + @Override + public boolean isFrozen() { + return frozen; + } + @Override public List getFieldNames() { return fieldNames; @@ -104,6 +116,14 @@ public List getFieldTypes() { return fieldTypes; } + @Override + public UserDefinedType copy(boolean newFrozen) { + return (newFrozen == frozen) + ? this + : new DefaultUserDefinedType( + keyspace, name, newFrozen, fieldNames, fieldTypes, attachmentPoint); + } + @Override public UdtValue newValue() { return new DefaultUdtValue(this); @@ -133,6 +153,7 @@ public boolean equals(Object other) { return true; } else if (other instanceof UserDefinedType) { UserDefinedType that = (UserDefinedType) other; + // frozen is ignored in comparisons return this.keyspace.equals(that.getKeyspace()) && this.name.equals(that.getName()) && this.fieldNames.equals(that.getFieldNames()) @@ -149,7 +170,7 @@ public int hashCode() { @Override public String toString() { - return "UDT(" + keyspace.asPrettyCql() + "." + name.asPrettyCql() + ")"; + return "UDT(" + keyspace.asCql(true) + "." + name.asCql(true) + ")"; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java index d5dbc5ae70e..71538369805 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/PrimitiveType.java @@ -60,6 +60,11 @@ public int hashCode() { return protocolCode; } + @Override + public String asCql(boolean includeFrozen, boolean pretty) { + return codeName(protocolCode).toLowerCase(); + } + @Override public String toString() { return codeName(protocolCode); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java index c9a9e85cb48..86ca4d86ac6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java @@ -32,6 +32,7 @@ public class UserDefinedTypeBuilder { private final CqlIdentifier keyspaceName; private final CqlIdentifier typeName; + private boolean frozen; private final ImmutableList.Builder fieldNames; private final ImmutableList.Builder fieldTypes; @@ -52,8 +53,14 @@ public UserDefinedTypeBuilder withField(CqlIdentifier name, DataType type) { return this; } + /** Makes the type frozen (by default, it is not). */ + public UserDefinedTypeBuilder frozen() { + this.frozen = true; + return this; + } + public UserDefinedType build() { return new DefaultUserDefinedType( - keyspaceName, typeName, fieldNames.build(), fieldTypes.build()); + keyspaceName, typeName, frozen, fieldNames.build(), fieldTypes.build()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java index 6360179bbb6..f009ae22466 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ParseUtils.java @@ -124,11 +124,11 @@ public static int skipCQLId(String toParse, int idx) { throw new IllegalArgumentException(); } - private static boolean isBlank(int c) { + public static boolean isBlank(int c) { return c == ' ' || c == '\t' || c == '\n'; } - private static boolean isCqlIdentifierChar(int c) { + public static boolean isCqlIdentifierChar(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 5cf677983b3..7f831283641 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -138,7 +138,7 @@ public String format(UdtValue value) { sb.append(","); } CqlIdentifier elementName = cqlType.getFieldNames().get(i); - sb.append(elementName.asPrettyCql()); + sb.append(elementName.asCql(true)); sb.append(":"); DataType elementType = cqlType.getFieldTypes().get(i); TypeCodec codec = registry.codecFor(elementType); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java new file mode 100644 index 00000000000..f366cd40779 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +/** A basic directed graph implementation to perform topological sorts. */ +public class DirectedGraph { + + // We need to keep track of the predecessor count. For simplicity, use a map to store it + // alongside the vertices. + private final Map vertices; + private final Multimap adjacencyList; + private boolean wasSorted; + + public DirectedGraph(Collection vertices) { + this.vertices = Maps.newLinkedHashMapWithExpectedSize(vertices.size()); + this.adjacencyList = LinkedHashMultimap.create(); + + for (V vertex : vertices) { + this.vertices.put(vertex, 0); + } + } + + @VisibleForTesting + @SafeVarargs + DirectedGraph(V... vertices) { + this(Arrays.asList(vertices)); + } + + /** + * this assumes that {@code from} and {@code to} were part of the vertices passed to the + * constructor + */ + public void addEdge(V from, V to) { + Preconditions.checkArgument(vertices.containsKey(from) && vertices.containsKey(to)); + adjacencyList.put(from, to); + vertices.put(to, vertices.get(to) + 1); + } + + /** one-time use only, calling this multiple times on the same graph won't work */ + public List topologicalSort() { + Preconditions.checkState(!wasSorted); + wasSorted = true; + + Queue queue = new LinkedList(); + + for (Map.Entry entry : vertices.entrySet()) { + if (entry.getValue() == 0) { + queue.add(entry.getKey()); + } + } + + List result = Lists.newArrayList(); + while (!queue.isEmpty()) { + V vertex = queue.remove(); + result.add(vertex); + for (V successor : adjacencyList.get(vertex)) { + if (decrementAndGetCount(successor) == 0) { + queue.add(successor); + } + } + } + + if (result.size() != vertices.size()) { + throw new IllegalArgumentException("failed to perform topological sort, graph has a cycle"); + } + + return result; + } + + private int decrementAndGetCount(V vertex) { + Integer count = vertices.get(vertex); + count = count - 1; + vertices.put(vertex, count); + return count; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java new file mode 100644 index 00000000000..00942d6cffb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.function.Function; + +public class ImmutableMaps { + + /** Returns a new immutable map that overrides one mapping in the source map. */ + public static ImmutableMap replace(Map source, K key, V newValue) { + return ImmutableMap.builder() + .put(key, newValue) + .putAll(Maps.filterKeys(source, k -> !key.equals(k))) + .build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java new file mode 100644 index 00000000000..be25faefa33 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +public class NanoTime { + + private static final long ONE_HOUR = 3600L * 1000 * 1000 * 1000; + private static final long ONE_MINUTE = 60L * 1000 * 1000 * 1000; + private static final long ONE_SECOND = 1000 * 1000 * 1000; + private static final long ONE_MILLISECOND = 1000 * 1000; + private static final long ONE_MICROSECOND = 1000; + + /** Formats a duration in the best unit (truncating the fractional part). */ + public static String formatTimeSince(long startTimeNs) { + long delta = System.nanoTime() - startTimeNs; + if (delta >= ONE_HOUR) { + return (delta / ONE_HOUR) + " h"; + } else if (delta >= ONE_MINUTE) { + return (delta / ONE_MINUTE) + " mn"; + } else if (delta >= ONE_SECOND) { + return (delta / ONE_SECOND) + " s"; + } else if (delta >= ONE_MILLISECOND) { + return (delta / ONE_MILLISECOND) + " ms"; + } else if (delta >= ONE_MICROSECOND) { + return (delta / ONE_MICROSECOND) + " us"; + } else { + return delta + " ns"; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java index 091bdd17c9f..0365b2cefe7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java @@ -103,12 +103,14 @@ public void receive(T element) { } } - private void flushNow() { + public void flushNow() { assert adminExecutor.inEventLoop(); LOG.debug("Flushing now"); cancelNextFlush(); - onFlush.accept(coalescer.apply(currentBatch)); - currentBatch = new ArrayList<>(); + if (!currentBatch.isEmpty()) { + onFlush.accept(coalescer.apply(currentBatch)); + currentBatch = new ArrayList<>(); + } } private void scheduleFlush() { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 93b2ce73a32..40127215c04 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -369,6 +369,33 @@ datastax-java-driver { # if the window keeps getting reset. max-events = 20 } + + schema { + # Whether schema and token metadata is enabled. If this is false, that metadata will remain + # empty, or to the value from the last update. + # Note that this option can be overridden programmatically with + # Cluster.setSchemaMetadataEnabled. + enabled = true + # The list of keyspaces for which schema and token metadata should be maintained. If this + # property is absent or empty, all existing keyspaces are processed. + // refreshed-keyspaces = [ "ks1", "ks2" ] + # The timeout for the requests to the schema tables. + request-timeout = ${datastax-java-driver.request.timeout} + # The page size for the requests to the schema tables. + request-page-size = ${datastax-java-driver.request.page-size} + # Protects against bursts of schema updates (for example when a client issues a sequence of + # DDL queries), by coalescing them into a single update. + # Debouncing may be disabled by setting the window to 0 or max-events to 1 (this is highly + # discouraged for schema refreshes). + debouncer { + # How long the driver waits to apply a refresh. If another refresh is requested within that + # time, the window is reset and a single refresh will be triggered when it ends. + window = 1 second + # The maximum number of refreshes that can accumulate. If this count is reached, a refresh + # is done immediately and the window is reset. + max-events = 20 + } + } } # The address translator to use to convert the addresses sent by Cassandra nodes into ones that diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java index b52797159e5..83ada3ea9b1 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java @@ -51,19 +51,19 @@ public void should_fail_to_build_from_valid_cql_if_reserved_keyword() { @Test public void should_format_as_cql() { - assertThat(CqlIdentifier.fromInternal("foo").asCql()).isEqualTo("\"foo\""); - assertThat(CqlIdentifier.fromInternal("Foo").asCql()).isEqualTo("\"Foo\""); - assertThat(CqlIdentifier.fromInternal("foo bar").asCql()).isEqualTo("\"foo bar\""); - assertThat(CqlIdentifier.fromInternal("foo\"bar").asCql()).isEqualTo("\"foo\"\"bar\""); - assertThat(CqlIdentifier.fromInternal("create").asCql()).isEqualTo("\"create\""); + assertThat(CqlIdentifier.fromInternal("foo").asCql(false)).isEqualTo("\"foo\""); + assertThat(CqlIdentifier.fromInternal("Foo").asCql(false)).isEqualTo("\"Foo\""); + assertThat(CqlIdentifier.fromInternal("foo bar").asCql(false)).isEqualTo("\"foo bar\""); + assertThat(CqlIdentifier.fromInternal("foo\"bar").asCql(false)).isEqualTo("\"foo\"\"bar\""); + assertThat(CqlIdentifier.fromInternal("create").asCql(false)).isEqualTo("\"create\""); } @Test public void should_format_as_pretty_cql() { - assertThat(CqlIdentifier.fromInternal("foo").asPrettyCql()).isEqualTo("foo"); - assertThat(CqlIdentifier.fromInternal("Foo").asPrettyCql()).isEqualTo("\"Foo\""); - assertThat(CqlIdentifier.fromInternal("foo bar").asPrettyCql()).isEqualTo("\"foo bar\""); - assertThat(CqlIdentifier.fromInternal("foo\"bar").asPrettyCql()).isEqualTo("\"foo\"\"bar\""); - assertThat(CqlIdentifier.fromInternal("create").asPrettyCql()).isEqualTo("\"create\""); + assertThat(CqlIdentifier.fromInternal("foo").asCql(true)).isEqualTo("foo"); + assertThat(CqlIdentifier.fromInternal("Foo").asCql(true)).isEqualTo("\"Foo\""); + assertThat(CqlIdentifier.fromInternal("foo bar").asCql(true)).isEqualTo("\"foo bar\""); + assertThat(CqlIdentifier.fromInternal("foo\"bar").asCql(true)).isEqualTo("\"foo\"\"bar\""); + assertThat(CqlIdentifier.fromInternal("create").asCql(true)).isEqualTo("\"create\""); } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java new file mode 100644 index 00000000000..c062cb59807 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.type; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UserDefinedTypeTest { + + private static final UserDefinedType ADDRESS_TYPE = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("test"), CqlIdentifier.fromInternal("address")) + // Not actually used in this test, but UDTs must have fields: + .withField(CqlIdentifier.fromInternal("street"), DataTypes.TEXT) + .frozen() + .build(); + private static final UserDefinedType ACCOUNT_TYPE = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("test"), CqlIdentifier.fromInternal("account")) + .withField(CqlIdentifier.fromInternal("ID"), DataTypes.TEXT) // case-sensitive + .withField(CqlIdentifier.fromInternal("name"), DataTypes.TEXT) + .withField(CqlIdentifier.fromInternal("address"), ADDRESS_TYPE) + .withField( + CqlIdentifier.fromInternal("frozen_list"), DataTypes.frozenListOf(DataTypes.TEXT)) + .withField( + CqlIdentifier.fromInternal("list_of_map"), + DataTypes.listOf(DataTypes.frozenMapOf(DataTypes.TEXT, DataTypes.INT))) + .build(); + + @Test + public void should_describe_as_cql() { + assertThat(ACCOUNT_TYPE.describe(false)) + .isEqualTo( + "CREATE TYPE \"test\".\"account\" ( \"ID\" text, \"name\" text, \"address\" frozen<\"test\".\"address\">, \"frozen_list\" frozen>, \"list_of_map\" list>> );"); + } + + @Test + public void should_describe_as_pretty_cql() { + assertThat(ACCOUNT_TYPE.describe(true)) + .isEqualTo( + "CREATE TYPE test.account (\n" + + " \"ID\" text,\n" + + " name text,\n" + + " address frozen,\n" + + " frozen_list frozen>,\n" + + " list_of_map list>>\n" + + ");"); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 2444737d0eb..9925ff902f0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.channel.EventCallback; -import com.datastax.oss.driver.internal.core.metadata.SchemaElementKind; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.event.SchemaChangeEvent; @@ -45,7 +44,7 @@ public void should_register_for_all_events_if_topology_requested() { .thenReturn(CompletableFuture.completedFuture(channel1)); // When - controlConnection.init(true); + controlConnection.init(true, false); waitForPendingAdminTasks(); DriverChannelOptions channelOptions = optionsCaptor.getValue(); @@ -68,7 +67,7 @@ public void should_register_for_schema_events_only_if_topology_not_requested() { .thenReturn(CompletableFuture.completedFuture(channel1)); // When - controlConnection.init(false); + controlConnection.init(false, false); waitForPendingAdminTasks(); DriverChannelOptions channelOptions = optionsCaptor.getValue(); @@ -86,7 +85,7 @@ public void should_process_status_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(true); + controlConnection.init(true, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; StatusChangeEvent event = @@ -108,7 +107,7 @@ public void should_process_topology_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(true); + controlConnection.init(true, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; TopologyChangeEvent event = @@ -130,7 +129,7 @@ public void should_process_schema_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(ADDRESS1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(false); + controlConnection.init(false, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; SchemaChangeEvent event = @@ -145,7 +144,6 @@ public void should_process_schema_change_events() { callback.onEvent(event); // Then - Mockito.verify(metadataManager) - .refreshSchema(SchemaElementKind.FUNCTION, "ks", "fn", ImmutableList.of("text", "text")); + Mockito.verify(metadataManager).refreshSchema("ks", false, false); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 29e3597a35b..717c642037b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -58,7 +58,7 @@ public void should_init_with_first_contact_point_if_reachable() { MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); // When - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -78,9 +78,9 @@ public void should_always_return_same_init_future() { MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); // When - CompletionStage initFuture1 = controlConnection.init(false); + CompletionStage initFuture1 = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); - CompletionStage initFuture2 = controlConnection.init(false); + CompletionStage initFuture2 = controlConnection.init(false, false); // Then assertThat(initFuture1).isEqualTo(initFuture2); @@ -99,7 +99,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { .build(); // When - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); @@ -124,7 +124,7 @@ public void should_fail_to_init_if_all_contact_points_fail() { .build(); // When - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); @@ -151,7 +151,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -190,7 +190,7 @@ public void should_reconnect_if_node_becomes_ignored() { .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -230,7 +230,7 @@ public void should_reconnect_if_node_is_removed_or_forced_down(NodeStateEvent ev .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -274,7 +274,7 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( .success(ADDRESS1, channel3) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -324,7 +324,7 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( .success(ADDRESS1, channel3) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); @@ -368,7 +368,7 @@ public void should_force_reconnection_if_pending() { .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -406,7 +406,7 @@ public void should_force_reconnection_even_if_connected() { .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -444,7 +444,7 @@ public void should_not_force_reconnection_if_closed() { DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -468,7 +468,7 @@ public void should_close_channel_when_closing() { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory).success(ADDRESS1, channel1).build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -499,7 +499,7 @@ public void should_close_channel_if_closed_during_reconnection() { .pending(ADDRESS2, channel2Future) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); @@ -546,7 +546,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { .success(ADDRESS2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false); + CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(ADDRESS1); waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 05bf1aa335f..fbf1c25c7b6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -39,18 +39,18 @@ public void should_add_new_node() { .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo, "test"); + AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - Map newNodes = refresh.newMetadata.getNodes(); + Map newNodes = result.newMetadata.getNodes(); assertThat(newNodes).containsOnlyKeys(ADDRESS1, ADDRESS2); Node node2 = newNodes.get(ADDRESS2); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); - assertThat(refresh.events).containsExactly(NodeStateEvent.added((DefaultNode) node2)); + assertThat(result.events).containsExactly(NodeStateEvent.added((DefaultNode) node2)); } @Test @@ -63,16 +63,16 @@ public void should_not_add_existing_node() { .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(oldMetadata, newNodeInfo, "test"); + AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); // Info is not copied over: assertThat(node1.getDatacenter()).isNull(); assertThat(node1.getRack()).isNull(); - assertThat(refresh.events).isEmpty(); + assertThat(result.events).isEmpty(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 0eb0fcc2259..786f8dd1067 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; @@ -97,7 +98,7 @@ public void should_initialize_control_connection() { topologyMonitor.init(); // Then - Mockito.verify(controlConnection).init(true); + Mockito.verify(controlConnection).init(true, false); } @Test @@ -137,8 +138,8 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present() { public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() { // Given node2.broadcastAddress = Optional.empty(); - AdminResult.Row peer3 = mockPeersRow(3); - AdminResult.Row peer2 = mockPeersRow(2); + AdminRow peer3 = mockPeersRow(3); + AdminRow peer2 = mockPeersRow(2); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2))); @@ -167,9 +168,9 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() @Test public void should_get_new_node_from_peers() { // Given - AdminResult.Row peer3 = mockPeersRow(3); - AdminResult.Row peer2 = mockPeersRow(2); - AdminResult.Row peer1 = mockPeersRow(1); + AdminRow peer3 = mockPeersRow(3); + AdminRow peer2 = mockPeersRow(2); + AdminRow peer1 = mockPeersRow(1); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2, peer1))); @@ -202,8 +203,8 @@ public void should_get_new_node_from_peers() { @Test public void should_refresh_node_list_from_local_and_peers() { // Given - AdminResult.Row peer3 = mockPeersRow(3); - AdminResult.Row peer2 = mockPeersRow(2); + AdminRow peer3 = mockPeersRow(3); + AdminRow peer2 = mockPeersRow(2); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.local", mockResult(mockLocalRow(1))), new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2))); @@ -284,9 +285,9 @@ private StubbedQuery(String queryString, AdminResult result) { } } - private AdminResult.Row mockLocalRow(int i) { + private AdminRow mockLocalRow(int i) { try { - AdminResult.Row row = Mockito.mock(AdminResult.Row.class); + AdminRow row = Mockito.mock(AdminRow.class); Mockito.when(row.getInetAddress("broadcast_address")) .thenReturn(InetAddress.getByName("127.0.0." + i)); Mockito.when(row.getString("data_center")).thenReturn("dc" + i); @@ -307,9 +308,9 @@ private AdminResult.Row mockLocalRow(int i) { } } - private AdminResult.Row mockPeersRow(int i) { + private AdminRow mockPeersRow(int i) { try { - AdminResult.Row row = Mockito.mock(AdminResult.Row.class); + AdminRow row = Mockito.mock(AdminRow.class); Mockito.when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); Mockito.when(row.getString("data_center")).thenReturn("dc" + i); Mockito.when(row.getString("rack")).thenReturn("rack" + i); @@ -324,7 +325,7 @@ private AdminResult.Row mockPeersRow(int i) { } } - private AdminResult mockResult(AdminResult.Row... rows) { + private AdminResult mockResult(AdminRow... rows) { AdminResult result = Mockito.mock(AdminResult.class); Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); return result; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index c383acc98dd..3b5ca517a2b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -41,14 +41,14 @@ public void should_add_and_remove_nodes() { ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos, "test"); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); - assertThat(refresh.events) + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); + assertThat(result.events) .containsOnly(NodeStateEvent.removed(node1), NodeStateEvent.added(node3)); } @@ -69,17 +69,17 @@ public void should_update_existing_nodes() { .withDatacenter("dc1") .withRack("rack2") .build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(oldMetadata, newInfos, "test"); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); assertThat(node1.getDatacenter()).isEqualTo("dc1"); assertThat(node1.getRack()).isEqualTo("rack1"); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); - assertThat(refresh.events).isEmpty(); + assertThat(result.events).isEmpty(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index e5184ddfda7..0d59bbf3cc3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -30,14 +30,13 @@ public class InitContactPointsRefreshTest { public void should_create_nodes() { // Given InitContactPointsRefresh refresh = - new InitContactPointsRefresh( - DefaultMetadata.EMPTY, ImmutableSet.of(ADDRESS1, ADDRESS2), "test"); + new InitContactPointsRefresh(ImmutableSet.of(ADDRESS1, ADDRESS2), "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); - assertThat(refresh.events).isEmpty(); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); + assertThat(result.events).isEmpty(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 01c80796d94..4ef16252774 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -15,15 +15,22 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultEventLoopGroup; import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -38,6 +45,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -51,6 +59,11 @@ public class MetadataManagerTest { @Mock private InternalDriverContext context; @Mock private NettyOptions nettyOptions; @Mock private TopologyMonitor topologyMonitor; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultProfile; + @Mock private EventBus eventBus; + @Mock private SchemaQueriesFactory schemaQueriesFactory; + @Mock private SchemaParserFactory schemaParserFactory; private DefaultEventLoopGroup adminEventLoopGroup; @@ -66,6 +79,16 @@ public void setup() { Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); + Mockito.when(defaultProfile.getDuration(CoreDriverOption.METADATA_SCHEMA_WINDOW)) + .thenReturn(Duration.ZERO); + Mockito.when(defaultProfile.getInt(CoreDriverOption.METADATA_SCHEMA_MAX_EVENTS)).thenReturn(1); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(context.config()).thenReturn(config); + + Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.schemaQueriesFactory()).thenReturn(schemaQueriesFactory); + Mockito.when(context.schemaParserFactory()).thenReturn(schemaParserFactory); + metadataManager = new TestMetadataManager(context); } @@ -214,7 +237,7 @@ public TestMetadataManager(InternalDriverContext context) { } @Override - Void refresh(MetadataRefresh refresh) { + Void apply(MetadataRefresh refresh) { // Do not execute refreshes, just store them for inspection in the test refreshes.add(refresh); return null; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index eea8da8ded7..0334da5db82 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -34,27 +34,27 @@ public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2, "test"); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); - assertThat(refresh.events).containsExactly(NodeStateEvent.removed(node2)); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.events).containsExactly(NodeStateEvent.removed(node2)); } @Test public void should_not_remove_nonexistent_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(oldMetadata, ADDRESS2, "test"); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); // When - refresh.compute(); + MetadataRefresh.Result result = refresh.compute(oldMetadata); // Then - assertThat(refresh.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); - assertThat(refresh.events).isEmpty(); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.events).isEmpty(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java new file mode 100644 index 00000000000..b8dbc34b64e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class AggregateParserTest extends SchemaParserTestBase { + + private static final AdminRow SUM_AND_TO_STRING_ROW_2_2 = + mockAggregateRow( + "ks", + "sum_and_to_string", + ImmutableList.of("org.apache.cassandra.db.marshal.Int32Type"), + "plus", + "org.apache.cassandra.db.marshal.Int32Type", + "to_string", + "org.apache.cassandra.db.marshal.UTF8Type", + Bytes.fromHexString("0x00000000")); + + static final AdminRow SUM_AND_TO_STRING_ROW_3_0 = + mockAggregateRow( + "ks", + "sum_and_to_string", + ImmutableList.of("int"), + "plus", + "int", + "to_string", + "text", + "0"); + + @Before + public void setup() { + Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + Mockito.when(context.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + } + + @Test + public void should_parse_modern_table() { + AggregateParser parser = new AggregateParser(new DataTypeCqlNameParser(), context); + AggregateMetadata aggregate = + parser.parseAggregate(SUM_AND_TO_STRING_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(aggregate.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(aggregate.getSignature().getName().asInternal()).isEqualTo("sum_and_to_string"); + assertThat(aggregate.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + + FunctionSignature stateFuncSignature = aggregate.getStateFuncSignature(); + assertThat(stateFuncSignature.getName().asInternal()).isEqualTo("plus"); + assertThat(stateFuncSignature.getParameterTypes()) + .containsExactly(DataTypes.INT, DataTypes.INT); + assertThat(aggregate.getStateType()).isEqualTo(DataTypes.INT); + + FunctionSignature finalFuncSignature = aggregate.getFinalFuncSignature(); + assertThat(finalFuncSignature.getName().asInternal()).isEqualTo("to_string"); + assertThat(finalFuncSignature.getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(aggregate.getReturnType()).isEqualTo(DataTypes.TEXT); + + assertThat(aggregate.getInitCond()).isInstanceOf(Integer.class).isEqualTo(0); + } + + @Test + public void should_parse_legacy_table() { + AggregateParser parser = new AggregateParser(new DataTypeClassNameParser(), context); + AggregateMetadata aggregate = + parser.parseAggregate(SUM_AND_TO_STRING_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(aggregate.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(aggregate.getSignature().getName().asInternal()).isEqualTo("sum_and_to_string"); + assertThat(aggregate.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + + FunctionSignature stateFuncSignature = aggregate.getStateFuncSignature(); + assertThat(stateFuncSignature.getName().asInternal()).isEqualTo("plus"); + assertThat(stateFuncSignature.getParameterTypes()) + .containsExactly(DataTypes.INT, DataTypes.INT); + assertThat(aggregate.getStateType()).isEqualTo(DataTypes.INT); + + FunctionSignature finalFuncSignature = aggregate.getFinalFuncSignature(); + assertThat(finalFuncSignature.getName().asInternal()).isEqualTo("to_string"); + assertThat(finalFuncSignature.getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(aggregate.getReturnType()).isEqualTo(DataTypes.TEXT); + + assertThat(aggregate.getInitCond()).isInstanceOf(Integer.class).isEqualTo(0); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java new file mode 100644 index 00000000000..44bd6a1418e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.datastax.oss.driver.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class DataTypeClassNameParserTest { + + private static final CqlIdentifier KEYSPACE_ID = CqlIdentifier.fromInternal("ks"); + + @Mock private InternalDriverContext context; + private DataTypeClassNameParser parser; + + @Before + public void setUp() throws Exception { + parser = new DataTypeClassNameParser(); + } + + @Test + public void should_parse_native_types() { + for (Map.Entry entry : + DataTypeClassNameParser.NATIVE_TYPES_BY_CLASS_NAME.entrySet()) { + + String className = entry.getKey(); + DataType expectedType = entry.getValue(); + + assertThat(parse(className)).isEqualTo(expectedType); + } + } + + @Test + public void should_parse_collection_types() { + assertThat( + parse( + "org.apache.cassandra.db.marshal.ListType(" + + "org.apache.cassandra.db.marshal.UTF8Type)")) + .isEqualTo(DataTypes.listOf(DataTypes.TEXT)); + + assertThat( + parse( + "org.apache.cassandra.db.marshal.FrozenType(" + + ("org.apache.cassandra.db.marshal.ListType(" + + "org.apache.cassandra.db.marshal.UTF8Type))"))) + .isEqualTo(DataTypes.frozenListOf(DataTypes.TEXT)); + + assertThat( + parse( + "org.apache.cassandra.db.marshal.SetType(" + + "org.apache.cassandra.db.marshal.UTF8Type)")) + .isEqualTo(DataTypes.setOf(DataTypes.TEXT)); + + assertThat( + parse( + "org.apache.cassandra.db.marshal.MapType(" + + "org.apache.cassandra.db.marshal.UTF8Type," + + "org.apache.cassandra.db.marshal.UTF8Type)")) + .isEqualTo(DataTypes.mapOf(DataTypes.TEXT, DataTypes.TEXT)); + + assertThat( + parse( + "org.apache.cassandra.db.marshal.MapType(" + + "org.apache.cassandra.db.marshal.UTF8Type," + + "org.apache.cassandra.db.marshal.FrozenType(" + + ("org.apache.cassandra.db.marshal.MapType(" + + "org.apache.cassandra.db.marshal.Int32Type," + + "org.apache.cassandra.db.marshal.Int32Type)))"))) + .isEqualTo( + DataTypes.mapOf(DataTypes.TEXT, DataTypes.frozenMapOf(DataTypes.INT, DataTypes.INT))); + } + + @Test + public void should_parse_user_type_when_definition_not_already_available() { + UserDefinedType addressType = + (UserDefinedType) + parse( + "org.apache.cassandra.db.marshal.UserType(" + + "foo,61646472657373," + + ("737472656574:org.apache.cassandra.db.marshal.UTF8Type," + + "7a6970636f6465:org.apache.cassandra.db.marshal.Int32Type," + + ("70686f6e6573:org.apache.cassandra.db.marshal.SetType(" + + "org.apache.cassandra.db.marshal.UserType(foo,70686f6e65," + + "6e616d65:org.apache.cassandra.db.marshal.UTF8Type," + + "6e756d626572:org.apache.cassandra.db.marshal.UTF8Type)") + + "))")); + + assertThat(addressType.getKeyspace().asInternal()).isEqualTo("foo"); + assertThat(addressType.getName().asInternal()).isEqualTo("address"); + assertThat(addressType.isFrozen()).isTrue(); + assertThat(addressType.getFieldNames().size()).isEqualTo(3); + + assertThat(addressType.getFieldNames().get(0).asInternal()).isEqualTo("street"); + assertThat(addressType.getFieldTypes().get(0)).isEqualTo(DataTypes.TEXT); + + assertThat(addressType.getFieldNames().get(1).asInternal()).isEqualTo("zipcode"); + assertThat(addressType.getFieldTypes().get(1)).isEqualTo(DataTypes.INT); + + assertThat(addressType.getFieldNames().get(2).asInternal()).isEqualTo("phones"); + DataType phonesType = addressType.getFieldTypes().get(2); + assertThat(phonesType).isInstanceOf(SetType.class); + UserDefinedType phoneType = ((UserDefinedType) ((SetType) phonesType).getElementType()); + + assertThat(phoneType.getKeyspace().asInternal()).isEqualTo("foo"); + assertThat(phoneType.getName().asInternal()).isEqualTo("phone"); + assertThat(phoneType.isFrozen()).isTrue(); + assertThat(phoneType.getFieldNames().size()).isEqualTo(2); + + assertThat(phoneType.getFieldNames().get(0).asInternal()).isEqualTo("name"); + assertThat(phoneType.getFieldTypes().get(0)).isEqualTo(DataTypes.TEXT); + + assertThat(phoneType.getFieldNames().get(1).asInternal()).isEqualTo("number"); + assertThat(phoneType.getFieldTypes().get(1)).isEqualTo(DataTypes.TEXT); + } + + @Test + public void should_make_a_frozen_copy_user_type_when_definition_already_available() { + UserDefinedType existing = Mockito.mock(UserDefinedType.class); + + parse( + "org.apache.cassandra.db.marshal.UserType(foo,70686f6e65," + + "6e616d65:org.apache.cassandra.db.marshal.UTF8Type," + + "6e756d626572:org.apache.cassandra.db.marshal.UTF8Type)", + ImmutableMap.of(CqlIdentifier.fromInternal("phone"), existing)); + + Mockito.verify(existing).copy(true); + } + + @Test + public void should_parse_tuple() { + TupleType tupleType = + (TupleType) + parse( + "org.apache.cassandra.db.marshal.TupleType(" + + "org.apache.cassandra.db.marshal.Int32Type," + + "org.apache.cassandra.db.marshal.UTF8Type," + + "org.apache.cassandra.db.marshal.FloatType)"); + + assertThat(tupleType.getComponentTypes().size()).isEqualTo(3); + assertThat(tupleType.getComponentTypes().get(0)).isEqualTo(DataTypes.INT); + assertThat(tupleType.getComponentTypes().get(1)).isEqualTo(DataTypes.TEXT); + assertThat(tupleType.getComponentTypes().get(2)).isEqualTo(DataTypes.FLOAT); + } + + private DataType parse(String toParse) { + return parse(toParse, null); + } + + private DataType parse(String toParse, Map existingTypes) { + return parser.parse(KEYSPACE_ID, toParse, existingTypes, context); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java new file mode 100644 index 00000000000..8c79fc822aa --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.ShallowUserDefinedType; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class DataTypeCqlNameParserTest { + + private static final CqlIdentifier KEYSPACE_ID = CqlIdentifier.fromInternal("ks"); + + @Mock private InternalDriverContext context; + private DataTypeCqlNameParser parser; + + @Before + public void setUp() throws Exception { + parser = new DataTypeCqlNameParser(); + } + + @Test + public void should_parse_native_types() { + for (Map.Entry entry : + DataTypeCqlNameParser.NATIVE_TYPES_BY_NAME.entrySet()) { + + String className = entry.getKey(); + DataType expectedType = entry.getValue(); + + assertThat(parse(className)).isEqualTo(expectedType); + } + } + + @Test + public void should_parse_collection_types() { + assertThat(parse("list")).isEqualTo(DataTypes.listOf(DataTypes.TEXT)); + assertThat(parse("frozen>")).isEqualTo(DataTypes.frozenListOf(DataTypes.TEXT)); + assertThat(parse("set")).isEqualTo(DataTypes.setOf(DataTypes.TEXT)); + assertThat(parse("map")).isEqualTo(DataTypes.mapOf(DataTypes.TEXT, DataTypes.TEXT)); + assertThat(parse("map>>")) + .isEqualTo( + DataTypes.mapOf(DataTypes.TEXT, DataTypes.frozenMapOf(DataTypes.INT, DataTypes.INT))); + } + + @Test + public void should_parse_top_level_user_type_as_shallow() { + UserDefinedType addressType = (UserDefinedType) parse("address"); + assertThat(addressType).isInstanceOf(ShallowUserDefinedType.class); + assertThat(addressType.getKeyspace()).isEqualTo(KEYSPACE_ID); + assertThat(addressType.getName().asInternal()).isEqualTo("address"); + assertThat(addressType.isFrozen()).isFalse(); + + UserDefinedType frozenAddressType = (UserDefinedType) parse("frozen
      "); + assertThat(frozenAddressType).isInstanceOf(ShallowUserDefinedType.class); + assertThat(frozenAddressType.getKeyspace()).isEqualTo(KEYSPACE_ID); + assertThat(frozenAddressType.getName().asInternal()).isEqualTo("address"); + assertThat(frozenAddressType.isFrozen()).isTrue(); + } + + @Test + public void should_reuse_existing_user_type_when_not_top_level() { + UserDefinedType addressType = Mockito.mock(UserDefinedType.class); + UserDefinedType frozenAddressType = Mockito.mock(UserDefinedType.class); + Mockito.when(addressType.copy(false)).thenReturn(addressType); + Mockito.when(addressType.copy(true)).thenReturn(frozenAddressType); + + ImmutableMap existingTypes = + ImmutableMap.of(CqlIdentifier.fromInternal("address"), addressType); + + ListType listOfAddress = (ListType) parse("list
      ", existingTypes); + assertThat(listOfAddress.getElementType()).isEqualTo(addressType); + + ListType listOfFrozenAddress = (ListType) parse("list>", existingTypes); + assertThat(listOfFrozenAddress.getElementType()).isEqualTo(frozenAddressType); + } + + @Test + public void should_parse_tuple() { + TupleType tupleType = (TupleType) parse("tuple"); + + assertThat(tupleType.getComponentTypes().size()).isEqualTo(3); + assertThat(tupleType.getComponentTypes().get(0)).isEqualTo(DataTypes.INT); + assertThat(tupleType.getComponentTypes().get(1)).isEqualTo(DataTypes.TEXT); + assertThat(tupleType.getComponentTypes().get(2)).isEqualTo(DataTypes.FLOAT); + } + + private DataType parse(String toParse) { + return parse(toParse, null); + } + + private DataType parse(String toParse, Map existingTypes) { + return parser.parse(KEYSPACE_ID, toParse, existingTypes, context); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java new file mode 100644 index 00000000000..f7d4b6e088b --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class FunctionParserTest extends SchemaParserTestBase { + + private static final AdminRow ID_ROW_2_2 = + mockFunctionRow( + "ks", + "id", + ImmutableList.of("i"), + ImmutableList.of("org.apache.cassandra.db.marshal.Int32Type"), + "return i;", + false, + "java", + "org.apache.cassandra.db.marshal.Int32Type"); + + static final AdminRow ID_ROW_3_0 = + mockFunctionRow( + "ks", + "id", + ImmutableList.of("i"), + ImmutableList.of("int"), + "return i;", + false, + "java", + "int"); + + @Test + public void should_parse_modern_table() { + FunctionParser parser = new FunctionParser(new DataTypeCqlNameParser(), context); + FunctionMetadata function = + parser.parseFunction(ID_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(function.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(function.getSignature().getName().asInternal()).isEqualTo("id"); + assertThat(function.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(function.getParameterNames()).containsExactly(CqlIdentifier.fromInternal("i")); + assertThat(function.getBody()).isEqualTo("return i;"); + assertThat(function.isCalledOnNullInput()).isFalse(); + assertThat(function.getLanguage()).isEqualTo("java"); + assertThat(function.getReturnType()).isEqualTo(DataTypes.INT); + } + + @Test + public void should_parse_legacy_table() { + FunctionParser parser = new FunctionParser(new DataTypeClassNameParser(), context); + FunctionMetadata function = + parser.parseFunction(ID_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(function.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(function.getSignature().getName().asInternal()).isEqualTo("id"); + assertThat(function.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(function.getParameterNames()).containsExactly(CqlIdentifier.fromInternal("i")); + assertThat(function.getBody()).isEqualTo("return i;"); + assertThat(function.isCalledOnNullInput()).isFalse(); + assertThat(function.getLanguage()).isEqualTo("java"); + assertThat(function.getReturnType()).isEqualTo(DataTypes.INT); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java new file mode 100644 index 00000000000..ee3719eb166 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; +import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; +import com.google.common.collect.ImmutableList; +import java.util.Map; +import java.util.function.Consumer; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class SchemaParserTest extends SchemaParserTestBase { + + @Test + public void should_parse_modern_keyspace_row() { + SchemaRefresh refresh = + (SchemaRefresh) + parse(rows -> rows.withKeyspaces(ImmutableList.of(mockModernKeyspaceRow("ks")))); + + assertThat(refresh.newKeyspaces).hasSize(1); + KeyspaceMetadata keyspace = refresh.newKeyspaces.values().iterator().next(); + checkKeyspace(keyspace); + } + + @Test + public void should_parse_legacy_keyspace_row() { + SchemaRefresh refresh = + (SchemaRefresh) + parse(rows -> rows.withKeyspaces(ImmutableList.of(mockLegacyKeyspaceRow("ks")))); + + assertThat(refresh.newKeyspaces).hasSize(1); + KeyspaceMetadata keyspace = refresh.newKeyspaces.values().iterator().next(); + checkKeyspace(keyspace); + } + + @Test + public void should_parse_keyspace_with_all_children() { + // Needed to parse the aggregate + Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + + SchemaRefresh refresh = + (SchemaRefresh) + parse( + rows -> + rows.withKeyspaces(ImmutableList.of(mockModernKeyspaceRow("ks"))) + .withTypes( + ImmutableList.of( + mockTypeRow( + "ks", "t", ImmutableList.of("i"), ImmutableList.of("int")))) + .withTables(ImmutableList.of(TableParserTest.TABLE_ROW_3_0)) + .withColumns(TableParserTest.COLUMN_ROWS_3_0) + .withIndexes(TableParserTest.INDEX_ROWS_3_0) + .withViews(ImmutableList.of(ViewParserTest.VIEW_ROW_3_0)) + .withColumns(ViewParserTest.COLUMN_ROWS_3_0) + .withFunctions(ImmutableList.of(FunctionParserTest.ID_ROW_3_0)) + .withAggregates( + ImmutableList.of(AggregateParserTest.SUM_AND_TO_STRING_ROW_3_0))); + + assertThat(refresh.newKeyspaces).hasSize(1); + KeyspaceMetadata keyspace = refresh.newKeyspaces.values().iterator().next(); + checkKeyspace(keyspace); + + assertThat(keyspace.getUserDefinedTypes()) + .hasSize(1) + .containsKey(CqlIdentifier.fromInternal("t")); + assertThat(keyspace.getTables()).hasSize(1).containsKey(CqlIdentifier.fromInternal("foo")); + assertThat(keyspace.getViews()) + .hasSize(1) + .containsKey(CqlIdentifier.fromInternal("alltimehigh")); + assertThat(keyspace.getFunctions()) + .hasSize(1) + .containsKey(new FunctionSignature(CqlIdentifier.fromInternal("id"), DataTypes.INT)); + assertThat(keyspace.getAggregates()) + .hasSize(1) + .containsKey( + new FunctionSignature(CqlIdentifier.fromInternal("sum_and_to_string"), DataTypes.INT)); + } + + // Common assertions, the keyspace has the same info in all of our single keyspace examples + private void checkKeyspace(KeyspaceMetadata keyspace) { + assertThat(keyspace.getName().asInternal()).isEqualTo("ks"); + assertThat(keyspace.isDurableWrites()).isTrue(); + assertThat(keyspace.getReplication()) + .hasSize(2) + .containsEntry("class", "org.apache.cassandra.locator.SimpleStrategy") + .containsEntry("replication_factor", "1"); + } + + @Test + public void should_parse_multiple_keyspaces() { + SchemaRefresh refresh = + (SchemaRefresh) + parse( + rows -> + rows.withKeyspaces( + ImmutableList.of( + mockModernKeyspaceRow("ks1"), mockModernKeyspaceRow("ks2"))) + .withTypes( + ImmutableList.of( + mockTypeRow( + "ks1", "t1", ImmutableList.of("i"), ImmutableList.of("int")), + mockTypeRow( + "ks2", "t2", ImmutableList.of("i"), ImmutableList.of("int"))))); + + Map keyspaces = refresh.newKeyspaces; + assertThat(keyspaces).hasSize(2); + KeyspaceMetadata ks1 = keyspaces.get(CqlIdentifier.fromInternal("ks1")); + KeyspaceMetadata ks2 = keyspaces.get(CqlIdentifier.fromInternal("ks2")); + + assertThat(ks1.getName().asInternal()).isEqualTo("ks1"); + assertThat(ks1.getUserDefinedTypes()).hasSize(1).containsKey(CqlIdentifier.fromInternal("t1")); + assertThat(ks2.getName().asInternal()).isEqualTo("ks2"); + assertThat(ks2.getUserDefinedTypes()).hasSize(1).containsKey(CqlIdentifier.fromInternal("t2")); + } + + private MetadataRefresh parse(Consumer builderConfig) { + SchemaRows.Builder builder = new SchemaRows.Builder(true, null, "test"); + builderConfig.accept(builder); + SchemaRows rows = builder.build(); + return new SchemaParser(rows, context).parse(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java new file mode 100644 index 00000000000..2564c94afbd --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.nio.ByteBuffer; +import java.util.List; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.fail; + +@RunWith(MockitoJUnitRunner.Silent.class) +public abstract class SchemaParserTestBase { + + protected static final CqlIdentifier KEYSPACE_ID = CqlIdentifier.fromInternal("ks"); + @Mock protected DefaultMetadata currentMetadata; + @Mock protected InternalDriverContext context; + + protected static AdminRow mockFunctionRow( + String keyspace, + String name, + List argumentNames, + List argumentTypes, + String body, + boolean calledOnNullInput, + String language, + String returnType) { + + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.contains("keyspace_name")).thenReturn(true); + Mockito.when(row.contains("function_name")).thenReturn(true); + Mockito.when(row.contains("argument_names")).thenReturn(true); + Mockito.when(row.contains("argument_types")).thenReturn(true); + Mockito.when(row.contains("body")).thenReturn(true); + Mockito.when(row.contains("called_on_null_input")).thenReturn(true); + Mockito.when(row.contains("language")).thenReturn(true); + Mockito.when(row.contains("return_type")).thenReturn(true); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); + Mockito.when(row.getString("function_name")).thenReturn(name); + Mockito.when(row.getListOfString("argument_names")).thenReturn(argumentNames); + Mockito.when(row.getListOfString("argument_types")).thenReturn(argumentTypes); + Mockito.when(row.getString("body")).thenReturn(body); + Mockito.when(row.getBoolean("called_on_null_input")).thenReturn(calledOnNullInput); + Mockito.when(row.getString("language")).thenReturn(language); + Mockito.when(row.getString("return_type")).thenReturn(returnType); + + return row; + } + + protected static AdminRow mockAggregateRow( + String keyspace, + String name, + List argumentTypes, + String stateFunc, + String stateType, + String finalFunc, + String returnType, + Object initCond) { + + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.contains("keyspace_name")).thenReturn(true); + Mockito.when(row.contains("aggregate_name")).thenReturn(true); + Mockito.when(row.contains("argument_types")).thenReturn(true); + Mockito.when(row.contains("state_func")).thenReturn(true); + Mockito.when(row.contains("state_type")).thenReturn(true); + Mockito.when(row.contains("final_func")).thenReturn(true); + Mockito.when(row.contains("return_type")).thenReturn(true); + Mockito.when(row.contains("initcond")).thenReturn(true); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); + Mockito.when(row.getString("aggregate_name")).thenReturn(name); + Mockito.when(row.getListOfString("argument_types")).thenReturn(argumentTypes); + Mockito.when(row.getString("state_func")).thenReturn(stateFunc); + Mockito.when(row.getString("state_type")).thenReturn(stateType); + Mockito.when(row.getString("final_func")).thenReturn(finalFunc); + Mockito.when(row.getString("return_type")).thenReturn(returnType); + + if (initCond instanceof ByteBuffer) { + Mockito.when(row.isString("initcond")).thenReturn(false); + Mockito.when(row.getByteBuffer("initcond")).thenReturn(((ByteBuffer) initCond)); + } else if (initCond instanceof String) { + Mockito.when(row.isString("initcond")).thenReturn(true); + Mockito.when(row.getString("initcond")).thenReturn(((String) initCond)); + } else { + fail("Unsupported initcond type" + initCond.getClass()); + } + + return row; + } + + protected static AdminRow mockTypeRow( + String keyspace, String name, List fieldNames, List fieldTypes) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); + Mockito.when(row.getString("type_name")).thenReturn(name); + Mockito.when(row.getListOfString("field_names")).thenReturn(fieldNames); + Mockito.when(row.getListOfString("field_types")).thenReturn(fieldTypes); + + return row; + } + + protected static AdminRow mockLegacyTableRow(String keyspace, String name, String comparator) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); + Mockito.when(row.getString("columnfamily_name")).thenReturn(name); + Mockito.when(row.getBoolean("is_dense")).thenReturn(false); + Mockito.when(row.getString("comparator")).thenReturn(comparator); + Mockito.when(row.isString("caching")).thenReturn(true); + Mockito.when(row.getString("caching")) + .thenReturn("{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}"); + Mockito.when(row.getString("compaction_strategy_class")) + .thenReturn("org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"); + Mockito.when(row.getString("compaction_strategy_options")) + .thenReturn("{\"mock_option\":\"1\"}"); + + return row; + } + + protected static AdminRow mockLegacyColumnRow( + String keyspaceName, + String tableName, + String name, + String kind, + String dataType, + Integer position) { + return mockLegacyColumnRow( + keyspaceName, tableName, name, kind, dataType, position, null, null, null); + } + + protected static AdminRow mockLegacyColumnRow( + String keyspaceName, + String tableName, + String name, + String kind, + String dataType, + int position, + String indexName, + String indexType, + String indexOptions) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.contains("validator")).thenReturn(true); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getString("columnfamily_name")).thenReturn(tableName); + Mockito.when(row.getString("column_name")).thenReturn(name); + Mockito.when(row.getString("type")).thenReturn(kind); + Mockito.when(row.getString("validator")).thenReturn(dataType); + Mockito.when(row.getInteger("component_index")).thenReturn(position); + Mockito.when(row.getString("index_name")).thenReturn(indexName); + Mockito.when(row.getString("index_type")).thenReturn(indexType); + Mockito.when(row.getString("index_options")).thenReturn(indexOptions); + + return row; + } + + protected static AdminRow mockModernTableRow(String keyspace, String name) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.contains("flags")).thenReturn(true); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); + Mockito.when(row.getString("table_name")).thenReturn(name); + Mockito.when(row.getSetOfString("flags")).thenReturn(ImmutableSet.of("compound")); + Mockito.when(row.isString("caching")).thenReturn(false); + Mockito.when(row.get("caching", RelationParser.MAP_OF_TEXT_TO_TEXT)) + .thenReturn(ImmutableMap.of("keys", "ALL", "rows_per_partition", "NONE")); + Mockito.when(row.get("compaction", RelationParser.MAP_OF_TEXT_TO_TEXT)) + .thenReturn( + ImmutableMap.of( + "class", + "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "mock_option", + "1")); + + return row; + } + + protected static AdminRow mockModernColumnRow( + String keyspaceName, + String tableName, + String name, + String kind, + String dataType, + String clusteringOrder, + Integer position) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.contains("kind")).thenReturn(true); + Mockito.when(row.contains("position")).thenReturn(true); + Mockito.when(row.contains("clustering_order")).thenReturn(true); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getString("table_name")).thenReturn(tableName); + Mockito.when(row.getString("column_name")).thenReturn(name); + Mockito.when(row.getString("kind")).thenReturn(kind); + Mockito.when(row.getString("type")).thenReturn(dataType); + Mockito.when(row.getInteger("position")).thenReturn(position); + Mockito.when(row.getString("clustering_order")).thenReturn(clusteringOrder); + + return row; + } + + protected static AdminRow mockIndexRow( + String keyspaceName, + String tableName, + String name, + String kind, + ImmutableMap options) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getString("table_name")).thenReturn(tableName); + Mockito.when(row.getString("index_name")).thenReturn(name); + Mockito.when(row.getString("kind")).thenReturn(kind); + Mockito.when(row.getMapOfStringToString("options")).thenReturn(options); + + return row; + } + + protected static AdminRow mockViewRow( + String keyspaceName, + String viewName, + String baseTableName, + boolean includeAllColumns, + String whereClause) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getString("view_name")).thenReturn(viewName); + Mockito.when(row.getString("base_table_name")).thenReturn(baseTableName); + Mockito.when(row.getBoolean("include_all_columns")).thenReturn(includeAllColumns); + Mockito.when(row.getString("where_clause")).thenReturn(whereClause); + + return row; + } + + protected static AdminRow mockModernKeyspaceRow(String keyspaceName) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getBoolean("durable_writes")).thenReturn(true); + + Mockito.when(row.contains("strategy_class")).thenReturn(false); + Mockito.when(row.getMapOfStringToString("replication")) + .thenReturn( + ImmutableMap.of( + "class", "org.apache.cassandra.locator.SimpleStrategy", "replication_factor", "1")); + + return row; + } + + protected static AdminRow mockLegacyKeyspaceRow(String keyspaceName) { + AdminRow row = Mockito.mock(AdminRow.class); + + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); + Mockito.when(row.getBoolean("durable_writes")).thenReturn(true); + + Mockito.when(row.contains("strategy_class")).thenReturn(true); + Mockito.when(row.getString("strategy_class")) + .thenReturn("org.apache.cassandra.locator.SimpleStrategy"); + Mockito.when(row.getString("strategy_options")).thenReturn("{\"replication_factor\":\"1\"}"); + + return row; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java new file mode 100644 index 00000000000..cc35c24a3fd --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.IndexKind; +import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class TableParserTest extends SchemaParserTestBase { + + private static final AdminRow TABLE_ROW_2_2 = + mockLegacyTableRow( + "ks", + "foo", + "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)"); + private static final Iterable COLUMN_ROWS_2_2 = + ImmutableList.of( + mockLegacyColumnRow( + "ks", "foo", "k2", "partition_key", "org.apache.cassandra.db.marshal.UTF8Type", 1), + mockLegacyColumnRow( + "ks", "foo", "k1", "partition_key", "org.apache.cassandra.db.marshal.Int32Type", 0), + mockLegacyColumnRow( + "ks", "foo", "cc1", "clustering_key", "org.apache.cassandra.db.marshal.Int32Type", 0), + mockLegacyColumnRow( + "ks", + "foo", + "cc2", + "clustering_key", + "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)", + 1), + mockLegacyColumnRow( + "ks", + "foo", + "v", + "regular", + "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)", + -1, + "foo_v_idx", + "COMPOSITES", + "{}")); + + static final AdminRow TABLE_ROW_3_0 = mockModernTableRow("ks", "foo"); + static final Iterable COLUMN_ROWS_3_0 = + ImmutableList.of( + mockModernColumnRow("ks", "foo", "k2", "partition_key", "text", "none", 1), + mockModernColumnRow("ks", "foo", "k1", "partition_key", "int", "none", 0), + mockModernColumnRow("ks", "foo", "cc1", "clustering", "int", "asc", 0), + mockModernColumnRow("ks", "foo", "cc2", "clustering", "int", "desc", 1), + mockModernColumnRow("ks", "foo", "v", "regular", "int", "none", -1)); + static final Iterable INDEX_ROWS_3_0 = + ImmutableList.of( + mockIndexRow("ks", "foo", "foo_v_idx", "COMPOSITES", ImmutableMap.of("target", "v"))); + + @Test + public void should_skip_when_no_column_rows() { + SchemaRows rows = legacyRows(TABLE_ROW_2_2, Collections.emptyList()); + TableParser parser = new TableParser(rows, new DataTypeClassNameParser(), context); + TableMetadata table = parser.parseTable(TABLE_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(table).isNull(); + } + + @Test + public void should_parse_legacy_tables() { + SchemaRows rows = legacyRows(TABLE_ROW_2_2, COLUMN_ROWS_2_2); + TableParser parser = new TableParser(rows, new DataTypeClassNameParser(), context); + TableMetadata table = parser.parseTable(TABLE_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); + + checkTable(table); + + assertThat(table.getOptions().get(CqlIdentifier.fromInternal("caching"))) + .isEqualTo("{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}"); + } + + @Test + public void should_parse_modern_tables() { + SchemaRows rows = modernRows(TABLE_ROW_3_0, COLUMN_ROWS_3_0, INDEX_ROWS_3_0); + TableParser parser = new TableParser(rows, new DataTypeCqlNameParser(), context); + TableMetadata table = parser.parseTable(TABLE_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); + + checkTable(table); + + assertThat((Map) table.getOptions().get(CqlIdentifier.fromInternal("caching"))) + .hasSize(2) + .containsEntry("keys", "ALL") + .containsEntry("rows_per_partition", "NONE"); + } + + // Shared between 2.2 and 3.0 tests, all expected values are the same except the 'caching' option + private void checkTable(TableMetadata table) { + assertThat(table.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(table.getName().asInternal()).isEqualTo("foo"); + + assertThat(table.getPartitionKey()).hasSize(2); + ColumnMetadata pk0 = table.getPartitionKey().get(0); + assertThat(pk0.getName().asInternal()).isEqualTo("k1"); + assertThat(pk0.getType()).isEqualTo(DataTypes.INT); + ColumnMetadata pk1 = table.getPartitionKey().get(1); + assertThat(pk1.getName().asInternal()).isEqualTo("k2"); + assertThat(pk1.getType()).isEqualTo(DataTypes.TEXT); + + assertThat(table.getClusteringColumns().entrySet()).hasSize(2); + Iterator clusteringColumnsIterator = + table.getClusteringColumns().keySet().iterator(); + ColumnMetadata clusteringColumn1 = clusteringColumnsIterator.next(); + assertThat(clusteringColumn1.getName().asInternal()).isEqualTo("cc1"); + ColumnMetadata clusteringColumn2 = clusteringColumnsIterator.next(); + assertThat(clusteringColumn2.getName().asInternal()).isEqualTo("cc2"); + assertThat(table.getClusteringColumns().values()) + .containsExactly(ClusteringOrder.ASC, ClusteringOrder.DESC); + + assertThat(table.getColumns()) + .containsOnlyKeys( + CqlIdentifier.fromInternal("k1"), + CqlIdentifier.fromInternal("k2"), + CqlIdentifier.fromInternal("cc1"), + CqlIdentifier.fromInternal("cc2"), + CqlIdentifier.fromInternal("v")); + ColumnMetadata regularColumn = table.getColumns().get(CqlIdentifier.fromInternal("v")); + assertThat(regularColumn.getName().asInternal()).isEqualTo("v"); + assertThat(regularColumn.getType()).isEqualTo(DataTypes.INT); + + assertThat(table.getIndexes()).containsOnlyKeys(CqlIdentifier.fromInternal("foo_v_idx")); + IndexMetadata index = table.getIndexes().get(CqlIdentifier.fromInternal("foo_v_idx")); + assertThat(index.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(index.getTable().asInternal()).isEqualTo("foo"); + assertThat(index.getName().asInternal()).isEqualTo("foo_v_idx"); + assertThat(index.getClassName()).isNull(); + assertThat(index.getKind()).isEqualTo(IndexKind.COMPOSITES); + assertThat(index.getTarget()).isEqualTo("v"); + assertThat( + (Map) table.getOptions().get(CqlIdentifier.fromInternal("compaction"))) + .hasSize(2) + .containsEntry("class", "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy") + .containsEntry("mock_option", "1"); + } + + private SchemaRows legacyRows(AdminRow tableRow, Iterable columnRows) { + return rows(tableRow, columnRows, null, false); + } + + private SchemaRows modernRows( + AdminRow tableRow, Iterable columnRows, Iterable indexesRows) { + return rows(tableRow, columnRows, indexesRows, true); + } + + private SchemaRows rows( + AdminRow tableRow, + Iterable columnRows, + Iterable indexesRows, + boolean isCassandraV3) { + SchemaRows.Builder builder = + new SchemaRows.Builder(isCassandraV3, null, "test") + .withTables(ImmutableList.of(tableRow)) + .withColumns(columnRows); + if (indexesRows != null) { + builder.withIndexes(indexesRows); + } + return builder.build(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java new file mode 100644 index 00000000000..f885862aabe --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.ListType; +import com.datastax.oss.driver.api.core.type.MapType; +import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.google.common.collect.ImmutableList; +import java.util.Map; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UserDefinedTypeListParserTest extends SchemaParserTestBase { + + private static final AdminRow PERSON_ROW_2_2 = + mockTypeRow( + "ks", + "person", + ImmutableList.of("first_name", "last_name", "address"), + ImmutableList.of( + "org.apache.cassandra.db.marshal.UTF8Type", + "org.apache.cassandra.db.marshal.UTF8Type", + "org.apache.cassandra.db.marshal.UserType(" + + "ks,61646472657373," // address + + "737472656574:org.apache.cassandra.db.marshal.UTF8Type," // street + + "7a6970636f6465:org.apache.cassandra.db.marshal.Int32Type)")); // zipcode + + private static final AdminRow PERSON_ROW_3_0 = + mockTypeRow( + "ks", + "person", + ImmutableList.of("first_name", "last_name", "address"), + ImmutableList.of("text", "text", "address")); + + private static final AdminRow ADDRESS_ROW_3_0 = + mockTypeRow( + "ks", "address", ImmutableList.of("street", "zipcode"), ImmutableList.of("text", "int")); + + @Test + public void should_parse_modern_table() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse(KEYSPACE_ID, PERSON_ROW_3_0, ADDRESS_ROW_3_0); + + assertThat(types).hasSize(2); + UserDefinedType personType = types.get(CqlIdentifier.fromInternal("person")); + UserDefinedType addressType = types.get(CqlIdentifier.fromInternal("address")); + + assertThat(personType.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(personType.getName().asInternal()).isEqualTo("person"); + assertThat(personType.getFieldNames()) + .containsExactly( + CqlIdentifier.fromInternal("first_name"), + CqlIdentifier.fromInternal("last_name"), + CqlIdentifier.fromInternal("address")); + assertThat(personType.getFieldTypes().get(0)).isEqualTo(DataTypes.TEXT); + assertThat(personType.getFieldTypes().get(1)).isEqualTo(DataTypes.TEXT); + assertThat(personType.getFieldTypes().get(2)).isSameAs(addressType); + } + + @Test + public void should_parse_legacy_table() { + UserDefinedTypeParser parser = + new UserDefinedTypeParser(new DataTypeClassNameParser(), context); + // no need to add a column for the address type, because in 2.2 UDTs are always fully redefined + // in column and field types (instead of referencing an existing type) + Map types = parser.parse(KEYSPACE_ID, PERSON_ROW_2_2); + + assertThat(types).hasSize(1); + UserDefinedType personType = types.get(CqlIdentifier.fromInternal("person")); + + assertThat(personType.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(personType.getName().asInternal()).isEqualTo("person"); + assertThat(personType.getFieldNames()) + .containsExactly( + CqlIdentifier.fromInternal("first_name"), + CqlIdentifier.fromInternal("last_name"), + CqlIdentifier.fromInternal("address")); + assertThat(personType.getFieldTypes().get(0)).isEqualTo(DataTypes.TEXT); + assertThat(personType.getFieldTypes().get(1)).isEqualTo(DataTypes.TEXT); + UserDefinedType addressType = ((UserDefinedType) personType.getFieldTypes().get(2)); + assertThat(addressType.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(addressType.getName().asInternal()).isEqualTo("address"); + assertThat(addressType.getFieldNames()) + .containsExactly( + CqlIdentifier.fromInternal("street"), CqlIdentifier.fromInternal("zipcode")); + } + + @Test + public void should_parse_empty_list() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + assertThat(parser.parse(KEYSPACE_ID /* no types*/)).isEmpty(); + } + + @Test + public void should_parse_singleton_list() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, mockTypeRow("ks", "t", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(1); + UserDefinedType type = types.get(CqlIdentifier.fromInternal("t")); + assertThat(type.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(type.getName().asInternal()).isEqualTo("t"); + assertThat(type.getFieldNames()).containsExactly(CqlIdentifier.fromInternal("i")); + assertThat(type.getFieldTypes()).containsExactly(DataTypes.INT); + } + + @Test + public void should_resolve_list_dependency() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, + mockTypeRow( + "ks", "a", ImmutableList.of("bs"), ImmutableList.of("frozen>>")), + mockTypeRow("ks", "b", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(2); + UserDefinedType aType = types.get(CqlIdentifier.fromInternal("a")); + UserDefinedType bType = types.get(CqlIdentifier.fromInternal("b")); + assertThat(((ListType) aType.getFieldTypes().get(0)).getElementType()).isEqualTo(bType); + } + + @Test + public void should_resolve_set_dependency() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, + mockTypeRow( + "ks", "a", ImmutableList.of("bs"), ImmutableList.of("frozen>>")), + mockTypeRow("ks", "b", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(2); + UserDefinedType aType = types.get(CqlIdentifier.fromInternal("a")); + UserDefinedType bType = types.get(CqlIdentifier.fromInternal("b")); + assertThat(((SetType) aType.getFieldTypes().get(0)).getElementType()).isEqualTo(bType); + } + + @Test + public void should_resolve_map_dependency() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, + mockTypeRow( + "ks", + "a1", + ImmutableList.of("bs"), + ImmutableList.of("frozen>>")), + mockTypeRow( + "ks", + "a2", + ImmutableList.of("bs"), + ImmutableList.of("frozen, int>>")), + mockTypeRow("ks", "b", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(3); + UserDefinedType a1Type = types.get(CqlIdentifier.fromInternal("a1")); + UserDefinedType a2Type = types.get(CqlIdentifier.fromInternal("a2")); + UserDefinedType bType = types.get(CqlIdentifier.fromInternal("b")); + assertThat(((MapType) a1Type.getFieldTypes().get(0)).getValueType()).isEqualTo(bType); + assertThat(((MapType) a2Type.getFieldTypes().get(0)).getKeyType()).isEqualTo(bType); + } + + @Test + public void should_resolve_tuple_dependency() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, + mockTypeRow( + "ks", + "a", + ImmutableList.of("b"), + ImmutableList.of("frozen>>")), + mockTypeRow("ks", "b", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(2); + UserDefinedType aType = types.get(CqlIdentifier.fromInternal("a")); + UserDefinedType bType = types.get(CqlIdentifier.fromInternal("b")); + assertThat(((TupleType) aType.getFieldTypes().get(0)).getComponentTypes().get(1)) + .isEqualTo(bType); + } + + @Test + public void should_resolve_nested_dependency() { + UserDefinedTypeParser parser = new UserDefinedTypeParser(new DataTypeCqlNameParser(), context); + Map types = + parser.parse( + KEYSPACE_ID, + mockTypeRow( + "ks", + "a", + ImmutableList.of("bs"), + ImmutableList.of("frozen>>>>")), + mockTypeRow("ks", "b", ImmutableList.of("i"), ImmutableList.of("int"))); + + assertThat(types).hasSize(2); + UserDefinedType aType = types.get(CqlIdentifier.fromInternal("a")); + UserDefinedType bType = types.get(CqlIdentifier.fromInternal("b")); + TupleType tupleType = (TupleType) aType.getFieldTypes().get(0); + ListType listType = (ListType) tupleType.getComponentTypes().get(1); + assertThat(listType.getElementType()).isEqualTo(bType); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java new file mode 100644 index 00000000000..1d5270b7a00 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.Iterator; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ViewParserTest extends SchemaParserTestBase { + + static final AdminRow VIEW_ROW_3_0 = + mockViewRow("ks", "alltimehigh", "scores", false, "game IS NOT NULL"); + static final Iterable COLUMN_ROWS_3_0 = + ImmutableList.of( + mockModernColumnRow("ks", "alltimehigh", "game", "partition_key", "text", "none", 0), + mockModernColumnRow("ks", "alltimehigh", "score", "clustering", "int", "desc", 0), + mockModernColumnRow("ks", "alltimehigh", "user", "clustering", "text", "asc", 1), + mockModernColumnRow("ks", "alltimehigh", "year", "clustering", "int", "asc", 2), + mockModernColumnRow("ks", "alltimehigh", "month", "clustering", "int", "asc", 3), + mockModernColumnRow("ks", "alltimehigh", "day", "clustering", "int", "asc", 4)); + + @Test + public void should_skip_when_no_column_rows() { + SchemaRows rows = rows(VIEW_ROW_3_0, Collections.emptyList()); + ViewParser parser = new ViewParser(rows, new DataTypeClassNameParser(), context); + ViewMetadata view = parser.parseView(VIEW_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(view).isNull(); + } + + @Test + public void should_parse_view() { + SchemaRows rows = rows(VIEW_ROW_3_0, COLUMN_ROWS_3_0); + ViewParser parser = new ViewParser(rows, new DataTypeCqlNameParser(), context); + ViewMetadata view = parser.parseView(VIEW_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); + + assertThat(view.getKeyspace().asInternal()).isEqualTo("ks"); + assertThat(view.getName().asInternal()).isEqualTo("alltimehigh"); + assertThat(view.getBaseTable().asInternal()).isEqualTo("scores"); + + assertThat(view.getPartitionKey()).hasSize(1); + ColumnMetadata pk0 = view.getPartitionKey().get(0); + assertThat(pk0.getName().asInternal()).isEqualTo("game"); + assertThat(pk0.getType()).isEqualTo(DataTypes.TEXT); + + assertThat(view.getClusteringColumns().entrySet()).hasSize(5); + Iterator clusteringColumnsIterator = + view.getClusteringColumns().keySet().iterator(); + assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("score"); + assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("user"); + assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("year"); + assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("month"); + assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("day"); + + assertThat(view.getColumns()) + .containsOnlyKeys( + CqlIdentifier.fromInternal("game"), + CqlIdentifier.fromInternal("score"), + CqlIdentifier.fromInternal("user"), + CqlIdentifier.fromInternal("year"), + CqlIdentifier.fromInternal("month"), + CqlIdentifier.fromInternal("day")); + } + + private SchemaRows rows(AdminRow viewRow, Iterable columnRows) { + return new SchemaRows.Builder(true, null, "test") + .withViews(ImmutableList.of(viewRow)) + .withColumns(columnRows) + .build(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java new file mode 100644 index 00000000000..8132f6a3152 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.LinkedBlockingDeque; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +// Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is +// shared, this class just validates the query strings. +public class Cassandra21SchemaQueriesTest extends SchemaQueriesTest { + + @Test + public void should_query() { + Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + .thenReturn(false); + + SchemaQueriesWithMockedChannel queries = + new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); + + CompletionStage result = queries.execute(); + + // Keyspace + Call call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_keyspaces"); + call.result.complete( + mockResult(mockRow("keyspace_name", "ks1"), mockRow("keyspace_name", "ks2"))); + + // Types + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_usertypes"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "type_name", "type"))); + + // Tables + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_columnfamilies"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "columnfamily_name", "foo"))); + + // Columns + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_columns"); + call.result.complete( + mockResult( + mockRow("keyspace_name", "ks1", "columnfamily_name", "foo", "column_name", "k"))); + + channel.runPendingTasks(); + + assertThat(result) + .isSuccess( + rows -> { + // Keyspace + assertThat(rows.keyspaces).hasSize(2); + assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + + // Types + assertThat(rows.types.keySet()).containsOnly(KS1_ID); + assertThat(rows.types.get(KS1_ID)).hasSize(1); + assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + .isEqualTo("type"); + + // Tables + assertThat(rows.tables.keySet()).containsOnly(KS1_ID); + assertThat(rows.tables.get(KS1_ID)).hasSize(1); + assertThat(rows.tables.get(KS1_ID).iterator().next().getString("columnfamily_name")) + .isEqualTo("foo"); + + // Rows + assertThat(rows.columns.keySet()).containsOnly(KS1_ID); + assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat( + rows.columns + .get(KS1_ID) + .get(FOO_ID) + .iterator() + .next() + .getString("column_name")) + .isEqualTo("k"); + + // No views, functions or aggregates in this version + assertThat(rows.views.keySet()).isEmpty(); + assertThat(rows.functions.keySet()).isEmpty(); + assertThat(rows.aggregates.keySet()).isEmpty(); + }); + } + + /** Extends the class under test to mock the query execution logic. */ + static class SchemaQueriesWithMockedChannel extends Cassandra21SchemaQueries { + + final Queue calls = new LinkedBlockingDeque<>(); + + SchemaQueriesWithMockedChannel( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, refreshFuture, config, logPrefix); + } + + @Override + protected CompletionStage query(String query) { + Call call = new Call(query); + calls.add(call); + return call.result; + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java new file mode 100644 index 00000000000..d1db6e904d1 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.LinkedBlockingDeque; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +// Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is +// shared, this class just validates the query strings. +public class Cassandra22SchemaQueriesTest extends SchemaQueriesTest { + + @Test + public void should_query() { + Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + .thenReturn(false); + + SchemaQueriesWithMockedChannel queries = + new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); + + CompletionStage result = queries.execute(); + + // Keyspace + Call call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_keyspaces"); + call.result.complete( + mockResult(mockRow("keyspace_name", "ks1"), mockRow("keyspace_name", "ks2"))); + + // Types + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_usertypes"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "type_name", "type"))); + + // Tables + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_columnfamilies"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "columnfamily_name", "foo"))); + + // Columns + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_columns"); + call.result.complete( + mockResult( + mockRow("keyspace_name", "ks1", "columnfamily_name", "foo", "column_name", "k"))); + + // Functions + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_functions"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks2", "function_name", "add"))); + + // Aggregates + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system.schema_aggregates"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks2", "aggregate_name", "add"))); + + channel.runPendingTasks(); + + assertThat(result) + .isSuccess( + rows -> { + // Keyspace + assertThat(rows.keyspaces).hasSize(2); + assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + + // Types + assertThat(rows.types.keySet()).containsOnly(KS1_ID); + assertThat(rows.types.get(KS1_ID)).hasSize(1); + assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + .isEqualTo("type"); + + // Tables + assertThat(rows.tables.keySet()).containsOnly(KS1_ID); + assertThat(rows.tables.get(KS1_ID)).hasSize(1); + assertThat(rows.tables.get(KS1_ID).iterator().next().getString("columnfamily_name")) + .isEqualTo("foo"); + + // Rows + assertThat(rows.columns.keySet()).containsOnly(KS1_ID); + assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat( + rows.columns + .get(KS1_ID) + .get(FOO_ID) + .iterator() + .next() + .getString("column_name")) + .isEqualTo("k"); + + // Functions + assertThat(rows.functions.keySet()).containsOnly(KS2_ID); + assertThat(rows.functions.get(KS2_ID)).hasSize(1); + assertThat(rows.functions.get(KS2_ID).iterator().next().getString("function_name")) + .isEqualTo("add"); + + // Aggregates + assertThat(rows.aggregates.keySet()).containsOnly(KS2_ID); + assertThat(rows.aggregates.get(KS2_ID)).hasSize(1); + assertThat(rows.aggregates.get(KS2_ID).iterator().next().getString("aggregate_name")) + .isEqualTo("add"); + + // No views in this version + assertThat(rows.views.keySet()).isEmpty(); + }); + } + + /** Extends the class under test to mock the query execution logic. */ + static class SchemaQueriesWithMockedChannel extends Cassandra22SchemaQueries { + + final Queue calls = new LinkedBlockingDeque<>(); + + SchemaQueriesWithMockedChannel( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, refreshFuture, config, logPrefix); + } + + @Override + protected CompletionStage query(String query) { + Call call = new Call(query); + calls.add(call); + return call.result; + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java new file mode 100644 index 00000000000..5a1f02b924f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.google.common.collect.ImmutableList; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.LinkedBlockingDeque; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class Cassandra3SchemaQueriesTest extends SchemaQueriesTest { + + @Before + public void setup() { + super.setup(); + + // By default, no keyspace filter + Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + .thenReturn(false); + } + + @Test + public void should_query_without_keyspace_filter() { + should_query_with_where_clause(""); + } + + @Test + public void should_query_with_keyspace_filter() { + Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + .thenReturn(true); + Mockito.when(config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + .thenReturn(ImmutableList.of("ks1", "ks2")); + + should_query_with_where_clause(" WHERE keyspace_name in ('ks1','ks2')"); + } + + private void should_query_with_where_clause(String whereClause) { + SchemaQueriesWithMockedChannel queries = + new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); + CompletionStage result = queries.execute(); + + // Keyspace + Call call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.keyspaces" + whereClause); + call.result.complete( + mockResult(mockRow("keyspace_name", "ks1"), mockRow("keyspace_name", "ks2"))); + + // Types + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.types" + whereClause); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "type_name", "type"))); + + // Tables + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.tables" + whereClause); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo"))); + + // Columns + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.columns" + whereClause); + call.result.complete( + mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo", "column_name", "k"))); + + // Indexes + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.indexes" + whereClause); + call.result.complete( + mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo", "index_name", "index"))); + + // Views + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.views" + whereClause); + call.result.complete(mockResult(mockRow("keyspace_name", "ks2", "view_name", "foo"))); + + // Functions + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.functions" + whereClause); + call.result.complete(mockResult(mockRow("keyspace_name", "ks2", "function_name", "add"))); + + // Aggregates + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.aggregates" + whereClause); + call.result.complete(mockResult(mockRow("keyspace_name", "ks2", "aggregate_name", "add"))); + + channel.runPendingTasks(); + + assertThat(result) + .isSuccess( + rows -> { + // Keyspace + assertThat(rows.keyspaces).hasSize(2); + assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + + // Types + assertThat(rows.types.keySet()).containsOnly(KS1_ID); + assertThat(rows.types.get(KS1_ID)).hasSize(1); + assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + .isEqualTo("type"); + + // Tables + assertThat(rows.tables.keySet()).containsOnly(KS1_ID); + assertThat(rows.tables.get(KS1_ID)).hasSize(1); + assertThat(rows.tables.get(KS1_ID).iterator().next().getString("table_name")) + .isEqualTo("foo"); + + // Columns + assertThat(rows.columns.keySet()).containsOnly(KS1_ID); + assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat( + rows.columns + .get(KS1_ID) + .get(FOO_ID) + .iterator() + .next() + .getString("column_name")) + .isEqualTo("k"); + + // Indexes + assertThat(rows.indexes.keySet()).containsOnly(KS1_ID); + assertThat(rows.indexes.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat( + rows.indexes + .get(KS1_ID) + .get(FOO_ID) + .iterator() + .next() + .getString("index_name")) + .isEqualTo("index"); + + // Views + assertThat(rows.views.keySet()).containsOnly(KS2_ID); + assertThat(rows.views.get(KS2_ID)).hasSize(1); + assertThat(rows.views.get(KS2_ID).iterator().next().getString("view_name")) + .isEqualTo("foo"); + + // Functions + assertThat(rows.functions.keySet()).containsOnly(KS2_ID); + assertThat(rows.functions.get(KS2_ID)).hasSize(1); + assertThat(rows.functions.get(KS2_ID).iterator().next().getString("function_name")) + .isEqualTo("add"); + + // Aggregates + assertThat(rows.aggregates.keySet()).containsOnly(KS2_ID); + assertThat(rows.aggregates.get(KS2_ID)).hasSize(1); + assertThat(rows.aggregates.get(KS2_ID).iterator().next().getString("aggregate_name")) + .isEqualTo("add"); + }); + } + + @Test + public void should_query_with_paging() { + SchemaQueriesWithMockedChannel queries = + new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); + CompletionStage result = queries.execute(); + + // Keyspace + Call call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.keyspaces"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1"))); + + // No types + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.types"); + call.result.complete(mockResult(/*empty*/ )); + + // Tables + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.tables"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo"))); + + // Columns: paged + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.columns"); + + AdminResult page2 = + mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo", "column_name", "v")); + AdminResult page1 = + mockResult(page2, mockRow("keyspace_name", "ks1", "table_name", "foo", "column_name", "k")); + call.result.complete(page1); + + // No indexes + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.indexes"); + call.result.complete(mockResult(/*empty*/ )); + + // No views + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.views"); + call.result.complete(mockResult(/*empty*/ )); + + // No functions + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.functions"); + call.result.complete(mockResult(/*empty*/ )); + + // No aggregates + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.aggregates"); + call.result.complete(mockResult(/*empty*/ )); + + channel.runPendingTasks(); + + assertThat(result) + .isSuccess( + rows -> { + assertThat(rows.columns.keySet()).containsOnly(KS1_ID); + assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns.get(KS1_ID).get(FOO_ID)) + .extracting(r -> r.getString("column_name")) + .containsExactly("k", "v"); + }); + } + + @Test + public void should_ignore_malformed_rows() { + SchemaQueriesWithMockedChannel queries = + new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); + CompletionStage result = queries.execute(); + + // Keyspace + Call call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.keyspaces"); + call.result.complete(mockResult(mockRow("keyspace_name", "ks1"))); + + // No types + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.types"); + call.result.complete(mockResult(/*empty*/ )); + + // Tables + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.tables"); + call.result.complete( + mockResult( + mockRow("keyspace_name", "ks", "table_name", "foo"), + // Missing keyspace name: + mockRow("table_name", "foo"))); + + // Columns + call = queries.calls.poll(); + call.result.complete( + mockResult( + mockRow("keyspace_name", "ks", "table_name", "foo", "column_name", "k"), + // Missing keyspace name: + mockRow("table_name", "foo", "column_name", "k"), + // Missing table name: + mockRow("keyspace_name", "ks", "column_name", "k"))); + + AdminResult page2 = + mockResult(mockRow("keyspace_name", "ks1", "table_name", "foo", "column_name", "v")); + AdminResult page1 = + mockResult(page2, mockRow("keyspace_name", "ks1", "table_name", "foo", "column_name", "k")); + call.result.complete(page1); + + // No indexes + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.indexes"); + call.result.complete(mockResult(/*empty*/ )); + + // No views + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.views"); + call.result.complete(mockResult(/*empty*/ )); + + // No functions + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.functions"); + call.result.complete(mockResult(/*empty*/ )); + + // No aggregates + call = queries.calls.poll(); + assertThat(call.query).isEqualTo("SELECT * FROM system_schema.aggregates"); + call.result.complete(mockResult(/*empty*/ )); + + channel.runPendingTasks(); + + assertThat(result) + .isSuccess( + rows -> { + assertThat(rows.tables.keySet()).containsOnly(KS_ID); + assertThat(rows.tables.get(KS_ID)).hasSize(1); + assertThat(rows.tables.get(KS_ID).iterator().next().getString("table_name")) + .isEqualTo("foo"); + + assertThat(rows.columns.keySet()).containsOnly(KS_ID); + assertThat(rows.columns.get(KS_ID).keySet()).containsOnly(FOO_ID); + assertThat( + rows.columns + .get(KS_ID) + .get(FOO_ID) + .iterator() + .next() + .getString("column_name")) + .isEqualTo("k"); + }); + } + + /** Extends the class under test to mock the query execution logic. */ + static class SchemaQueriesWithMockedChannel extends Cassandra3SchemaQueries { + + final Queue calls = new LinkedBlockingDeque<>(); + + SchemaQueriesWithMockedChannel( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + super(channel, refreshFuture, config, logPrefix); + } + + @Override + protected CompletionStage query(String query) { + Call call = new Call(query); + calls.add(call); + return call.result; + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java new file mode 100644 index 00000000000..c37aa664577 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.google.common.collect.Iterators; +import io.netty.channel.embedded.EmbeddedChannel; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public abstract class SchemaQueriesTest { + + protected static final CqlIdentifier KS_ID = CqlIdentifier.fromInternal("ks"); + protected static final CqlIdentifier KS1_ID = CqlIdentifier.fromInternal("ks1"); + protected static final CqlIdentifier KS2_ID = CqlIdentifier.fromInternal("ks2"); + protected static final CqlIdentifier FOO_ID = CqlIdentifier.fromInternal("foo"); + + @Mock protected Node node; + @Mock protected DriverConfigProfile config; + @Mock protected DriverChannel driverChannel; + protected EmbeddedChannel channel; + + @Before + public void setup() { + // Whatever, not actually used because the requests are mocked + Mockito.when(config.getDuration(CoreDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT)) + .thenReturn(Duration.ZERO); + Mockito.when(config.getInt(CoreDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE)) + .thenReturn(5000); + + channel = new EmbeddedChannel(); + driverChannel = Mockito.mock(DriverChannel.class); + Mockito.when(driverChannel.eventLoop()).thenReturn(channel.eventLoop()); + } + + protected static AdminRow mockRow(String... values) { + AdminRow row = Mockito.mock(AdminRow.class); + assertThat(values.length % 2).as("Expecting an even number of parameters").isZero(); + for (int i = 0; i < values.length / 2; i++) { + Mockito.when(row.getString(values[i * 2])).thenReturn(values[i * 2 + 1]); + } + return row; + } + + protected static AdminResult mockResult(AdminRow... rows) { + return mockResult(null, rows); + } + + protected static AdminResult mockResult(AdminResult next, AdminRow... rows) { + AdminResult result = Mockito.mock(AdminResult.class); + if (next == null) { + Mockito.when(result.hasNextPage()).thenReturn(false); + } else { + Mockito.when(result.hasNextPage()).thenReturn(true); + Mockito.when(result.nextPage()).thenReturn(CompletableFuture.completedFuture(next)); + } + Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + return result; + } + + protected static class Call { + final String query; + final CompletableFuture result; + + Call(String query) { + this.query = query; + this.result = new CompletableFuture<>(); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java new file mode 100644 index 00000000000..52268f93f54 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.refresh; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; +import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultKeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.events.KeyspaceChangeEvent; +import com.datastax.oss.driver.internal.core.metadata.schema.events.TypeChangeEvent; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class SchemaRefreshTest { + + private static final UserDefinedType OLD_T1 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t1")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .build(); + private static final UserDefinedType OLD_T2 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t2")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .build(); + private static final DefaultKeyspaceMetadata OLD_KS1 = newKeyspace("ks1", true, OLD_T1, OLD_T2); + + private DefaultMetadata oldMetadata = + DefaultMetadata.EMPTY.withKeyspaces(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1)); + + private static DefaultKeyspaceMetadata newKeyspace( + String name, boolean durableWrites, UserDefinedType... userTypes) { + ImmutableMap.Builder typesMapBuilder = ImmutableMap.builder(); + for (UserDefinedType type : userTypes) { + typesMapBuilder.put(type.getName(), type); + } + return new DefaultKeyspaceMetadata( + CqlIdentifier.fromInternal(name), + durableWrites, + Collections.emptyMap(), + typesMapBuilder.build(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap()); + } + + @Test + public void should_detect_dropped_keyspace() { + SchemaRefresh refresh = new SchemaRefresh(Collections.emptyMap(), "test"); + MetadataRefresh.Result result = refresh.compute(oldMetadata); + assertThat(result.newMetadata.getKeyspaces()).isEmpty(); + assertThat(result.events).containsExactly(KeyspaceChangeEvent.dropped(OLD_KS1)); + } + + @Test + public void should_detect_created_keyspace() { + DefaultKeyspaceMetadata ks2 = newKeyspace("ks2", true); + SchemaRefresh refresh = + new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1, ks2.getName(), ks2), "test"); + MetadataRefresh.Result result = refresh.compute(oldMetadata); + assertThat(result.newMetadata.getKeyspaces()).hasSize(2); + assertThat(result.events).containsExactly(KeyspaceChangeEvent.created(ks2)); + } + + @Test + public void should_detect_top_level_update_in_keyspace() { + // Change only one top-level option (durable writes) + DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, OLD_T1, OLD_T2); + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); + MetadataRefresh.Result result = refresh.compute(oldMetadata); + assertThat(result.newMetadata.getKeyspaces()).hasSize(1); + assertThat(result.events).containsExactly(KeyspaceChangeEvent.updated(OLD_KS1, newKs1)); + } + + @Test + public void should_detect_updated_children_in_keyspace() { + // Drop one type, modify the other and add a third one + UserDefinedType newT2 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t2")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.TEXT) + .build(); + UserDefinedType t3 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t3")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .build(); + DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", true, newT2, t3); + + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); + MetadataRefresh.Result result = refresh.compute(oldMetadata); + assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); + assertThat(result.events) + .containsExactly( + TypeChangeEvent.dropped(OLD_T1), + TypeChangeEvent.updated(OLD_T2, newT2), + TypeChangeEvent.created(t3)); + } + + @Test + public void should_detect_top_level_change_and_children_changes() { + // Drop one type, modify the other and add a third one + UserDefinedType newT2 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t2")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.TEXT) + .build(); + UserDefinedType t3 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks1"), CqlIdentifier.fromInternal("t3")) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .build(); + // Also disable durable writes + DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, newT2, t3); + + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); + MetadataRefresh.Result result = refresh.compute(oldMetadata); + assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); + assertThat(result.events) + .containsExactly( + KeyspaceChangeEvent.updated(OLD_KS1, newKs1), + TypeChangeEvent.dropped(OLD_T1), + TypeChangeEvent.updated(OLD_T2, newT2), + TypeChangeEvent.created(t3)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 76daef03d9a..0f576b0cdba 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -72,6 +72,7 @@ public void setup() { new DefaultUserDefinedType( CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type"), + false, ImmutableList.of( CqlIdentifier.fromInternal("field1"), CqlIdentifier.fromInternal("field2"), diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java new file mode 100644 index 00000000000..db9e6a7959d --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DirectedGraphTest { + + @Test + public void should_sort_empty_graph() { + DirectedGraph g = new DirectedGraph<>(); + assertThat(g.topologicalSort()).isEmpty(); + } + + @Test + public void should_sort_graph_with_one_node() { + DirectedGraph g = new DirectedGraph<>("A"); + assertThat(g.topologicalSort()).containsExactly("A"); + } + + @Test + public void should_sort_complex_graph() { + // H G + // / \ /\ + // F | E + // \ / / + // D / + // / \/ + // B C + // | + // A + DirectedGraph g = new DirectedGraph<>("A", "B", "C", "D", "E", "F", "G", "H"); + g.addEdge("H", "F"); + g.addEdge("G", "E"); + g.addEdge("H", "D"); + g.addEdge("F", "D"); + g.addEdge("G", "D"); + g.addEdge("D", "C"); + g.addEdge("E", "C"); + g.addEdge("D", "B"); + g.addEdge("B", "A"); + + // The graph uses linked hash maps internally, so this order will be consistent across JVMs + assertThat(g.topologicalSort()).containsExactly("G", "H", "E", "F", "D", "C", "B", "A"); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_sort_if_graph_has_a_cycle() { + DirectedGraph g = new DirectedGraph<>("A", "B", "C"); + g.addEdge("A", "B"); + g.addEdge("B", "C"); + g.addEdge("C", "B"); + + g.topologicalSort(); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_sort_if_graph_is_a_cycle() { + DirectedGraph g = new DirectedGraph<>("A", "B", "C"); + g.addEdge("A", "B"); + g.addEdge("B", "C"); + g.addEdge("C", "A"); + + g.topologicalSort(); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 966148afdb6..1a9e87268c8 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -48,6 +48,11 @@ logback-classic test + + org.mockito + mockito-core + test + com.google.guava guava diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 5bada76e8d7..8ab712e4ff1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -149,7 +149,7 @@ DataTypes.VARINT, new BigInteger(Integer.toString(Integer.MAX_VALUE) + "000") || dataType == DataTypes.SMALLINT || dataType == DataTypes.DATE || dataType == DataTypes.TIME) { - return version.compareTo(CassandraVersion.parse("2.2.0")) >= 0; + return version.compareTo(CassandraVersion.V2_2_0) >= 0; } return true; }) @@ -233,6 +233,7 @@ public static Object[][] typeSamples() { new DefaultUserDefinedType( cluster.keyspace(), CqlIdentifier.fromCql(userTypeFor(types)), + false, typeNames, types); @@ -729,7 +730,7 @@ private void readValue( for (int i = 0; i < exUdtValue.getType().getFieldTypes().size(); i++) { DataType compType = exUdtValue.getType().getFieldTypes().get(i); - String compName = exUdtValue.getType().getFieldNames().get(i).asCql(); + String compName = exUdtValue.getType().getFieldNames().get(i).asCql(false); TypeCodec typeCodec = cluster.cluster().getContext().codecRegistry().codecFor(compType); @@ -783,7 +784,7 @@ private static String typeFor(DataType dataType) { // Create type if it doesn't already exist. List fieldParts = new ArrayList<>(); for (int i = 0; i < udt.getFieldNames().size(); i++) { - String fieldName = udt.getFieldNames().get(i).asCql(); + String fieldName = udt.getFieldNames().get(i).asCql(false); String fieldType = typeFor(udt.getFieldTypes().get(i)); fieldParts.add(fieldName + " " + fieldType); } @@ -794,11 +795,11 @@ private static String typeFor(DataType dataType) { SimpleStatement.builder( String.format( "CREATE TYPE IF NOT EXISTS %s (%s)", - udt.getName().asCql(), String.join(",", fieldParts))) + udt.getName().asCql(false), String.join(",", fieldParts))) .withConfigProfile(cluster.slowProfile()) .build()); - typeName = "frozen<" + udt.getName().asCql() + ">"; + typeName = "frozen<" + udt.getName().asCql(false) + ">"; } else { typeName = dataType.toString(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java new file mode 100644 index 00000000000..19eeab075f2 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.google.common.collect.ImmutableList; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SchemaChangesIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + // A client that we only use to set up the tests + @ClassRule + public static ClusterRule adminClusterRule = + new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + + @Before + public void setup() { + // Always drop and re-create the keyspace to start from a clean state + adminClusterRule + .session() + .execute(String.format("DROP KEYSPACE %s", adminClusterRule.keyspace())); + ClusterUtils.createKeyspace(adminClusterRule.cluster(), adminClusterRule.keyspace()); + } + + @Test + public void should_handle_keyspace_creation() { + CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + should_handle_creation( + null, + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + newKeyspaceId), + metadata -> metadata.getKeyspace(newKeyspaceId), + keyspace -> { + assertThat(keyspace.getName()).isEqualTo(newKeyspaceId); + assertThat(keyspace.isDurableWrites()).isTrue(); + assertThat(keyspace.getReplication()) + .hasSize(2) + .containsEntry("class", "org.apache.cassandra.locator.SimpleStrategy") + .containsEntry("replication_factor", "1"); + }, + (listener, keyspace) -> Mockito.verify(listener).onKeyspaceCreated(keyspace)); + } + + @Test + public void should_handle_keyspace_drop() { + CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + should_handle_drop( + ImmutableList.of( + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + newKeyspaceId.asCql(true))), + String.format("DROP KEYSPACE %s", newKeyspaceId.asCql(true)), + metadata -> metadata.getKeyspace(newKeyspaceId), + (listener, oldKeyspace) -> Mockito.verify(listener).onKeyspaceDropped(oldKeyspace)); + } + + @Test + public void should_handle_keyspace_update() { + CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + should_handle_update( + ImmutableList.of( + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + newKeyspaceId.asCql(true))), + String.format( + "ALTER KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} " + + "AND durable_writes = 'false'", + newKeyspaceId.asCql(true)), + metadata -> metadata.getKeyspace(newKeyspaceId), + newKeyspace -> assertThat(newKeyspace.isDurableWrites()).isFalse(), + (listener, oldKeyspace, newKeyspace) -> + Mockito.verify(listener).onKeyspaceUpdated(newKeyspace, oldKeyspace)); + } + + @Test + public void should_handle_table_creation() { + should_handle_creation( + null, + "CREATE TABLE foo(k int primary key)", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getTable(CqlIdentifier.fromInternal("foo")), + table -> { + assertThat(table.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(table.getName().asInternal()).isEqualTo("foo"); + assertThat(table.getColumns()).containsOnlyKeys(CqlIdentifier.fromInternal("k")); + ColumnMetadata k = table.getColumn(CqlIdentifier.fromInternal("k")); + assertThat(k.getType()).isEqualTo(DataTypes.INT); + assertThat(table.getPartitionKey()).containsExactly(k); + assertThat(table.getClusteringColumns()).isEmpty(); + }, + (listener, table) -> Mockito.verify(listener).onTableCreated(table)); + } + + @Test + public void should_handle_table_drop() { + should_handle_drop( + ImmutableList.of("CREATE TABLE foo(k int primary key)"), + "DROP TABLE foo", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getTable(CqlIdentifier.fromInternal("foo")), + (listener, oldTable) -> Mockito.verify(listener).onTableDropped(oldTable)); + } + + @Test + public void should_handle_table_update() { + should_handle_update( + ImmutableList.of("CREATE TABLE foo(k int primary key)"), + "ALTER TABLE foo ADD v int", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getTable(CqlIdentifier.fromInternal("foo")), + newTable -> assertThat(newTable.getColumn(CqlIdentifier.fromInternal("v"))).isNotNull(), + (listener, oldTable, newTable) -> + Mockito.verify(listener).onTableUpdated(newTable, oldTable)); + } + + @Test + public void should_handle_type_creation() { + should_handle_creation( + null, + "CREATE TYPE t(i int)", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getUserDefinedType(CqlIdentifier.fromInternal("t")), + type -> { + assertThat(type.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(type.getName().asInternal()).isEqualTo("t"); + assertThat(type.getFieldNames()).containsExactly(CqlIdentifier.fromInternal("i")); + assertThat(type.getFieldTypes()).containsExactly(DataTypes.INT); + }, + (listener, type) -> Mockito.verify(listener).onUserDefinedTypeCreated(type)); + } + + @Test + public void should_handle_type_drop() { + should_handle_drop( + ImmutableList.of("CREATE TYPE t(i int)"), + "DROP TYPE t", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getUserDefinedType(CqlIdentifier.fromInternal("t")), + (listener, oldType) -> Mockito.verify(listener).onUserDefinedTypeDropped(oldType)); + } + + @Test + public void should_handle_type_update() { + should_handle_update( + ImmutableList.of("CREATE TYPE t(i int)"), + "ALTER TYPE t ADD j int", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getUserDefinedType(CqlIdentifier.fromInternal("t")), + newType -> + assertThat(newType.getFieldNames()) + .containsExactly(CqlIdentifier.fromInternal("i"), CqlIdentifier.fromInternal("j")), + (listener, oldType, newType) -> + Mockito.verify(listener).onUserDefinedTypeUpdated(newType, oldType)); + } + + @Test + @CassandraRequirement(min = "3.0") + public void should_handle_view_creation() { + should_handle_creation( + "CREATE TABLE scores(user text, game text, score int, PRIMARY KEY (user, game))", + "CREATE MATERIALIZED VIEW highscores " + + "AS SELECT user, score FROM scores " + + "WHERE game IS NOT NULL AND score IS NOT NULL PRIMARY KEY (game, score, user) " + + "WITH CLUSTERING ORDER BY (score DESC)", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getView(CqlIdentifier.fromInternal("highscores")), + view -> { + assertThat(view.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(view.getName().asInternal()).isEqualTo("highscores"); + assertThat(view.getBaseTable().asInternal()).isEqualTo("scores"); + assertThat(view.includesAllColumns()).isFalse(); + assertThat(view.getWhereClause()).isEqualTo("game IS NOT NULL AND score IS NOT NULL"); + assertThat(view.getColumns()) + .containsOnlyKeys( + CqlIdentifier.fromInternal("game"), + CqlIdentifier.fromInternal("score"), + CqlIdentifier.fromInternal("user")); + }, + (listener, view) -> Mockito.verify(listener).onViewCreated(view)); + } + + @Test + @CassandraRequirement(min = "3.0") + public void should_handle_view_drop() { + should_handle_drop( + ImmutableList.of( + "CREATE TABLE scores(user text, game text, score int, PRIMARY KEY (user, game))", + "CREATE MATERIALIZED VIEW highscores " + + "AS SELECT user, score FROM scores " + + "WHERE game IS NOT NULL AND score IS NOT NULL PRIMARY KEY (game, score, user) " + + "WITH CLUSTERING ORDER BY (score DESC)"), + "DROP MATERIALIZED VIEW highscores", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getView(CqlIdentifier.fromInternal("highscores")), + (listener, oldView) -> Mockito.verify(listener).onViewDropped(oldView)); + } + + @Test + public void should_handle_view_update() { + should_handle_update( + ImmutableList.of( + "CREATE TABLE scores(user text, game text, score int, PRIMARY KEY (user, game))", + "CREATE MATERIALIZED VIEW highscores " + + "AS SELECT user, score FROM scores " + + "WHERE game IS NOT NULL AND score IS NOT NULL PRIMARY KEY (game, score, user) " + + "WITH CLUSTERING ORDER BY (score DESC)"), + "ALTER MATERIALIZED VIEW highscores WITH comment = 'The best score for each game'", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getView(CqlIdentifier.fromInternal("highscores")), + newView -> + assertThat(newView.getOptions().get(CqlIdentifier.fromInternal("comment"))) + .isEqualTo("The best score for each game"), + (listener, oldView, newView) -> Mockito.verify(listener).onViewUpdated(newView, oldView)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_function_creation() { + should_handle_creation( + null, + "CREATE FUNCTION id(i int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i;'", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), + function -> { + assertThat(function.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(function.getSignature().getName().asInternal()).isEqualTo("id"); + assertThat(function.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(function.getReturnType()).isEqualTo(DataTypes.INT); + assertThat(function.getLanguage()).isEqualTo("java"); + assertThat(function.isCalledOnNullInput()).isFalse(); + assertThat(function.getBody()).isEqualTo("return i;"); + }, + (listener, function) -> Mockito.verify(listener).onFunctionCreated(function)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_function_drop() { + should_handle_drop( + ImmutableList.of( + "CREATE FUNCTION id(i int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i;'"), + "DROP FUNCTION id", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), + (listener, oldFunction) -> Mockito.verify(listener).onFunctionDropped(oldFunction)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_function_update() { + should_handle_update_via_drop_and_recreate( + ImmutableList.of( + "CREATE FUNCTION id(i int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i;'"), + "DROP FUNCTION id", + "CREATE FUNCTION id(j int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return j;'", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), + newFunction -> assertThat(newFunction.getBody()).isEqualTo("return j;"), + (listener, oldFunction, newFunction) -> + Mockito.verify(listener).onFunctionUpdated(newFunction, oldFunction)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_aggregate_creation() { + should_handle_creation( + "CREATE FUNCTION plus(i int, j int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i+j;'", + "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), + aggregate -> { + assertThat(aggregate.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(aggregate.getSignature().getName().asInternal()).isEqualTo("sum"); + assertThat(aggregate.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); + assertThat(aggregate.getStateType()).isEqualTo(DataTypes.INT); + assertThat(aggregate.getStateFuncSignature().getName().asInternal()).isEqualTo("plus"); + assertThat(aggregate.getStateFuncSignature().getParameterTypes()) + .containsExactly(DataTypes.INT, DataTypes.INT); + assertThat(aggregate.getFinalFuncSignature()).isNull(); + assertThat(aggregate.getInitCond()).isEqualTo(0); + }, + (listener, aggregate) -> Mockito.verify(listener).onAggregateCreated(aggregate)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_aggregate_drop() { + should_handle_drop( + ImmutableList.of( + "CREATE FUNCTION plus(i int, j int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i+j;'", + "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0"), + "DROP AGGREGATE sum", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), + (listener, oldAggregate) -> Mockito.verify(listener).onAggregateDropped(oldAggregate)); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_handle_aggregate_update() { + should_handle_update_via_drop_and_recreate( + ImmutableList.of( + "CREATE FUNCTION plus(i int, j int) RETURNS NULL ON NULL INPUT RETURNS int " + + "LANGUAGE java AS 'return i+j;'", + "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0"), + "DROP AGGREGATE sum", + "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 1", + metadata -> + metadata + .getKeyspace(adminClusterRule.keyspace()) + .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), + newAggregate -> assertThat(newAggregate.getInitCond()).isEqualTo(1), + (listener, oldAggregate, newAggregate) -> + Mockito.verify(listener).onAggregateUpdated(newAggregate, oldAggregate)); + } + + private void should_handle_creation( + String beforeStatement, + String createStatement, + Function extract, + Consumer verifyMetadata, + BiConsumer verifyListener) { + + if (beforeStatement != null) { + adminClusterRule.session().execute(beforeStatement); + } + + // cluster1 executes the DDL query and gets a SCHEMA_CHANGE response. + // cluster2 gets a SCHEMA_CHANGE push event on its control connection. + try (Cluster cluster1 = + ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); + Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + + SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); + cluster1.register(listener1); + SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + cluster2.register(listener2); + + cluster1.connect(adminClusterRule.keyspace()).execute(createStatement); + + // Refreshes on a response are synchronous: + T newElement1 = extract.apply(cluster1.getMetadata()); + verifyMetadata.accept(newElement1); + verifyListener.accept(listener1, newElement1); + + // Refreshes on a server event are asynchronous: + ConditionChecker.checkThat( + () -> { + T newElement2 = extract.apply(cluster2.getMetadata()); + verifyMetadata.accept(newElement2); + verifyListener.accept(listener2, newElement2); + }) + .becomesTrue(); + } + } + + private void should_handle_drop( + Iterable beforeStatements, + String dropStatement, + Function extract, + BiConsumer verifyListener) { + + for (String statement : beforeStatements) { + adminClusterRule.session().execute(statement); + } + + try (Cluster cluster1 = + ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); + Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + + T oldElement = extract.apply(cluster1.getMetadata()); + assertThat(oldElement).isNotNull(); + + SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); + cluster1.register(listener1); + SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + cluster2.register(listener2); + + cluster1.connect(adminClusterRule.keyspace()).execute(dropStatement); + + assertThat(extract.apply(cluster1.getMetadata())).isNull(); + verifyListener.accept(listener1, oldElement); + + ConditionChecker.checkThat( + () -> { + assertThat(extract.apply(cluster2.getMetadata())).isNull(); + verifyListener.accept(listener2, oldElement); + }) + .becomesTrue(); + } + } + + private void should_handle_update( + Iterable beforeStatements, + String updateStatement, + Function extract, + Consumer verifyNewMetadata, + TriConsumer verifyListener) { + + for (String statement : beforeStatements) { + adminClusterRule.session().execute(statement); + } + + try (Cluster cluster1 = + ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); + Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + + T oldElement = extract.apply(cluster1.getMetadata()); + assertThat(oldElement).isNotNull(); + + SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); + cluster1.register(listener1); + SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + cluster2.register(listener2); + + cluster1.connect(adminClusterRule.keyspace()).execute(updateStatement); + + T newElement = extract.apply(cluster1.getMetadata()); + verifyNewMetadata.accept(newElement); + verifyListener.accept(listener1, oldElement, newElement); + + ConditionChecker.checkThat( + () -> { + verifyNewMetadata.accept(extract.apply(cluster2.getMetadata())); + verifyListener.accept(listener2, oldElement, newElement); + }) + .becomesTrue(); + } + } + + // Some element types don't have an ALTER command, but we can still observe an update if they get + // dropped and recreated while schema metadata is disabled + private void should_handle_update_via_drop_and_recreate( + Iterable beforeStatements, + String dropStatement, + String recreateStatement, + Function extract, + Consumer verifyNewMetadata, + TriConsumer verifyListener) { + + for (String statement : beforeStatements) { + adminClusterRule.session().execute(statement); + } + + try (Cluster cluster1 = + ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); + Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + + T oldElement = extract.apply(cluster1.getMetadata()); + assertThat(oldElement).isNotNull(); + + SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); + cluster1.register(listener1); + SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + cluster2.register(listener2); + + cluster1.setSchemaMetadataEnabled(false); + cluster2.setSchemaMetadataEnabled(false); + + cluster1.connect(adminClusterRule.keyspace()).execute(dropStatement); + cluster1.connect(adminClusterRule.keyspace()).execute(recreateStatement); + + cluster1.setSchemaMetadataEnabled(true); + cluster2.setSchemaMetadataEnabled(true); + + ConditionChecker.checkThat( + () -> { + T newElement = extract.apply(cluster1.getMetadata()); + verifyNewMetadata.accept(newElement); + verifyListener.accept(listener1, oldElement, newElement); + }) + .becomesTrue(); + + ConditionChecker.checkThat( + () -> { + T newElement = extract.apply(cluster2.getMetadata()); + verifyNewMetadata.accept(extract.apply(cluster2.getMetadata())); + verifyListener.accept(listener2, oldElement, newElement); + }) + .becomesTrue(); + } + } + + interface TriConsumer { + void accept(T t, U u, V v); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java new file mode 100644 index 00000000000..b9637ae7cc8 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import java.util.Map; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SchemaIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @ClassRule public static ClusterRule clusterRule = new ClusterRule(ccmRule); + + @Test + public void should_expose_system_and_test_keyspace() { + Map keyspaces = + clusterRule.cluster().getMetadata().getKeyspaces(); + assertThat(keyspaces) + .containsKeys( + // Don't test exhaustively because system keyspaces depend on the Cassandra version, and + // keyspaces from other tests might also be present + CqlIdentifier.fromInternal("system"), + CqlIdentifier.fromInternal("system_traces"), + clusterRule.keyspace()); + assertThat(keyspaces.get(CqlIdentifier.fromInternal("system")).getTables()) + .containsKeys(CqlIdentifier.fromInternal("local"), CqlIdentifier.fromInternal("peers")); + } + + @Test + public void should_filter_by_keyspaces() { + try (Cluster cluster = + ClusterUtils.newCluster( + ccmRule, + String.format( + "metadata.schema.refreshed-keyspaces = [ \"%s\"] ", + clusterRule.keyspace().asInternal()))) { + + assertThat(cluster.getMetadata().getKeyspaces()).containsOnlyKeys(clusterRule.keyspace()); + + CqlIdentifier otherKeyspace = ClusterUtils.uniqueKeyspaceId(); + ClusterUtils.createKeyspace(cluster, otherKeyspace); + + assertThat(cluster.getMetadata().getKeyspaces()).containsOnlyKeys(clusterRule.keyspace()); + } + } + + @Test + public void should_not_load_schema_if_disabled_in_config() { + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + + assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + } + } + + @Test + public void should_enable_schema_programmatically_when_disabled_in_config() { + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + + assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + + cluster.setSchemaMetadataEnabled(true); + assertThat(cluster.isSchemaMetadataEnabled()).isTrue(); + + ConditionChecker.checkThat( + () -> assertThat(cluster.getMetadata().getKeyspaces()).isNotEmpty()) + .becomesTrue(); + assertThat(cluster.getMetadata().getKeyspaces()) + .containsKeys( + CqlIdentifier.fromInternal("system"), + CqlIdentifier.fromInternal("system_traces"), + clusterRule.keyspace()); + + cluster.setSchemaMetadataEnabled(null); + assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + } + } + + @Test + public void should_disable_schema_programmatically_when_enabled_in_config() { + Cluster cluster = clusterRule.cluster(); + cluster.setSchemaMetadataEnabled(false); + assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + + // Create a table, metadata should not be updated + DriverConfigProfile slowProfile = ClusterUtils.slowProfile(cluster); + clusterRule + .session() + .execute( + SimpleStatement.builder("CREATE TABLE foo(k int primary key)") + .withConfigProfile(slowProfile) + .build()); + assertThat(cluster.getMetadata().getKeyspace(clusterRule.keyspace()).getTables()) + .doesNotContainKey(CqlIdentifier.fromInternal("foo")); + + // Reset to config value (true), should refresh and load the new table + cluster.setSchemaMetadataEnabled(null); + assertThat(cluster.isSchemaMetadataEnabled()).isTrue(); + ConditionChecker.checkThat( + () -> + assertThat(cluster.getMetadata().getKeyspace(clusterRule.keyspace()).getTables()) + .containsKey(CqlIdentifier.fromInternal("foo"))) + .becomesTrue(); + } + + @Test + public void should_refresh_schema_manually() { + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + + assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + + Metadata newMetadata = cluster.refreshSchema(); + assertThat(newMetadata.getKeyspaces()) + .containsKeys( + CqlIdentifier.fromInternal("system"), + CqlIdentifier.fromInternal("system_traces"), + clusterRule.keyspace()); + + assertThat(cluster.getMetadata()).isSameAs(newMetadata); + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 02f032e04f1..ced5ff1f1ac 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -303,6 +303,9 @@ public Builder withSslAuth() { } public CcmBridge build() { + if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) >= 0) { + cassandraConfiguration.put("enable_user_defined_functions", "true"); + } return new CcmBridge( directory, cassandraVersion, nodes, ipPrefix, cassandraConfiguration, jvmArgs); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index f31696ba52e..11a925b26df 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -84,7 +84,7 @@ public static void createKeyspace( SimpleStatement.builder( String.format( "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", - keyspace.asCql())) + keyspace.asCql(false))) .withConfigProfile(profile) .build(); session.execute(createKeyspace); @@ -108,7 +108,8 @@ public static void dropKeyspace( Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { try (CqlSession session = cluster.connect()) { session.execute( - SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql())) + SimpleStatement.builder( + String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql(false))) .withConfigProfile(profile) .build()); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java index 47d992bd89a..a66789793a4 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java @@ -114,7 +114,7 @@ public CassandraVersion getCassandraVersion() { @Override public ProtocolVersion getHighestProtocolVersion() { - if (cassandraVersion.compareTo(CassandraVersion.parse("2.2")) >= 0) { + if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) >= 0) { return CoreProtocolVersion.V4; } else { return CoreProtocolVersion.V3; From e532fd2b5b67d4a1413802778965279b3c7e3de9 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 12 Oct 2017 10:50:40 -0500 Subject: [PATCH 211/742] JAVA-1493: Add extra tests for schema metadata --- .../core/ProtocolVersionMixedClusterIT.java | 8 +- .../core/auth/PlainTextAuthProviderIT.java | 12 + .../driver/api/core/metadata/DescribeIT.java | 239 ++++++++++++++++++ .../api/core/metadata/SchemaChangesIT.java | 72 ++++-- .../driver/api/core/metadata/SchemaIT.java | 6 +- .../test/resources/describe_it_test_2.1.cql | 65 +++++ .../test/resources/describe_it_test_2.2.cql | 112 ++++++++ .../test/resources/describe_it_test_3.0.cql | 187 ++++++++++++++ .../test/resources/describe_it_test_3.11.cql | 187 ++++++++++++++ 9 files changed, 862 insertions(+), 26 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java create mode 100644 integration-tests/src/test/resources/describe_it_test_2.1.cql create mode 100644 integration-tests/src/test/resources/describe_it_test_2.2.cql create mode 100644 integration-tests/src/test/resources/describe_it_test_3.0.cql create mode 100644 integration-tests/src/test/resources/describe_it_test_3.11.cql diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 2338ab4e4db..fd218d3222d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -50,7 +50,7 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader()) + .withConfigLoader(new TestConfigLoader("metadata.schema.enabled = false")) .build()) { InternalDriverContext context = (InternalDriverContext) cluster.getContext(); @@ -90,7 +90,7 @@ public void should_keep_current_if_supported_by_all_peers() { Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader()) + .withConfigLoader(new TestConfigLoader("metadata.schema.enabled = false")) .build()) { InternalDriverContext context = (InternalDriverContext) cluster.getContext(); @@ -129,7 +129,9 @@ public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { Cluster cluster = Cluster.builder() .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader("protocol.version = V4")) + .withConfigLoader( + new TestConfigLoader( + "protocol.version = V4", "metadata.schema.enabled = false")) .build()) { assertThat(cluster.getContext().protocolVersion()).isEqualTo(CoreProtocolVersion.V4); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index 384385d706f..41eff15b42d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -16,11 +16,15 @@ package com.datastax.oss.driver.api.core.auth; import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.LongTests; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.concurrent.TimeUnit; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -35,6 +39,14 @@ public class PlainTextAuthProviderIT { .withJvmArgs("-Dcassandra.superuser_setup_delay_ms=0") .build(); + @BeforeClass + public static void sleepForAuth() { + if (ccm.getCassandraVersion().compareTo(CassandraVersion.V2_2_0) < 0) { + // Sleep for 1 second to allow C* auth to do its work. This is only needed for 2.1 + Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); + } + } + @Test public void should_connect_with_credentials() { try (Cluster authCluster = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java new file mode 100644 index 00000000000..4a9837f0266 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.google.common.io.ByteStreams; +import com.google.common.io.Closer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import org.junit.ClassRule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class DescribeIT { + + private static final Logger logger = LoggerFactory.getLogger(DescribeIT.class); + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, + false, + true, + "request.timeout = 30 seconds", + "metadata.schema.debouncer.window = 0 seconds"); // disable debouncer to speed up test. + + /** + * Creates a keyspace using a variety of features and ensures {@link + * com.datastax.oss.driver.api.core.metadata.schema.Describable#describe(boolean)} contains the + * expected data in the expected order. This is not exhaustive, but covers quite a bit of + * different scenarios (materialized views, aggregates, functions, nested UDTs, etc.). + * + *

      The test also verifies that the generated schema is the same whether the keyspace and its + * schema was created during the lifecycle of the cluster or before connecting. + * + *

      Note that this test might be fragile in the future if default option values change in + * cassandra. In order to deal with new features, we create a schema for each tested C* version, + * and if one is not present the test is failed. + */ + @Test + public void create_schema_and_ensure_exported_cql_is_as_expected() { + CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); + String keyspaceAsCql = keyspace.asCql(true); + String expectedCql = getExpectedCqlString(keyspaceAsCql); + + // create keyspace + clusterRule + .session() + .execute( + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + keyspace)); + + // create session from this keyspace. + CqlSession session = clusterRule.cluster().connect(keyspace); + + KeyspaceMetadata originalKsMeta = clusterRule.cluster().getMetadata().getKeyspace(keyspace); + + // Usertype 'ztype' with two columns. Given name to ensure that even though it has an alphabetically + // later name, it shows up before other user types ('ctype') that depend on it. + session.execute("CREATE TYPE ztype(c text, a int)"); + + // Usertype 'xtype' with two columns. At same level as 'ztype' since both are depended on by ctype, should + // show up before 'ztype' because it's alphabetically before, even though it was created after. + session.execute("CREATE TYPE xtype(d text)"); + + // Usertype 'ctype' which depends on both ztype and xtype, therefore ztype and xtype should show up earlier. + session.execute( + String.format( + "CREATE TYPE ctype(z frozen<%s.ztype>, x frozen<%s.xtype>)", + keyspaceAsCql, keyspaceAsCql)); + + // Usertype 'btype' which has no dependencies, should show up before 'xtype' and 'ztype' since it's + // alphabetically before. + session.execute("CREATE TYPE btype(a text)"); + + // Usertype 'atype' which depends on 'ctype', so should show up after 'ctype', 'xtype' and 'ztype'. + session.execute(String.format("CREATE TYPE atype(c frozen<%s.ctype>)", keyspaceAsCql)); + + // A simple table with a udt column and LCS compaction strategy. + session.execute( + String.format( + "CREATE TABLE ztable(zkey text, a frozen<%s.atype>, PRIMARY KEY(zkey)) " + + "WITH compaction = {'class' : 'LeveledCompactionStrategy', 'sstable_size_in_mb' : 95}", + keyspaceAsCql)); + + // date type requries 2.2+ + if (ccmRule.getCassandraVersion().compareTo(CassandraVersion.V2_2_0) >= 0) { + // A table that will have materialized views (copied from mv docs) + session.execute( + "CREATE TABLE cyclist_mv(cid uuid, name text, age int, birthday date, country text, " + + "PRIMARY KEY(cid))"); + + // index on table with view, index should be printed first. + session.execute("CREATE INDEX cyclist_by_country ON cyclist_mv(country)"); + + // materialized views require 3.0+ + if (ccmRule.getCassandraVersion().compareTo(CassandraVersion.V3_0_0) >= 0) { + // A materialized view for cyclist_mv, reverse clustering. created first to ensure creation order does not + // matter, alphabetical does. + session.execute( + "CREATE MATERIALIZED VIEW cyclist_by_r_age " + + "AS SELECT age, birthday, name, country " + + "FROM cyclist_mv " + + "WHERE age IS NOT NULL AND cid IS NOT NULL " + + "PRIMARY KEY (age, cid) " + + "WITH CLUSTERING ORDER BY (cid DESC)"); + + // A materialized view for cyclist_mv, select * + session.execute( + "CREATE MATERIALIZED VIEW cyclist_by_a_age " + + "AS SELECT * " + + "FROM cyclist_mv " + + "WHERE age IS NOT NULL AND cid IS NOT NULL " + + "PRIMARY KEY (age, cid)"); + + // A materialized view for cyclist_mv, select columns + session.execute( + "CREATE MATERIALIZED VIEW cyclist_by_age " + + "AS SELECT age, birthday, name, country " + + "FROM cyclist_mv " + + "WHERE age IS NOT NULL AND cid IS NOT NULL " + + "PRIMARY KEY (age, cid) WITH comment = 'simple view'"); + } + } + + // A table with a secondary index, taken from documentation on secondary index. + session.execute( + "CREATE TABLE rank_by_year_and_name(race_year int, race_name text, rank int, cyclist_name text, " + + "PRIMARY KEY((race_year, race_name), rank))"); + + session.execute("CREATE INDEX ryear ON rank_by_year_and_name(race_year)"); + + session.execute("CREATE INDEX rrank ON rank_by_year_and_name(rank)"); + + // udfs and udas require 2.22+ + if (ccmRule.getCassandraVersion().compareTo(CassandraVersion.V2_2_0) >= 0) { + // UDFs + session.execute( + "CREATE OR REPLACE FUNCTION avgState ( state tuple, val int ) CALLED ON NULL INPUT RETURNS tuple LANGUAGE java AS \n" + + " 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;';"); + session.execute( + "CREATE OR REPLACE FUNCTION avgFinal ( state tuple ) CALLED ON NULL INPUT RETURNS double LANGUAGE java AS \n" + + " 'double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r /= state.getInt(0); return Double.valueOf(r);';"); + + // UDAs + session.execute( + "CREATE AGGREGATE IF NOT EXISTS mean ( int ) \n" + + "SFUNC avgState STYPE tuple FINALFUNC avgFinal INITCOND (0,0);"); + session.execute( + "CREATE AGGREGATE IF NOT EXISTS average ( int ) \n" + + "SFUNC avgState STYPE tuple FINALFUNC avgFinal INITCOND (0,0);"); + } + + // Since metadata is immutable, do not expect anything in the original keyspace meta. + assertThat(originalKsMeta.getTables()).isEmpty(); + assertThat(originalKsMeta.getViews()).isEmpty(); + assertThat(originalKsMeta.getFunctions()).isEmpty(); + assertThat(originalKsMeta.getAggregates()).isEmpty(); + assertThat(originalKsMeta.getUserDefinedTypes()).isEmpty(); + + // validate that the exported schema matches what was expected exactly. + KeyspaceMetadata ks = clusterRule.cluster().getMetadata().getKeyspace(keyspace); + assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); + + // Also validate that when you create a Cluster with schema already created that the exported string + // is the same. + try (Cluster newCluster = ClusterUtils.newCluster(ccmRule)) { + ks = newCluster.getMetadata().getKeyspace(keyspace); + assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); + } + } + + private String getExpectedCqlString(String keyspace) { + String majorMinor = + ccmRule.getCassandraVersion().getMajor() + "." + ccmRule.getCassandraVersion().getMinor(); + String resourceName = "/describe_it_test_" + majorMinor + ".cql"; + + Closer closer = Closer.create(); + try { + InputStream is = DescribeIT.class.getResourceAsStream(resourceName); + if (is == null) { + // If no schema file is defined for tested cassandra version, just try 3.11. + if (ccmRule.getCassandraVersion().compareTo(CassandraVersion.V3_0_0) >= 0) { + logger.warn("Could not find schema file for {}, assuming C* 3.11.x", majorMinor); + is = DescribeIT.class.getResourceAsStream("/describe_it_test_3.11.cql"); + if (is == null) { + throw new IOException(); + } + } + } + + closer.register(is); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + ByteStreams.copy(is, ps); + return baos.toString().replaceAll("ks_0", keyspace).trim(); + } catch (IOException e) { + logger.warn("Failure to read {}", resourceName, e); + fail("Unable to read " + resourceName + " is it defined?", e); + } finally { + try { + closer.close(); + } catch (IOException e) { // no op + logger.warn("Failure closing streams", e); + } + } + return ""; + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 19eeab075f2..17d36d8c620 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -27,11 +27,13 @@ import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.google.common.collect.ImmutableList; +import java.util.Arrays; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.Before; -import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; @@ -39,12 +41,13 @@ public class SchemaChangesIT { - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + @Rule public CcmRule ccmRule = CcmRule.getInstance(); // A client that we only use to set up the tests - @ClassRule - public static ClusterRule adminClusterRule = - new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + @Rule + public ClusterRule adminClusterRule = + new ClusterRule( + ccmRule, "request.timeout = 30 seconds", "metadata.schema.debouncer.window = 0 seconds"); @Before public void setup() { @@ -73,7 +76,8 @@ public void should_handle_keyspace_creation() { .containsEntry("class", "org.apache.cassandra.locator.SimpleStrategy") .containsEntry("replication_factor", "1"); }, - (listener, keyspace) -> Mockito.verify(listener).onKeyspaceCreated(keyspace)); + (listener, keyspace) -> Mockito.verify(listener).onKeyspaceCreated(keyspace), + newKeyspaceId); } @Test @@ -87,7 +91,8 @@ public void should_handle_keyspace_drop() { newKeyspaceId.asCql(true))), String.format("DROP KEYSPACE %s", newKeyspaceId.asCql(true)), metadata -> metadata.getKeyspace(newKeyspaceId), - (listener, oldKeyspace) -> Mockito.verify(listener).onKeyspaceDropped(oldKeyspace)); + (listener, oldKeyspace) -> Mockito.verify(listener).onKeyspaceDropped(oldKeyspace), + newKeyspaceId); } @Test @@ -107,7 +112,8 @@ public void should_handle_keyspace_update() { metadata -> metadata.getKeyspace(newKeyspaceId), newKeyspace -> assertThat(newKeyspace.isDurableWrites()).isFalse(), (listener, oldKeyspace, newKeyspace) -> - Mockito.verify(listener).onKeyspaceUpdated(newKeyspace, oldKeyspace)); + Mockito.verify(listener).onKeyspaceUpdated(newKeyspace, oldKeyspace), + newKeyspaceId); } @Test @@ -250,6 +256,7 @@ public void should_handle_view_drop() { } @Test + @CassandraRequirement(min = "3.0") public void should_handle_view_update() { should_handle_update( ImmutableList.of( @@ -386,12 +393,26 @@ public void should_handle_aggregate_update() { Mockito.verify(listener).onAggregateUpdated(newAggregate, oldAggregate)); } + private String keyspaceFilterOption(CqlIdentifier... keyspaces) { + // create option to filter keyspace refreshes based on input keyspaces, if none are provided, assume the + // one associated wiht the cluster rule. + if (keyspaces.length == 0) { + keyspaces = new CqlIdentifier[] {adminClusterRule.keyspace()}; + } + + String keyspaceStr = + Arrays.stream(keyspaces).map(i -> i.asCql(false)).collect(Collectors.joining(",")); + + return String.format("metadata.schema.refreshed-keyspaces = [%s]", keyspaceStr); + } + private void should_handle_creation( String beforeStatement, String createStatement, Function extract, Consumer verifyMetadata, - BiConsumer verifyListener) { + BiConsumer verifyListener, + CqlIdentifier... keyspaces) { if (beforeStatement != null) { adminClusterRule.session().execute(beforeStatement); @@ -400,8 +421,10 @@ private void should_handle_creation( // cluster1 executes the DDL query and gets a SCHEMA_CHANGE response. // cluster2 gets a SCHEMA_CHANGE push event on its control connection. try (Cluster cluster1 = - ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); - Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + ClusterUtils.newCluster( + ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); + Cluster cluster2 = + ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); cluster1.register(listener1); @@ -430,15 +453,18 @@ private void should_handle_drop( Iterable beforeStatements, String dropStatement, Function extract, - BiConsumer verifyListener) { + BiConsumer verifyListener, + CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { adminClusterRule.session().execute(statement); } try (Cluster cluster1 = - ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); - Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + ClusterUtils.newCluster( + ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); + Cluster cluster2 = + ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { T oldElement = extract.apply(cluster1.getMetadata()); assertThat(oldElement).isNotNull(); @@ -467,15 +493,18 @@ private void should_handle_update( String updateStatement, Function extract, Consumer verifyNewMetadata, - TriConsumer verifyListener) { + TriConsumer verifyListener, + CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { adminClusterRule.session().execute(statement); } try (Cluster cluster1 = - ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); - Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + ClusterUtils.newCluster( + ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); + Cluster cluster2 = + ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { T oldElement = extract.apply(cluster1.getMetadata()); assertThat(oldElement).isNotNull(); @@ -508,15 +537,18 @@ private void should_handle_update_via_drop_and_recreate( String recreateStatement, Function extract, Consumer verifyNewMetadata, - TriConsumer verifyListener) { + TriConsumer verifyListener, + CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { adminClusterRule.session().execute(statement); } try (Cluster cluster1 = - ClusterUtils.newCluster(ccmRule, "request.timeout = 30 seconds"); - Cluster cluster2 = ClusterUtils.newCluster(ccmRule)) { + ClusterUtils.newCluster( + ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); + Cluster cluster2 = + ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { T oldElement = extract.apply(cluster1.getMetadata()); assertThat(oldElement).isNotNull(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index b9637ae7cc8..1e83dbebe26 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -25,16 +25,16 @@ import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import java.util.Map; -import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SchemaIT { - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + @Rule public CcmRule ccmRule = CcmRule.getInstance(); - @ClassRule public static ClusterRule clusterRule = new ClusterRule(ccmRule); + @Rule public ClusterRule clusterRule = new ClusterRule(ccmRule); @Test public void should_expose_system_and_test_keyspace() { diff --git a/integration-tests/src/test/resources/describe_it_test_2.1.cql b/integration-tests/src/test/resources/describe_it_test_2.1.cql new file mode 100644 index 00000000000..b05df71a503 --- /dev/null +++ b/integration-tests/src/test/resources/describe_it_test_2.1.cql @@ -0,0 +1,65 @@ +CREATE KEYSPACE ks_0 WITH replication = { 'class' : 'org.apache.cassandra.locator.SimpleStrategy', 'replication_factor': '1' } AND durable_writes = true; + +CREATE TYPE ks_0.btype ( + a text +); + +CREATE TYPE ks_0.xtype ( + d text +); + +CREATE TYPE ks_0.ztype ( + c text, + a int +); + +CREATE TYPE ks_0.ctype ( + z frozen, + x frozen +); + +CREATE TYPE ks_0.atype ( + c frozen +); + +CREATE TABLE ks_0.rank_by_year_and_name ( + race_year int, + race_name text, + rank int, + cyclist_name text, + PRIMARY KEY ((race_year, race_name), rank) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND dclocal_read_repair_chance = 0.1 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX ryear ON ks_0.rank_by_year_and_name (race_year); + +CREATE INDEX rrank ON ks_0.rank_by_year_and_name (rank); + +CREATE TABLE ks_0.ztable ( + zkey text, + a frozen, + PRIMARY KEY (zkey) +) WITH bloom_filter_fp_chance = 0.1 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.LeveledCompactionStrategy','sstable_size_in_mb':'95'} + AND compression = {'sstable_compression':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND dclocal_read_repair_chance = 0.1 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file diff --git a/integration-tests/src/test/resources/describe_it_test_2.2.cql b/integration-tests/src/test/resources/describe_it_test_2.2.cql new file mode 100644 index 00000000000..5749778e71b --- /dev/null +++ b/integration-tests/src/test/resources/describe_it_test_2.2.cql @@ -0,0 +1,112 @@ +CREATE KEYSPACE ks_0 WITH replication = { 'class' : 'org.apache.cassandra.locator.SimpleStrategy', 'replication_factor': '1' } AND durable_writes = true; + +CREATE TYPE ks_0.btype ( + a text +); + +CREATE TYPE ks_0.xtype ( + d text +); + +CREATE TYPE ks_0.ztype ( + c text, + a int +); + +CREATE TYPE ks_0.ctype ( + z frozen, + x frozen +); + +CREATE TYPE ks_0.atype ( + c frozen +); + +CREATE TABLE ks_0.cyclist_mv ( + cid uuid, + age int, + birthday date, + country text, + name text, + PRIMARY KEY (cid) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND dclocal_read_repair_chance = 0.1 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX cyclist_by_country ON ks_0.cyclist_mv (country); + +CREATE TABLE ks_0.rank_by_year_and_name ( + race_year int, + race_name text, + rank int, + cyclist_name text, + PRIMARY KEY ((race_year, race_name), rank) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND dclocal_read_repair_chance = 0.1 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX ryear ON ks_0.rank_by_year_and_name (race_year); + +CREATE INDEX rrank ON ks_0.rank_by_year_and_name (rank); + +CREATE TABLE ks_0.ztable ( + zkey text, + a frozen, + PRIMARY KEY (zkey) +) WITH bloom_filter_fp_chance = 0.1 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.LeveledCompactionStrategy','sstable_size_in_mb':'95'} + AND compression = {'sstable_compression':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND dclocal_read_repair_chance = 0.1 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE FUNCTION ks_0.avgfinal(state tuple) + CALLED ON NULL INPUT + RETURNS double + LANGUAGE java + AS 'double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r /= state.getInt(0); return Double.valueOf(r);'; + +CREATE FUNCTION ks_0.avgstate(state tuple,val int) + CALLED ON NULL INPUT + RETURNS tuple + LANGUAGE java + AS 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;'; + +CREATE AGGREGATE ks_0.average(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); + +CREATE AGGREGATE ks_0.mean(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); \ No newline at end of file diff --git a/integration-tests/src/test/resources/describe_it_test_3.0.cql b/integration-tests/src/test/resources/describe_it_test_3.0.cql new file mode 100644 index 00000000000..fe606992a44 --- /dev/null +++ b/integration-tests/src/test/resources/describe_it_test_3.0.cql @@ -0,0 +1,187 @@ +CREATE KEYSPACE ks_0 WITH replication = { 'class' : 'org.apache.cassandra.locator.SimpleStrategy', 'replication_factor': '1' } AND durable_writes = true; + +CREATE TYPE ks_0.btype ( + a text +); + +CREATE TYPE ks_0.xtype ( + d text +); + +CREATE TYPE ks_0.ztype ( + c text, + a int +); + +CREATE TYPE ks_0.ctype ( + z frozen, + x frozen +); + +CREATE TYPE ks_0.atype ( + c frozen +); + +CREATE TABLE ks_0.cyclist_mv ( + cid uuid, + age int, + birthday date, + country text, + name text, + PRIMARY KEY (cid) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE INDEX cyclist_by_country ON ks_0.cyclist_mv (country); + +CREATE TABLE ks_0.rank_by_year_and_name ( + race_year int, + race_name text, + rank int, + cyclist_name text, + PRIMARY KEY ((race_year, race_name), rank) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE INDEX rrank ON ks_0.rank_by_year_and_name (rank); + +CREATE INDEX ryear ON ks_0.rank_by_year_and_name (race_year); + +CREATE TABLE ks_0.ztable ( + zkey text, + a frozen, + PRIMARY KEY (zkey) +) WITH bloom_filter_fp_chance = 0.1 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.LeveledCompactionStrategy','sstable_size_in_mb':'95'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_a_age AS +SELECT * FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_age AS +SELECT + age, + cid, + birthday, + country, + name +FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = 'simple view' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_r_age AS +SELECT + age, + cid, + birthday, + country, + name +FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE FUNCTION ks_0.avgfinal(state tuple) + CALLED ON NULL INPUT + RETURNS double + LANGUAGE java + AS 'double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r /= state.getInt(0); return Double.valueOf(r);'; + +CREATE FUNCTION ks_0.avgstate(state tuple,val int) + CALLED ON NULL INPUT + RETURNS tuple + LANGUAGE java + AS 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;'; + +CREATE AGGREGATE ks_0.average(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); + +CREATE AGGREGATE ks_0.mean(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); \ No newline at end of file diff --git a/integration-tests/src/test/resources/describe_it_test_3.11.cql b/integration-tests/src/test/resources/describe_it_test_3.11.cql new file mode 100644 index 00000000000..fe606992a44 --- /dev/null +++ b/integration-tests/src/test/resources/describe_it_test_3.11.cql @@ -0,0 +1,187 @@ +CREATE KEYSPACE ks_0 WITH replication = { 'class' : 'org.apache.cassandra.locator.SimpleStrategy', 'replication_factor': '1' } AND durable_writes = true; + +CREATE TYPE ks_0.btype ( + a text +); + +CREATE TYPE ks_0.xtype ( + d text +); + +CREATE TYPE ks_0.ztype ( + c text, + a int +); + +CREATE TYPE ks_0.ctype ( + z frozen, + x frozen +); + +CREATE TYPE ks_0.atype ( + c frozen +); + +CREATE TABLE ks_0.cyclist_mv ( + cid uuid, + age int, + birthday date, + country text, + name text, + PRIMARY KEY (cid) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE INDEX cyclist_by_country ON ks_0.cyclist_mv (country); + +CREATE TABLE ks_0.rank_by_year_and_name ( + race_year int, + race_name text, + rank int, + cyclist_name text, + PRIMARY KEY ((race_year, race_name), rank) +) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE INDEX rrank ON ks_0.rank_by_year_and_name (rank); + +CREATE INDEX ryear ON ks_0.rank_by_year_and_name (race_year); + +CREATE TABLE ks_0.ztable ( + zkey text, + a frozen, + PRIMARY KEY (zkey) +) WITH bloom_filter_fp_chance = 0.1 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.LeveledCompactionStrategy','sstable_size_in_mb':'95'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_a_age AS +SELECT * FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_age AS +SELECT + age, + cid, + birthday, + country, + name +FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = 'simple view' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW ks_0.cyclist_by_r_age AS +SELECT + age, + cid, + birthday, + country, + name +FROM ks_0.cyclist_mv +WHERE age IS NOT NULL AND cid IS NOT NULL +PRIMARY KEY (age, cid) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys':'ALL','rows_per_partition':'NONE'} + AND comment = '' + AND compaction = {'class':'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy','max_threshold':'32','min_threshold':'4'} + AND compression = {'chunk_length_in_kb':'64','class':'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND extensions = {} + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE FUNCTION ks_0.avgfinal(state tuple) + CALLED ON NULL INPUT + RETURNS double + LANGUAGE java + AS 'double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r /= state.getInt(0); return Double.valueOf(r);'; + +CREATE FUNCTION ks_0.avgstate(state tuple,val int) + CALLED ON NULL INPUT + RETURNS tuple + LANGUAGE java + AS 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;'; + +CREATE AGGREGATE ks_0.average(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); + +CREATE AGGREGATE ks_0.mean(int) + SFUNC avgstate + STYPE tuple + FINALFUNC avgfinal + INITCOND (0,0); \ No newline at end of file From 5e45e6fc99595bf2f616288722b72a743b2cec4c Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 3 Oct 2017 11:39:38 -0700 Subject: [PATCH 212/742] JAVA-1520: Add node state listeners --- changelog/README.md | 1 + .../datastax/oss/driver/api/core/Cluster.java | 15 ++ .../oss/driver/api/core/ClusterBuilder.java | 11 +- .../api/core/metadata/NodeStateListener.java | 73 +++++++ .../driver/internal/core/ClusterWrapper.java | 11 + .../driver/internal/core/DefaultCluster.java | 79 +++++++- .../internal/core/channel/ChannelEvent.java | 8 +- .../core/control/ControlConnection.java | 1 + .../core/metadata/NodeStateManager.java | 21 ++ .../core/control/ControlConnectionTest.java | 4 +- integration-tests/pom.xml | 5 + .../driver/api/core/metadata/DescribeIT.java | 1 + .../driver/api/core/metadata/NodeStateIT.java | 188 +++++++++++++++++- .../api/testinfra/cluster/ClusterRule.java | 8 +- .../testinfra/cluster/ClusterRuleBuilder.java | 10 +- .../api/testinfra/cluster/ClusterUtils.java | 15 +- .../driver/assertions/NodeMetadataAssert.java | 5 + 17 files changed, 441 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java diff --git a/changelog/README.md b/changelog/README.md index f5e39be9b14..d6d6ee61a73 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1520: Add node state listeners - [new feature] JAVA-1493: Handle schema metadata - [improvement] JAVA-1605: Refactor request execution model - [improvement] JAVA-1597: Fix raw usages of Statement diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 0aa93cf502e..0ef7fa8f045 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; @@ -163,4 +164,18 @@ default SessionT connect() { *

      This is a no-op if the listener was not registered. */ Cluster unregister(SchemaChangeListener listener); + + /** + * Registers the provided node state listener. + * + *

      This is a no-op if the listener was registered already. + */ + Cluster register(NodeStateListener listener); + + /** + * Unregisters the provided node state listener. + * + *

      This is a no-op if the listener was not registered. + */ + Cluster unregister(NodeStateListener listener); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 8a12d8add62..4d1250916a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; @@ -52,6 +53,7 @@ public abstract class ClusterBuilder { protected DriverConfigLoader configLoader; protected Set programmaticContactPoints = new HashSet<>(); protected List> typeCodecs = new ArrayList<>(); + protected final Set nodeStateListeners = new HashSet<>(); /** * Sets the configuration loader to use. @@ -127,6 +129,11 @@ public SelfT addTypeCodecs(TypeCodec... typeCodecs) { return self; } + public SelfT addNodeStateListeners(NodeStateListener... newListeners) { + Collections.addAll(this.nodeStateListeners, newListeners); + return self; + } + /** * Creates the cluster with the options set by this builder. * @@ -161,7 +168,9 @@ protected final CompletionStage> buildDefaultClusterAsync() ContactPoints.merge(programmaticContactPoints, configContactPoints); return DefaultCluster.init( - (InternalDriverContext) buildContext(configLoader, typeCodecs), contactPoints); + (InternalDriverContext) buildContext(configLoader, typeCodecs), + contactPoints, + nodeStateListeners); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java new file mode 100644 index 00000000000..3c0e839ffc3 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.ClusterBuilder; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; + +/** + * A listener that gets notified when nodes states change. + * + *

      An implementation of this interface can be registered with {@link + * ClusterBuilder#addNodeStateListeners(NodeStateListener...)} or at runtime with {@link + * Cluster#register(NodeStateListener)}. + * + *

      Note that the methods defined by this interface will be executed by internal driver threads, + * and are therefore expected to have short execution times. If you need to perform long + * computations or blocking calls in response to schema change events, it is strongly recommended to + * schedule them asynchronously on a separate thread provided by your application code. + */ +public interface NodeStateListener { + + /** + * Invoked when a node is first added to the cluster. + * + *

      The node is not up yet at this point. {@link #onUp(Node)} will be notified later if the + * driver successfully connects to the node (provided that a session is opened and the node is not + * {@link NodeDistance#IGNORED ignored}), or receives a topology event for it. + * + *

      This method is not invoked for the contact points provided at initialization. It is + * however for new nodes discovered during the full node list refresh after the first connection. + */ + void onAdd(Node node); + + /** Invoked when a node's state switches to {@link NodeState#UP}. */ + void onUp(Node node); + + /** + * Invoked when a node's state switches to {@link NodeState#DOWN} or {@link + * NodeState#FORCED_DOWN}. + */ + void onDown(Node node); + + /** + * Invoked when a node leaves the cluster. + * + *

      This can be triggered by a topology event, or during a full node list refresh if the node is + * absent from the new list. + */ + void onRemove(Node node); + + /** Invoked when the listener is registered with a cluster. */ + void onRegister(Cluster cluster); + + /** + * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever + * comes first. + */ + void onUnregister(Cluster cluster); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java index aaa2d88ea43..d8f2813cc13 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; @@ -80,6 +81,16 @@ public Cluster unregister(SchemaChangeListener listener) { return delegate.unregister(listener); } + @Override + public Cluster register(NodeStateListener listener) { + return delegate.register(listener); + } + + @Override + public Cluster unregister(NodeStateListener listener) { + return delegate.unregister(listener); + } + @Override public CompletionStage closeFuture() { return delegate.closeFuture(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 7b1caf72397..b41657a71f2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -22,12 +22,15 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -49,8 +52,10 @@ public class DefaultCluster implements Cluster { private static final Logger LOG = LoggerFactory.getLogger(DefaultCluster.class); public static CompletableFuture> init( - InternalDriverContext context, Set contactPoints) { - DefaultCluster cluster = new DefaultCluster(context, contactPoints); + InternalDriverContext context, + Set contactPoints, + Set nodeStateListeners) { + DefaultCluster cluster = new DefaultCluster(context, contactPoints, nodeStateListeners); return cluster.init(); } @@ -60,11 +65,14 @@ public static CompletableFuture> init( private final MetadataManager metadataManager; private final String logPrefix; - private DefaultCluster(InternalDriverContext context, Set contactPoints) { + private DefaultCluster( + InternalDriverContext context, + Set contactPoints, + Set nodeStateListeners) { LOG.debug("Creating new cluster {}", context.clusterName()); this.context = context; this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(context, contactPoints); + this.singleThreaded = new SingleThreaded(context, contactPoints, nodeStateListeners); this.metadataManager = context.metadataManager(); this.logPrefix = context.clusterName(); } @@ -123,6 +131,18 @@ public Cluster unregister(SchemaChangeListener listener) { return this; } + @Override + public Cluster register(NodeStateListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); + return this; + } + + @Override + public Cluster unregister(NodeStateListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); + return this; + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -155,13 +175,22 @@ private class SingleThreaded { private List sessions; private int sessionCounter; private Set schemaChangeListeners = new HashSet<>(); + private Set nodeStateListeners; - private SingleThreaded(InternalDriverContext context, Set contactPoints) { + private SingleThreaded( + InternalDriverContext context, + Set contactPoints, + Set nodeStateListeners) { this.context = context; this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; + this.nodeStateListeners = nodeStateListeners; this.sessions = new ArrayList<>(); new SchemaListenerNotifier(schemaChangeListeners, context.eventBus(), adminExecutor); + context + .eventBus() + .register( + NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onNodeStateChanged)); } private void init() { @@ -172,6 +201,8 @@ private void init() { initWasCalled = true; LOG.debug("[{}] Starting initialization", logPrefix); + nodeStateListeners.forEach(l -> l.onRegister(DefaultCluster.this)); + // If any contact points were provided, store them in the metadata right away (the // control connection will need them if it has to initialize) MetadataManager metadataManager = context.metadataManager(); @@ -227,6 +258,7 @@ private void afterInitialNodeListRefresh(@SuppressWarnings("unused") Void ignore private void afterInitialSchemaRefresh(@SuppressWarnings("unused") Void ignored) { try { + nodeStateManager.markInitialized(); context.loadBalancingPolicyWrapper().init(); context.configLoader().onDriverInit(context); LOG.debug("[{}] Initialization complete, ready", logPrefix); @@ -285,6 +317,39 @@ private void unregister(SchemaChangeListener listener) { } } + private void register(NodeStateListener listener) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + if (nodeStateListeners.add(listener)) { + listener.onRegister(DefaultCluster.this); + } + } + + private void unregister(NodeStateListener listener) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + if (nodeStateListeners.remove(listener)) { + listener.onUnregister(DefaultCluster.this); + } + } + + private void onNodeStateChanged(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + if (event.newState == null) { + nodeStateListeners.forEach(listener -> listener.onRemove(event.node)); + } else if (event.oldState == null && event.newState == NodeState.UNKNOWN) { + nodeStateListeners.forEach(listener -> listener.onAdd(event.node)); + } else if (event.newState == NodeState.UP) { + nodeStateListeners.forEach(listener -> listener.onUp(event.node)); + } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { + nodeStateListeners.forEach(listener -> listener.onDown(event.node)); + } + } + private void close() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { @@ -297,6 +362,10 @@ private void close() { listener.onUnregister(DefaultCluster.this); } schemaChangeListeners.clear(); + for (NodeStateListener listener : nodeStateListeners) { + listener.onUnregister(DefaultCluster.this); + } + nodeStateListeners.clear(); List> childrenCloseStages = new ArrayList<>(); closePolicies(); for (AsyncAutoCloseable closeable : internalComponentsToClose()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java index ad3a7af8706..4e34654813d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelEvent.java @@ -24,7 +24,8 @@ public enum Type { OPENED, CLOSED, RECONNECTION_STARTED, - RECONNECTION_STOPPED + RECONNECTION_STOPPED, + CONTROL_CONNECTION_FAILED } public static ChannelEvent channelOpened(Node node) { @@ -43,6 +44,11 @@ public static ChannelEvent reconnectionStopped(Node node) { return new ChannelEvent(Type.RECONNECTION_STOPPED, node); } + /** The control connection tried to use this node, but failed to open a channel. */ + public static ChannelEvent controlConnectionFailed(Node node) { + return new ChannelEvent(Type.CONTROL_CONNECTION_FAILED, node); + } + public final Type type; public final Node node; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index adca8c7f92f..9f350a3a3d4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -293,6 +293,7 @@ private void connect( Map newErrors = (errors == null) ? new LinkedHashMap<>() : errors; newErrors.put(node, error); + context.eventBus().fire(ChannelEvent.controlConnectionFailed(node)); connect(nodes, newErrors, onSuccess, onFailure); } } else if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 63a22232c3f..317dc8f095b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -55,6 +55,14 @@ public NodeStateManager(InternalDriverContext context) { this.logPrefix = context.clusterName(); } + /** + * Indicates when the driver initialization is complete (that is, we have performed the first node + * list refresh and are about to initialize the load balancing policy). + */ + public void markInitialized() { + RunOrSchedule.on(adminExecutor, singleThreaded::markInitialized); + } + @Override public CompletionStage closeFuture() { return singleThreaded.closeFuture; @@ -77,6 +85,7 @@ private class SingleThreaded { private final EventBus eventBus; private final Debouncer> topologyEventDebouncer; private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean isInitialized = false; private boolean closeWasCalled; private SingleThreaded(InternalDriverContext context) { @@ -98,7 +107,11 @@ private SingleThreaded(InternalDriverContext context) { TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); // Note: this component exists for the whole life of the driver instance, so don't worry about // unregistering the listeners. + } + private void markInitialized() { + assert adminExecutor.inEventLoop(); + isInitialized = true; } private void onChannelEvent(ChannelEvent event) { @@ -131,6 +144,14 @@ private void onChannelEvent(ChannelEvent event) { case RECONNECTION_STOPPED: node.reconnections -= 1; break; + case CONTROL_CONNECTION_FAILED: + // Special case for init, where this means that a contact point is down. In other + // situations that information is not really useful, we rely on + // openConnections/reconnections instead. + if (!isInitialized) { + setState(node, NodeState.DOWN, "it was tried as a contact point but failed"); + } + break; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 717c642037b..96efcc21bcb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -107,6 +107,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { // Then assertThat(initFuture) .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE1)); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); // each attempt tries all nodes, so there is no reconnection Mockito.verify(reconnectionPolicy, never()).newSchedule(); @@ -131,7 +132,8 @@ public void should_fail_to_init_if_all_contact_points_fail() { // Then assertThat(initFuture).isFailed(); - Mockito.verify(eventBus, never()).fire(any(ChannelEvent.class)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE2)); // no reconnections at init Mockito.verify(reconnectionPolicy, never()).newSchedule(); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 1a9e87268c8..04d5d35fe0a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -43,6 +43,11 @@ junit-dataprovider test + + org.mockito + mockito-core + test + ch.qos.logback logback-classic diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 4a9837f0266..cf662a54820 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -49,6 +49,7 @@ public class DescribeIT { ccmRule, false, true, + new NodeStateListener[0], "request.timeout = 30 seconds", "metadata.schema.debouncer.window = 0 seconds"); // disable debouncer to speed up test. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 33755508fa7..047ef74f290 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -26,33 +26,46 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.NodeConnectionReport; import com.datastax.oss.simulacron.common.stubbing.CloseType; import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.RejectScope; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; import static com.datastax.oss.driver.assertions.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static com.datastax.oss.driver.assertions.Assertions.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; public class NodeStateIT { public @Rule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + private NodeStateListener nodeStateListener = Mockito.mock(NodeStateListener.class); + private InOrder inOrder; + public @Rule ClusterRule cluster = ClusterRule.builder(simulacron) .withOptions( "connection.pool.local.size = 2", "connection.reconnection-policy.max-delay = 1 second") + .withNodeStateListeners(nodeStateListener) .build(); private InternalDriverContext driverContext; @@ -65,6 +78,8 @@ public class NodeStateIT { @Before public void setup() { + inOrder = Mockito.inOrder(nodeStateListener); + driverContext = (InternalDriverContext) cluster.cluster().getContext(); driverContext.eventBus().register(NodeStateEvent.class, stateEvents::add); @@ -95,6 +110,16 @@ public void setup() { (DefaultNode) nodesMetadata.get(simulacronControlNode.inetSocketAddress()); metadataRegularNode = (DefaultNode) nodesMetadata.get(simulacronRegularNode.inetSocketAddress()); + + // ClusterRule uses all nodes as contact points, so we only get onUp notifications for them (no + // onAdd) + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataControlNode); + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + } + + @After + public void teardown() { + Mockito.reset(nodeStateListener); } @Test @@ -120,6 +145,7 @@ public void should_keep_regular_node_up_when_still_one_connection() { .as("Reconnection started") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, never()).onDown(metadataRegularNode); } @Test @@ -133,6 +159,7 @@ public void should_mark_regular_node_down_when_no_more_connections() { .becomesTrue(); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode)); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); } @Test @@ -152,6 +179,7 @@ public void should_mark_control_node_down_when_control_connection_is_last_connec .as("Control node lost its non-control connections") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, never()).onDown(metadataRegularNode); simulacron.cluster().closeConnection(controlAddress, CloseType.DISCONNECT); ConditionChecker.checkThat( @@ -159,6 +187,7 @@ public void should_mark_control_node_down_when_control_connection_is_last_connec .as("Control node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataControlNode); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataControlNode)); } @@ -172,6 +201,7 @@ public void should_bring_node_back_up_when_reconnection_succeeds() { .as("Node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); simulacronRegularNode.acceptConnections(); @@ -180,6 +210,7 @@ public void should_bring_node_back_up_when_reconnection_succeeds() { .as("Connections re-established") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); expect( NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode), @@ -215,6 +246,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .as("SUGGEST_DOWN event applied") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( @@ -227,6 +259,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .as("SUGGEST_UP event applied") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); } @Test @@ -249,6 +282,9 @@ public void should_force_immediate_reconnection_when_up_topology_event() "connection.reconnection-policy.max-delay = 1 hour")) { localCluster.connect(); + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + localCluster.register(localNodeStateListener); + BoundNode localSimulacronNode = simulacron.cluster().getNodes().iterator().next(); assertThat(localSimulacronNode).isNotNull(); @@ -263,6 +299,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); + Mockito.verify(localNodeStateListener, timeout(100)).onDown(localMetadataNode); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, localMetadataNode)); @@ -275,6 +312,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node coming back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode); expect(NodeStateEvent.changed(NodeState.DOWN, NodeState.UP, localMetadataNode)); } @@ -292,6 +330,7 @@ public void should_force_down_when_not_ignored() throws InterruptedException { .as("Node forced down") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); @@ -311,6 +350,7 @@ public void should_force_down_when_not_ignored() throws InterruptedException { .as("Node forced back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); } @Test @@ -328,6 +368,7 @@ public void should_force_down_when_ignored() throws InterruptedException { .as("Node forced down") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); @@ -353,10 +394,137 @@ public void should_force_down_when_ignored() throws InterruptedException { .as("Node forced back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); + inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); driverContext.loadBalancingPolicyWrapper().setDistance(metadataRegularNode, NodeDistance.LOCAL); } + @Test + public void should_signal_non_contact_points_as_added() { + // Since we need to observe the behavior of non-contact points, build a dedicated cluster with + // just one contact point. + Iterator contactPoints = simulacron.getContactPoints().iterator(); + InetSocketAddress address1 = contactPoints.next(); + InetSocketAddress address2 = contactPoints.next(); + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + try (Cluster localCluster = + Cluster.builder() + .addContactPoint(address1) + .addNodeStateListeners(localNodeStateListener) + .withConfigLoader( + new TestConfigLoader( + "connection.reconnection-policy.base-delay = 1 hour", + "connection.reconnection-policy.max-delay = 1 hour")) + .build()) { + + Map nodes = localCluster.getMetadata().getNodes(); + Node localMetadataNode1 = nodes.get(address1); + Node localMetadataNode2 = nodes.get(address2); + + // Successful contact point goes to up directly + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + // Non-contact point only added since we don't have a connection or events for it yet + Mockito.verify(localNodeStateListener, timeout(100)).onAdd(localMetadataNode2); + + localCluster.connect(); + + // Non-contact point now has a connection opened => up + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode2); + } + } + + @Test + public void should_remove_invalid_contact_point() { + // Initialize the driver with 1 wrong address and 1 valid address + InetSocketAddress wrongContactPoint = unusedAddress(); + + Iterator contactPoints = simulacron.getContactPoints().iterator(); + InetSocketAddress address1 = contactPoints.next(); + InetSocketAddress address2 = contactPoints.next(); + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + + try (Cluster localCluster = + Cluster.builder() + .addContactPoint(address1) + .addContactPoint(wrongContactPoint) + .addNodeStateListeners(localNodeStateListener) + .withConfigLoader( + new TestConfigLoader( + "connection.reconnection-policy.base-delay = 1 hour", + "connection.reconnection-policy.max-delay = 1 hour")) + .build()) { + + Map nodes = localCluster.getMetadata().getNodes(); + assertThat(nodes).doesNotContainKey(wrongContactPoint); + Node localMetadataNode1 = nodes.get(address1); + Node localMetadataNode2 = nodes.get(address2); + + // The order of the calls is not deterministic because contact points are shuffled, but it + // does not matter here since Mockito.verify does not enforce order. + Mockito.verify(localNodeStateListener, timeout(100)) + .onRemove(new DefaultNode(wrongContactPoint)); + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verify(localNodeStateListener, timeout(100)).onAdd(localMetadataNode2); + + // Note: there might be an additional onDown for wrongContactPoint if it was hit first at + // init. This is hard to test since the node was removed later, so we simply don't call + // verifyNoMoreInteractions. + } + } + + @Test + public void should_mark_unreachable_contact_point_down() { + // This time we connect with two valid contact points, but is unresponsive, it should be marked + // down + Iterator simulacronNodes = simulacron.cluster().getNodes().iterator(); + BoundNode localSimulacronNode1 = simulacronNodes.next(); + BoundNode localSimulacronNode2 = simulacronNodes.next(); + + InetSocketAddress address1 = localSimulacronNode1.inetSocketAddress(); + InetSocketAddress address2 = localSimulacronNode2.inetSocketAddress(); + + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + + localSimulacronNode2.stop(); + try { + // Since contact points are shuffled, we have a 50% chance that our bad contact point will be + // hit first. So we retry the scenario a few times if needed. + for (int i = 0; i < 10; i++) { + try (Cluster localCluster = + Cluster.builder() + .addContactPoint(address1) + .addContactPoint(address2) + .addNodeStateListeners(localNodeStateListener) + .withConfigLoader( + new TestConfigLoader( + "connection.reconnection-policy.base-delay = 1 hour", + "connection.reconnection-policy.max-delay = 1 hour")) + .build()) { + + Map nodes = localCluster.getMetadata().getNodes(); + Node localMetadataNode1 = nodes.get(address1); + Node localMetadataNode2 = nodes.get(address2); + if (localMetadataNode2.getState() == NodeState.DOWN) { + // Stopped node was tried first and marked down, that's our target scenario + Mockito.verify(localNodeStateListener, timeout(100)).onDown(localMetadataNode2); + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verifyNoMoreInteractions(localNodeStateListener); + return; + } else { + // Stopped node was not tried + assertThat(localMetadataNode2).isUnknown(); + Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verifyNoMoreInteractions(localNodeStateListener); + } + } + Mockito.reset(localNodeStateListener); + } + fail("Couldn't get the driver to try stopped node first (tried 5 times)"); + } finally { + localSimulacronNode2.acceptConnections(); + } + } + private void expect(NodeStateEvent... expectedEvents) { for (NodeStateEvent expected : expectedEvents) { try { @@ -367,4 +535,22 @@ private void expect(NodeStateEvent... expectedEvents) { } } } + + // Generates a socket address that is not the connect address of one of the nodes in the cluster + private InetSocketAddress unusedAddress() { + try { + byte[] bytes = new byte[] {127, 0, 1, 2}; + for (int i = 0; i < 100; i++) { + bytes[3] += 1; + InetSocketAddress address = new InetSocketAddress(InetAddress.getByAddress(bytes), 9043); + if (!simulacron.getContactPoints().contains(address)) { + return address; + } + } + } catch (UnknownHostException e) { + fail("unexpected error", e); + } + fail("Could not find unused address"); + return null; // never reached + } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index f5693a8a0ed..2611a98dbe0 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -52,6 +53,7 @@ public class ClusterRule extends ExternalResource { // the CCM or Simulacron rule to depend on private final CassandraResourceRule cassandraResource; + private final NodeStateListener[] nodeStateListeners; private final CqlIdentifier keyspace; private final boolean createDefaultSession; private final String[] defaultClusterOptions; @@ -75,7 +77,7 @@ public static ClusterRuleBuilder builder(CassandraResourceRule cassandraResource /** @see #builder(CassandraResourceRule) */ public ClusterRule(CassandraResourceRule cassandraResource, String... options) { - this(cassandraResource, true, true, options); + this(cassandraResource, true, true, new NodeStateListener[0], options); } /** @see #builder(CassandraResourceRule) */ @@ -83,8 +85,10 @@ public ClusterRule( CassandraResourceRule cassandraResource, boolean createKeyspace, boolean createDefaultSession, + NodeStateListener[] nodeStateListeners, String... options) { this.cassandraResource = cassandraResource; + this.nodeStateListeners = nodeStateListeners; this.keyspace = (cassandraResource instanceof SimulacronRule || !createKeyspace) ? null @@ -97,7 +101,7 @@ public ClusterRule( protected void before() { // ensure resource is initialized before initializing the defaultCluster. cassandraResource.setUp(); - cluster = ClusterUtils.newCluster(cassandraResource, defaultClusterOptions); + cluster = ClusterUtils.newCluster(cassandraResource, nodeStateListeners, defaultClusterOptions); slowProfile = ClusterUtils.slowProfile(cluster); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java index c8e6d376cc5..c43099e9ca5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -25,6 +26,7 @@ public class ClusterRuleBuilder { private boolean createDefaultSession = true; private boolean createKeyspace = true; private String[] options = new String[] {}; + private NodeStateListener[] nodeStateListeners = new NodeStateListener[] {}; public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { this.cassandraResource = cassandraResource; @@ -69,7 +71,13 @@ public ClusterRuleBuilder withOptions(String... options) { return this; } + public ClusterRuleBuilder withNodeStateListeners(NodeStateListener... listeners) { + this.nodeStateListeners = listeners; + return this; + } + public ClusterRule build() { - return new ClusterRule(cassandraResource, createKeyspace, createDefaultSession, options); + return new ClusterRule( + cassandraResource, createKeyspace, createDefaultSession, nodeStateListeners, options); } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index 11a925b26df..fb77dd859ef 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; @@ -44,9 +45,9 @@ * } * } * - * The instances returned by {@link #newCluster(CassandraResourceRule, String...)} are not managed - * automatically, you need to close them yourself (this is done with a try-with-resources block in - * the example above). + * The instances returned by {@link #newCluster(CassandraResourceRule, NodeStateListener[], + * String...)} are not managed automatically, you need to close them yourself (this is done with a + * try-with-resources block in the example above). * *

      If you can share the same {@code Cluster} instance between all test methods, {@link * ClusterRule} provides a simpler alternative. @@ -61,8 +62,16 @@ public class ClusterUtils { */ public static Cluster newCluster( CassandraResourceRule cassandraResource, String... options) { + return newCluster(cassandraResource, new NodeStateListener[0], options); + } + + public static Cluster newCluster( + CassandraResourceRule cassandraResource, + NodeStateListener[] nodeStateListeners, + String... options) { return Cluster.builder() .addContactPoints(cassandraResource.getContactPoints()) + .addNodeStateListeners(nodeStateListeners) .withConfigLoader(new TestConfigLoader(options)) .build(); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java index afc963484c9..7c03f4eb6e2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java @@ -38,6 +38,11 @@ public NodeMetadataAssert isDown() { return this; } + public NodeMetadataAssert isUnknown() { + assertThat(actual.getState()).isSameAs(NodeState.UNKNOWN); + return this; + } + public NodeMetadataAssert isForcedDown() { assertThat(actual.getState()).isSameAs(NodeState.FORCED_DOWN); return this; From 9b97032cd599d075cf311ed22fece0b3ccc94087 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 12 Oct 2017 17:08:19 -0500 Subject: [PATCH 213/742] Add tests for NodeStateListener registration --- .../driver/api/core/metadata/NodeStateIT.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 047ef74f290..afb90e62e0c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -501,6 +501,8 @@ public void should_mark_unreachable_contact_point_down() { "connection.reconnection-policy.max-delay = 1 hour")) .build()) { + Mockito.verify(localNodeStateListener).onRegister(localCluster); + Map nodes = localCluster.getMetadata().getNodes(); Node localMetadataNode1 = nodes.get(address1); Node localMetadataNode2 = nodes.get(address2); @@ -525,6 +527,32 @@ public void should_mark_unreachable_contact_point_down() { } } + @Test + public void should_call_onRegister_and_onUnregister_implicitly() { + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + Cluster localClusterRef; + // onRegister should be called implicitly when added as a listener on builder. + try (Cluster localCluster = + Cluster.builder() + .addContactPoints(simulacron.getContactPoints()) + .addNodeStateListeners(localNodeStateListener) + .build()) { + Mockito.verify(localNodeStateListener).onRegister(localCluster); + localClusterRef = localCluster; + } + // onUnregister should be called implicitly when cluster is closed. + Mockito.verify(localNodeStateListener).onUnregister(localClusterRef); + } + + @Test + public void should_call_onRegister_and_onUnregister_when_used() { + NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + cluster.cluster().register(localNodeStateListener); + Mockito.verify(localNodeStateListener, timeout(1000)).onRegister(cluster.cluster()); + cluster.cluster().unregister(localNodeStateListener); + Mockito.verify(localNodeStateListener, timeout(1000)).onUnregister(cluster.cluster()); + } + private void expect(NodeStateEvent... expectedEvents) { for (NodeStateEvent expected : expectedEvents) { try { From 40e71ef7eab55a4828af51684a7aec17e07f70cd Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 30 Sep 2017 21:14:30 -0700 Subject: [PATCH 214/742] Fix bug when native library is not in the classpath We still want Native to initialize correctly, so use an inner class (like in 3.x). --- .../oss/driver/internal/core/os/Native.java | 102 ++++++++++-------- .../oss/driver/internal/core/time/Clock.java | 2 +- 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java index 531b9483873..470affbcbac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java @@ -29,65 +29,79 @@ public class Native { private static final Logger LOG = LoggerFactory.getLogger(Native.class); - /** Handles libc calls through JNR (must be public). */ - public interface LibC { - int gettimeofday(@Out @Transient Timeval tv, Pointer unused); - } - - // See http://man7.org/linux/man-pages/man2/settimeofday.2.html - private static class Timeval extends Struct { - private final time_t tv_sec = new time_t(); - private final Unsigned32 tv_usec = new Unsigned32(); - - private Timeval(Runtime runtime) { - super(runtime); - } - } - - private static final LibC LIB_C; - private static final Runtime LIB_C_RUNTIME; - /** Whether {@link Native#currentTimeMicros()} is available on this system. */ - public static final boolean CURRENT_TIME_MICROS_AVAILABLE; - - static { - LibC libc; - Runtime runtime = null; + public static boolean isCurrentTimeMicrosAvailable() { try { - libc = LibraryLoader.create(LibC.class).load("c"); - runtime = Runtime.getRuntime(libc); - } catch (Throwable t) { - libc = null; - LOG.debug("Error loading libc", t); + return LibCLoader.GET_TIME_OF_DAY_AVAILABLE; + } catch (NoClassDefFoundError e) { + return false; } - LIB_C = libc; - LIB_C_RUNTIME = runtime; - boolean gettimeofday = false; - if (LIB_C_RUNTIME != null) { - try { - gettimeofday = LIB_C.gettimeofday(new Timeval(LIB_C_RUNTIME), null) == 0; - } catch (Throwable t) { - LOG.debug("Error accessing libc.gettimeofday()", t); - } - } - CURRENT_TIME_MICROS_AVAILABLE = gettimeofday; } /** * The current time in microseconds, as returned by libc.gettimeofday(); can only be used if - * {@link #CURRENT_TIME_MICROS_AVAILABLE} is true. + * {@link #isCurrentTimeMicrosAvailable()} is true. */ public static long currentTimeMicros() { - if (!CURRENT_TIME_MICROS_AVAILABLE) { + if (!isCurrentTimeMicrosAvailable()) { throw new IllegalStateException( "Native call not available. " - + "Check CURRENT_TIME_MICROS_AVAILABLE before calling this method."); + + "Check isCurrentTimeMicrosAvailable() before calling this method."); } - Timeval tv = new Timeval(LIB_C_RUNTIME); - int res = LIB_C.gettimeofday(tv, null); + LibCLoader.Timeval tv = new LibCLoader.Timeval(LibCLoader.LIB_C_RUNTIME); + int res = LibCLoader.LIB_C.gettimeofday(tv, null); if (res != 0) { throw new IllegalStateException("Call to libc.gettimeofday() failed with result " + res); } return tv.tv_sec.get() * 1000000 + tv.tv_usec.get(); } + + /** + * If jnr-ffi is not in the classpath at runtime, we'll fail to initialize the static fields + * below, but we still want {@link Native} to initialize successfully, so use an inner class. + */ + private static class LibCLoader { + + /** Handles libc calls through JNR (must be public). */ + public interface LibC { + int gettimeofday(@Out @Transient Timeval tv, Pointer unused); + } + + // See http://man7.org/linux/man-pages/man2/settimeofday.2.html + private static class Timeval extends Struct { + private final time_t tv_sec = new time_t(); + private final Unsigned32 tv_usec = new Unsigned32(); + + private Timeval(Runtime runtime) { + super(runtime); + } + } + + private static final LibC LIB_C; + private static final Runtime LIB_C_RUNTIME; + private static final boolean GET_TIME_OF_DAY_AVAILABLE; + + static { + LibC libc; + Runtime runtime = null; + try { + libc = LibraryLoader.create(LibC.class).load("c"); + runtime = Runtime.getRuntime(libc); + } catch (Throwable t) { + libc = null; + LOG.debug("Error loading libc", t); + } + LIB_C = libc; + LIB_C_RUNTIME = runtime; + boolean getTimeOfDayAvailable = false; + if (LIB_C_RUNTIME != null) { + try { + getTimeOfDayAvailable = LIB_C.gettimeofday(new Timeval(LIB_C_RUNTIME), null) == 0; + } catch (Throwable t) { + LOG.debug("Error accessing libc.gettimeofday()", t); + } + } + GET_TIME_OF_DAY_AVAILABLE = getTimeOfDayAvailable; + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java index ed6c9cd8cd0..653c4aac565 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/Clock.java @@ -31,7 +31,7 @@ static Clock getInstance(boolean forceJavaClock) { if (forceJavaClock) { LOG.info("Using Java system clock because this was explicitly required in the configuration"); return new JavaClock(); - } else if (!Native.CURRENT_TIME_MICROS_AVAILABLE) { + } else if (!Native.isCurrentTimeMicrosAvailable()) { LOG.info( "Could not access native clock (see debug logs for details), " + "falling back to Java system clock"); From 9f49bb606c9660f84a747ac2c47494ffb364fd97 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 30 Sep 2017 21:28:57 -0700 Subject: [PATCH 215/742] JAVA-1514: Port Uuids utility class --- changelog/README.md | 1 + core/pom.xml | 4 + .../oss/driver/api/core/uuid/Uuids.java | 382 ++++++++++++++++++ .../oss/driver/internal/core/os/Native.java | 46 +++ .../oss/driver/api/core/uuid/UuidsTest.java | 173 ++++++++ pom.xml | 5 + 6 files changed, 611 insertions(+) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java diff --git a/changelog/README.md b/changelog/README.md index d6d6ee61a73..51d6a0b9bc5 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1514: Port Uuids utility class - [new feature] JAVA-1520: Add node state listeners - [new feature] JAVA-1493: Handle schema metadata - [improvement] JAVA-1605: Refactor request execution model diff --git a/core/pom.xml b/core/pom.xml index cc9e10e3396..08407eaba93 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -59,6 +59,10 @@ com.github.jnr jnr-ffi + + com.github.jnr + jnr-posix + org.slf4j slf4j-api diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java new file mode 100644 index 00000000000..c42188a7340 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.uuid; + +import com.datastax.oss.driver.internal.core.os.Native; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Random; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods to help working with UUIDs, and more specifically, with time-based UUIDs (also + * known as Version 1 UUIDs). + * + *

      The algorithm to generate time-based UUIDs roughly follows the description in RFC-4122, but + * with the following adaptations: + * + *

        + *
      1. Since Java does not provide direct access to the host's MAC address, that information is + * replaced with a digest of all IP addresses available on the host; + *
      2. The process ID (PID) isn't easily available to Java either, so it is determined by one of + * the following methods, in the order they are listed below: + *
          + *
        1. If the System property {@value PID_SYSTEM_PROPERTY} is set then the + * value to use as a PID will be read from that property; + *
        2. Otherwise, if a native call to {@code POSIX.getpid()} is possible, then the PID will + * be read from that call; + *
        3. Otherwise, an attempt will be made to read the PID from JMX's {@link + * ManagementFactory#getRuntimeMXBean() RuntimeMXBean}, since most JVMs tend to use the + * JVM's PID as part of that MXBean name (however that behavior is not officially part + * of the specification, so it may not work for all JVMs); + *
        4. If all of the above fail, a random integer will be generated and used as a surrogate + * PID. + *
        + * + *
      + * + * @see JAVA-444 + * @see A Universally Unique IDentifier (UUID) URN + * Namespace (RFC 4122) + */ +public final class Uuids { + + /** The system property to use to force the value of the process ID ({@value}). */ + public static final String PID_SYSTEM_PROPERTY = "com.datastax.oss.driver.PID"; + + private static final Logger LOG = LoggerFactory.getLogger(Uuids.class); + + private Uuids() {} + + private static final long START_EPOCH = makeEpoch(); + private static final long CLOCK_SEQ_AND_NODE = makeClockSeqAndNode(); + + // The min and max possible lsb for a UUID. + // + // This is not 0 and all 1's because Cassandra's TimeUUIDType compares the lsb parts as signed + // byte arrays. So the min value is 8 times -128 and the max is 8 times +127. + // + // We ignore the UUID variant (namely, MIN_CLOCK_SEQ_AND_NODE has variant 2 as it should, but + // MAX_CLOCK_SEQ_AND_NODE has variant 0) because I don't trust all UUID implementations to have + // correctly set those (pycassa doesn't always for instance). + private static final long MIN_CLOCK_SEQ_AND_NODE = 0x8080808080808080L; + private static final long MAX_CLOCK_SEQ_AND_NODE = 0x7f7f7f7f7f7f7f7fL; + + private static final AtomicLong lastTimestamp = new AtomicLong(0L); + + private static long makeEpoch() { + // UUID v1 timestamps must be in 100-nanoseconds interval since 00:00:00.000 15 Oct 1582. + Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT-0")); + c.set(Calendar.YEAR, 1582); + c.set(Calendar.MONTH, Calendar.OCTOBER); + c.set(Calendar.DAY_OF_MONTH, 15); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c.getTimeInMillis(); + } + + private static long makeNode() { + + // We don't have access to the MAC address (in pure JAVA at least) but need to generate a node + // part that identifies this host as uniquely as possible. + // The spec says that one option is to take as many sources that identify this node as possible + // and hash them together. That's what we do here by gathering all the IPs of this host as well + // as a few other sources. + try { + + MessageDigest digest = MessageDigest.getInstance("MD5"); + for (String address : getAllLocalAddresses()) update(digest, address); + + Properties props = System.getProperties(); + update(digest, props.getProperty("java.vendor")); + update(digest, props.getProperty("java.vendor.url")); + update(digest, props.getProperty("java.version")); + update(digest, props.getProperty("os.arch")); + update(digest, props.getProperty("os.name")); + update(digest, props.getProperty("os.version")); + update(digest, getProcessPiece()); + + byte[] hash = digest.digest(); + + long node = 0; + for (int i = 0; i < 6; i++) node |= (0x00000000000000ffL & (long) hash[i]) << (i * 8); + // Since we don't use the MAC address, the spec says that the multicast bit (least significant + // bit of the first byte of the node ID) must be 1. + return node | 0x0000010000000000L; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static String getProcessPiece() { + Integer pid = null; + String pidProperty = System.getProperty(PID_SYSTEM_PROPERTY); + if (pidProperty != null) { + try { + pid = Integer.parseInt(pidProperty); + LOG.info("PID obtained from System property {}: {}", PID_SYSTEM_PROPERTY, pid); + } catch (NumberFormatException e) { + LOG.warn( + "Incorrect integer specified for PID in System property {}: {}", + PID_SYSTEM_PROPERTY, + pidProperty); + } + } + if (pid == null && Native.isGetProcessIdAvailable()) { + try { + pid = Native.getProcessId(); + LOG.info("PID obtained through native call to getpid(): {}", pid); + } catch (Exception e) { + LOG.warn("Native call to getpid() failed", e); + } + } + if (pid == null) { + try { + String pidJmx = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + pid = Integer.parseInt(pidJmx); + LOG.info("PID obtained through JMX: {}", pid); + } catch (Exception e) { + LOG.warn("Failed to obtain PID from JMX", e); + } + } + if (pid == null) { + pid = new Random().nextInt(); + LOG.warn("Could not determine PID, falling back to a random integer: {}", pid); + } + ClassLoader loader = Uuids.class.getClassLoader(); + int loaderId = loader != null ? System.identityHashCode(loader) : 0; + return Integer.toHexString(pid) + Integer.toHexString(loaderId); + } + + private static void update(MessageDigest digest, String value) { + if (value != null) { + digest.update(value.getBytes(Charsets.UTF_8)); + } + } + + private static long makeClockSeqAndNode() { + long clock = new Random(System.currentTimeMillis()).nextLong(); + long node = makeNode(); + + long lsb = 0; + lsb |= (clock & 0x0000000000003FFFL) << 48; + lsb |= 0x8000000000000000L; + lsb |= node; + return lsb; + } + + /** + * Creates a new random (version 4) UUID. + * + *

      This method is just a convenience for {@code UUID.randomUUID()}. + */ + public static UUID random() { + return UUID.randomUUID(); + } + + /** + * Creates a new time-based (version 1) UUID. + * + *

      UUIDs generated by this method are suitable for use with the {@code timeuuid} Cassandra + * type. In particular the generated UUID includes the timestamp of its generation. + * + *

      Note that there is no way to provide your own timestamp. This is deliberate, as we feel that + * this does not conform to the UUID specification, and therefore don't want to encourage it + * through the API. If you want to do it anyway, use the following workaround: + * + *

      +   * Random random = new Random();
      +   * UUID uuid = new UUID(UUIDs.startOf(userProvidedTimestamp).getMostSignificantBits(), random.nextLong());
      +   * 
      + * + * If you simply need to perform a range query on a {@code timeuuid} column, use the "fake" UUID + * generated by {@link #startOf(long)} and {@link #endOf(long)}. + */ + public static UUID timeBased() { + return new UUID(makeMsb(getCurrentTimestamp()), CLOCK_SEQ_AND_NODE); + } + + /** + * Creates a "fake" time-based UUID that sorts as the smallest possible version 1 UUID generated + * at the provided timestamp. + * + *

      Such created UUIDs are useful in queries to select a time range of a {@code timeuuid} + * column. + * + *

      The UUIDs created by this method are not unique and as such are not suitable + * for anything else than querying a specific time range. In particular, you should not insert + * such UUIDs. "True" UUIDs from user-provided timestamps are not supported (see {@link + * #timeBased()} for more explanations). + * + *

      Also, the timestamp to provide as a parameter must be a Unix timestamp (as returned by + * {@link System#currentTimeMillis} or {@link Date#getTime}), and not a count of + * 100-nanosecond intervals since 00:00:00.00, 15 October 1582 (as required by RFC-4122). + * + *

      In other words, given a UUID {@code uuid}, you should never call {@code + * startOf(uuid.timestamp())} but rather {@code startOf(unixTimestamp(uuid))}. + * + *

      Lastly, please note that Cassandra's {@code timeuuid} sorting is not compatible with {@link + * UUID#compareTo} and hence the UUIDs created by this method are not necessarily lower bound for + * that latter method. + * + * @param timestamp the Unix timestamp for which the created UUID must be a lower bound. + * @return the smallest (for Cassandra {@code timeuuid} sorting) UUID of {@code timestamp}. + */ + public static UUID startOf(long timestamp) { + return new UUID(makeMsb(fromUnixTimestamp(timestamp)), MIN_CLOCK_SEQ_AND_NODE); + } + + /** + * Creates a "fake" time-based UUID that sorts as the biggest possible version 1 UUID generated at + * the provided timestamp. + * + *

      See {@link #startOf(long)} for explanations about the intended usage of such UUID. + * + * @param timestamp the Unix timestamp for which the created UUID must be an upper bound. + * @return the biggest (for Cassandra {@code timeuuid} sorting) UUID of {@code timestamp}. + */ + public static UUID endOf(long timestamp) { + long uuidTstamp = fromUnixTimestamp(timestamp + 1) - 1; + return new UUID(makeMsb(uuidTstamp), MAX_CLOCK_SEQ_AND_NODE); + } + + /** + * Returns the Unix timestamp contained by the provided time-based UUID. + * + *

      This method is not equivalent to {@link UUID#timestamp()}. More precisely, a version 1 UUID + * stores a timestamp that represents the number of 100-nanoseconds intervals since midnight, 15 + * October 1582 and that is what {@link UUID#timestamp()} returns. This method however converts + * that timestamp to the equivalent Unix timestamp in milliseconds, i.e. a timestamp representing + * a number of milliseconds since midnight, January 1, 1970 UTC. In particular, the timestamps + * returned by this method are comparable to the timestamps returned by {@link + * System#currentTimeMillis}, {@link Date#getTime}, etc. + * + * @throws IllegalArgumentException if {@code uuid} is not a version 1 UUID. + */ + public static long unixTimestamp(UUID uuid) { + if (uuid.version() != 1) { + throw new IllegalArgumentException( + String.format( + "Can only retrieve the unix timestamp for version 1 uuid (provided version %d)", + uuid.version())); + } + long timestamp = uuid.timestamp(); + return (timestamp / 10000) + START_EPOCH; + } + + // Use {@link System#currentTimeMillis} for a base time in milliseconds, and if we are in the same + // millisecond as the previous generation, increment the number of nanoseconds. + // However, since the precision is 100-nanosecond intervals, we can only generate 10K UUIDs within + // a millisecond safely. If we detect we have already generated that much UUIDs within a + // millisecond (which, while admittedly unlikely in a real application, is very achievable on even + // modest machines), then we stall the generator (busy spin) until the next millisecond as + // required by the RFC. + private static long getCurrentTimestamp() { + while (true) { + long now = fromUnixTimestamp(System.currentTimeMillis()); + long last = lastTimestamp.get(); + if (now > last) { + if (lastTimestamp.compareAndSet(last, now)) { + return now; + } + } else { + long lastMillis = millisOf(last); + // If the clock went back in time, bail out + if (millisOf(now) < millisOf(last)) { + return lastTimestamp.incrementAndGet(); + } + long candidate = last + 1; + // If we've generated more than 10k uuid in that millisecond, restart the whole process + // until we get to the next millis. Otherwise, we try use our candidate ... unless we've + // been beaten by another thread in which case we try again. + if (millisOf(candidate) == lastMillis && lastTimestamp.compareAndSet(last, candidate)) { + return candidate; + } + } + } + } + + @VisibleForTesting + static long fromUnixTimestamp(long tstamp) { + return (tstamp - START_EPOCH) * 10000; + } + + private static long millisOf(long timestamp) { + return timestamp / 10000; + } + + @VisibleForTesting + static long makeMsb(long timestamp) { + long msb = 0L; + msb |= (0x00000000ffffffffL & timestamp) << 32; + msb |= (0x0000ffff00000000L & timestamp) >>> 16; + msb |= (0x0fff000000000000L & timestamp) >>> 48; + msb |= 0x0000000000001000L; // sets the version to 1. + return msb; + } + + private static Set getAllLocalAddresses() { + Set allIps = new HashSet<>(); + try { + InetAddress localhost = InetAddress.getLocalHost(); + allIps.add(localhost.toString()); + // Also return the hostname if available, it won't hurt (this does a dns lookup, it's only done once at startup) + allIps.add(localhost.getCanonicalHostName()); + InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); + if (allMyIps != null) { + for (InetAddress allMyIp : allMyIps) { + allIps.add(allMyIp.toString()); + } + } + } catch (UnknownHostException e) { + // Ignore, we'll try the network interfaces anyway + } + + try { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + if (en != null) { + while (en.hasMoreElements()) { + Enumeration enumIpAddr = en.nextElement().getInetAddresses(); + while (enumIpAddr.hasMoreElements()) { + allIps.add(enumIpAddr.nextElement().toString()); + } + } + } + } catch (SocketException e) { + // Ignore, if we've really got nothing so far, we'll throw an exception + } + return allIps; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java index 470affbcbac..490cc2a844c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java @@ -21,6 +21,8 @@ import jnr.ffi.Struct; import jnr.ffi.annotations.Out; import jnr.ffi.annotations.Transient; +import jnr.posix.POSIXFactory; +import jnr.posix.util.DefaultPOSIXHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +58,23 @@ public static long currentTimeMicros() { return tv.tv_sec.get() * 1000000 + tv.tv_usec.get(); } + public static boolean isGetProcessIdAvailable() { + try { + return PosixLoader.GET_PID_AVAILABLE; + } catch (NoClassDefFoundError e) { + return false; + } + } + + public static int getProcessId() { + if (!isGetProcessIdAvailable()) { + throw new IllegalStateException( + "Native call not available. " + + "Check isGetProcessIdAvailable() before calling this method."); + } + return PosixLoader.POSIX.getpid(); + } + /** * If jnr-ffi is not in the classpath at runtime, we'll fail to initialize the static fields * below, but we still want {@link Native} to initialize successfully, so use an inner class. @@ -104,4 +123,31 @@ private Timeval(Runtime runtime) { GET_TIME_OF_DAY_AVAILABLE = getTimeOfDayAvailable; } } + + /** @see LibCLoader */ + private static class PosixLoader { + private static final jnr.posix.POSIX POSIX; + private static final boolean GET_PID_AVAILABLE; + + static { + jnr.posix.POSIX posix; + try { + posix = POSIXFactory.getPOSIX(new DefaultPOSIXHandler(), true); + } catch (Throwable t) { + posix = null; + LOG.debug("Error loading POSIX", t); + } + POSIX = posix; + boolean getPidAvailable = false; + if (POSIX != null) { + try { + POSIX.getpid(); + getPidAvailable = true; + } catch (Throwable t) { + LOG.debug("Error accessing posix.getpid()", t); + } + } + GET_PID_AVAILABLE = getPidAvailable; + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java new file mode 100644 index 00000000000..0c5d05ee503 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.uuid; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentSkipListSet; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class UuidsTest { + + @Test + public void should_generate_timestamp_within_10_ms() { + + // The Uuids class does some computation at class initialization, which may screw up our + // assumption below that Uuids.timeBased() takes less than 10ms, so force class loading now. + Uuids.random(); + + long start = System.currentTimeMillis(); + UUID uuid = Uuids.timeBased(); + + assertThat(uuid.version()).isEqualTo(1); + assertThat(uuid.variant()).isEqualTo(2); + + long timestamp = Uuids.unixTimestamp(uuid); + + assertThat(timestamp) + .as("Generated timestamp should be within 10 ms") + .isBetween(start, start + 10); + } + + @Test + public void should_generate_unique_time_based_uuids() { + int count = 1_000_000; + Set generated = new HashSet<>(count); + + for (int i = 0; i < count; ++i) { + generated.add(Uuids.timeBased()); + } + + assertThat(generated).hasSize(count); + } + + @Test + public void should_generate_unique_time_based_uuids_across_threads() throws Exception { + int threadCount = 10; + int uuidsPerThread = 10_000; + Set generated = new ConcurrentSkipListSet<>(); + + UUIDGenerator[] generators = new UUIDGenerator[threadCount]; + for (int i = 0; i < threadCount; i++) { + generators[i] = new UUIDGenerator(uuidsPerThread, generated); + } + for (int i = 0; i < threadCount; i++) { + generators[i].start(); + } + for (int i = 0; i < threadCount; i++) { + generators[i].join(); + } + + assertThat(generated).hasSize(threadCount * uuidsPerThread); + } + + @Test + public void should_generate_ever_increasing_timestamps() { + int count = 1_000_000; + long previous = 0; + for (int i = 0; i < count; i++) { + long current = Uuids.timeBased().timestamp(); + assertThat(current).isGreaterThan(previous); + previous = current; + } + } + + @Test + public void should_generate_within_bounds_for_given_timestamp() { + + Random random = new Random(System.currentTimeMillis()); + + int timestampsCount = 10; + int uuidsPerTimestamp = 10; + + for (int i = 0; i < timestampsCount; i++) { + long timestamp = (long) random.nextInt(); + for (int j = 0; j < uuidsPerTimestamp; j++) { + UUID uuid = new UUID(Uuids.makeMsb(Uuids.fromUnixTimestamp(timestamp)), random.nextLong()); + assertBetween(uuid, Uuids.startOf(timestamp), Uuids.endOf(timestamp)); + } + } + } + + // Compares using Cassandra's sorting algorithm (not the same as compareTo). + private static void assertBetween(UUID uuid, UUID lowerBound, UUID upperBound) { + ByteBuffer uuidBytes = TypeCodecs.UUID.encode(uuid, CoreProtocolVersion.V3); + ByteBuffer lb = TypeCodecs.UUID.encode(lowerBound, CoreProtocolVersion.V3); + ByteBuffer ub = TypeCodecs.UUID.encode(upperBound, CoreProtocolVersion.V3); + assertThat(compareTimestampBytes(lb, uuidBytes)).isLessThanOrEqualTo(0); + assertThat(compareTimestampBytes(ub, uuidBytes)).isGreaterThanOrEqualTo(0); + } + + private static int compareTimestampBytes(ByteBuffer o1, ByteBuffer o2) { + int o1Pos = o1.position(); + int o2Pos = o2.position(); + + int d = (o1.get(o1Pos + 6) & 0xF) - (o2.get(o2Pos + 6) & 0xF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos + 7) & 0xFF) - (o2.get(o2Pos + 7) & 0xFF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos + 4) & 0xFF) - (o2.get(o2Pos + 4) & 0xFF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos + 5) & 0xFF) - (o2.get(o2Pos + 5) & 0xFF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos) & 0xFF) - (o2.get(o2Pos) & 0xFF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos + 1) & 0xFF) - (o2.get(o2Pos + 1) & 0xFF); + if (d != 0) { + return d; + } + d = (o1.get(o1Pos + 2) & 0xFF) - (o2.get(o2Pos + 2) & 0xFF); + if (d != 0) { + return d; + } + return (o1.get(o1Pos + 3) & 0xFF) - (o2.get(o2Pos + 3) & 0xFF); + } + + private static class UUIDGenerator extends Thread { + + private final int toGenerate; + private final Set generated; + + UUIDGenerator(int toGenerate, Set generated) { + this.toGenerate = toGenerate; + this.generated = generated; + } + + @Override + public void run() { + for (int i = 0; i < toGenerate; ++i) { + generated.add(Uuids.timeBased()); + } + } + } +} diff --git a/pom.xml b/pom.xml index defd5ddb8b6..82424f12823 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,11 @@ jnr-ffi 2.1.6 + + com.github.jnr + jnr-posix + 3.0.27 + junit junit From a637a8f8a58e9a76e5a28a161566891b6b66a28b Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 13 Oct 2017 10:13:38 -0700 Subject: [PATCH 216/742] Support latest v5-beta changes in native-protocol --- .../internal/core/adminrequest/AdminRequestHandler.java | 3 ++- .../oss/driver/internal/core/cql/Conversions.java | 9 ++++++--- .../datastax/oss/driver/internal/core/TestResponses.java | 2 +- .../driver/internal/core/cql/CqlPrepareHandlerTest.java | 9 ++++++--- .../driver/internal/core/cql/CqlRequestHandlerTest.java | 2 +- .../internal/core/cql/CqlRequestHandlerTestBase.java | 4 ++-- .../driver/internal/core/session/ReprepareOnUpTest.java | 3 ++- pom.xml | 2 +- 8 files changed, 21 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index d8256648d4e..161c179b273 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -175,7 +175,8 @@ private static QueryOptions buildQueryOptions( pageSize, pagingState, ProtocolConstants.ConsistencyLevel.SERIAL, - Long.MIN_VALUE); + Long.MIN_VALUE, + null); } private static Map serialize( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index c34d28ffc72..8c2a33e5774 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -116,7 +116,8 @@ static Message toMessage( pageSize, statement.getPagingState(), serialConsistency, - timestamp); + timestamp, + null); return new Query(simpleStatement.getQuery(), queryOptions); } else if (statement instanceof BoundStatement) { BoundStatement boundStatement = (BoundStatement) statement; @@ -129,7 +130,8 @@ static Message toMessage( pageSize, statement.getPagingState(), serialConsistency, - timestamp); + timestamp, + null); ByteBuffer id = boundStatement.getPreparedStatement().getId(); return new Execute(Bytes.getArray(id), queryOptions); } else if (statement instanceof BatchStatement) { @@ -163,7 +165,8 @@ static Message toMessage( values, consistency, serialConsistency, - timestamp); + timestamp, + null); } else { throw new IllegalArgumentException( "Unsupported statement type: " + statement.getClass().getName()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java index 97b2b827ecd..67436653d49 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/TestResponses.java @@ -38,7 +38,7 @@ public static Rows clusterNameResponse(String actualClusterName) { "cluster_name", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR)); - RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null); + RowsMetadata metadata = new RowsMetadata(ImmutableList.of(colSpec), null, null, null); Queue> data = Lists.newLinkedList(); data.add(Lists.newArrayList(ByteBuffer.wrap(actualClusterName.getBytes(Charsets.UTF_8)))); return new DefaultRows(metadata, data); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 5b87e0aeedf..9e6a768333b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -355,7 +355,8 @@ private static Message simplePrepared() { 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), null, - new int[] {0}); + new int[] {0}, + null); RowsMetadata resultMetadata = new RowsMetadata( ImmutableList.of( @@ -366,8 +367,10 @@ private static Message simplePrepared() { 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), null, - new int[] {}); - return new Prepared(Bytes.fromHexString("0xffff").array(), variablesMetadata, resultMetadata); + new int[] {}, + null); + return new Prepared( + Bytes.fromHexString("0xffff").array(), null, variablesMetadata, resultMetadata); } private static void assertMatchesSimplePrepared(PreparedStatement statement) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 93ae9721796..200aa64115d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -188,7 +188,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce // Before we proceed, mock the PREPARE exchange that will occur as soon as we complete the // first response. node1Behavior.mockFollowupRequest( - Prepare.class, defaultFrameOf(new Prepared(mockId.array(), null, null))); + Prepare.class, defaultFrameOf(new Prepared(mockId.array(), null, null, null))); node1Behavior.setWriteSuccess(); node1Behavior.setResponseSuccess( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 5f01f59de11..8d78f553c4d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -25,7 +25,6 @@ import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.DefaultRows; import com.datastax.oss.protocol.internal.response.result.RawType; -import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.RowsMetadata; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; @@ -82,7 +81,8 @@ protected static Message singleRow() { 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.VARCHAR))), null, - new int[] {}); + new int[] {}, + null); Queue> data = new LinkedList<>(); data.add(ImmutableList.of(Bytes.fromHexString("0x68656C6C6F2C20776F726C64"))); return new DefaultRows(metadata, data); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index cf56bcdde10..3e882963a8f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -330,7 +330,8 @@ private Rows preparedIdRows(char... values) { "prepared_id", 0, RawType.PRIMITIVES.get(ProtocolConstants.DataType.BLOB)); - RowsMetadata rowsMetadata = new RowsMetadata(ImmutableList.of(preparedIdSpec), null, null); + RowsMetadata rowsMetadata = + new RowsMetadata(ImmutableList.of(preparedIdSpec), null, null, null); Queue> data = new LinkedList<>(); for (char value : values) { data.add(ImmutableList.of(Bytes.fromHexString("0x0" + value))); diff --git a/pom.xml b/pom.xml index 82424f12823..76584f4dd4b 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ com.datastax.oss native-protocol - 1.4.0 + 1.4.1-SNAPSHOT io.netty From 812913a5a8ec47ee14f0095845a75e6a9fbc800c Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 29 Sep 2017 17:21:01 -0700 Subject: [PATCH 217/742] Propagate errors from channel initializer --- .../internal/core/channel/ChannelFactory.java | 97 +++++++++++-------- .../driver/internal/core/util/Reflection.java | 9 +- .../core/channel/ChannelFactoryTestBase.java | 52 +++++----- 3 files changed, 90 insertions(+), 68 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 4fece48eb59..8a1ca6e16c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -126,7 +126,8 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler(initializer(address, currentVersion, options, availableIdsHolder)); + .handler( + initializer(address, currentVersion, options, availableIdsHolder, resultFuture)); nettyOptions.afterBootstrapInitialized(bootstrap); @@ -172,6 +173,8 @@ private void connect( UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); } } else { + // Note: might be completed already if the failure happened in initializer(), this is + // fine resultFuture.completeExceptionally(error); } } @@ -183,51 +186,59 @@ ChannelInitializer initializer( SocketAddress address, final ProtocolVersion protocolVersion, final DriverChannelOptions options, - AvailableIdsHolder availableIdsHolder) { + AvailableIdsHolder availableIdsHolder, + CompletableFuture resultFuture) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); - - long setKeyspaceTimeoutMillis = - defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) - .toMillis(); - int maxFrameLength = - (int) defaultConfigProfile.getBytes(CoreDriverOption.PROTOCOL_MAX_FRAME_LENGTH); - int maxRequestsPerConnection = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); - int maxOrphanRequests = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); - - InFlightHandler inFlightHandler = - new InFlightHandler( - protocolVersion, - new StreamIdGenerator(maxRequestsPerConnection), - maxOrphanRequests, - setKeyspaceTimeoutMillis, - availableIdsHolder, - channel.newPromise(), - options.eventCallback, - options.ownerLogPrefix); - HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); - ProtocolInitHandler initHandler = - new ProtocolInitHandler( - context, protocolVersion, clusterName, options, heartbeatHandler); - - ChannelPipeline pipeline = channel.pipeline(); - context - .sslHandlerFactory() - .map(f -> f.newSslHandler(channel, address)) - .map(h -> pipeline.addLast("ssl", h)); - pipeline - .addLast("encoder", new FrameEncoder(context.frameCodec(), maxFrameLength)) - .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) - // Note: HeartbeatHandler is inserted here once init completes - .addLast("inflight", inFlightHandler) - .addLast("init", initHandler); - - context.nettyOptions().afterChannelInitialized(channel); + try { + DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); + + long setKeyspaceTimeoutMillis = + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .toMillis(); + int maxFrameLength = + (int) defaultConfigProfile.getBytes(CoreDriverOption.PROTOCOL_MAX_FRAME_LENGTH); + int maxRequestsPerConnection = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + int maxOrphanRequests = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); + + InFlightHandler inFlightHandler = + new InFlightHandler( + protocolVersion, + new StreamIdGenerator(maxRequestsPerConnection), + maxOrphanRequests, + setKeyspaceTimeoutMillis, + availableIdsHolder, + channel.newPromise(), + options.eventCallback, + options.ownerLogPrefix); + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + ProtocolInitHandler initHandler = + new ProtocolInitHandler( + context, protocolVersion, clusterName, options, heartbeatHandler); + + ChannelPipeline pipeline = channel.pipeline(); + context + .sslHandlerFactory() + .map(f -> f.newSslHandler(channel, address)) + .map(h -> pipeline.addLast("ssl", h)); + pipeline + .addLast("encoder", new FrameEncoder(context.frameCodec(), maxFrameLength)) + .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) + // Note: HeartbeatHandler is inserted here once init completes + .addLast("inflight", inFlightHandler) + .addLast("init", initHandler); + + context.nettyOptions().afterChannelInitialized(channel); + } catch (Throwable t) { + // If the init handler throws an exception, Netty swallows it and closes the channel. We + // want to propagate it instead, so fail the outer future (the result of connect()). + resultFuture.completeExceptionally(t); + throw t; + } } }; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 08959bf2108..d99ee0a8faf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Optional; public class Reflection { @@ -108,9 +109,13 @@ public static Optional buildFromConfig( Object instance = constructor.newInstance(context, rootOption); return Optional.of(expectedSuperType.cast(instance)); } catch (Exception e) { + // ITE just wraps an exception thrown by the constructor, get rid of it: + Throwable cause = (e instanceof InvocationTargetException) ? e.getCause() : e; throw new IllegalArgumentException( - String.format("Error instantiating class %s (specified by %s)", className, configPath), - e); + String.format( + "Error instantiating class %s (specified by %s): %s", + className, configPath, cause.getMessage()), + cause); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index cc5bde4a707..6e210edc204 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -46,6 +46,7 @@ import java.time.Duration; import java.util.Collections; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -222,35 +223,40 @@ ChannelInitializer initializer( SocketAddress address, ProtocolVersion protocolVersion, DriverChannelOptions options, - AvailableIdsHolder availableIdsHolder) { + AvailableIdsHolder availableIdsHolder, + CompletableFuture resultFuture) { return new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); + try { + DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); - long setKeyspaceTimeoutMillis = - defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) - .toMillis(); - int maxRequestsPerConnection = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + long setKeyspaceTimeoutMillis = + defaultConfigProfile + .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .toMillis(); + int maxRequestsPerConnection = + defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); - InFlightHandler inFlightHandler = - new InFlightHandler( - protocolVersion, - new StreamIdGenerator(maxRequestsPerConnection), - Integer.MAX_VALUE, - setKeyspaceTimeoutMillis, - availableIdsHolder, - channel.newPromise(), - null, - "test"); + InFlightHandler inFlightHandler = + new InFlightHandler( + protocolVersion, + new StreamIdGenerator(maxRequestsPerConnection), + Integer.MAX_VALUE, + setKeyspaceTimeoutMillis, + availableIdsHolder, + channel.newPromise(), + null, + "test"); - HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); - ProtocolInitHandler initHandler = - new ProtocolInitHandler( - context, protocolVersion, clusterName, options, heartbeatHandler); - channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + ProtocolInitHandler initHandler = + new ProtocolInitHandler( + context, protocolVersion, clusterName, options, heartbeatHandler); + channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); + } catch (Throwable t) { + resultFuture.completeExceptionally(t); + } } }; } From 046c36733f797911f5531653fe0278027f132f54 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 29 Sep 2017 21:28:22 -0700 Subject: [PATCH 218/742] JAVA-1494: Implement Snappy and LZ4 compression --- changelog/README.md | 1 + core/pom.xml | 10 + .../api/core/config/CoreDriverOption.java | 1 + .../core/channel/ProtocolInitHandler.java | 2 +- .../core/context/DefaultDriverContext.java | 6 +- .../core/protocol/ByteBufCompressor.java | 59 ++++++ .../internal/core/protocol/Lz4Compressor.java | 171 ++++++++++++++++++ .../core/protocol/SnappyCompressor.java | 161 +++++++++++++++++ core/src/main/resources/reference.conf | 14 ++ .../core/channel/ChannelFactoryTestBase.java | 3 + .../core/channel/ProtocolInitHandlerTest.java | 35 +++- integration-tests/pom.xml | 10 + .../core/compression/DirectCompressionIT.java | 98 ++++++++++ .../core/compression/HeapCompressionIT.java | 104 +++++++++++ pom.xml | 10 + 15 files changed, 681 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufCompressor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java diff --git a/changelog/README.md b/changelog/README.md index 51d6a0b9bc5..17311a49960 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1494: Implement Snappy and LZ4 compression - [new feature] JAVA-1514: Port Uuids utility class - [new feature] JAVA-1520: Add node state listeners - [new feature] JAVA-1493: Handle schema metadata diff --git a/core/pom.xml b/core/pom.xml index 08407eaba93..1967f08c9bd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -63,6 +63,16 @@ com.github.jnr jnr-posix + + org.xerial.snappy + snappy-java + true + + + net.jpountz.lz4 + lz4 + true + org.slf4j slf4j-api diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 96feeb08da6..5c0be813342 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -25,6 +25,7 @@ public enum CoreDriverOption implements DriverOption { PROTOCOL_VERSION("protocol.version", false), PROTOCOL_MAX_FRAME_LENGTH("protocol.max-frame-length", true), + PROTOCOL_COMPRESSOR("protocol.compressor", false), CLUSTER_NAME("cluster-name", false), CONFIG_RELOAD_INTERVAL("config-reload-interval", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 21cf1536224..8585e2b7e5e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -140,7 +140,7 @@ String describe() { Message getRequest() { switch (step) { case STARTUP: - return new Startup(); + return new Startup(internalDriverContext.compressor().algorithm()); case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 383553e0e52..4caea1a3867 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -217,9 +217,11 @@ protected EventBus buildEventBus() { return new EventBus(clusterName()); } + @SuppressWarnings("unchecked") protected Compressor buildCompressor() { - // TODO build alternate implementation if specified in conf - return Compressor.none(); + return (Compressor) + Reflection.buildFromConfig(this, CoreDriverOption.PROTOCOL_COMPRESSOR, Compressor.class) + .orElse(Compressor.none()); } protected FrameCodec buildFrameCodec() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufCompressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufCompressor.java new file mode 100644 index 00000000000..928a2800286 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufCompressor.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.protocol.internal.Compressor; +import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; + +public abstract class ByteBufCompressor implements Compressor { + + @Override + public ByteBuf compress(ByteBuf uncompressed) { + return uncompressed.isDirect() ? compressDirect(uncompressed) : compressHeap(uncompressed); + } + + protected abstract ByteBuf compressDirect(ByteBuf input); + + protected abstract ByteBuf compressHeap(ByteBuf input); + + @Override + public ByteBuf decompress(ByteBuf compressed) { + return compressed.isDirect() ? decompressDirect(compressed) : decompressHeap(compressed); + } + + protected abstract ByteBuf decompressDirect(ByteBuf input); + + protected abstract ByteBuf decompressHeap(ByteBuf input); + + protected static ByteBuffer inputNioBuffer(ByteBuf buf) { + // Using internalNioBuffer(...) as we only hold the reference in this method and so can + // reduce Object allocations. + int index = buf.readerIndex(); + int len = buf.readableBytes(); + return buf.nioBufferCount() == 1 + ? buf.internalNioBuffer(index, len) + : buf.nioBuffer(index, len); + } + + protected static ByteBuffer outputNioBuffer(ByteBuf buf) { + int index = buf.writerIndex(); + int len = buf.writableBytes(); + return buf.nioBufferCount() == 1 + ? buf.internalNioBuffer(index, len) + : buf.nioBuffer(index, len); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java new file mode 100644 index 00000000000..871616d7a16 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.protocol.internal.Compressor; +import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Lz4Compressor extends ByteBufCompressor { + + private static final Logger LOG = LoggerFactory.getLogger(Lz4Compressor.class); + + private final LZ4Compressor compressor; + private final LZ4FastDecompressor decompressor; + + public Lz4Compressor(DriverContext context, @SuppressWarnings("unused") DriverOption configRoot) { + try { + LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); + LOG.info("[{}] Using {}", context.clusterName(), lz4Factory.toString()); + this.compressor = lz4Factory.fastCompressor(); + this.decompressor = lz4Factory.fastDecompressor(); + } catch (NoClassDefFoundError e) { + throw new IllegalStateException( + "Error initializing compressor, make sure that the LZ4 library is in the classpath " + + "(the driver declares it as an optional dependency, " + + "so you need to declare it explicitly)", + e); + } + } + + @Override + public String algorithm() { + return "lz4"; + } + + @Override + protected ByteBuf compressDirect(ByteBuf input) { + int maxCompressedLength = compressor.maxCompressedLength(input.readableBytes()); + // If the input is direct we will allocate a direct output buffer as well as this will allow us + // to use LZ4Compressor.compress and so eliminate memory copies. + ByteBuf output = input.alloc().directBuffer(4 + maxCompressedLength); + try { + ByteBuffer in = inputNioBuffer(input); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + output.writeInt(in.remaining()); + + ByteBuffer out = outputNioBuffer(output); + int written = + compressor.compress( + in, in.position(), in.remaining(), out, out.position(), out.remaining()); + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + written); + } catch (Exception e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw e; + } + return output; + } + + @Override + protected ByteBuf compressHeap(ByteBuf input) { + int maxCompressedLength = compressor.maxCompressedLength(input.readableBytes()); + + // Not a direct buffer so use byte arrays... + int inOffset = input.arrayOffset() + input.readerIndex(); + byte[] in = input.array(); + int len = input.readableBytes(); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + // Allocate a heap buffer from the ByteBufAllocator as we may use a PooledByteBufAllocator and + // so can eliminate the overhead of allocate a new byte[]. + ByteBuf output = input.alloc().heapBuffer(4 + maxCompressedLength); + try { + output.writeInt(len); + // calculate the correct offset. + int offset = output.arrayOffset() + output.writerIndex(); + byte[] out = output.array(); + int written = compressor.compress(in, inOffset, len, out, offset); + + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + written); + } catch (Exception e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw e; + } + return output; + } + + @Override + protected ByteBuf decompressDirect(ByteBuf input) { + // If the input is direct we will allocate a direct output buffer as well as this will allow us + // to use LZ4Compressor.decompress and so eliminate memory copies. + int readable = input.readableBytes(); + int uncompressedLength = input.readInt(); + ByteBuffer in = inputNioBuffer(input); + // Increase reader index. + input.readerIndex(input.writerIndex()); + ByteBuf output = input.alloc().directBuffer(uncompressedLength); + try { + ByteBuffer out = outputNioBuffer(output); + int read = decompressor.decompress(in, in.position(), out, out.position(), out.remaining()); + if (read != readable - 4) { + throw new IllegalArgumentException("Compressed lengths mismatch"); + } + + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + uncompressedLength); + } catch (Exception e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw e; + } + return output; + } + + @Override + protected ByteBuf decompressHeap(ByteBuf input) { + // Not a direct buffer so use byte arrays... + byte[] in = input.array(); + int len = input.readableBytes(); + int uncompressedLength = input.readInt(); + int inOffset = input.arrayOffset() + input.readerIndex(); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + // Allocate a heap buffer from the ByteBufAllocator as we may use a PooledByteBufAllocator and + // so can eliminate the overhead of allocate a new byte[]. + ByteBuf output = input.alloc().heapBuffer(uncompressedLength); + try { + int offset = output.arrayOffset() + output.writerIndex(); + byte out[] = output.array(); + int read = decompressor.decompress(in, inOffset, out, offset, uncompressedLength); + if (read != len - 4) { + throw new IllegalArgumentException("Compressed lengths mismatch"); + } + + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + uncompressedLength); + } catch (Exception e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw e; + } + return output; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java new file mode 100644 index 00000000000..39a2fdbc012 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.protocol; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import io.netty.buffer.ByteBuf; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.xerial.snappy.Snappy; + +public class SnappyCompressor extends ByteBufCompressor { + + public SnappyCompressor( + @SuppressWarnings("unused") DriverContext context, + @SuppressWarnings("unused") DriverOption configRoot) { + try { + Snappy.getNativeLibraryVersion(); + } catch (NoClassDefFoundError e) { + throw new IllegalStateException( + "Error initializing compressor, make sure that the Snappy library is in the classpath " + + "(the driver declares it as an optional dependency, " + + "so you need to declare it explicitly)", + e); + } + } + + @Override + public String algorithm() { + return "snappy"; + } + + @Override + protected ByteBuf compressDirect(ByteBuf input) { + int maxCompressedLength = Snappy.maxCompressedLength(input.readableBytes()); + // If the input is direct we will allocate a direct output buffer as well as this will allow us + // to use Snappy.compress(ByteBuffer, ByteBuffer) and so eliminate memory copies. + ByteBuf output = input.alloc().directBuffer(maxCompressedLength); + try { + ByteBuffer in = inputNioBuffer(input); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + ByteBuffer out = outputNioBuffer(output); + int written = Snappy.compress(in, out); + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + written); + return output; + } catch (IOException e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw new RuntimeException(e); + } + } + + @Override + protected ByteBuf compressHeap(ByteBuf input) { + int maxCompressedLength = Snappy.maxCompressedLength(input.readableBytes()); + int inOffset = input.arrayOffset() + input.readerIndex(); + byte[] in = input.array(); + int len = input.readableBytes(); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + // Allocate a heap buffer from the ByteBufAllocator as we may use a PooledByteBufAllocator and + // so can eliminate the overhead of allocate a new byte[]. + ByteBuf output = input.alloc().heapBuffer(maxCompressedLength); + try { + // Calculate the correct offset. + int offset = output.arrayOffset() + output.writerIndex(); + byte[] out = output.array(); + int written = Snappy.compress(in, inOffset, len, out, offset); + + // Increase the writerIndex with the written bytes. + output.writerIndex(output.writerIndex() + written); + return output; + } catch (IOException e) { + // release output buffer so we not leak and rethrow exception. + output.release(); + throw new RuntimeException(e); + } + } + + @Override + protected ByteBuf decompressDirect(ByteBuf input) { + ByteBuffer in = inputNioBuffer(input); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + ByteBuf output = null; + try { + if (!Snappy.isValidCompressedBuffer(in)) { + throw new IllegalArgumentException( + "Provided frame does not appear to be Snappy compressed"); + } + // If the input is direct we will allocate a direct output buffer as well as this will allow + // us to use Snappy.compress(ByteBuffer, ByteBuffer) and so eliminate memory copies. + output = input.alloc().directBuffer(Snappy.uncompressedLength(in)); + ByteBuffer out = outputNioBuffer(output); + + int size = Snappy.uncompress(in, out); + // Set the writer index so the amount of written bytes is reflected + output.writerIndex(output.writerIndex() + size); + return output; + } catch (IOException e) { + // release output buffer so we not leak and rethrow exception. + if (output != null) { + output.release(); + } + throw new RuntimeException(e); + } + } + + @Override + protected ByteBuf decompressHeap(ByteBuf input) throws RuntimeException { + // Not a direct buffer so use byte arrays... + int inOffset = input.arrayOffset() + input.readerIndex(); + byte[] in = input.array(); + int len = input.readableBytes(); + // Increase reader index. + input.readerIndex(input.writerIndex()); + + ByteBuf output = null; + try { + if (!Snappy.isValidCompressedBuffer(in, inOffset, len)) { + throw new IllegalArgumentException( + "Provided frame does not appear to be Snappy compressed"); + } + // Allocate a heap buffer from the ByteBufAllocator as we may use a PooledByteBufAllocator and + // so can eliminate the overhead of allocate a new byte[]. + output = input.alloc().heapBuffer(Snappy.uncompressedLength(in, inOffset, len)); + // Calculate the correct offset. + int offset = output.arrayOffset() + output.writerIndex(); + byte[] out = output.array(); + int written = Snappy.uncompress(in, inOffset, len, out, offset); + + // Increase the writerIndex with the written bytes. + output.writerIndex(output.writerIndex() + written); + return output; + } catch (IOException e) { + // release output buffer so we not leak and rethrow exception. + if (output != null) { + output.release(); + } + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 40127215c04..07bf97ad278 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -64,6 +64,20 @@ datastax-java-driver { // username = cassandra // password = cassandra } + + # The compressor to use for protocol frames. + # The driver provides two built-in implementations for the algorithms supported by Cassandra: + # - com.datastax.oss.driver.internal.core.protocol.Lz4Compressor + # Requires org.xerial.snappy:snappy-java in the classpath. + # - com.datastax.oss.driver.internal.core.protocol.SnappyCompressor + # Requires net.jpountz.lz4:lz4 in the classpath. + # + # The driver depends on the compression libraries, but they are optional. Make sure you + # redeclare an explicit dependency in your project. Refer to the driver's POM or manual for the + # exact version. + # + # If this property is absent, protocol frames are not compressed. + // compressor.class = } # A name that uniquely identifies the driver instance created from this configuration. This is diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 6e210edc204..2d803cd66d0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -32,6 +32,7 @@ import com.datastax.oss.protocol.internal.response.Ready; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -88,6 +89,7 @@ public abstract class ChannelFactoryTestBase { @Mock NettyOptions nettyOptions; @Mock ProtocolVersionRegistry protocolVersionRegistry; @Mock EventBus eventBus; + @Mock Compressor compressor; // The server's I/O thread will store the last received request here, and block until the test // thread retrieves it. This assumes readOutboundFrame() is called for each actual request, else @@ -133,6 +135,7 @@ public void setup() throws InterruptedException { Mockito.when(context.sslHandlerFactory()).thenReturn(Optional.empty()); Mockito.when(context.eventBus()).thenReturn(eventBus); Mockito.when(context.writeCoalescer()).thenReturn(new PassThroughWriteCoalescer(null, null)); + Mockito.when(context.compressor()).thenReturn(compressor); // Start local server ServerBootstrap serverBootstrap = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 660fc8db55c..c57d0dc13e6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; @@ -41,6 +42,7 @@ import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; import java.time.Duration; @@ -62,6 +64,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private Compressor compressor; private ProtocolVersionRegistry protocolVersionRegistry = new CassandraProtocolVersionRegistry("test"); private HeartbeatHandler heartbeatHandler; @@ -79,6 +82,8 @@ public void setup() { .thenReturn(Duration.ofMillis(30000)); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); + Mockito.when(internalDriverContext.compressor()).thenReturn(compressor); + Mockito.when(compressor.algorithm()).thenReturn(null); channel .pipeline() @@ -98,7 +103,7 @@ public void setup() { } @Test - public void should_initialize_without_authentication() { + public void should_initialize() { channel .pipeline() .addLast( @@ -131,6 +136,34 @@ public void should_initialize_without_authentication() { assertThat(connectFuture).isSuccess(); } + @Test + public void should_initialize_with_compression() { + Mockito.when(compressor.algorithm()).thenReturn("lz4"); + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + Startup startup = (Startup) requestFrame.message; + + // STARTUP message should request compression + assertThat(startup.options).containsEntry("COMPRESSION", "lz4"); + + writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); + writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); + assertThat(connectFuture).isSuccess(); + } + @Test public void should_add_heartbeat_handler_to_pipeline_on_success() { ProtocolInitHandler protocolInitHandler = diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 04d5d35fe0a..42dff931754 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -63,6 +63,16 @@ guava test + + org.xerial.snappy + snappy-java + test + + + net.jpountz.lz4 + lz4 + test + diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java new file mode 100644 index 00000000000..5425bdfe0ab --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.compression; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + +public class DirectCompressionIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @ClassRule + public static ClusterRule schemaClusterRule = + new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + + @BeforeClass + public static void setup() { + schemaClusterRule + .session() + .execute("CREATE TABLE test (k text PRIMARY KEY, t text, i int, f float)"); + } + + /** + * Validates that a cluster configured with Snappy compression and can execute queries that insert + * and retrieve data. + * + * @test_category connection:compression + * @expected_result session established and queries made successfully using it. + */ + @Test + public void should_execute_queries_with_snappy_compression() throws Exception { + createAndCheckCluster( + "protocol.compressor.class = " + + "com.datastax.oss.driver.internal.core.protocol.SnappyCompressor"); + } + + /** + * Validates that a cluster configured with LZ4 compression and can execute queries that insert + * and retrieve data. + * + * @test_category connection:compression + * @expected_result session established and queries made successfully using it. + */ + @Test + public void should_execute_queries_with_lz4_compression() throws Exception { + createAndCheckCluster( + "protocol.compressor.class = " + + "com.datastax.oss.driver.internal.core.protocol.Lz4Compressor"); + } + + private void createAndCheckCluster(String compressorOption) { + + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { + Session session = cluster.connect(schemaClusterRule.keyspace()); + + // Run a couple of simple test queries + ResultSet rs = + session.execute( + SimpleStatement.newInstance( + "INSERT INTO test (k, t, i, f) VALUES (?, ?, ?, ?)", "key", "foo", 42, 24.03f)); + assertThat(rs.iterator().hasNext()).isFalse(); + + ResultSet rs1 = session.execute("SELECT * FROM test WHERE k = 'key'"); + assertThat(rs1.iterator().hasNext()).isTrue(); + Row row = rs1.iterator().next(); + assertThat(rs1.iterator().hasNext()).isFalse(); + assertThat(row.getString("k")).isEqualTo("key"); + assertThat(row.getString("t")).isEqualTo("foo"); + assertThat(row.getInt("i")).isEqualTo(42); + assertThat(row.getFloat("f")).isEqualTo(24.03f, offset(0.1f)); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java new file mode 100644 index 00000000000..65c7deeb3e6 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.compression; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.IsolatedTests; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + +@Category(IsolatedTests.class) +public class HeapCompressionIT { + + static { + System.setProperty("io.netty.noPreferDirect", "true"); + System.setProperty("io.netty.noUnsafe", "true"); + } + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @ClassRule + public static ClusterRule schemaClusterRule = + new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + + @BeforeClass + public static void setup() { + schemaClusterRule + .session() + .execute("CREATE TABLE test (k text PRIMARY KEY, t text, i int, f float)"); + } + + /** + * Validates that Snappy compression still works when using heap buffers. + * + * @test_category connection:compression + * @expected_result session established and queries made successfully using it. + */ + @Test + public void should_execute_queries_with_snappy_compression() throws Exception { + createAndCheckCluster( + "protocol.compressor.class = " + + "com.datastax.oss.driver.internal.core.protocol.SnappyCompressor"); + } + + /** + * Validates that LZ4 compression still works when using heap buffers. + * + * @test_category connection:compression + * @expected_result session established and queries made successfully using it. + */ + @Test + public void should_execute_queries_with_lz4_compression() throws Exception { + createAndCheckCluster( + "protocol.compressor.class = " + + "com.datastax.oss.driver.internal.core.protocol.Lz4Compressor"); + } + + private void createAndCheckCluster(String compressorOption) { + + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { + Session session = cluster.connect(schemaClusterRule.keyspace()); + + // Run a couple of simple test queries + ResultSet rs = + session.execute( + SimpleStatement.newInstance( + "INSERT INTO test (k, t, i, f) VALUES (?, ?, ?, ?)", "key", "foo", 42, 24.03f)); + assertThat(rs.iterator().hasNext()).isFalse(); + + ResultSet rs1 = session.execute("SELECT * FROM test WHERE k = 'key'"); + assertThat(rs1.iterator().hasNext()).isTrue(); + Row row = rs1.iterator().next(); + assertThat(rs1.iterator().hasNext()).isFalse(); + assertThat(row.getString("k")).isEqualTo("key"); + assertThat(row.getString("t")).isEqualTo("foo"); + assertThat(row.getInt("i")).isEqualTo(42); + assertThat(row.getFloat("f")).isEqualTo(24.03f, offset(0.1f)); + } + } +} diff --git a/pom.xml b/pom.xml index 76584f4dd4b..151a4b87ee8 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,16 @@ jnr-ffi 2.1.6 + + org.xerial.snappy + snappy-java + 1.1.2.6 + + + net.jpountz.lz4 + lz4 + 1.3.0 + com.github.jnr jnr-posix From 21d7a7a18e525b7d73fd41ccd69bd3828f2b9b57 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 5 Oct 2017 08:48:32 -0700 Subject: [PATCH 219/742] Fix scheduling bug in ReprepareOnUp --- .../oss/driver/internal/core/session/ReprepareOnUp.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 25124fd99c6..beae064e39e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.request.Query; @@ -117,8 +118,12 @@ void start() { "[{}] {} is disabled, repreparing directly", logPrefix, CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); - serverKnownIds = Collections.emptySet(); - gatherPayloadsToReprepare(); + RunOrSchedule.on( + channel.eventLoop(), + () -> { + serverKnownIds = Collections.emptySet(); + gatherPayloadsToReprepare(); + }); } } } From fa7625e6c3c52b8f7eff66bacfc0ddef3719630c Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 4 Oct 2017 17:42:54 -0700 Subject: [PATCH 220/742] JAVA-1638: Check schema agreement --- changelog/README.md | 1 + .../datastax/oss/driver/api/core/Cluster.java | 37 ++ .../api/core/config/CoreDriverOption.java | 7 +- .../driver/api/core/cql/ExecutionInfo.java | 20 ++ .../driver/internal/core/ClusterWrapper.java | 5 + .../driver/internal/core/DefaultCluster.java | 5 + .../core/cql/CqlRequestHandlerBase.java | 31 +- .../core/cql/DefaultExecutionInfo.java | 10 +- .../core/metadata/DefaultTopologyMonitor.java | 11 + .../core/metadata/MetadataManager.java | 3 +- .../core/metadata/SchemaAgreementChecker.java | 210 ++++++++++++ .../core/metadata/TopologyMonitor.java | 8 + .../internal/core/session/DefaultSession.java | 2 +- .../internal/core/session/ReprepareOnUp.java | 68 ++-- .../driver/internal/core/util/NanoTime.java | 28 +- core/src/main/resources/reference.conf | 38 ++- .../metadata/SchemaAgreementCheckerTest.java | 320 ++++++++++++++++++ .../core/session/ReprepareOnUpTest.java | 44 ++- 18 files changed, 786 insertions(+), 62 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java diff --git a/changelog/README.md b/changelog/README.md index 17311a49960..7acb688002a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1638: Check schema agreement - [new feature] JAVA-1494: Implement Snappy and LZ4 compression - [new feature] JAVA-1514: Port Uuids utility class - [new feature] JAVA-1520: Add node state listeners diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 0ef7fa8f045..9309ad3aa35 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.CqlSession; @@ -117,6 +118,42 @@ default Metadata refreshSchema() { return CompletableFutures.getUninterruptibly(refreshSchemaAsync()); } + /** + * Checks if all nodes in the cluster agree on a common schema version. + * + *

      Due to the distributed nature of Cassandra, schema changes made on one node might not be + * immediately visible to others. Under certain circumstances, the driver waits until all nodes + * agree on a common schema version (namely: before a schema refresh, before repreparing all + * queries on a newly up node, and before completing a successful schema-altering query). To do + * so, it queries system tables to find out the schema version of all nodes that are currently + * {@link NodeState#UP UP}. If all the versions match, the check succeeds, otherwise it is retried + * periodically, until a given timeout (specified in the configuration). + * + *

      A schema agreement failure is not fatal, but it might produce unexpected results (for + * example, getting an "unconfigured table" error for a table that you created right before, just + * because the two queries went to different coordinators). + * + *

      Note that schema agreement never succeeds in a mixed-version cluster (it would be + * challenging because the way the schema version is computed varies across server versions); the + * assumption is that schema updates are unlikely to happen during a rolling upgrade anyway. + * + * @return a future that completes with {@code true} if the nodes agree, or {@code false} if the + * timeout fired. + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT + */ + CompletionStage checkSchemaAgreementAsync(); + + /** + * Convenience method to call {@link #checkSchemaAgreementAsync()} and block for the result. + * + *

      This must not be called on a driver thread. + */ + default boolean checkSchemaAgreement() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(checkSchemaAgreementAsync()); + } + /** Returns a context that provides access to all the policies used by this driver instance. */ DriverContext getContext(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 5c0be813342..1d785c843ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -51,7 +51,12 @@ public enum CoreDriverOption implements DriverOption { RELATIVE_SPECULATIVE_EXECUTION_DELAY("delay", false), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), - CONTROL_CONNECTION_PAGE_SIZE("connection.control-connection.page-size", true), + CONTROL_CONNECTION_AGREEMENT_INTERVAL( + "connection.control-connection.schema-agreement.interval", true), + CONTROL_CONNECTION_AGREEMENT_TIMEOUT( + "connection.control-connection.schema-agreement.timeout", true), + CONTROL_CONNECTION_AGREEMENT_WARN( + "connection.control-connection.schema-agreement.warn-on-failure", true), COALESCER_ROOT("connection.coalescer", true), RELATIVE_COALESCER_MAX_RUNS("max-runs-with-no-work", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 40702dc804f..9f7395a10d3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import java.nio.ByteBuffer; @@ -92,4 +94,22 @@ public interface ExecutionInfo { * versions, this map will always be empty. */ Map getIncomingPayload(); + + /** + * Whether the cluster reached schema agreement after the execution of this query. + * + *

      After a successful schema-altering query (ex: creating a table), the driver will check if + * the cluster's nodes agree on the new schema version. If not, it will keep retrying a few times + * (the retry delay and timeout are set through the configuration). + * + *

      If this method returns {@code false}, clients can call {@link + * Cluster#checkSchemaAgreement()} later to perform the check manually. + * + *

      Schema agreement is only checked for schema-altering queries. For other query types, this + * method will always return {@code true}. + * + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT + */ + boolean isSchemaInAgreement(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java index d8f2813cc13..acb713f9ad8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java @@ -61,6 +61,11 @@ public CompletionStage refreshSchemaAsync() { return delegate.refreshSchemaAsync(); } + @Override + public CompletionStage checkSchemaAgreementAsync() { + return delegate.checkSchemaAgreementAsync(); + } + @Override public DriverContext getContext() { return delegate.getContext(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index b41657a71f2..a60725c553d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -107,6 +107,11 @@ public CompletionStage refreshSchemaAsync() { return metadataManager.refreshSchema(null, true, true); } + @Override + public CompletionStage checkSchemaAgreementAsync() { + return context.topologyMonitor().checkSchemaAgreement(); + } + @Override public DriverContext getContext() { return context; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 0341f7bc875..cd43a4e8b09 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -291,9 +291,13 @@ private void cancelScheduledTasks() { } private void setFinalResult( - Result resultMessage, Frame responseFrame, NodeResponseCallback callback) { + Result resultMessage, + Frame responseFrame, + boolean schemaInAgreement, + NodeResponseCallback callback) { try { - ExecutionInfo executionInfo = buildExecutionInfo(callback, resultMessage, responseFrame); + ExecutionInfo executionInfo = + buildExecutionInfo(callback, resultMessage, responseFrame, schemaInAgreement); AsyncResultSet resultSet = Conversions.toResultSet(resultMessage, executionInfo, session, context); if (result.complete(resultSet)) { @@ -310,7 +314,10 @@ private void setFinalResult( } private ExecutionInfo buildExecutionInfo( - NodeResponseCallback callback, Result resultMessage, Frame responseFrame) { + NodeResponseCallback callback, + Result resultMessage, + Frame responseFrame, + boolean schemaInAgreement) { ByteBuffer pagingState = (resultMessage instanceof Rows) ? ((Rows) resultMessage).getMetadata().pagingState : null; return new DefaultExecutionInfo( @@ -320,7 +327,8 @@ private ExecutionInfo buildExecutionInfo( callback.execution, errors, pagingState, - responseFrame); + responseFrame, + schemaInAgreement); } private void setFinalError(Throwable error) { @@ -394,16 +402,19 @@ public void onResponse(Frame responseFrame) { try { Message responseMessage = responseFrame.message; if (responseMessage instanceof SchemaChange) { - // TODO schema agreement, and chain setFinalResult to the result SchemaChange schemaChange = (SchemaChange) responseMessage; context - .metadataManager() - .refreshSchema(schemaChange.keyspace, false, false) + .topologyMonitor() + .checkSchemaAgreement() + .thenCombine( + context.metadataManager().refreshSchema(schemaChange.keyspace, false, false), + (schemaInAgreement, metadata) -> schemaInAgreement) .whenComplete( - ((metadata, error) -> setFinalResult(schemaChange, responseFrame, this))); + ((schemaInAgreement, error) -> + setFinalResult(schemaChange, responseFrame, schemaInAgreement, this))); } else if (responseMessage instanceof Result) { LOG.debug("[{}] Got result, completing", logPrefix); - setFinalResult((Result) responseMessage, responseFrame, this); + setFinalResult((Result) responseMessage, responseFrame, true, this); } else if (responseMessage instanceof Error) { LOG.debug("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); @@ -518,7 +529,7 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { setFinalError(error); break; case IGNORE: - setFinalResult(Void.INSTANCE, null, this); + setFinalResult(Void.INSTANCE, null, true, this); break; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index 4c2ecebe328..278afc6ac49 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -36,6 +36,7 @@ public class DefaultExecutionInfo implements ExecutionInfo { private final UUID tracingId; private final List warnings; private final Map customPayload; + private final boolean schemaInAgreement; public DefaultExecutionInfo( Statement statement, @@ -44,7 +45,8 @@ public DefaultExecutionInfo( int successfulExecutionIndex, List> errors, ByteBuffer pagingState, - Frame frame) { + Frame frame, + boolean schemaInAgreement) { this.statement = statement; this.coordinator = coordinator; this.speculativeExecutionCount = speculativeExecutionCount; @@ -56,6 +58,7 @@ public DefaultExecutionInfo( // Note: the collections returned by the protocol layer are already unmodifiable this.warnings = (frame == null) ? Collections.emptyList() : frame.warnings; this.customPayload = (frame == null) ? Collections.emptyMap() : frame.customPayload; + this.schemaInAgreement = schemaInAgreement; } @Override @@ -99,4 +102,9 @@ public List getWarnings() { public Map getIncomingPayload() { return customPayload; } + + @Override + public boolean isSchemaInAgreement() { + return schemaInAgreement; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 52af8a1a833..a827b292607 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -54,6 +54,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { private static final int INFINITE_PAGE_SIZE = -1; private final String logPrefix; + private final InternalDriverContext context; private final ControlConnection controlConnection; private final AddressTranslator addressTranslator; private final Duration timeout; @@ -63,6 +64,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { public DefaultTopologyMonitor(InternalDriverContext context) { this.logPrefix = context.clusterName(); + this.context = context; this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); DriverConfigProfile config = context.config().getDefaultProfile(); @@ -146,6 +148,15 @@ public CompletionStage> refreshNodeList() { }); } + @Override + public CompletionStage checkSchemaAgreement() { + if (closeFuture.isDone()) { + return CompletableFuture.completedFuture(true); + } + DriverChannel channel = controlConnection.channel(); + return new SchemaAgreementChecker(channel, context, port, logPrefix).run(); + } + @Override public CompletionStage closeFuture() { return closeFuture; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index ad746fe773b..26bc267c356 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -350,8 +350,9 @@ private void startSchemaRequest(CompletableFuture future) { currentSchemaRefresh = future; LOG.debug("[{}] Starting schema refresh", logPrefix); maybeInitControlConnection() + .thenCompose(v -> context.topologyMonitor().checkSchemaAgreement()) // 1. Query system tables - .thenCompose(v -> schemaQueriesFactory.newInstance(future).execute()) + .thenCompose(b -> schemaQueriesFactory.newInstance(future).execute()) // 2. Parse the rows into metadata objects, put them in a MetadataRefresh // 3. Apply the MetadataRefresh .thenApplyAsync(this::parseAndApplySchemaRows, adminExecutor) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java new file mode 100644 index 00000000000..3b50cf378ea --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SchemaAgreementChecker { + + private static final Logger LOG = LoggerFactory.getLogger(SchemaAgreementChecker.class); + private static final int INFINITE_PAGE_SIZE = -1; + @VisibleForTesting static final InetAddress BIND_ALL_ADDRESS; + + static { + try { + BIND_ALL_ADDRESS = InetAddress.getByAddress(new byte[4]); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + private final DriverChannel channel; + private final InternalDriverContext context; + private final int port; + private final String logPrefix; + private final Duration queryTimeout; + private final long intervalNs; + private final long timeoutNs; + private final boolean warnOnFailure; + private final long start; + private final CompletableFuture result = new CompletableFuture<>(); + + SchemaAgreementChecker( + DriverChannel channel, InternalDriverContext context, int port, String logPrefix) { + this.channel = channel; + this.context = context; + this.port = port; + this.logPrefix = logPrefix; + DriverConfigProfile config = context.config().getDefaultProfile(); + this.queryTimeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); + this.intervalNs = + config.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL).toNanos(); + this.timeoutNs = + config.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT).toNanos(); + this.warnOnFailure = config.getBoolean(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN); + this.start = System.nanoTime(); + } + + public CompletionStage run() { + LOG.debug("[{}] Checking schema agreement", logPrefix); + if (timeoutNs == 0) { + result.complete(false); + } else { + sendQueries(); + } + return result; + } + + private void sendQueries() { + long elapsedNs = System.nanoTime() - start; + if (elapsedNs > timeoutNs) { + String message = + String.format( + "[%s] Schema agreement not reached after %s", logPrefix, NanoTime.format(elapsedNs)); + if (warnOnFailure) { + LOG.warn(message); + } else { + LOG.debug(message); + } + result.complete(false); + } else { + CompletionStage localQuery = + query("SELECT schema_version FROM system.local WHERE key='local'"); + CompletionStage peersQuery = + query("SELECT peer, rpc_address, schema_version FROM system.peers"); + + localQuery + .thenCombine(peersQuery, this::extractSchemaVersions) + .whenComplete(this::completeOrReschedule); + } + } + + private Set extractSchemaVersions(AdminResult controlNodeResult, AdminResult peersResult) { + ImmutableSet.Builder uuids = ImmutableSet.builder(); + + UUID uuid; + Iterator iterator = controlNodeResult.iterator(); + if (iterator.hasNext() && (uuid = iterator.next().getUuid("schema_version")) != null) { + uuids.add(uuid); + } + + Map nodes = context.metadataManager().getMetadata().getNodes(); + for (AdminRow peerRow : peersResult) { + InetSocketAddress connectAddress = getConnectAddress(peerRow); + Node node = nodes.get(connectAddress); + if (node == null || node.getState() != NodeState.UP) { + continue; + } + uuid = peerRow.getUuid("schema_version"); + if (uuid != null) { + uuids.add(uuid); + } + } + return uuids.build(); + } + + private InetSocketAddress getConnectAddress(AdminRow peerRow) { + // This is actually broadcast_address + InetAddress broadcastAddress = peerRow.getInetAddress("peer"); + // The address we are looking for (this corresponds to broadcast_rpc_address in the peer's + // cassandra yaml file; if this setting if unset, it defaults to the value for rpc_address or + // rpc_interface + InetAddress rpcAddress = peerRow.getInetAddress("rpc_address"); + + if (rpcAddress == null) { + LOG.warn( + "[{}] Found corrupted row with null rpc_address in system.peers (peer = {}), " + + "excluding from schema agreement", + logPrefix, + broadcastAddress); + return null; + } else if (rpcAddress.equals(BIND_ALL_ADDRESS)) { + LOG.warn( + "[{}] Found peer with 0.0.0.0 as rpc_address in system.peers, using peer ({}) instead", + logPrefix, + broadcastAddress); + rpcAddress = broadcastAddress; + } + return context.addressTranslator().translate(new InetSocketAddress(rpcAddress, port)); + } + + private void completeOrReschedule(Set uuids, Throwable error) { + if (error != null) { + LOG.debug( + "[{}] Error while checking schema agreement, completing now (false)", logPrefix, error); + result.complete(false); + } else if (uuids.size() == 1) { + LOG.debug( + "[{}] Schema agreement reached ({}), completing", logPrefix, uuids.iterator().next()); + result.complete(true); + } else { + LOG.debug( + "[{}] Schema agreement not reached yet ({}), rescheduling in {}", + logPrefix, + uuids, + NanoTime.format(intervalNs)); + channel + .eventLoop() + .schedule(this::sendQueries, intervalNs, TimeUnit.NANOSECONDS) + .addListener( + f -> { + if (!f.isSuccess()) { + LOG.debug( + "[{}] Error while rescheduling schema agreement, completing now (false)", + logPrefix, + f.cause()); + } + }); + } + } + + @VisibleForTesting + protected CompletionStage query(String queryString) { + return AdminRequestHandler.query( + channel, + queryString, + Collections.emptyMap(), + queryTimeout, + INFINITE_PAGE_SIZE, + logPrefix) + .start(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index b41024eb362..d882d96f38e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -85,4 +85,12 @@ public interface TopologyMonitor extends AsyncAutoCloseable { * always be returned in a single message (no paging). */ CompletionStage> refreshNodeList(); + + /** + * Checks whether the nodes in the cluster agree on a common schema version. + * + *

      This should typically be implemented with a few retries and a timeout, as the schema can + * take a while to replicate across nodes. + */ + CompletionStage checkSchemaAgreement(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 25f44b88313..bc7ff91dc2f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -443,7 +443,7 @@ private void reprepareStatements(ChannelPool pool) { logPrefix + "|" + pool.getNode().getConnectAddress(), pool, repreparePayloads, - config, + context, () -> RunOrSchedule.on(adminExecutor, () -> onPoolReady(pool))) .start(); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index beae064e39e..42a8f35137f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -21,7 +21,9 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.protocol.internal.Message; @@ -61,6 +63,7 @@ class ReprepareOnUp { private final String logPrefix; private final DriverChannel channel; private final Map repreparePayloads; + private final TopologyMonitor topologyMonitor; private final Runnable whenPrepared; private final boolean checkSystemTable; private final int maxStatements; @@ -77,14 +80,16 @@ class ReprepareOnUp { String logPrefix, ChannelPool pool, Map repreparePayloads, - DriverConfig config, + InternalDriverContext context, Runnable whenPrepared) { this.logPrefix = logPrefix; this.channel = pool.next(); this.repreparePayloads = repreparePayloads; + this.topologyMonitor = context.topologyMonitor(); this.whenPrepared = whenPrepared; + DriverConfig config = context.config(); this.checkSystemTable = config.getDefaultProfile().getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); this.timeout = config.getDefaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); @@ -103,28 +108,45 @@ void start() { LOG.debug("[{}] No channel available to reprepare, done", logPrefix); whenPrepared.run(); } else { - if (LOG.isDebugEnabled()) { // check because ConcurrentMap.size is not a constant operation - LOG.debug( - "[{}] {} statements to reprepare on newly added/up node", - logPrefix, - repreparePayloads.size()); - } - if (checkSystemTable) { - LOG.debug("[{}] Checking which statements the server knows about", logPrefix); - queryAsync(QUERY_SERVER_IDS, Collections.emptyMap(), "QUERY system.prepared_statements") - .whenComplete(this::gatherServerIds); - } else { - LOG.debug( - "[{}] {} is disabled, repreparing directly", - logPrefix, - CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); - RunOrSchedule.on( - channel.eventLoop(), - () -> { - serverKnownIds = Collections.emptySet(); - gatherPayloadsToReprepare(); - }); - } + topologyMonitor + .checkSchemaAgreement() + .whenComplete( + (agreed, error) -> { + if (error != null) { + LOG.debug( + "[{}] Error while checking schema agreement, proceeding anyway", + logPrefix, + error); + } else if (!agreed) { + LOG.debug("[{}] Did not reach schema agreement, proceeding anyway", logPrefix); + } + // Check log level because ConcurrentMap.size is not a constant operation + if (LOG.isDebugEnabled()) { + LOG.debug( + "[{}] {} statements to reprepare on newly added/up node", + logPrefix, + repreparePayloads.size()); + } + if (checkSystemTable) { + LOG.debug("[{}] Checking which statements the server knows about", logPrefix); + queryAsync( + QUERY_SERVER_IDS, + Collections.emptyMap(), + "QUERY system.prepared_statements") + .whenComplete(this::gatherServerIds); + } else { + LOG.debug( + "[{}] {} is disabled, repreparing directly", + logPrefix, + CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); + RunOrSchedule.on( + channel.eventLoop(), + () -> { + serverKnownIds = Collections.emptySet(); + gatherPayloadsToReprepare(); + }); + } + }); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java index be25faefa33..e7d290118a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/NanoTime.java @@ -25,19 +25,23 @@ public class NanoTime { /** Formats a duration in the best unit (truncating the fractional part). */ public static String formatTimeSince(long startTimeNs) { - long delta = System.nanoTime() - startTimeNs; - if (delta >= ONE_HOUR) { - return (delta / ONE_HOUR) + " h"; - } else if (delta >= ONE_MINUTE) { - return (delta / ONE_MINUTE) + " mn"; - } else if (delta >= ONE_SECOND) { - return (delta / ONE_SECOND) + " s"; - } else if (delta >= ONE_MILLISECOND) { - return (delta / ONE_MILLISECOND) + " ms"; - } else if (delta >= ONE_MICROSECOND) { - return (delta / ONE_MICROSECOND) + " us"; + return format(System.nanoTime() - startTimeNs); + } + + /** Formats a duration in the best unit (truncating the fractional part). */ + public static String format(long elapsedNs) { + if (elapsedNs >= ONE_HOUR) { + return (elapsedNs / ONE_HOUR) + " h"; + } else if (elapsedNs >= ONE_MINUTE) { + return (elapsedNs / ONE_MINUTE) + " mn"; + } else if (elapsedNs >= ONE_SECOND) { + return (elapsedNs / ONE_SECOND) + " s"; + } else if (elapsedNs >= ONE_MILLISECOND) { + return (elapsedNs / ONE_MILLISECOND) + " ms"; + } else if (elapsedNs >= ONE_MICROSECOND) { + return (elapsedNs / ONE_MICROSECOND) + " us"; } else { - return delta + " ns"; + return elapsedNs + " ns"; } } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 07bf97ad278..21831bd34ca 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -165,9 +165,41 @@ datastax-java-driver { # How long the driver waits for responses to control queries (e.g. fetching the list of # nodes, refreshing the schema). timeout = 500 milliseconds - # The page size used for control queries. If a query returns more than this number of - # results, it will be fetched in multiple requests. - page-size = 5000 + + # Due to the distributed nature of Cassandra, schema changes made on one node might not be + # immediately visible to others. Under certain circumstances, the driver waits until all nodes + # agree on a common schema version (namely: before a schema refresh, before repreparing all + # queries on a newly up node, and before completing a successful schema-altering query). + # To do so, it queries system tables to find out the schema version of all nodes that are + # currently UP. If all the versions match, the check succeeds, otherwise it is retried + # periodically, until a given timeout. + # + # A schema agreement failure is not fatal, but it might produce unexpected results (for + # example, getting an "unconfigured table" error for a table that you created right before, + # just because the two queries went to different coordinators). + # + # Note that schema agreement never succeeds in a mixed-version cluster (it would be + # challenging because the way the schema version is computed varies across server versions); + # the assumption is that schema updates are unlikely to happen during a rolling upgrade + # anyway. + schema-agreement { + # The interval between each attempt. + # This option can be changed at runtime, the new value will be used for checks issued after + # the change. + interval = 200 milliseconds + + # The timeout after which schema agreement fails. + # If this is set to 0, schema agreement is skipped and will always fail. + # This option can be changed at runtime, the new value will be used for checks issued after + # the change. + timeout = 10 seconds + + # Whether to log a warning if schema agreement fails. + # You might want to change this if you've set the timeout to 0. + # This option can be changed at runtime, the new value will be used for checks issued after + # the change. + warn-on-failure = true + } } # The component that coalesces writes on the connections. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java new file mode 100644 index 00000000000..7158b4b3102 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterators; +import io.netty.channel.EventLoop; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; + +@RunWith(MockitoJUnitRunner.class) +public class SchemaAgreementCheckerTest { + + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + private static final UUID VERSION1 = UUID.randomUUID(); + private static final UUID VERSION2 = UUID.randomUUID(); + + @Mock private InternalDriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultConfig; + @Mock private DriverChannel channel; + @Mock private EventLoop eventLoop; + @Mock private MetadataManager metadataManager; + @Mock private Metadata metadata; + @Mock private DefaultNode node1; + @Mock private DefaultNode node2; + private AddressTranslator addressTranslator; + + @Before + public void setup() { + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT)) + .thenReturn(Duration.ofSeconds(1)); + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) + .thenReturn(Duration.ofMillis(200)); + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + .thenReturn(Duration.ofSeconds(10)); + Mockito.when(defaultConfig.getBoolean(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) + .thenReturn(true); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); + Mockito.when(context.config()).thenReturn(config); + + addressTranslator = + Mockito.spy( + new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); + Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + + Map nodes = ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2); + Mockito.when(metadata.getNodes()).thenReturn(nodes); + Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + + Mockito.when(node2.getState()).thenReturn(NodeState.UP); + + Mockito.when(eventLoop.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + .thenAnswer( + invocation -> { // Ignore delay and run immediately: + Runnable task = invocation.getArgument(0); + task.run(); + return null; + }); + Mockito.when(channel.eventLoop()).thenReturn(eventLoop); + } + + @Test + public void should_skip_if_timeout_is_zero() { + // Given + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + .thenReturn(Duration.ZERO); + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isFalse()); + } + + @Test + public void should_succeed_if_only_one_node() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", mockResult(/*empty*/ ))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + } + + @Test + public void should_succeed_if_versions_match_on_first_try() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + Mockito.verify(addressTranslator).translate(ADDRESS2); + } + + @Test + public void should_ignore_down_peers() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + Mockito.when(node2.getState()).thenReturn(NodeState.DOWN); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION2)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + Mockito.verify(addressTranslator).translate(ADDRESS2); + } + + @Test + public void should_ignore_malformed_rows() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, null, VERSION2)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + Mockito.verify(addressTranslator, never()).translate(ADDRESS2); + } + + @Test + public void should_use_peer_if_rpc_address_is_0_0_0_0() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + Mockito.when(node2.getState()).thenReturn(NodeState.DOWN); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult( + mockRow( + ADDRESS2.getAddress(), SchemaAgreementChecker.BIND_ALL_ADDRESS, VERSION2)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + Mockito.verify(addressTranslator).translate(ADDRESS2); + } + + @Test + public void should_reschedule_if_versions_do_not_match_on_first_try() { + // Given + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + checker.stubQueries( + // First round + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION2))), + + // Second round + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + } + + @Test + public void should_fail_if_versions_do_not_match_after_timeout() { + // Given + Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + .thenReturn(Duration.ofNanos(10)); + TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); + checker.stubQueries( + new StubbedQuery( + "SELECT schema_version FROM system.local WHERE key='local'", + mockResult(mockRow(null, null, VERSION1))), + new StubbedQuery( + "SELECT peer, rpc_address, schema_version FROM system.peers", + mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + + // When + CompletionStage future = checker.run(); + + // Then + assertThat(future).isSuccess(b -> assertThat(b).isFalse()); + } + + /** Extend to mock the query execution logic. */ + private static class TestSchemaAgreementChecker extends SchemaAgreementChecker { + + private final Queue queries = new LinkedList<>(); + + TestSchemaAgreementChecker(DriverChannel channel, InternalDriverContext context) { + super(channel, context, 9042, "test"); + } + + private void stubQueries(StubbedQuery... queries) { + this.queries.addAll(Arrays.asList(queries)); + } + + @Override + protected CompletionStage query(String queryString) { + StubbedQuery nextQuery = queries.poll(); + assertThat(nextQuery).isNotNull(); + assertThat(nextQuery.queryString).isEqualTo(queryString); + return CompletableFuture.completedFuture(nextQuery.result); + } + } + + private static class StubbedQuery { + private final String queryString; + private final AdminResult result; + + private StubbedQuery(String queryString, AdminResult result) { + this.queryString = queryString; + this.result = result; + } + } + + private AdminRow mockRow(InetAddress peer, InetAddress rpcAddress, UUID uuid) { + AdminRow row = Mockito.mock(AdminRow.class); + Mockito.when(row.getInetAddress("peer")).thenReturn(peer); + Mockito.when(row.getInetAddress("rpc_address")).thenReturn(rpcAddress); + Mockito.when(row.getUuid("schema_version")).thenReturn(uuid); + return row; + } + + private AdminResult mockResult(AdminRow... rows) { + AdminResult result = Mockito.mock(AdminResult.class); + Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + return result; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index 3e882963a8f..d1b0ecd7888 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -21,7 +21,10 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Prepare; @@ -56,8 +59,10 @@ public class ReprepareOnUpTest { @Mock private ChannelPool pool; @Mock private DriverChannel channel; @Mock private EventLoop eventLoop; + @Mock private InternalDriverContext context; @Mock private DriverConfig config; @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private TopologyMonitor topologyMonitor; private Runnable whenPrepared; private CompletionStage done; @@ -78,6 +83,11 @@ public void setup() { .thenReturn(0); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(100); + Mockito.when(context.config()).thenReturn(config); + + Mockito.when(topologyMonitor.checkSchemaAgreement()) + .thenReturn(CompletableFuture.completedFuture(true)); + Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); done = new CompletableFuture<>(); whenPrepared = () -> ((CompletableFuture) done).complete(null); @@ -87,7 +97,7 @@ public void setup() { public void should_complete_immediately_if_no_prepared_statements() { // Given MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads(/*none*/ ), config, whenPrepared); + new MockReprepareOnUp("test", pool, getMockPayloads(/*none*/ ), context, whenPrepared); // When reprepareOnUp.start(); @@ -101,7 +111,7 @@ public void should_complete_immediately_if_pool_empty() { // Given Mockito.when(pool.next()).thenReturn(null); MockReprepareOnUp reprepareOnUp = - new MockReprepareOnUp("test", pool, getMockPayloads('a'), config, whenPrepared); + new MockReprepareOnUp("test", pool, getMockPayloads('a'), context, whenPrepared); // When reprepareOnUp.start(); @@ -114,7 +124,7 @@ public void should_complete_immediately_if_pool_empty() { public void should_reprepare_all_if_system_table_query_fails() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -138,7 +148,7 @@ public void should_reprepare_all_if_system_table_query_fails() { public void should_reprepare_all_if_system_table_empty() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -167,7 +177,7 @@ public void should_reprepare_all_if_system_query_disabled() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -186,7 +196,7 @@ public void should_reprepare_all_if_system_query_disabled() { public void should_not_reprepare_already_known_statements() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -208,6 +218,20 @@ public void should_not_reprepare_already_known_statements() { assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } + @Test + public void should_proceed_if_schema_agreement_not_reached() { + Mockito.when(topologyMonitor.checkSchemaAgreement()) + .thenReturn(CompletableFuture.completedFuture(false)); + should_not_reprepare_already_known_statements(); + } + + @Test + public void should_proceed_if_schema_agreement_fails() { + Mockito.when(topologyMonitor.checkSchemaAgreement()) + .thenReturn(CompletableFutures.failedFuture(new RuntimeException("test"))); + should_not_reprepare_already_known_statements(); + } + @Test public void should_limit_number_of_statements_to_reprepare() { Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) @@ -215,7 +239,7 @@ public void should_limit_number_of_statements_to_reprepare() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -244,7 +268,7 @@ public void should_limit_number_of_statements_reprepared_in_parallel() { MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( - "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), config, whenPrepared); + "test", pool, getMockPayloads('a', 'b', 'c', 'd', 'e', 'f'), context, whenPrepared); reprepareOnUp.start(); @@ -298,9 +322,9 @@ private static class MockReprepareOnUp extends ReprepareOnUp { String logPrefix, ChannelPool pool, Map repreparePayloads, - DriverConfig config, + InternalDriverContext context, Runnable whenPrepared) { - super(logPrefix, pool, repreparePayloads, config, whenPrepared); + super(logPrefix, pool, repreparePayloads, context, whenPrepared); } @Override From 13a6a018cf8c822d1609dfbe33e1d783dd832f48 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 13 Oct 2017 14:50:57 -0500 Subject: [PATCH 221/742] JAVA-1638: Add integration tests for schema agreement Also adds node-level start, stop, pause and resume. --- .../api/core/metadata/SchemaAgreementIT.java | 116 ++++++++++++++++++ .../driver/api/testinfra/ccm/CcmBridge.java | 16 +++ .../api/testinfra/ccm/CustomCcmRule.java | 4 + .../internal/testinfra/ccm/BaseCcmRule.java | 2 +- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java new file mode 100644 index 00000000000..d896ff70df4 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.LongTests; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +@Category(LongTests.class) +public class SchemaAgreementIT { + + private static CustomCcmRule ccm = CustomCcmRule.builder().withNodes(3).build(); + private static ClusterRule clusterRule = + ClusterRule.builder(ccm) + .withOptions( + "request.timeout = 30s", + "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", + "connection.control-connection.schema-agreement.timeout = 3s") + .build(); + + @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(ccm).around(clusterRule); + + @Rule public TestName name = new TestName(); + + @Test + public void should_succeed_when_all_nodes_agree() { + ResultSet result = createTable(); + + assertThat(result.getExecutionInfo().isSchemaInAgreement()).isTrue(); + assertThat(clusterRule.cluster().checkSchemaAgreement()).isTrue(); + } + + @Test + public void should_fail_on_timeout() { + ccm.getCcmBridge().pause(2); + try { + // Can't possibly agree since one node is paused. + ResultSet result = createTable(); + + assertThat(result.getExecutionInfo().isSchemaInAgreement()).isFalse(); + assertThat(clusterRule.cluster().checkSchemaAgreement()).isFalse(); + } finally { + ccm.getCcmBridge().resume(2); + } + } + + @Test + public void should_agree_when_up_nodes_agree() { + ccm.getCcmBridge().stop(2); + try { + // Should agree since up hosts should agree. + ResultSet result = createTable(); + + assertThat(result.getExecutionInfo().isSchemaInAgreement()).isTrue(); + assertThat(clusterRule.cluster().checkSchemaAgreement()).isTrue(); + } finally { + ccm.getCcmBridge().start(2); + } + } + + @Test + public void should_fail_if_timeout_is_zero() { + try (Cluster cluster = + ClusterUtils.newCluster( + ccm, + "request.timeout = 30s", + "connection.control-connection.schema-agreement.timeout = 0s")) { + CqlSession session = cluster.connect(clusterRule.keyspace()); + ResultSet result = createTable(session); + + // Should not agree because schema metadata is disabled + assertThat(result.getExecutionInfo().isSchemaInAgreement()).isFalse(); + assertThat(cluster.checkSchemaAgreement()).isFalse(); + } + } + + private ResultSet createTable() { + return createTable(clusterRule.session()); + } + + private final AtomicInteger tableCounter = new AtomicInteger(); + + private ResultSet createTable(CqlSession session) { + String tableName = name.getMethodName(); + if (tableName.length() > 48) { + tableName = tableName.substring(0, 44) + tableCounter.getAndIncrement(); + } + return session.execute(String.format("CREATE TABLE %s (k int primary key, v int)", tableName)); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index ced5ff1f1ac..39bce901765 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -160,6 +160,22 @@ public void remove() { execute("remove"); } + public void pause(int n) { + execute("node" + n, "pause"); + } + + public void resume(int n) { + execute("node" + n, "resume"); + } + + public void start(int n) { + execute("node" + n, "start"); + } + + public void stop(int n) { + execute("node" + n, "stop"); + } + synchronized void execute(String... args) { String command = "ccm " + String.join(" ", args) + " --config-dir=" + directory.toFile().getAbsolutePath(); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index 3943052568f..3ca936d3c54 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -52,6 +52,10 @@ protected void after() { current.compareAndSet(this, null); } + public CcmBridge getCcmBridge() { + return ccmBridge; + } + public static Builder builder() { return new Builder(); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java index a66789793a4..09768029f50 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java @@ -27,7 +27,7 @@ public abstract class BaseCcmRule extends CassandraResourceRule { - private final CcmBridge ccmBridge; + protected final CcmBridge ccmBridge; private final CassandraVersion cassandraVersion; From 61dfe29764f949ce05a7f83f9ade89e024a4d6e4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Oct 2017 13:37:07 -0700 Subject: [PATCH 222/742] Fix raw usages of Cluster --- .../datastax/oss/driver/api/core/Cluster.java | 8 ++++---- .../api/core/metadata/NodeStateListener.java | 4 ++-- .../metadata/schema/SchemaChangeListener.java | 5 +++-- .../schema/SchemaChangeListenerBase.java | 4 ++-- .../driver/internal/core/ClusterWrapper.java | 20 +++++++++++-------- .../driver/internal/core/DefaultCluster.java | 8 ++++---- .../core/compression/DirectCompressionIT.java | 5 +++-- .../core/compression/HeapCompressionIT.java | 5 +++-- .../driver/api/core/metadata/DescribeIT.java | 2 +- .../driver/api/core/metadata/NodeStateIT.java | 8 ++++---- .../driver/api/core/metadata/SchemaIT.java | 14 ++++++++----- .../api/testinfra/cluster/ClusterUtils.java | 2 +- 12 files changed, 48 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index 9309ad3aa35..bdbd0d3e1c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -193,26 +193,26 @@ default SessionT connect() { * *

      This is a no-op if the listener was registered already. */ - Cluster register(SchemaChangeListener listener); + Cluster register(SchemaChangeListener listener); /** * Unregisters the provided schema change listener. * *

      This is a no-op if the listener was not registered. */ - Cluster unregister(SchemaChangeListener listener); + Cluster unregister(SchemaChangeListener listener); /** * Registers the provided node state listener. * *

      This is a no-op if the listener was registered already. */ - Cluster register(NodeStateListener listener); + Cluster register(NodeStateListener listener); /** * Unregisters the provided node state listener. * *

      This is a no-op if the listener was not registered. */ - Cluster unregister(NodeStateListener listener); + Cluster unregister(NodeStateListener listener); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java index 3c0e839ffc3..ad5eee47e84 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java @@ -63,11 +63,11 @@ public interface NodeStateListener { void onRemove(Node node); /** Invoked when the listener is registered with a cluster. */ - void onRegister(Cluster cluster); + void onRegister(Cluster cluster); /** * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever * comes first. */ - void onUnregister(Cluster cluster); + void onUnregister(Cluster cluster); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java index 59ac343bf49..93b40df2bca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java @@ -66,12 +66,13 @@ public interface SchemaChangeListener { void onViewDropped(ViewMetadata view); void onViewUpdated(ViewMetadata current, ViewMetadata previous); + /** Invoked when the listener is registered with a cluster. */ - void onRegister(Cluster cluster); + void onRegister(Cluster cluster); /** * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever * comes first. */ - void onUnregister(Cluster cluster); + void onUnregister(Cluster cluster); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java index 986c96a9098..4cd78dd9d9b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java @@ -81,8 +81,8 @@ public void onViewDropped(ViewMetadata view) {} public void onViewUpdated(ViewMetadata current, ViewMetadata previous) {} @Override - public void onRegister(Cluster cluster) {} + public void onRegister(Cluster cluster) {} @Override - public void onUnregister(Cluster cluster) {} + public void onUnregister(Cluster cluster) {} } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java index acb713f9ad8..803febb167b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java @@ -77,23 +77,27 @@ public CompletionStage connectAsync(CqlIdentifier keyspace) { } @Override - public Cluster register(SchemaChangeListener listener) { - return delegate.register(listener); + public Cluster register(SchemaChangeListener listener) { + delegate.register(listener); + return this; } @Override - public Cluster unregister(SchemaChangeListener listener) { - return delegate.unregister(listener); + public Cluster unregister(SchemaChangeListener listener) { + delegate.unregister(listener); + return this; } @Override - public Cluster register(NodeStateListener listener) { - return delegate.register(listener); + public Cluster register(NodeStateListener listener) { + delegate.register(listener); + return this; } @Override - public Cluster unregister(NodeStateListener listener) { - return delegate.unregister(listener); + public Cluster unregister(NodeStateListener listener) { + delegate.unregister(listener); + return this; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index a60725c553d..e782e4adb95 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -125,25 +125,25 @@ public CompletionStage connectAsync(CqlIdentifier keyspace) { } @Override - public Cluster register(SchemaChangeListener listener) { + public Cluster register(SchemaChangeListener listener) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); return this; } @Override - public Cluster unregister(SchemaChangeListener listener) { + public Cluster unregister(SchemaChangeListener listener) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); return this; } @Override - public Cluster register(NodeStateListener listener) { + public Cluster register(NodeStateListener listener) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); return this; } @Override - public Cluster unregister(NodeStateListener listener) { + public Cluster unregister(NodeStateListener listener) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); return this; } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 5425bdfe0ab..af1370c8637 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; @@ -75,8 +76,8 @@ public void should_execute_queries_with_lz4_compression() throws Exception { private void createAndCheckCluster(String compressorOption) { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { - Session session = cluster.connect(schemaClusterRule.keyspace()); + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { + CqlSession session = cluster.connect(schemaClusterRule.keyspace()); // Run a couple of simple test queries ResultSet rs = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index 65c7deeb3e6..906360b050a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; @@ -81,8 +82,8 @@ public void should_execute_queries_with_lz4_compression() throws Exception { private void createAndCheckCluster(String compressorOption) { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { - Session session = cluster.connect(schemaClusterRule.keyspace()); + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { + CqlSession session = cluster.connect(schemaClusterRule.keyspace()); // Run a couple of simple test queries ResultSet rs = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index cf662a54820..7fdd5d059ae 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -195,7 +195,7 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { // Also validate that when you create a Cluster with schema already created that the exported string // is the same. - try (Cluster newCluster = ClusterUtils.newCluster(ccmRule)) { + try (Cluster newCluster = ClusterUtils.newCluster(ccmRule)) { ks = newCluster.getMetadata().getKeyspace(keyspace); assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index afb90e62e0c..316e646bc39 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -407,7 +407,7 @@ public void should_signal_non_contact_points_as_added() { InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - try (Cluster localCluster = + try (Cluster localCluster = Cluster.builder() .addContactPoint(address1) .addNodeStateListeners(localNodeStateListener) @@ -490,7 +490,7 @@ public void should_mark_unreachable_contact_point_down() { // Since contact points are shuffled, we have a 50% chance that our bad contact point will be // hit first. So we retry the scenario a few times if needed. for (int i = 0; i < 10; i++) { - try (Cluster localCluster = + try (Cluster localCluster = Cluster.builder() .addContactPoint(address1) .addContactPoint(address2) @@ -530,9 +530,9 @@ public void should_mark_unreachable_contact_point_down() { @Test public void should_call_onRegister_and_onUnregister_implicitly() { NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - Cluster localClusterRef; + Cluster localClusterRef; // onRegister should be called implicitly when added as a listener on builder. - try (Cluster localCluster = + try (Cluster localCluster = Cluster.builder() .addContactPoints(simulacron.getContactPoints()) .addNodeStateListeners(localNodeStateListener) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 1e83dbebe26..cf307ff687c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; @@ -53,7 +54,7 @@ public void should_expose_system_and_test_keyspace() { @Test public void should_filter_by_keyspaces() { - try (Cluster cluster = + try (Cluster cluster = ClusterUtils.newCluster( ccmRule, String.format( @@ -71,7 +72,8 @@ public void should_filter_by_keyspaces() { @Test public void should_not_load_schema_if_disabled_in_config() { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (Cluster cluster = + ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); @@ -80,7 +82,8 @@ public void should_not_load_schema_if_disabled_in_config() { @Test public void should_enable_schema_programmatically_when_disabled_in_config() { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (Cluster cluster = + ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); @@ -104,7 +107,7 @@ public void should_enable_schema_programmatically_when_disabled_in_config() { @Test public void should_disable_schema_programmatically_when_enabled_in_config() { - Cluster cluster = clusterRule.cluster(); + Cluster cluster = clusterRule.cluster(); cluster.setSchemaMetadataEnabled(false); assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); @@ -131,7 +134,8 @@ public void should_disable_schema_programmatically_when_enabled_in_config() { @Test public void should_refresh_schema_manually() { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (Cluster cluster = + ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index fb77dd859ef..776e5d2b5e6 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -65,7 +65,7 @@ public static Cluster newCluster( return newCluster(cassandraResource, new NodeStateListener[0], options); } - public static Cluster newCluster( + public static Cluster newCluster( CassandraResourceRule cassandraResource, NodeStateListener[] nodeStateListeners, String... options) { From b159b2d31fe996282b5e119f1919057398bbe3ca Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Sep 2017 15:39:38 -0700 Subject: [PATCH 223/742] JAVA-1525: Handle token metadata --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 1 + .../oss/driver/api/core/cql/ResultSet.java | 36 + .../driver/api/core/data/GettableById.java | 24 + .../driver/api/core/data/GettableByIndex.java | 35 + .../driver/api/core/data/GettableByName.java | 24 + .../driver/api/core/data/SettableById.java | 17 + .../driver/api/core/data/SettableByIndex.java | 29 + .../driver/api/core/data/SettableByName.java | 17 + .../driver/api/core/metadata/Metadata.java | 22 + .../oss/driver/api/core/metadata/Node.java | 2 - .../driver/api/core/metadata/TokenMap.java | 98 +++ .../driver/api/core/metadata/token/Token.java | 19 + .../api/core/metadata/token/TokenRange.java | 140 ++++ .../core/context/DefaultDriverContext.java | 13 + .../core/context/InternalDriverContext.java | 3 + .../driver/internal/core/cql/Conversions.java | 28 +- .../core/cql/CqlRequestHandlerBase.java | 10 +- .../core/cql/DefaultPreparedStatement.java | 27 +- .../core/metadata/AddNodeRefresh.java | 7 +- .../core/metadata/DefaultMetadata.java | 114 ++- .../internal/core/metadata/DefaultNode.java | 11 + .../core/metadata/DefaultNodeInfo.java | 13 + .../core/metadata/DefaultTopologyMonitor.java | 1 + .../core/metadata/FullNodeListRefresh.java | 36 +- .../metadata/InitContactPointsRefresh.java | 5 +- .../core/metadata/MetadataManager.java | 27 +- .../core/metadata/MetadataRefresh.java | 2 +- .../internal/core/metadata/NodeInfo.java | 12 + .../internal/core/metadata/NodesRefresh.java | 19 +- .../core/metadata/RemoveNodeRefresh.java | 4 +- .../core/metadata/TokensChangedRefresh.java | 28 + .../schema/refresh/SchemaRefresh.java | 4 +- .../core/metadata/token/ByteOrderedToken.java | 79 ++ .../token/ByteOrderedTokenFactory.java | 65 ++ .../metadata/token/ByteOrderedTokenRange.java | 139 ++++ .../token/DefaultTokenFactoryRegistry.java | 49 ++ .../core/metadata/token/DefaultTokenMap.java | 265 +++++++ .../core/metadata/token/KeyspaceTokenMap.java | 127 ++++ .../token/LocalReplicationStrategy.java | 36 + .../core/metadata/token/Murmur3Token.java | 64 ++ .../metadata/token/Murmur3TokenFactory.java | 190 +++++ .../metadata/token/Murmur3TokenRange.java | 61 ++ .../NetworkTopologyReplicationStrategy.java | 168 +++++ .../core/metadata/token/RandomToken.java | 64 ++ .../metadata/token/RandomTokenFactory.java | 101 +++ .../core/metadata/token/RandomTokenRange.java | 61 ++ .../metadata/token/ReplicationStrategy.java | 45 ++ .../token/SimpleReplicationStrategy.java | 69 ++ .../core/metadata/token/TokenFactory.java | 38 + .../metadata/token/TokenFactoryRegistry.java | 21 + .../core/metadata/token/TokenRangeBase.java | 286 +++++++ .../internal/core/session/DefaultSession.java | 47 +- .../driver/internal/core/util/RoutingKey.java | 44 ++ core/src/main/resources/reference.conf | 28 +- .../com/datastax/oss/driver/Assertions.java | 6 + .../core/cql/RequestHandlerTestHarness.java | 4 + .../core/metadata/AddNodeRefreshTest.java | 8 +- .../metadata/DefaultMetadataTokenMapTest.java | 125 ++++ .../metadata/FullNodeListRefreshTest.java | 19 +- .../InitContactPointsRefreshTest.java | 2 +- .../core/metadata/RemoveNodeRefreshTest.java | 8 +- .../schema/refresh/SchemaRefreshTest.java | 12 +- .../token/ByteOrderedTokenRangeTest.java | 59 ++ .../metadata/token/DefaultTokenMapTest.java | 346 +++++++++ .../metadata/token/Murmur3TokenRangeTest.java | 80 ++ ...etworkTopologyReplicationStrategyTest.java | 701 ++++++++++++++++++ .../metadata/token/RandomTokenRangeTest.java | 81 ++ .../token/SimpleReplicationStrategyTest.java | 215 ++++++ .../core/metadata/token/TokenRangeAssert.java | 100 +++ .../core/metadata/token/TokenRangeTest.java | 284 +++++++ .../api/core/metadata/ByteOrderedTokenIT.java | 58 ++ .../metadata/ByteOrderedTokenVnodesIT.java | 62 ++ .../api/core/metadata/Murmur3TokenIT.java | 56 ++ .../core/metadata/Murmur3TokenVnodesIT.java | 58 ++ .../api/core/metadata/RandomTokenIT.java | 58 ++ .../core/metadata/RandomTokenVnodesIT.java | 62 ++ .../driver/api/core/metadata/TokenITBase.java | 344 +++++++++ .../driver/api/testinfra/ccm/CcmBridge.java | 23 +- .../api/testinfra/ccm/CustomCcmRule.java | 5 + 80 files changed, 5555 insertions(+), 97 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Token.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedToken.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/LocalReplicationStrategy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3Token.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRange.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomToken.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactoryRegistry.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeBase.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/RoutingKey.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java diff --git a/changelog/README.md b/changelog/README.md index 7acb688002a..a28bd115ce7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha2 (in progress) +- [new feature] JAVA-1525: Handle token metadata - [new feature] JAVA-1638: Check schema agreement - [new feature] JAVA-1494: Implement Snappy and LZ4 compression - [new feature] JAVA-1514: Port Uuids utility class diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 1d785c843ec..cb33305782b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -95,6 +95,7 @@ public enum CoreDriverOption implements DriverOption { METADATA_SCHEMA_REFRESHED_KEYSPACES("metadata.schema.refreshed-keyspaces", false), METADATA_SCHEMA_WINDOW("metadata.schema.debouncer.window", true), METADATA_SCHEMA_MAX_EVENTS("metadata.schema.debouncer.max-events", true), + METADATA_TOKEN_MAP_ENABLED("metadata.token-map.enabled", true), TIMESTAMP_GENERATOR_ROOT("request.timestamp-generator", true), RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 64beb3533d4..119d4f8543d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -16,6 +16,10 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.session.Session; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import java.util.Collections; +import java.util.Iterator; import java.util.List; /** @@ -64,6 +68,38 @@ default ExecutionInfo getExecutionInfo() { */ List getExecutionInfos(); + /** + * Returns the next row, or {@code null} if the result set is exhausted. + * + *

      This is convenient for queries that are known to return exactly one row, for example count + * queries. + */ + default Row one() { + Iterator iterator = iterator(); + return iterator.hasNext() ? iterator.next() : null; + } + + /** + * Returns all the remaining rows as a list; not recommended for queries that return a large + * number of rows. + * + *

      Contrary to {@link #iterator()} or successive calls to {@link #one()}, this method forces + * fetching the full contents of the result set at once; in particular, this means that a + * large number of background queries might have to be run, and that all the data will be held in + * memory locally. Therefore it is crucial to only call this method for queries that are known to + * return a reasonable number of results. + */ + default List all() { + if (!iterator().hasNext()) { + return Collections.emptyList(); + } + // We can't know the actual size in advance since more pages could be fetched, but we can at + // least allocate for what we already have. + List result = Lists.newArrayListWithExpectedSize(getAvailableWithoutFetching()); + Iterables.addAll(result, this); + return result; + } + /** * Whether all pages have been fetched from the database. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java index 70c6ee81421..b8f72f2ba6a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; @@ -488,6 +489,29 @@ default CqlDuration getCqlDuration(CqlIdentifier id) { return getCqlDuration(firstIndexOf(id)); } + /** + * Returns the value for the first occurrence of {@code id} as a token. + * + *

      Note that, for simplicity, this method relies on the CQL type of the column to pick the + * correct token implementation. Therefore it must only be called on columns of the type that + * matches the partitioner in use for this cluster: {@code bigint} for {@code Murmur3Partitioner}, + * {@code blob} for {@code ByteOrderedPartitioner}, and {@code varint} for {@code + * RandomPartitioner}. Calling it for the wrong type will produce corrupt tokens that are unusable + * with this driver instance. + * + *

      If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the column type can not be converted to a known token type. + */ + default Token getToken(CqlIdentifier id) { + return getToken(firstIndexOf(id)); + } + /** * Returns the value for the first occurrence of {@code id} as a Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java index 4fb907edc45..f7fcbbbb552 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.api.core.data; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.PrimitiveBooleanCodec; import com.datastax.oss.driver.api.core.type.codec.PrimitiveByteCodec; @@ -26,6 +28,9 @@ import com.datastax.oss.driver.api.core.type.codec.PrimitiveShortCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -384,6 +389,36 @@ default CqlDuration getCqlDuration(int i) { return get(i, CqlDuration.class); } + /** + * Returns the {@code i}th value as a token. + * + *

      Note that, for simplicity, this method relies on the CQL type of the column to pick the + * correct token implementation. Therefore it must only be called on columns of the type that + * matches the partitioner in use for this cluster: {@code bigint} for {@code Murmur3Partitioner}, + * {@code blob} for {@code ByteOrderedPartitioner}, and {@code varint} for {@code + * RandomPartitioner}. Calling it for the wrong type will produce corrupt tokens that are unusable + * with this driver instance. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the column type can not be converted to a known token type. + */ + default Token getToken(int i) { + DataType type = getType(i); + // Simply enumerate all known implementations. This goes against the concept of TokenFactory, + // but injecting the factory here is too much of a hassle. + // The only issue is if someone uses a custom partitioner, but this is highly unlikely, and even + // then they can get the value manually as a workaround. + if (type.equals(DataTypes.BIGINT)) { + return isNull(i) ? null : new Murmur3Token(getLong(i)); + } else if (type.equals(DataTypes.BLOB)) { + return isNull(i) ? null : new ByteOrderedToken(getByteBuffer(i)); + } else if (type.equals(DataTypes.VARINT)) { + return isNull(i) ? null : new RandomToken(getBigInteger(i)); + } else { + throw new IllegalArgumentException("Can't convert CQL type " + type + " into a token"); + } + } + /** * Returns the {@code i}th value as a Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java index 6e7d49b5c17..e15f6b7c213 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; @@ -484,6 +485,29 @@ default CqlDuration getCqlDuration(String name) { return getCqlDuration(firstIndexOf(name)); } + /** + * Returns the value for the first occurrence of {@code name} as a token. + * + *

      Note that, for simplicity, this method relies on the CQL type of the column to pick the + * correct token implementation. Therefore it must only be called on columns of the type that + * matches the partitioner in use for this cluster: {@code bigint} for {@code Murmur3Partitioner}, + * {@code blob} for {@code ByteOrderedPartitioner}, and {@code varint} for {@code + * RandomPartitioner}. Calling it for the wrong type will produce corrupt tokens that are unusable + * with this driver instance. + * + *

      If an identifier appears multiple times, this can only be used to access the first value. + * For the other ones, use positional getters. + * + *

      This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the column type can not be converted to a known token type. + */ + default Token getToken(String name) { + return getToken(firstIndexOf(name)); + } + /** * Returns the value for the first occurrence of {@code name} as a Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index 051e08e414c..172ab649809 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.data; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -387,6 +388,22 @@ default T setCqlDuration(CqlIdentifier id, CqlDuration v) { return setCqlDuration(firstIndexOf(id), v); } + /** + * Sets the value for the first occurrence of {@code id} to the provided token. + * + *

      This works with the CQL type matching the partitioner in use for this cluster: {@code + * bigint} for {@code Murmur3Partitioner}, {@code blob} for {@code ByteOrderedPartitioner}, and + * {@code varint} for {@code RandomPartitioner}. + * + *

      If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of + * this method that takes a string argument. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToken(CqlIdentifier id, Token v) { + return setToken(firstIndexOf(id), v); + } + /** * Sets the value for the first occurrence of {@code id} to the provided Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java index 41cda0ee105..9548770eed4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.PrimitiveBooleanCodec; @@ -26,6 +27,9 @@ import com.datastax.oss.driver.api.core.type.codec.PrimitiveShortCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -352,6 +356,31 @@ default T setCqlDuration(int i, CqlDuration v) { return set(i, v, CqlDuration.class); } + /** + * Sets the {@code i}th value to the provided token. + * + *

      This works with the CQL type matching the partitioner in use for this cluster: {@code + * bigint} for {@code Murmur3Partitioner}, {@code blob} for {@code ByteOrderedPartitioner}, and + * {@code varint} for {@code RandomPartitioner}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToken(int i, Token v) { + // Simply enumerate all known implementations. This goes against the concept of TokenFactory, + // but injecting the factory here is too much of a hassle. + // The only issue is if someone uses a custom partitioner, but this is highly unlikely, and even + // then they can set the value manually as a workaround. + if (v instanceof Murmur3Token) { + return setLong(i, ((Murmur3Token) v).getValue()); + } else if (v instanceof ByteOrderedToken) { + return setByteBuffer(i, ((ByteOrderedToken) v).getValue()); + } else if (v instanceof RandomToken) { + return setBigInteger(i, ((RandomToken) v).getValue()); + } else { + throw new IllegalArgumentException("Unsupported token type " + v.getClass()); + } + } + /** * Sets the {@code i}th value to the provided Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index 9037a0e0507..b60b8f2f4e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -387,6 +388,22 @@ default T setCqlDuration(String name, CqlDuration v) { return setCqlDuration(firstIndexOf(name), v); } + /** + * Sets the value for the first occurrence of {@code name} to the provided token. + * + *

      This works with the CQL type matching the partitioner in use for this cluster: {@code + * bigint} for {@code Murmur3Partitioner}, {@code blob} for {@code ByteOrderedPartitioner}, and + * {@code varint} for {@code RandomPartitioner}. + * + *

      This method deals with case sensitivity in the way explained in the documentation of {@link + * AccessibleByName}. + * + * @throws IndexOutOfBoundsException if the index is invalid. + */ + default T setToken(String name, Token v) { + return setToken(firstIndexOf(name), v); + } + /** * Sets the value for the first occurrence of {@code name} to the provided Java list. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index de249a0ed6b..ecef7d2abb4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -17,9 +17,11 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import java.net.InetSocketAddress; import java.util.Map; +import java.util.Optional; /** * The metadata of the Cassandra cluster that this driver instance is connected to. @@ -38,9 +40,29 @@ public interface Metadata { */ Map getNodes(); + /** + * The keyspaces defined in this cluster. + * + *

      Note that schema metadata can be disabled or restricted to a subset of keyspaces, therefore + * this map might be empty or incomplete. + * + * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @see Cluster#setSchemaMetadataEnabled(Boolean) + * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES + */ Map getKeyspaces(); default KeyspaceMetadata getKeyspace(CqlIdentifier keyspaceId) { return getKeyspaces().get(keyspaceId); } + + /** + * The token map for this cluster. + * + *

      Note that this property might be absent if token metadata was disabled, or if there was a + * runtime error while computing the map (this would generate a warning log). + * + * @see CoreDriverOption#METADATA_TOKEN_MAP_ENABLED + */ + Optional getTokenMap(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index b83de4517e3..d0025c2a511 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -58,8 +58,6 @@ public interface Node { CassandraVersion getCassandraVersion(); - // TODO tokens? (might be better to have a method on TokenMap) - /** * An additional map of free-form properties. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java new file mode 100644 index 00000000000..b4578d7f138 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.google.common.collect.ImmutableSet; +import java.nio.ByteBuffer; +import java.util.Set; + +/** + * Utility component to work with the tokens of a given driver instance. + * + *

      Note that the methods that take a keyspace argument are based on schema metadata, which can be + * disabled or restricted to a subset of keyspaces; therefore these methods might return empty + * results for some or all of the keyspaces. + * + * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @see Cluster#setSchemaMetadataEnabled(Boolean) + * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES + */ +public interface TokenMap { + + /** Builds a token from its string representation. */ + Token parse(String tokenString); + + /** Formats a token into a string representation appropriate for concatenation in a CQL query. */ + String format(Token token); + + /** + * Builds a token from a partition key. + * + * @param partitionKey the partition key components, in their serialized form (which can be + * obtained with {@link TypeCodec#encode(Object, ProtocolVersion)} + */ + Token newToken(ByteBuffer... partitionKey); + + TokenRange newTokenRange(Token start, Token end); + + /** The token ranges that define data distribution on the ring. */ + Set getTokenRanges(); + + /** The token ranges for which a given node is the primary replica. */ + Set getTokenRanges(Node node); + + /** + * The tokens owned by the given node. + * + *

      This is functionally equivalent to {@code getTokenRanges(node).map(r -> r.getEnd())}. Note + * that the set is rebuilt every time you call this method. + */ + default Set getTokens(Node node) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (TokenRange range : getTokenRanges(node)) { + result.add(range.getEnd()); + } + return result.build(); + } + + /** The token ranges that are replicated on the given node, for the given keyspace. */ + Set getTokenRanges(CqlIdentifier keyspace, Node replica); + + /** The replicas for a given partition key in the given keyspace. */ + Set getReplicas(CqlIdentifier keyspace, ByteBuffer partitionKey); + + /** The replicas for a given token in the given keyspace. */ + Set getReplicas(CqlIdentifier keyspace, Token token); + + /** + * The replicas for a given range in the given keyspace. + * + *

      It is assumed that the input range does not overlap across multiple node ranges. If the + * range extends over multiple nodes, it only returns the nodes that are replicas for the last + * token of the range. In other words, this method is a shortcut for {@code getReplicas(keyspace, + * range.getEnd())}. + */ + default Set getReplicas(CqlIdentifier keyspace, TokenRange range) { + return getReplicas(keyspace, range.getEnd()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Token.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Token.java new file mode 100644 index 00000000000..c6ec17e5569 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Token.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.token; + +/** A token on the ring. */ +public interface Token extends Comparable {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java new file mode 100644 index 00000000000..8b87a85d7f0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata.token; + +import java.util.List; + +/** + * A range of tokens on the Cassandra ring. + * + *

      A range is start-exclusive and end-inclusive. It is empty when start and end are the same + * token, except if that is the minimum token, in which case the range covers the whole ring (this + * is consistent with the behavior of CQL range queries). + * + *

      Note that CQL does not handle wrapping. To query all partitions in a range, see {@link + * #unwrap()}. + */ +public interface TokenRange extends Comparable { + + /** The start of the range (exclusive). */ + Token getStart(); + + /** The end of the range (inclusive). */ + Token getEnd(); + + /** + * Splits this range into a number of smaller ranges of equal "size" (referring to the number of + * tokens, not the actual amount of data). + * + *

      Splitting an empty range is not permitted. But note that, in edge cases, splitting a range + * might produce one or more empty ranges. + * + * @throws IllegalArgumentException if the range is empty or if numberOfSplits < 1. + */ + List splitEvenly(int numberOfSplits); + + /** + * Whether this range is empty. + * + *

      A range is empty when {@link #getStart()} and {@link #getEnd()} are the same token, except + * if that is the minimum token, in which case the range covers the whole ring (this is consistent + * with the behavior of CQL range queries). + */ + boolean isEmpty(); + + /** Whether this range wraps around the end of the ring. */ + boolean isWrappedAround(); + + /** Whether this range represents the full ring. */ + boolean isFullRing(); + + /** + * Splits this range into a list of two non-wrapping ranges. This will return the range itself if + * it is non-wrapping, or two ranges otherwise. + * + *

      For example: + * + *

        + *
      • {@code ]1,10]} unwraps to itself; + *
      • {@code ]10,1]} unwraps to {@code ]10,min_token]} and {@code ]min_token,1]}. + *
      + * + *

      This is useful for CQL range queries, which do not handle wrapping: + * + *

      {@code
      +   * List rows = new ArrayList();
      +   * for (TokenRange subRange : range.unwrap()) {
      +   *     ResultSet rs = session.execute(
      +   *         "SELECT * FROM mytable WHERE token(pk) > ? and token(pk) <= ?",
      +   *         subRange.getStart(), subRange.getEnd());
      +   *     rows.addAll(rs.all());
      +   * }
      +   * }
      + */ + List unwrap(); + + /** + * Whether this range intersects another one. + * + *

      For example: + * + *

        + *
      • {@code ]3,5]} intersects {@code ]1,4]}, {@code ]4,5]}... + *
      • {@code ]3,5]} does not intersect {@code ]1,2]}, {@code ]2,3]}, {@code ]5,7]}... + *
      + */ + boolean intersects(TokenRange that); + + /** + * Computes the intersection of this range with another one, producing one or more ranges. + * + *

      If either of these ranges overlap the the ring, they are unwrapped and the unwrapped ranges + * are compared to one another. + * + *

      This call will fail if the two ranges do not intersect, you must check by calling {@link + * #intersects(TokenRange)} first. + * + * @param that the other range. + * @return the range(s) resulting from the intersection. + * @throws IllegalArgumentException if the ranges do not intersect. + */ + List intersectWith(TokenRange that); + + /** + * Checks whether this range contains a given token, i.e. {@code range.start < token <= + * range.end}. + */ + boolean contains(Token token); + + /** + * Merges this range with another one. + * + *

      The two ranges should either intersect or be adjacent; in other words, the merged range + * should not include tokens that are in neither of the original ranges. + * + *

      For example: + * + *

        + *
      • merging {@code ]3,5]} with {@code ]4,7]} produces {@code ]3,7]}; + *
      • merging {@code ]3,5]} with {@code ]4,5]} produces {@code ]3,5]}; + *
      • merging {@code ]3,5]} with {@code ]5,8]} produces {@code ]3,8]}; + *
      • merging {@code ]3,5]} with {@code ]6,8]} fails. + *
      + * + * @throws IllegalArgumentException if the ranges neither intersect nor are adjacent. + */ + TokenRange mergeWith(TokenRange that); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 4caea1a3867..bb8be632313 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -44,6 +44,8 @@ import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.DefaultSchemaQueriesFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenFactoryRegistry; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; @@ -132,6 +134,8 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("schemaQueriesFactory", this::buildSchemaQueriesFactory, cycleDetector); private final LazyReference schemaParserFactoryRef = new LazyReference<>("schemaParserFactory", this::buildSchemaParserFactory, cycleDetector); + private final LazyReference tokenFactoryRegistryRef = + new LazyReference<>("tokenFactoryRegistry", this::buildTokenFactoryRegistry, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -298,6 +302,10 @@ protected SchemaParserFactory buildSchemaParserFactory() { return new DefaultSchemaParserFactory(this); } + protected TokenFactoryRegistry buildTokenFactoryRegistry() { + return new DefaultTokenFactoryRegistry(this); + } + @Override public String clusterName() { return clusterName; @@ -433,6 +441,11 @@ public SchemaParserFactory schemaParserFactory() { return schemaParserFactoryRef.get(); } + @Override + public TokenFactoryRegistry tokenFactoryRegistry() { + return tokenFactoryRegistryRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 2f91e7cc460..2e283f6a68a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -70,4 +71,6 @@ public interface InternalDriverContext extends DriverContext { SchemaQueriesFactory schemaQueriesFactory(); SchemaParserFactory schemaParserFactory(); + + TokenFactoryRegistry tokenFactoryRegistry(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 8c2a33e5774..fb5cfd57e51 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.servererrors.AlreadyExistsException; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -50,8 +51,12 @@ import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Batch; @@ -180,7 +185,7 @@ private static List encode( } else { List encodedValues = new ArrayList<>(values.size()); for (Object value : values) { - encodedValues.add(codecRegistry.codecFor(value).encode(value, protocolVersion)); + encodedValues.add(encode(value, codecRegistry, protocolVersion)); } return encodedValues; } @@ -193,14 +198,29 @@ private static Map encode( } else { ImmutableMap.Builder encodedValues = ImmutableMap.builder(); for (Map.Entry entry : values.entrySet()) { - encodedValues.put( - entry.getKey(), - codecRegistry.codecFor(entry.getValue()).encode(entry.getValue(), protocolVersion)); + encodedValues.put(entry.getKey(), encode(entry.getValue(), codecRegistry, protocolVersion)); } return encodedValues.build(); } } + private static ByteBuffer encode( + Object value, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { + if (value instanceof Token) { + if (value instanceof Murmur3Token) { + return TypeCodecs.BIGINT.encode(((Murmur3Token) value).getValue(), protocolVersion); + } else if (value instanceof ByteOrderedToken) { + return TypeCodecs.BLOB.encode(((ByteOrderedToken) value).getValue(), protocolVersion); + } else if (value instanceof RandomToken) { + return TypeCodecs.VARINT.encode(((RandomToken) value).getValue(), protocolVersion); + } else { + throw new IllegalArgumentException("Unsupported token type " + value.getClass()); + } + } else { + return codecRegistry.codecFor(value).encode(value, protocolVersion); + } + } + static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index cd43a4e8b09..988cbaf8592 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -302,11 +302,6 @@ private void setFinalResult( Conversions.toResultSet(resultMessage, executionInfo, session, context); if (result.complete(resultSet)) { cancelScheduledTasks(); - if (resultMessage instanceof SetKeyspace) { - CqlIdentifier newKeyspace = - CqlIdentifier.fromInternal(((SetKeyspace) resultMessage).keyspace); - session.setKeyspace(newKeyspace); - } } } catch (Throwable error) { setFinalError(error); @@ -412,6 +407,11 @@ public void onResponse(Frame responseFrame) { .whenComplete( ((schemaInAgreement, error) -> setFinalResult(schemaChange, responseFrame, schemaInAgreement, this))); + } else if (responseMessage instanceof SetKeyspace) { + SetKeyspace setKeyspace = (SetKeyspace) responseMessage; + session + .setKeyspace(CqlIdentifier.fromInternal(setKeyspace.keyspace)) + .whenComplete((v, error) -> setFinalResult(setKeyspace, responseFrame, true, this)); } else if (responseMessage instanceof Result) { LOG.debug("[{}] Got result, completing", logPrefix); setFinalResult((Result) responseMessage, responseFrame, true, this); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 118c7ef758a..5025d37a796 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -21,8 +21,13 @@ import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.protocol.internal.ProtocolConstants; import java.nio.ByteBuffer; @@ -100,8 +105,26 @@ public BoundStatement bind(Object... values) { ByteBuffer[] encodedValues = new ByteBuffer[variableDefinitions.size()]; int i; for (i = 0; i < values.length; i++) { - TypeCodec codec = codecRegistry.codecFor(variableDefinitions.get(i).getType()); - encodedValues[i] = codec.encode(values[i], protocolVersion); + Object value = values[i]; + ByteBuffer encodedValue; + if (value instanceof Token) { + if (value instanceof Murmur3Token) { + encodedValue = + TypeCodecs.BIGINT.encode(((Murmur3Token) value).getValue(), protocolVersion); + } else if (value instanceof ByteOrderedToken) { + encodedValue = + TypeCodecs.BLOB.encode(((ByteOrderedToken) value).getValue(), protocolVersion); + } else if (value instanceof RandomToken) { + encodedValue = + TypeCodecs.VARINT.encode(((RandomToken) value).getValue(), protocolVersion); + } else { + throw new IllegalArgumentException("Unsupported token type " + value.getClass()); + } + } else { + TypeCodec codec = codecRegistry.codecFor(variableDefinitions.get(i).getType()); + encodedValue = codec.encode(value, protocolVersion); + } + encodedValues[i] = encodedValue; } for (; i < encodedValues.length; i++) { encodedValues[i] = ProtocolConstants.UNSET_VALUE; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 9c10bff3f31..a5e2fba8672 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -32,20 +32,21 @@ public class AddNodeRefresh extends NodesRefresh { } @Override - public Result compute(DefaultMetadata oldMetadata) { + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { Map oldNodes = oldMetadata.getNodes(); if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { return new Result(oldMetadata); } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); - copyInfos(newNodeInfo, newNode, logPrefix); + copyInfos(newNodeInfo, newNode, null, logPrefix); Map newNodes = ImmutableMap.builder() .putAll(oldNodes) .put(newNode.getConnectAddress(), newNode) .build(); return new Result( - oldMetadata.withNodes(newNodes), ImmutableList.of(NodeStateEvent.added(newNode))); + oldMetadata.withNodes(newNodes, tokenMapEnabled, false, null), + ImmutableList.of(NodeStateEvent.added(newNode))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 01161e53411..69a0e607a07 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -18,11 +18,18 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenMap; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; +import com.datastax.oss.driver.internal.core.util.NanoTime; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Collections; import java.util.Map; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class is immutable, so that metadata changes are atomic for the client. Every mutation @@ -30,20 +37,27 @@ * MetadataManager}'s volatile field. */ public class DefaultMetadata implements Metadata { - public static final DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); + private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadata.class); + public static DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap(), null); private final Map nodes; private final Map keyspaces; - // TODO token map + private final Optional tokenMap; + private final String logPrefix; - public DefaultMetadata(Map nodes) { - this(ImmutableMap.copyOf(nodes), Collections.emptyMap()); + public DefaultMetadata(Map nodes, String logPrefix) { + this(ImmutableMap.copyOf(nodes), Collections.emptyMap(), Optional.empty(), logPrefix); } private DefaultMetadata( - Map nodes, Map keyspaces) { + Map nodes, + Map keyspaces, + Optional tokenMap, + String logPrefix) { this.nodes = nodes; this.keyspaces = keyspaces; + this.tokenMap = tokenMap; + this.logPrefix = logPrefix; } @Override @@ -56,13 +70,91 @@ public Map getKeyspaces() { return keyspaces; } - public DefaultMetadata withNodes(Map newNodes) { - // TODO recompute token map - return new DefaultMetadata(ImmutableMap.copyOf(newNodes), this.keyspaces); + @Override + public Optional getTokenMap() { + return tokenMap; + } + + /** + * @param tokenMapEnabled + * @param tokensChanged whether we observed a change of tokens for at least one node. This will + * require a full rebuild of the token map. + * @param tokenFactory only needed for the initial refresh, afterwards the existing one in the + * token map is used. + */ + public DefaultMetadata withNodes( + Map newNodes, + boolean tokenMapEnabled, + boolean tokensChanged, + TokenFactory tokenFactory) { + + // Force a rebuild if at least one node has different tokens, or there are new or removed nodes. + boolean forceFullRebuild = tokensChanged || !newNodes.equals(nodes); + + return new DefaultMetadata( + ImmutableMap.copyOf(newNodes), + this.keyspaces, + rebuildTokenMap(newNodes, keyspaces, tokenMapEnabled, forceFullRebuild, tokenFactory), + logPrefix); + } + + public DefaultMetadata withSchema( + Map newKeyspaces, boolean tokenMapEnabled) { + return new DefaultMetadata( + this.nodes, + ImmutableMap.copyOf(newKeyspaces), + rebuildTokenMap(nodes, newKeyspaces, tokenMapEnabled, false, null), + logPrefix); } - public DefaultMetadata withKeyspaces(Map newKeyspaces) { - // TODO recompute token map - return new DefaultMetadata(this.nodes, ImmutableMap.copyOf(newKeyspaces)); + private Optional rebuildTokenMap( + Map newNodes, + Map newKeyspaces, + boolean tokenMapEnabled, + boolean forceFullRebuild, + TokenFactory tokenFactory) { + + if (!tokenMapEnabled) { + LOG.debug("[{}] Token map is disabled, skipping", logPrefix); + return this.tokenMap; + } + long start = System.nanoTime(); + try { + DefaultTokenMap oldTokenMap = (DefaultTokenMap) this.tokenMap.orElse(null); + if (oldTokenMap == null) { + // Initial build, we need the token factory + if (tokenFactory == null) { + LOG.debug( + "[{}] Building initial token map but the token factory is missing, skipping", + logPrefix); + return this.tokenMap; + } else { + LOG.debug("[{}] Building initial token map", logPrefix); + return Optional.of( + DefaultTokenMap.build( + newNodes.values(), newKeyspaces.values(), tokenFactory, logPrefix)); + } + } else if (forceFullRebuild) { + LOG.debug( + "[{}] Updating token map but some nodes/tokens have changed, full rebuild", logPrefix); + return Optional.of( + DefaultTokenMap.build( + newNodes.values(), + newKeyspaces.values(), + oldTokenMap.getTokenFactory(), + logPrefix)); + } else { + LOG.debug("[{}] Refreshing token map (only schema has changed)", logPrefix); + return Optional.of(oldTokenMap.refresh(newNodes.values(), newKeyspaces.values())); + } + } catch (Throwable t) { + LOG.warn( + "[{}] Unexpected error while refreshing token map, keeping previous version", + logPrefix, + t); + return this.tokenMap; + } finally { + LOG.debug("[{}] Rebuilding token map took {}", logPrefix, NanoTime.formatTimeSince(start)); + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 4d1a46c0b00..077f529deb1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -21,8 +21,10 @@ import com.datastax.oss.driver.api.core.metadata.NodeState; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.Set; /** * Implementation note: all the mutable state in this class is read concurrently, but only mutated @@ -37,6 +39,8 @@ public class DefaultNode implements Node { volatile String datacenter; volatile String rack; volatile CassandraVersion cassandraVersion; + // Keep a copy of the raw tokens, to detect if they have changed when we refresh the node + volatile Set rawTokens; volatile Map extras; // These 3 fields are read concurrently, but only mutated on NodeStateManager's admin thread @@ -50,6 +54,8 @@ public DefaultNode(InetSocketAddress connectAddress) { this.connectAddress = connectAddress; this.state = NodeState.UNKNOWN; this.distance = NodeDistance.IGNORED; + this.rawTokens = Collections.emptySet(); + this.extras = Collections.emptyMap(); } @Override @@ -127,4 +133,9 @@ public int hashCode() { public String toString() { return connectAddress.toString(); } + + /** Note: deliberately not exposed by the public interface. */ + public Set getRawTokens() { + return rawTokens; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java index d53966b66fa..3c8667e3b9d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -34,6 +34,7 @@ public static Builder builder() { private final String datacenter; private final String rack; private final String cassandraVersion; + private final String partitioner; private final Set tokens; private final Map extras; @@ -44,6 +45,7 @@ private DefaultNodeInfo(Builder builder) { this.datacenter = builder.datacenter; this.rack = builder.rack; this.cassandraVersion = builder.cassandraVersion; + this.partitioner = builder.partitioner; this.tokens = (builder.tokens == null) ? Collections.emptySet() : builder.tokens; this.extras = (builder.extras == null) ? Collections.emptyMap() : builder.extras; } @@ -78,6 +80,11 @@ public String getCassandraVersion() { return cassandraVersion; } + @Override + public String getPartitioner() { + return partitioner; + } + @Override public Set getTokens() { return tokens; @@ -95,6 +102,7 @@ public static class Builder { private String datacenter; private String rack; private String cassandraVersion; + private String partitioner; private Set tokens; private Map extras; @@ -132,6 +140,11 @@ public Builder withCassandraVersion(String cassandraVersion) { return this; } + public Builder withPartitioner(String partitioner) { + this.partitioner = partitioner; + return this; + } + public Builder withTokens(Set tokens) { this.tokens = tokens; return this; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index a827b292607..86fccce5ee4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -209,6 +209,7 @@ private NodeInfo buildNodeInfo(AdminRow row, InetSocketAddress connectAddress) { builder.withRack(row.getString("rack")); builder.withCassandraVersion(row.getString("release_version")); builder.withTokens(row.getSetOfString("tokens")); + builder.withPartitioner(row.getString("partitioner")); return builder.build(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index e9a78af9f96..61786c690f7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -16,6 +16,10 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenMap; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -33,19 +37,25 @@ class FullNodeListRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); @VisibleForTesting final Iterable nodeInfos; + private final TokenFactoryRegistry tokenFactoryRegistry; - FullNodeListRefresh(Iterable nodeInfos, String logPrefix) { - super(logPrefix); + FullNodeListRefresh(Iterable nodeInfos, InternalDriverContext context) { + super(context.clusterName()); this.nodeInfos = nodeInfos; + this.tokenFactoryRegistry = context.tokenFactoryRegistry(); } @Override - public Result compute(DefaultMetadata oldMetadata) { + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { Map oldNodes = oldMetadata.getNodes(); Map added = new HashMap<>(); Set seen = new HashSet<>(); + TokenFactory tokenFactory = + oldMetadata.getTokenMap().map(m -> ((DefaultTokenMap) m).getTokenFactory()).orElse(null); + boolean tokensChanged = false; + for (NodeInfo nodeInfo : nodeInfos) { InetSocketAddress address = nodeInfo.getConnectAddress(); if (address == null) { @@ -59,13 +69,24 @@ public Result compute(DefaultMetadata oldMetadata) { LOG.debug("[{}] Adding new node {}", logPrefix, node); added.put(address, node); } - copyInfos(nodeInfo, node, logPrefix); + if (tokenFactory == null && nodeInfo.getPartitioner() != null) { + tokenFactory = tokenFactoryRegistry.tokenFactoryFor(nodeInfo.getPartitioner()); + } + tokensChanged |= copyInfos(nodeInfo, node, tokenFactory, logPrefix); } Set removed = Sets.difference(oldNodes.keySet(), seen); if (added.isEmpty() && removed.isEmpty()) { - return new Result(oldMetadata); + // Edge case: if all the nodes of the cluster were listed as contact points, and this is the + // first refresh, we get here so we need to set the token factory and trigger a token map + // rebuild: + if (!oldMetadata.getTokenMap().isPresent() && tokenFactory != null) { + return new Result( + oldMetadata.withNodes(oldMetadata.getNodes(), tokenMapEnabled, true, tokenFactory)); + } else { + return new Result(oldMetadata); + } } else { ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); ImmutableList.Builder eventsBuilder = ImmutableList.builder(); @@ -85,7 +106,10 @@ public Result compute(DefaultMetadata oldMetadata) { eventsBuilder.add(NodeStateEvent.removed((DefaultNode) node)); } - return new Result(oldMetadata.withNodes(newNodesBuilder.build()), eventsBuilder.build()); + return new Result( + oldMetadata.withNodes( + newNodesBuilder.build(), tokenMapEnabled, tokensChanged, tokenFactory), + eventsBuilder.build()); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index 35b54271b79..fd69ce3c198 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -19,7 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; -import java.util.Collections; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +35,7 @@ class InitContactPointsRefresh extends MetadataRefresh { } @Override - public Result compute(DefaultMetadata oldMetadata) { + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { assert oldMetadata == DefaultMetadata.EMPTY; LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); @@ -44,7 +43,7 @@ public Result compute(DefaultMetadata oldMetadata) { for (InetSocketAddress address : contactPoints) { newNodes.put(address, new DefaultNode(address)); } - return new Result(new DefaultMetadata(newNodes.build())); + return new Result(new DefaultMetadata(newNodes.build(), logPrefix)); // No token map refresh, because we don't have enough information yet } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 26bc267c356..96a9cc272e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -58,6 +58,7 @@ public class MetadataManager implements AsyncAutoCloseable { private volatile boolean schemaEnabledInConfig; private volatile List refreshedKeyspaces; private volatile Boolean schemaEnabledProgrammatically; + private volatile boolean tokenMapEnabled; public MetadataManager(InternalDriverContext context) { this.context = context; @@ -72,12 +73,14 @@ public MetadataManager(InternalDriverContext context) { config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) : Collections.emptyList(); + this.tokenMapEnabled = config.getBoolean(CoreDriverOption.METADATA_TOKEN_MAP_ENABLED); context.eventBus().register(ConfigChangeEvent.class, this::onConfigChanged); } private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event) { - boolean wasEnabledBefore = isSchemaEnabled(); + boolean schemaEnabledBefore = isSchemaEnabled(); + boolean tokenMapEnabledBefore = tokenMapEnabled; List keyspacesBefore = this.refreshedKeyspaces; this.schemaEnabledInConfig = config.getBoolean(CoreDriverOption.METADATA_SCHEMA_ENABLED); @@ -85,8 +88,12 @@ private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) : Collections.emptyList(); + this.tokenMapEnabled = config.getBoolean(CoreDriverOption.METADATA_TOKEN_MAP_ENABLED); - if ((!wasEnabledBefore || !keyspacesBefore.equals(refreshedKeyspaces)) && isSchemaEnabled()) { + if ((!schemaEnabledBefore + || !keyspacesBefore.equals(refreshedKeyspaces) + || (!tokenMapEnabledBefore && tokenMapEnabled)) + && isSchemaEnabled()) { refreshSchema(null, false, true); } } @@ -118,11 +125,14 @@ public CompletionStage refreshNode(Node node) { return context .topologyMonitor() .refreshNode(node) - // The callback only updates volatile fields so no need to schedule it on adminExecutor - .thenApply( + .thenApplyAsync( maybeInfo -> { if (maybeInfo.isPresent()) { - NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node, logPrefix); + boolean tokensChanged = + NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node, null, logPrefix); + if (tokensChanged) { + apply(new TokensChangedRefresh(logPrefix)); + } } else { LOG.debug( "[{}] Topology monitor did not return any info for the refresh of {}, skipping", @@ -130,7 +140,8 @@ public CompletionStage refreshNode(Node node) { node); } return null; - }); + }, + adminExecutor); } public void addNode(InetSocketAddress address) { @@ -250,7 +261,7 @@ private void initNodes( private Void refreshNodes(Iterable nodeInfos) { didFirstNodeListRefresh = true; - return apply(new FullNodeListRefresh(nodeInfos, logPrefix)); + return apply(new FullNodeListRefresh(nodeInfos, context)); } private void addNode(InetSocketAddress address, Optional maybeInfo) { @@ -420,7 +431,7 @@ private void close() { @VisibleForTesting Void apply(MetadataRefresh refresh) { assert adminExecutor.inEventLoop(); - MetadataRefresh.Result result = refresh.compute(metadata); + MetadataRefresh.Result result = refresh.compute(metadata, tokenMapEnabled); metadata = result.newMetadata; boolean isFirstSchemaRefresh = refresh instanceof SchemaRefresh && !singleThreaded.firstSchemaRefreshFuture.isDone(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java index 483c5a16d61..e77d4bc24ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -40,7 +40,7 @@ protected MetadataRefresh(String logPrefix) { this.logPrefix = logPrefix; } - public abstract Result compute(DefaultMetadata oldMetadata); + public abstract Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled); public static class Result { public final DefaultMetadata newMetadata; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java index 315c4882d79..e51a370cfbc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -85,6 +86,17 @@ public interface NodeInfo { */ String getCassandraVersion(); + /** + * The fully-qualifier name of the partitioner class that distributes data across the nodes, as it + * appears in {@code system.local.partitioner}. + * + *

      This is used to compute the driver-side token metadata (in particular, token-aware routing + * relies on this information). It is only really needed for the first node of the initial node + * list refresh (but it doesn't hurt to always include it if possible). If it is absent, {@link + * Metadata#getTokenMap()} will remain empty. + */ + String getPartitioner(); + /** * The tokens that this node owns on the ring. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 99a9c35845d..6583109af6e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.CassandraVersion; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; import com.google.common.collect.ImmutableMap; import java.util.Collections; import org.slf4j.Logger; @@ -29,7 +30,12 @@ protected NodesRefresh(String logPrefix) { super(logPrefix); } - protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node, String logPrefix) { + /** + * @return whether the node's token have changed as a result of this operation (unfortunately we + * mutate the tokens in-place, so there is no way to check this after the fact). + */ + protected static boolean copyInfos( + NodeInfo nodeInfo, DefaultNode node, TokenFactory tokenFactory, String logPrefix) { node.broadcastAddress = nodeInfo.getBroadcastAddress(); node.listenAddress = nodeInfo.getListenAddress(); node.datacenter = nodeInfo.getDatacenter(); @@ -38,11 +44,20 @@ protected static void copyInfos(NodeInfo nodeInfo, DefaultNode node, String logP try { node.cassandraVersion = CassandraVersion.parse(versionString); } catch (IllegalArgumentException e) { - LOG.warn("[{}] Error converting Cassandra version '{}'", logPrefix, versionString); + LOG.warn( + "[{}] Error converting Cassandra version '{}' for {}", + logPrefix, + versionString, + node.getConnectAddress()); + } + boolean tokensChanged = tokenFactory != null && !node.rawTokens.equals(nodeInfo.getTokens()); + if (tokensChanged) { + node.rawTokens = nodeInfo.getTokens(); } node.extras = (nodeInfo.getExtras() == null) ? Collections.emptyMap() : ImmutableMap.copyOf(nodeInfo.getExtras()); + return tokensChanged; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 97137a3ff6e..3922333576c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -36,7 +36,7 @@ public class RemoveNodeRefresh extends NodesRefresh { } @Override - public Result compute(DefaultMetadata oldMetadata) { + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { Map oldNodes = oldMetadata.getNodes(); Node node = oldNodes.get(toRemove); if (node == null) { @@ -52,7 +52,7 @@ public Result compute(DefaultMetadata oldMetadata) { } } return new Result( - oldMetadata.withNodes(newNodesBuilder.build()), + oldMetadata.withNodes(newNodesBuilder.build(), tokenMapEnabled, false, null), ImmutableList.of(NodeStateEvent.removed((DefaultNode) node))); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java new file mode 100644 index 00000000000..f75d3a98bb5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +class TokensChangedRefresh extends MetadataRefresh { + + TokensChangedRefresh(String logPrefix) { + super(logPrefix); + } + + @Override + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + return new Result(oldMetadata.withNodes(oldMetadata.getNodes(), tokenMapEnabled, true, null)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java index 45d80608995..f8e3c4af54a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java @@ -43,7 +43,7 @@ public SchemaRefresh(Map newKeyspaces, String l } @Override - public Result compute(DefaultMetadata oldMetadata) { + public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { ImmutableList.Builder events = ImmutableList.builder(); Map oldKeyspaces = oldMetadata.getKeyspaces(); @@ -55,7 +55,7 @@ public Result compute(DefaultMetadata oldMetadata) { computeEvents(oldKeyspaces.get(key), entry.getValue(), events); } - return new Result(oldMetadata.withKeyspaces(this.newKeyspaces), events.build()); + return new Result(oldMetadata.withSchema(this.newKeyspaces, tokenMapEnabled), events.build()); } private static boolean shallowEquals(KeyspaceMetadata keyspace1, KeyspaceMetadata keyspace2) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedToken.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedToken.java new file mode 100644 index 00000000000..07333c71fbb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedToken.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.base.Preconditions; +import com.google.common.primitives.UnsignedBytes; +import java.nio.ByteBuffer; + +/** A token generated by {@code ByteOrderedPartitioner}. */ +public class ByteOrderedToken implements Token { + + private final ByteBuffer value; + + public ByteOrderedToken(ByteBuffer value) { + this.value = stripTrailingZeroBytes(value); + } + + public ByteBuffer getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ByteOrderedToken) { + ByteOrderedToken that = (ByteOrderedToken) other; + return this.value.equals(that.getValue()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Token other) { + Preconditions.checkArgument( + other instanceof ByteOrderedToken, "Can only compare tokens of the same type"); + return UnsignedBytes.lexicographicalComparator() + .compare(Bytes.getArray(value), Bytes.getArray(((ByteOrderedToken) other).value)); + } + + @Override + public String toString() { + return "ByteOrderedToken(" + Bytes.toHexString(value) + ")"; + } + + private static ByteBuffer stripTrailingZeroBytes(ByteBuffer b) { + byte result[] = Bytes.getArray(b); + int zeroIndex = result.length; + for (int i = result.length - 1; i > 0; i--) { + if (result[i] == 0) { + zeroIndex = i; + } else { + break; + } + } + return ByteBuffer.wrap(result, 0, zeroIndex); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java new file mode 100644 index 00000000000..6ddcd9aed5e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.base.Preconditions; +import java.nio.ByteBuffer; + +public class ByteOrderedTokenFactory implements TokenFactory { + + public static final ByteOrderedToken MIN_TOKEN = new ByteOrderedToken(ByteBuffer.allocate(0)); + + @Override + public Token hash(ByteBuffer partitionKey) { + return new ByteOrderedToken(partitionKey); + } + + @Override + public Token parse(String tokenString) { + // This method must be able to parse the contents of system.peers.tokens, which do not have the + // "0x" prefix. On the other hand, OPPToken#toString has the "0x" because it should be usable in + // a CQL query, and it's nice to have fromString and toString symmetrical. So handle both cases: + if (!tokenString.startsWith("0x")) { + String prefix = (tokenString.length() % 2 == 0) ? "0x" : "0x0"; + tokenString = prefix + tokenString; + } + ByteBuffer value = Bytes.fromHexString(tokenString); + return new ByteOrderedToken(value); + } + + @Override + public String format(Token token) { + Preconditions.checkArgument( + token instanceof ByteOrderedToken, "Can only format ByteOrderedToken instances"); + return Bytes.toHexString(((ByteOrderedToken) token).getValue()); + } + + @Override + public Token minToken() { + return MIN_TOKEN; + } + + @Override + public TokenRange range(Token start, Token end) { + Preconditions.checkArgument( + start instanceof ByteOrderedToken && end instanceof ByteOrderedToken, + "Can only build ranges of ByteOrderedToken instances"); + return new ByteOrderedTokenRange(((ByteOrderedToken) start), ((ByteOrderedToken) end)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java new file mode 100644 index 00000000000..7e093fa0fbd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.Lists; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.List; + +public class ByteOrderedTokenRange extends TokenRangeBase { + + private static final BigInteger TWO = BigInteger.valueOf(2); + + public ByteOrderedTokenRange(ByteOrderedToken start, ByteOrderedToken end) { + super(start, end, ByteOrderedTokenFactory.MIN_TOKEN); + } + + @Override + protected TokenRange newTokenRange(Token start, Token end) { + return new ByteOrderedTokenRange(((ByteOrderedToken) start), ((ByteOrderedToken) end)); + } + + @Override + protected List split(Token rawStartToken, Token rawEndToken, int numberOfSplits) { + int tokenOrder = rawStartToken.compareTo(rawEndToken); + + // ]min,min] means the whole ring. However, since there is no "max token" with this partitioner, + // we can't come up with a magic end value that would cover the whole ring + if (tokenOrder == 0 && rawStartToken.equals(ByteOrderedTokenFactory.MIN_TOKEN)) { + throw new IllegalArgumentException("Cannot split whole ring with ordered partitioner"); + } + + ByteOrderedToken startToken = (ByteOrderedToken) rawStartToken; + ByteOrderedToken endToken = (ByteOrderedToken) rawEndToken; + + int significantBytes; + BigInteger start, end, range, ringEnd, ringLength; + BigInteger bigNumberOfSplits = BigInteger.valueOf(numberOfSplits); + if (tokenOrder < 0) { + // Since tokens are compared lexicographically, convert to integers using the largest length + // (ex: given 0x0A and 0x0BCD, switch to 0x0A00 and 0x0BCD) + significantBytes = Math.max(startToken.getValue().capacity(), endToken.getValue().capacity()); + + // If the number of splits does not fit in the difference between the two integers, use more + // bytes (ex: cannot fit 4 splits between 0x01 and 0x03, so switch to 0x0100 and 0x0300) + // At most 4 additional bytes will be needed, since numberOfSplits is an integer. + int addedBytes = 0; + while (true) { + start = toBigInteger(startToken.getValue(), significantBytes); + end = toBigInteger(endToken.getValue(), significantBytes); + range = end.subtract(start); + if (addedBytes == 4 || range.compareTo(bigNumberOfSplits) >= 0) { + break; + } + significantBytes += 1; + addedBytes += 1; + } + ringEnd = ringLength = null; // won't be used + } else { + // Same logic except that we wrap around the ring + significantBytes = Math.max(startToken.getValue().capacity(), endToken.getValue().capacity()); + int addedBytes = 0; + while (true) { + start = toBigInteger(startToken.getValue(), significantBytes); + end = toBigInteger(endToken.getValue(), significantBytes); + ringLength = TWO.pow(significantBytes * 8); + ringEnd = ringLength.subtract(BigInteger.ONE); + range = end.subtract(start).add(ringLength); + if (addedBytes == 4 || range.compareTo(bigNumberOfSplits) >= 0) { + break; + } + significantBytes += 1; + addedBytes += 1; + } + } + + List values = super.split(start, range, ringEnd, ringLength, numberOfSplits); + List tokens = Lists.newArrayListWithExpectedSize(values.size()); + for (BigInteger value : values) { + tokens.add(new ByteOrderedToken(toBytes(value, significantBytes))); + } + return tokens; + } + + // Convert a token's byte array to a number in order to perform computations. + // This depends on the number of "significant bytes" that we use to normalize all tokens to the same size. + // For example if the token is 0x01 but significantBytes is 2, the result is 8 (0x0100). + private BigInteger toBigInteger(ByteBuffer bb, int significantBytes) { + byte[] bytes = Bytes.getArray(bb); + byte[] target; + if (significantBytes != bytes.length) { + target = new byte[significantBytes]; + System.arraycopy(bytes, 0, target, 0, bytes.length); + } else { + target = bytes; + } + return new BigInteger(1, target); + } + + // Convert a numeric representation back to a byte array. + // Again, the number of significant bytes matters: if the input value is 1 but significantBytes is 2, the + // expected result is 0x0001 (a simple conversion would produce 0x01). + protected ByteBuffer toBytes(BigInteger value, int significantBytes) { + byte[] rawBytes = value.toByteArray(); + byte[] result; + if (rawBytes.length == significantBytes) { + result = rawBytes; + } else { + result = new byte[significantBytes]; + int start, length; + if (rawBytes[0] == 0) { + // that's a sign byte, ignore (it can cause rawBytes.length == significantBytes + 1) + start = 1; + length = rawBytes.length - 1; + } else { + start = 0; + length = rawBytes.length; + } + System.arraycopy(rawBytes, start, result, significantBytes - length, length); + } + return ByteBuffer.wrap(result); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java new file mode 100644 index 00000000000..cf66b6eb911 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultTokenFactoryRegistry implements TokenFactoryRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenFactoryRegistry.class); + + private final String logPrefix; + + public DefaultTokenFactoryRegistry(InternalDriverContext context) { + this.logPrefix = context.clusterName(); + } + + @Override + public TokenFactory tokenFactoryFor(String partitioner) { + if (partitioner.endsWith("Murmur3Partitioner")) { + LOG.debug("[{}] Detected Murmur3 partitioner ({})", logPrefix, partitioner); + return new Murmur3TokenFactory(); + } else if (partitioner.endsWith("RandomPartitioner")) { + LOG.debug("[{}] Detected random partitioner ({})", logPrefix, partitioner); + return new RandomTokenFactory(); + } else if (partitioner.endsWith("OrderedPartitioner")) { + LOG.debug("[{}] Detected byte ordered partitioner ({})", logPrefix, partitioner); + return new ByteOrderedTokenFactory(); + } else { + LOG.warn( + "[{}] Unsupported partitioner '{}', token map will be empty.", logPrefix, partitioner); + return null; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java new file mode 100644 index 00000000000..75b7ede2e67 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.TokenMap; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.util.RoutingKey; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultTokenMap implements TokenMap { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenMap.class); + + public static DefaultTokenMap build( + Collection nodes, + Collection keyspaces, + TokenFactory tokenFactory, + String logPrefix) { + + TokenToPrimaryAndRing tmp = buildTokenToPrimaryAndRing(nodes, tokenFactory); + Map tokenToPrimary = tmp.tokenToPrimary; + List ring = tmp.ring; + LOG.debug("[{}] Rebuilt ring ({} tokens)", logPrefix, ring.size()); + + Set tokenRanges = buildTokenRanges(ring, tokenFactory); + + ImmutableSetMultimap.Builder tokenRangesByPrimary = + ImmutableSetMultimap.builder(); + for (TokenRange range : tokenRanges) { + if (range.isFullRing()) { + // The full ring is always ]min, min], so getEnd() doesn't match the node's token + assert tokenToPrimary.size() == 1; + tokenRangesByPrimary.put(tokenToPrimary.values().iterator().next(), range); + } else { + tokenRangesByPrimary.put(tokenToPrimary.get(range.getEnd()), range); + } + } + + Map> replicationConfigs = + buildReplicationConfigs(keyspaces, logPrefix); + + ImmutableMap.Builder, KeyspaceTokenMap> keyspaceMapsBuilder = + ImmutableMap.builder(); + for (Map config : ImmutableSet.copyOf(replicationConfigs.values())) { + LOG.debug("[{}] Computing keyspace-level data for {}", logPrefix, config); + keyspaceMapsBuilder.put( + config, + KeyspaceTokenMap.build( + config, tokenToPrimary, ring, tokenRanges, tokenFactory, logPrefix)); + } + return new DefaultTokenMap( + tokenFactory, + tokenRanges, + tokenRangesByPrimary.build(), + replicationConfigs, + keyspaceMapsBuilder.build(), + logPrefix); + } + + private final TokenFactory tokenFactory; + @VisibleForTesting final Set tokenRanges; + @VisibleForTesting final SetMultimap tokenRangesByPrimary; + @VisibleForTesting final Map> replicationConfigs; + @VisibleForTesting final Map, KeyspaceTokenMap> keyspaceMaps; + private final String logPrefix; + + public DefaultTokenMap( + TokenFactory tokenFactory, + Set tokenRanges, + SetMultimap tokenRangesByPrimary, + Map> replicationConfigs, + Map, KeyspaceTokenMap> keyspaceMaps, + String logPrefix) { + this.tokenFactory = tokenFactory; + this.tokenRanges = tokenRanges; + this.tokenRangesByPrimary = tokenRangesByPrimary; + this.replicationConfigs = replicationConfigs; + this.keyspaceMaps = keyspaceMaps; + this.logPrefix = logPrefix; + } + + public TokenFactory getTokenFactory() { + return tokenFactory; + } + + @Override + public Token parse(String tokenString) { + return tokenFactory.parse(tokenString); + } + + @Override + public String format(Token token) { + return tokenFactory.format(token); + } + + @Override + public Token newToken(ByteBuffer... partitionKey) { + return tokenFactory.hash(RoutingKey.compose(partitionKey)); + } + + @Override + public TokenRange newTokenRange(Token start, Token end) { + return tokenFactory.range(start, end); + } + + @Override + public Set getTokenRanges() { + return tokenRanges; + } + + @Override + public Set getTokenRanges(Node node) { + return tokenRangesByPrimary.get(node); + } + + @Override + public Set getTokenRanges(CqlIdentifier keyspace, Node replica) { + KeyspaceTokenMap keyspaceMap = getKeyspaceMap(keyspace); + return (keyspaceMap == null) ? Collections.emptySet() : keyspaceMap.getTokenRanges(replica); + } + + @Override + public Set getReplicas(CqlIdentifier keyspace, ByteBuffer partitionKey) { + KeyspaceTokenMap keyspaceMap = getKeyspaceMap(keyspace); + return (keyspaceMap == null) ? Collections.emptySet() : keyspaceMap.getReplicas(partitionKey); + } + + @Override + public Set getReplicas(CqlIdentifier keyspace, Token token) { + KeyspaceTokenMap keyspaceMap = getKeyspaceMap(keyspace); + return (keyspaceMap == null) ? Collections.emptySet() : keyspaceMap.getReplicas(token); + } + + private KeyspaceTokenMap getKeyspaceMap(CqlIdentifier keyspace) { + Map config = replicationConfigs.get(keyspace); + return (config == null) ? null : keyspaceMaps.get(config); + } + + /** Called when only the schema has changed. */ + public DefaultTokenMap refresh(Collection nodes, Collection keyspaces) { + + Map> newReplicationConfigs = + buildReplicationConfigs(keyspaces, logPrefix); + if (newReplicationConfigs.equals(replicationConfigs)) { + LOG.debug("[{}] Schema changes do not impact the token map, no refresh needed", logPrefix); + return this; + } + ImmutableMap.Builder, KeyspaceTokenMap> newKeyspaceMapsBuilder = + ImmutableMap.builder(); + + // Will only be built if needed: + Map tokenToPrimary = null; + List ring = null; + + for (Map config : ImmutableSet.copyOf(newReplicationConfigs.values())) { + KeyspaceTokenMap oldKeyspaceMap = keyspaceMaps.get(config); + if (oldKeyspaceMap != null) { + LOG.debug("[{}] Reusing existing keyspace-level data for {}", logPrefix, config); + newKeyspaceMapsBuilder.put(config, oldKeyspaceMap); + } else { + LOG.debug("[{}] Computing new keyspace-level data for {}", logPrefix, config); + if (tokenToPrimary == null) { + TokenToPrimaryAndRing tmp = buildTokenToPrimaryAndRing(nodes, tokenFactory); + tokenToPrimary = tmp.tokenToPrimary; + ring = tmp.ring; + } + newKeyspaceMapsBuilder.put( + config, + KeyspaceTokenMap.build( + config, tokenToPrimary, ring, tokenRanges, tokenFactory, logPrefix)); + } + } + return new DefaultTokenMap( + tokenFactory, + tokenRanges, + tokenRangesByPrimary, + newReplicationConfigs, + newKeyspaceMapsBuilder.build(), + logPrefix); + } + + private static TokenToPrimaryAndRing buildTokenToPrimaryAndRing( + Collection nodes, TokenFactory tokenFactory) { + ImmutableMap.Builder tokenToPrimaryBuilder = ImmutableMap.builder(); + SortedSet sortedTokens = new TreeSet<>(); + for (Node node : nodes) { + for (String tokenString : ((DefaultNode) node).getRawTokens()) { + Token token = tokenFactory.parse(tokenString); + sortedTokens.add(token); + tokenToPrimaryBuilder.put(token, node); + } + } + return new TokenToPrimaryAndRing( + tokenToPrimaryBuilder.build(), ImmutableList.copyOf(sortedTokens)); + } + + static class TokenToPrimaryAndRing { + final Map tokenToPrimary; + final List ring; + + private TokenToPrimaryAndRing(Map tokenToPrimary, List ring) { + this.tokenToPrimary = tokenToPrimary; + this.ring = ring; + } + } + + private static Map> buildReplicationConfigs( + Collection keyspaces, String logPrefix) { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (KeyspaceMetadata keyspace : keyspaces) { + builder.put(keyspace.getName(), keyspace.getReplication()); + } + ImmutableMap> result = builder.build(); + LOG.trace("[{}] Computing keyspace-level data for {}", logPrefix, result); + return result; + } + + private static Set buildTokenRanges(List ring, TokenFactory factory) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + // JAVA-684: if there is only one token, return the full ring (]minToken, minToken]) + if (ring.size() == 1) { + builder.add(factory.range(factory.minToken(), factory.minToken())); + } else { + for (int i = 0; i < ring.size(); i++) { + Token start = ring.get(i); + Token end = ring.get((i + 1) % ring.size()); + builder.add(factory.range(start, end)); + } + } + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java new file mode 100644 index 00000000000..879cfa3911b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The token data for a given replication configuration. It's shared by all keyspaces that use that + * configuration. + */ +class KeyspaceTokenMap { + + private static final Logger LOG = LoggerFactory.getLogger(KeyspaceTokenMap.class); + + static KeyspaceTokenMap build( + Map replicationConfig, + Map tokenToPrimary, + List ring, + Set tokenRanges, + TokenFactory tokenFactory, + String logPrefix) { + + long start = System.nanoTime(); + try { + ReplicationStrategy strategy = ReplicationStrategy.newInstance(replicationConfig, logPrefix); + + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + SetMultimap tokenRangesByNode; + if (ring.size() == 1) { + // We forced the single range to ]minToken,minToken], make sure to use that instead of relying + // on the node's token + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + for (Node node : tokenToPrimary.values()) { + builder.putAll(node, tokenRanges); + } + tokenRangesByNode = builder.build(); + } else { + tokenRangesByNode = buildTokenRangesByNode(tokenRanges, replicasByToken); + } + return new KeyspaceTokenMap(ring, tokenRangesByNode, replicasByToken, tokenFactory); + } finally { + LOG.trace( + "[{}] Computing keyspace-level data for {} took {}", + logPrefix, + replicationConfig, + NanoTime.formatTimeSince(start)); + } + } + + private final List ring; + private final SetMultimap tokenRangesByNode; + private final SetMultimap replicasByToken; + private final TokenFactory tokenFactory; + + private KeyspaceTokenMap( + List ring, + SetMultimap tokenRangesByNode, + SetMultimap replicasByToken, + TokenFactory tokenFactory) { + this.ring = ring; + this.tokenRangesByNode = tokenRangesByNode; + this.replicasByToken = replicasByToken; + this.tokenFactory = tokenFactory; + } + + Set getTokenRanges(Node replica) { + return tokenRangesByNode.get(replica); + } + + Set getReplicas(ByteBuffer partitionKey) { + return getReplicas(tokenFactory.hash(partitionKey)); + } + + Set getReplicas(Token token) { + // If the token happens to be one of the "primary" tokens, get result directly + Set nodes = replicasByToken.get(token); + if (!nodes.isEmpty()) { + return nodes; + } + // Otherwise, find the closest "primary" token on the ring + int i = Collections.binarySearch(ring, token); + if (i < 0) { + i = -i - 1; + if (i >= ring.size()) { + i = 0; + } + } + return replicasByToken.get(ring.get(i)); + } + + private static SetMultimap buildTokenRangesByNode( + Set tokenRanges, SetMultimap replicasByToken) { + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (TokenRange range : tokenRanges) { + for (Node node : replicasByToken.get(range.getEnd())) { + result.put(node, range); + } + } + return result.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/LocalReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/LocalReplicationStrategy.java new file mode 100644 index 00000000000..d46a24bdf23 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/LocalReplicationStrategy.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import java.util.List; +import java.util.Map; + +class LocalReplicationStrategy implements ReplicationStrategy { + + @Override + public SetMultimap computeReplicasByToken( + Map tokenToPrimary, List ring) { + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (Map.Entry entry : tokenToPrimary.entrySet()) { + result.put(entry.getKey(), entry.getValue()); + } + return result.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3Token.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3Token.java new file mode 100644 index 00000000000..6414fd5ed18 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3Token.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Longs; + +/** A token generated by {@code Murmur3Partitioner}. */ +public class Murmur3Token implements Token { + + private final long value; + + public Murmur3Token(long value) { + this.value = value; + } + + public long getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Murmur3Token) { + Murmur3Token that = (Murmur3Token) other; + return this.value == that.value; + } else { + return false; + } + } + + @Override + public int hashCode() { + return (int) (value ^ (value >>> 32)); + } + + @Override + public int compareTo(Token other) { + Preconditions.checkArgument( + other instanceof Murmur3Token, "Can only compare tokens of the same type"); + Murmur3Token that = (Murmur3Token) other; + return Longs.compare(this.value, that.value); + } + + @Override + public String toString() { + return "Murmur3Token(" + value + ")"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java new file mode 100644 index 00000000000..c7108281039 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.base.Preconditions; +import java.nio.ByteBuffer; + +public class Murmur3TokenFactory implements TokenFactory { + + public static final Murmur3Token MIN_TOKEN = new Murmur3Token(Long.MIN_VALUE); + public static final Murmur3Token MAX_TOKEN = new Murmur3Token(Long.MAX_VALUE); + + @Override + public Token hash(ByteBuffer partitionKey) { + long v = murmur(partitionKey); + return new Murmur3Token(v == Long.MIN_VALUE ? Long.MAX_VALUE : v); + } + + @Override + public Token parse(String tokenString) { + return new Murmur3Token(Long.parseLong(tokenString)); + } + + @Override + public String format(Token token) { + Preconditions.checkArgument( + token instanceof Murmur3Token, "Can only format Murmur3Token instances"); + return Long.toString(((Murmur3Token) token).getValue()); + } + + @Override + public Token minToken() { + return MIN_TOKEN; + } + + @Override + public TokenRange range(Token start, Token end) { + Preconditions.checkArgument( + start instanceof Murmur3Token && end instanceof Murmur3Token, + "Can only build ranges of Murmur3Token instances"); + return new Murmur3TokenRange((Murmur3Token) start, (Murmur3Token) end); + } + + // This is an adapted version of the MurmurHash.hash3_x64_128 from Cassandra used + // for M3P. Compared to that methods, there's a few inlining of arguments and we + // only return the first 64-bits of the result since that's all M3P uses. + private long murmur(ByteBuffer data) { + int offset = data.position(); + int length = data.remaining(); + + int nblocks = length >> 4; // Process as 128-bit blocks. + + long h1 = 0; + long h2 = 0; + + long c1 = 0x87c37b91114253d5L; + long c2 = 0x4cf5ad432745937fL; + + //---------- + // body + + for (int i = 0; i < nblocks; i++) { + long k1 = getblock(data, offset, i * 2); + long k2 = getblock(data, offset, i * 2 + 1); + + k1 *= c1; + k1 = rotl64(k1, 31); + k1 *= c2; + h1 ^= k1; + h1 = rotl64(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + k2 *= c2; + k2 = rotl64(k2, 33); + k2 *= c1; + h2 ^= k2; + h2 = rotl64(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + //---------- + // tail + + // Advance offset to the unprocessed tail of the data. + offset += nblocks * 16; + + long k1 = 0; + long k2 = 0; + + switch (length & 15) { + case 15: + k2 ^= ((long) data.get(offset + 14)) << 48; + case 14: + k2 ^= ((long) data.get(offset + 13)) << 40; + case 13: + k2 ^= ((long) data.get(offset + 12)) << 32; + case 12: + k2 ^= ((long) data.get(offset + 11)) << 24; + case 11: + k2 ^= ((long) data.get(offset + 10)) << 16; + case 10: + k2 ^= ((long) data.get(offset + 9)) << 8; + case 9: + k2 ^= ((long) data.get(offset + 8)); + k2 *= c2; + k2 = rotl64(k2, 33); + k2 *= c1; + h2 ^= k2; + + case 8: + k1 ^= ((long) data.get(offset + 7)) << 56; + case 7: + k1 ^= ((long) data.get(offset + 6)) << 48; + case 6: + k1 ^= ((long) data.get(offset + 5)) << 40; + case 5: + k1 ^= ((long) data.get(offset + 4)) << 32; + case 4: + k1 ^= ((long) data.get(offset + 3)) << 24; + case 3: + k1 ^= ((long) data.get(offset + 2)) << 16; + case 2: + k1 ^= ((long) data.get(offset + 1)) << 8; + case 1: + k1 ^= ((long) data.get(offset)); + k1 *= c1; + k1 = rotl64(k1, 31); + k1 *= c2; + h1 ^= k1; + } + + //---------- + // finalization + + h1 ^= length; + h2 ^= length; + + h1 += h2; + h2 += h1; + + h1 = fmix(h1); + h2 = fmix(h2); + + h1 += h2; + + return h1; + } + + private long getblock(ByteBuffer key, int offset, int index) { + int i_8 = index << 3; + int blockOffset = offset + i_8; + return ((long) key.get(blockOffset) & 0xff) + + (((long) key.get(blockOffset + 1) & 0xff) << 8) + + (((long) key.get(blockOffset + 2) & 0xff) << 16) + + (((long) key.get(blockOffset + 3) & 0xff) << 24) + + (((long) key.get(blockOffset + 4) & 0xff) << 32) + + (((long) key.get(blockOffset + 5) & 0xff) << 40) + + (((long) key.get(blockOffset + 6) & 0xff) << 48) + + (((long) key.get(blockOffset + 7) & 0xff) << 56); + } + + private long rotl64(long v, int n) { + return ((v << n) | (v >>> (64 - n))); + } + + private long fmix(long k) { + k ^= k >>> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >>> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >>> 33; + return k; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRange.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRange.java new file mode 100644 index 00000000000..ea82d7ca85a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRange.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.collect.Lists; +import java.math.BigInteger; +import java.util.List; + +public class Murmur3TokenRange extends TokenRangeBase { + + private static final BigInteger RING_END = BigInteger.valueOf(Long.MAX_VALUE); + private static final BigInteger RING_LENGTH = + RING_END.subtract(BigInteger.valueOf(Long.MIN_VALUE)); + + public Murmur3TokenRange(Murmur3Token start, Murmur3Token end) { + super(start, end, Murmur3TokenFactory.MIN_TOKEN); + } + + @Override + protected TokenRange newTokenRange(Token start, Token end) { + return new Murmur3TokenRange((Murmur3Token) start, (Murmur3Token) end); + } + + @Override + protected List split(Token startToken, Token endToken, int numberOfSplits) { + // edge case: ]min, min] means the whole ring + if (startToken.equals(endToken) && startToken.equals(Murmur3TokenFactory.MIN_TOKEN)) { + endToken = Murmur3TokenFactory.MAX_TOKEN; + } + + BigInteger start = BigInteger.valueOf(((Murmur3Token) startToken).getValue()); + BigInteger end = BigInteger.valueOf(((Murmur3Token) endToken).getValue()); + + BigInteger range = end.subtract(start); + if (range.compareTo(BigInteger.ZERO) < 0) { + range = range.add(RING_LENGTH); + } + + List values = super.split(start, range, RING_END, RING_LENGTH, numberOfSplits); + List tokens = Lists.newArrayListWithExpectedSize(values.size()); + for (BigInteger value : values) { + tokens.add(new Murmur3Token(value.longValue())); + } + return tokens; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategy.java new file mode 100644 index 00000000000..75808797047 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategy.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class NetworkTopologyReplicationStrategy implements ReplicationStrategy { + + private static final Logger LOG = + LoggerFactory.getLogger(NetworkTopologyReplicationStrategy.class); + + private final Map replicationConfig; + private final Map replicationFactors; + private final String logPrefix; + + NetworkTopologyReplicationStrategy(Map replicationConfig, String logPrefix) { + this.replicationConfig = replicationConfig; + ImmutableMap.Builder factorsBuilder = ImmutableMap.builder(); + for (Map.Entry entry : replicationConfig.entrySet()) { + if (!entry.getKey().equals("class")) { + factorsBuilder.put(entry.getKey(), Integer.parseInt(entry.getValue())); + } + } + this.replicationFactors = factorsBuilder.build(); + this.logPrefix = logPrefix; + } + + @Override + public SetMultimap computeReplicasByToken( + Map tokenToPrimary, List ring) { + + // This is essentially a copy of org.apache.cassandra.locator.NetworkTopologyStrategy + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + Map> racks = getRacksInDcs(tokenToPrimary.values()); + Map dcNodeCount = Maps.newHashMapWithExpectedSize(replicationFactors.size()); + Set warnedDcs = Sets.newHashSetWithExpectedSize(replicationFactors.size()); + // find maximum number of nodes in each DC + for (Node node : Sets.newHashSet(tokenToPrimary.values())) { + String dc = node.getDatacenter(); + dcNodeCount.putIfAbsent(dc, 0); + dcNodeCount.put(dc, dcNodeCount.get(dc) + 1); + } + for (int i = 0; i < ring.size(); i++) { + Map> allDcReplicas = new HashMap<>(); + Map> seenRacks = new HashMap<>(); + Map> skippedDcEndpoints = new HashMap<>(); + for (String dc : replicationFactors.keySet()) { + allDcReplicas.put(dc, new HashSet<>()); + seenRacks.put(dc, new HashSet<>()); + skippedDcEndpoints.put(dc, new LinkedHashSet<>()); // preserve order + } + + // Preserve order - primary replica will be first + Set replicas = new LinkedHashSet<>(); + for (int j = 0; j < ring.size() && !allDone(allDcReplicas, dcNodeCount); j++) { + Node h = tokenToPrimary.get(getTokenWrapping(i + j, ring)); + String dc = h.getDatacenter(); + if (dc == null || !allDcReplicas.containsKey(dc)) { + continue; + } + Integer rf = replicationFactors.get(dc); + Set dcReplicas = allDcReplicas.get(dc); + if (rf == null || dcReplicas.size() >= rf) { + continue; + } + String rack = h.getRack(); + // Check if we already visited all racks in dc + if (rack == null || seenRacks.get(dc).size() == racks.get(dc).size()) { + replicas.add(h); + dcReplicas.add(h); + } else { + // Is this a new rack? + if (seenRacks.get(dc).contains(rack)) { + skippedDcEndpoints.get(dc).add(h); + } else { + replicas.add(h); + dcReplicas.add(h); + seenRacks.get(dc).add(rack); + // If we've run out of distinct racks, add the nodes skipped so far + if (seenRacks.get(dc).size() == racks.get(dc).size()) { + Iterator skippedIt = skippedDcEndpoints.get(dc).iterator(); + while (skippedIt.hasNext() && dcReplicas.size() < rf) { + Node nextSkipped = skippedIt.next(); + replicas.add(nextSkipped); + dcReplicas.add(nextSkipped); + } + } + } + } + } + // If we haven't found enough replicas after a whole trip around the ring, this probably + // means that the replication factors are broken. + // Warn the user because that leads to quadratic performance of this method (JAVA-702). + for (Map.Entry> entry : allDcReplicas.entrySet()) { + String dcName = entry.getKey(); + int expectedFactor = replicationFactors.get(dcName); + int achievedFactor = entry.getValue().size(); + if (achievedFactor < expectedFactor && !warnedDcs.contains(dcName)) { + LOG.warn( + "[{}] Error while computing token map for replication settings {}: " + + "could not achieve replication factor {} for datacenter {} (found only {} replicas).", + logPrefix, + replicationConfig, + expectedFactor, + dcName, + achievedFactor); + // only warn once per DC + warnedDcs.add(dcName); + } + } + + result.putAll(ring.get(i), replicas); + } + return result.build(); + } + + private boolean allDone(Map> map, Map dcNodeCount) { + for (Map.Entry> entry : map.entrySet()) { + String dc = entry.getKey(); + int dcCount = (dcNodeCount.get(dc) == null) ? 0 : dcNodeCount.get(dc); + if (entry.getValue().size() < Math.min(replicationFactors.get(dc), dcCount)) { + return false; + } + } + return true; + } + + private Map> getRacksInDcs(Iterable nodes) { + Map> result = new HashMap<>(); + for (Node node : nodes) { + Set racks = result.computeIfAbsent(node.getDatacenter(), k -> new HashSet<>()); + racks.add(node.getRack()); + } + return result; + } + + private static Token getTokenWrapping(int i, List ring) { + return ring.get(i % ring.size()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomToken.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomToken.java new file mode 100644 index 00000000000..fb5f409777b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomToken.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.base.Preconditions; +import java.math.BigInteger; + +/** A token generated by {@code RandomPartitioner}. */ +public class RandomToken implements Token { + + private final BigInteger value; + + public RandomToken(BigInteger value) { + this.value = value; + } + + public BigInteger getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof RandomToken) { + RandomToken that = (RandomToken) other; + return this.value.equals(that.value); + } else { + return false; + } + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Token other) { + Preconditions.checkArgument( + other instanceof RandomToken, "Can only compare tokens of the same type"); + RandomToken that = (RandomToken) other; + return this.value.compareTo(that.getValue()); + } + + @Override + public String toString() { + return "RandomToken(" + value + ")"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java new file mode 100644 index 00000000000..7c3add70390 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.base.Preconditions; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class RandomTokenFactory implements TokenFactory { + + private static final BigInteger MIN_VALUE = BigInteger.ONE.negate(); + static final BigInteger MAX_VALUE = BigInteger.valueOf(2).pow(127); + public static final RandomToken MIN_TOKEN = new RandomToken(MIN_VALUE); + public static final RandomToken MAX_TOKEN = new RandomToken(MAX_VALUE); + + private final MessageDigest prototype; + private final boolean supportsClone; + + public RandomTokenFactory() { + prototype = createMessageDigest(); + boolean supportsClone; + try { + prototype.clone(); + supportsClone = true; + } catch (CloneNotSupportedException e) { + supportsClone = false; + } + this.supportsClone = supportsClone; + } + + @Override + public Token hash(ByteBuffer partitionKey) { + return new RandomToken(md5(partitionKey)); + } + + @Override + public Token parse(String tokenString) { + return new RandomToken(new BigInteger(tokenString)); + } + + @Override + public String format(Token token) { + Preconditions.checkArgument( + token instanceof RandomToken, "Can only format RandomToken instances"); + return ((RandomToken) token).getValue().toString(); + } + + @Override + public Token minToken() { + return MIN_TOKEN; + } + + @Override + public TokenRange range(Token start, Token end) { + Preconditions.checkArgument( + start instanceof RandomToken && end instanceof RandomToken, + "Can only build ranges of RandomToken instances"); + return new RandomTokenRange((RandomToken) start, (RandomToken) end); + } + + private static MessageDigest createMessageDigest() { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 doesn't seem to be available on this JVM", e); + } + } + + private BigInteger md5(ByteBuffer data) { + MessageDigest digest = newMessageDigest(); + digest.update(data.duplicate()); + return new BigInteger(digest.digest()).abs(); + } + + private MessageDigest newMessageDigest() { + if (supportsClone) { + try { + return (MessageDigest) prototype.clone(); + } catch (CloneNotSupportedException ignored) { + } + } + return createMessageDigest(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java new file mode 100644 index 00000000000..b157684278b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.collect.Lists; +import java.math.BigInteger; +import java.util.List; + +import static com.datastax.oss.driver.internal.core.metadata.token.RandomTokenFactory.MAX_VALUE; + +public class RandomTokenRange extends TokenRangeBase { + + private static final BigInteger RING_LENGTH = MAX_VALUE.add(BigInteger.ONE); + + public RandomTokenRange(RandomToken start, RandomToken end) { + super(start, end, RandomTokenFactory.MIN_TOKEN); + } + + @Override + protected TokenRange newTokenRange(Token start, Token end) { + return new RandomTokenRange(((RandomToken) start), ((RandomToken) end)); + } + + @Override + protected List split(Token startToken, Token endToken, int numberOfSplits) { + // edge case: ]min, min] means the whole ring + if (startToken.equals(endToken) && startToken.equals(RandomTokenFactory.MIN_TOKEN)) { + endToken = RandomTokenFactory.MAX_TOKEN; + } + + BigInteger start = ((RandomToken) startToken).getValue(); + BigInteger end = ((RandomToken) endToken).getValue(); + + BigInteger range = end.subtract(start); + if (range.compareTo(BigInteger.ZERO) < 0) { + range = range.add(RING_LENGTH); + } + + List values = super.split(start, range, MAX_VALUE, RING_LENGTH, numberOfSplits); + List tokens = Lists.newArrayListWithExpectedSize(values.size()); + for (BigInteger value : values) { + tokens.add(new RandomToken(value)); + } + return tokens; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java new file mode 100644 index 00000000000..3a3dd4ae2cf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.base.Preconditions; +import com.google.common.collect.SetMultimap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +interface ReplicationStrategy { + static ReplicationStrategy newInstance(Map replicationConfig, String logPrefix) { + String strategyClass = replicationConfig.get("class"); + Preconditions.checkNotNull( + strategyClass, "Missing replication strategy class in " + replicationConfig); + switch (strategyClass) { + case "org.apache.cassandra.locator.LocalStrategy": + return new LocalReplicationStrategy(); + case "org.apache.cassandra.locator.SimpleStrategy": + return new SimpleReplicationStrategy(replicationConfig); + case "org.apache.cassandra.locator.NetworkTopologyStrategy": + return new NetworkTopologyReplicationStrategy(replicationConfig, logPrefix); + default: + throw new IllegalArgumentException("Unsupported replication strategy: " + strategyClass); + } + } + + SetMultimap computeReplicasByToken( + Map tokenToPrimary, List ring); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategy.java new file mode 100644 index 00000000000..e25d21dde40 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategy.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class SimpleReplicationStrategy implements ReplicationStrategy { + + private final int replicationFactor; + + SimpleReplicationStrategy(Map replicationConfig) { + this(extractReplicationFactor(replicationConfig)); + } + + @VisibleForTesting + SimpleReplicationStrategy(int replicationFactor) { + this.replicationFactor = replicationFactor; + } + + @Override + public SetMultimap computeReplicasByToken( + Map tokenToPrimary, List ring) { + + int rf = Math.min(replicationFactor, ring.size()); + + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (int i = 0; i < ring.size(); i++) { + // Consecutive sections of the ring can be assigned to the same node + Set replicas = new LinkedHashSet<>(); + for (int j = 0; j < ring.size() && replicas.size() < rf; j++) { + replicas.add(tokenToPrimary.get(getTokenWrapping(i + j, ring))); + } + result.putAll(ring.get(i), replicas); + } + return result.build(); + } + + private static Token getTokenWrapping(int i, List ring) { + return ring.get(i % ring.size()); + } + + private static int extractReplicationFactor(Map replicationConfig) { + String factorString = replicationConfig.get("replication_factor"); + Preconditions.checkNotNull(factorString, "Missing replication factor in " + replicationConfig); + return Integer.parseInt(factorString); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java new file mode 100644 index 00000000000..1138bf8f398 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import java.nio.ByteBuffer; + +/** Manages token instances for a partitioner implementation. */ +public interface TokenFactory { + + Token hash(ByteBuffer partitionKey); + + Token parse(String tokenString); + + String format(Token token); + + /** + * The minimum token is a special value that no key ever hashes to, it's used both as lower and + * upper bound. + */ + Token minToken(); + + TokenRange range(Token start, Token end); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactoryRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactoryRegistry.java new file mode 100644 index 00000000000..b39e240cd17 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactoryRegistry.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +/** A thin layer of indirection to make token factories pluggable. */ +public interface TokenFactoryRegistry { + TokenFactory tokenFactoryFor(String partitioner); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeBase.java new file mode 100644 index 00000000000..08f0657d108 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeBase.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public abstract class TokenRangeBase implements TokenRange { + + private final Token start; + private final Token end; + private final Token minToken; + + protected TokenRangeBase(Token start, Token end, Token minToken) { + this.start = start; + this.end = end; + this.minToken = minToken; + } + + @Override + public Token getStart() { + return start; + } + + @Override + public Token getEnd() { + return end; + } + + @Override + public List splitEvenly(int numberOfSplits) { + if (numberOfSplits < 1) + throw new IllegalArgumentException( + String.format("numberOfSplits (%d) must be greater than 0.", numberOfSplits)); + if (isEmpty()) { + throw new IllegalArgumentException("Can't split empty range " + this); + } + + List tokenRanges = new ArrayList<>(); + List splitPoints = split(start, end, numberOfSplits); + Token splitStart = start; + for (Token splitEnd : splitPoints) { + tokenRanges.add(newTokenRange(splitStart, splitEnd)); + splitStart = splitEnd; + } + tokenRanges.add(newTokenRange(splitStart, end)); + return tokenRanges; + } + + protected abstract List split(Token start, Token end, int numberOfSplits); + + /** This is used by {@link #split(Token, Token, int)} implementations. */ + protected List split( + BigInteger start, + BigInteger range, + BigInteger ringEnd, + BigInteger ringLength, + int numberOfSplits) { + BigInteger[] tmp = range.divideAndRemainder(BigInteger.valueOf(numberOfSplits)); + BigInteger divider = tmp[0]; + int remainder = tmp[1].intValue(); + + List results = Lists.newArrayListWithExpectedSize(numberOfSplits - 1); + BigInteger current = start; + BigInteger dividerPlusOne = + (remainder == 0) + ? null // won't be used + : divider.add(BigInteger.ONE); + + for (int i = 1; i < numberOfSplits; i++) { + current = current.add(remainder-- > 0 ? dividerPlusOne : divider); + if (ringEnd != null && current.compareTo(ringEnd) > 0) current = current.subtract(ringLength); + results.add(current); + } + return results; + } + + protected abstract TokenRange newTokenRange(Token start, Token end); + + @Override + public boolean isEmpty() { + return start.equals(end) && !start.equals(minToken); + } + + @Override + public boolean isWrappedAround() { + return start.compareTo(end) > 0 && !end.equals(minToken); + } + + @Override + public boolean isFullRing() { + return start.equals(minToken) && end.equals(minToken); + } + + @Override + public List unwrap() { + if (isWrappedAround()) { + return ImmutableList.of(newTokenRange(start, minToken), newTokenRange(minToken, end)); + } else { + return ImmutableList.of(this); + } + } + + @Override + public boolean intersects(TokenRange that) { + // Empty ranges never intersect any other range + if (this.isEmpty() || that.isEmpty()) { + return false; + } + + return contains(this, that.getStart(), true) + || contains(this, that.getEnd(), false) + || contains(that, this.start, true) + || contains(that, this.end, false); + } + + @Override + public List intersectWith(TokenRange that) { + if (!this.intersects(that)) { + throw new IllegalArgumentException( + "The two ranges do not intersect, use intersects() before calling this method"); + } + + List intersected = Lists.newArrayList(); + + // Compare the unwrapped ranges to one another. + List unwrappedForThis = this.unwrap(); + List unwrappedForThat = that.unwrap(); + for (TokenRange t1 : unwrappedForThis) { + for (TokenRange t2 : unwrappedForThat) { + if (t1.intersects(t2)) { + intersected.add( + newTokenRange( + (contains(t1, t2.getStart(), true)) ? t2.getStart() : t1.getStart(), + (contains(t1, t2.getEnd(), false)) ? t2.getEnd() : t1.getEnd())); + } + } + } + + // If two intersecting ranges were produced, merge them if they are adjacent. + // This could happen in the case that two wrapped ranges intersected. + if (intersected.size() == 2) { + TokenRange t1 = intersected.get(0); + TokenRange t2 = intersected.get(1); + if (t1.getEnd().equals(t2.getStart()) || t2.getEnd().equals(t1.getStart())) { + return ImmutableList.of(t1.mergeWith(t2)); + } + } + + return intersected; + } + + @Override + public boolean contains(Token token) { + return contains(this, token, false); + } + + // isStart handles the case where the token is the start of another range, for example: + // * ]1,2] contains 2, but it does not contain the start of ]2,3] + // * ]1,2] does not contain 1, but it contains the start of ]1,3] + @VisibleForTesting + boolean contains(TokenRange range, Token token, boolean isStart) { + if (range.isEmpty()) { + return false; + } + if (range.getEnd().equals(minToken)) { + if (range.getStart().equals(minToken)) { // ]min, min] = full ring, contains everything + return true; + } else if (token.equals(minToken)) { + return !isStart; + } else { + return isStart + ? token.compareTo(range.getStart()) >= 0 + : token.compareTo(range.getStart()) > 0; + } + } else { + boolean isAfterStart = + isStart ? token.compareTo(range.getStart()) >= 0 : token.compareTo(range.getStart()) > 0; + boolean isBeforeEnd = + isStart ? token.compareTo(range.getEnd()) < 0 : token.compareTo(range.getEnd()) <= 0; + return range.isWrappedAround() + ? isAfterStart || isBeforeEnd // ####]----]#### + : isAfterStart && isBeforeEnd; // ----]####]---- + } + } + + @Override + public TokenRange mergeWith(TokenRange that) { + if (this.equals(that)) { + return this; + } + + if (!(this.intersects(that) + || this.end.equals(that.getStart()) + || that.getEnd().equals(this.start))) { + throw new IllegalArgumentException( + String.format( + "Can't merge %s with %s because they neither intersect nor are adjacent", + this, that)); + } + + if (this.isEmpty()) { + return that; + } + + if (that.isEmpty()) { + return this; + } + + // That's actually "starts in or is adjacent to the end of" + boolean thisStartsInThat = contains(that, this.start, true) || this.start.equals(that.getEnd()); + boolean thatStartsInThis = + contains(this, that.getStart(), true) || that.getStart().equals(this.end); + + // This takes care of all the cases that return the full ring, so that we don't have to worry + // about them below + if (thisStartsInThat && thatStartsInThis) { + return fullRing(); + } + + // Starting at this.start, see how far we can go while staying in at least one of the ranges. + Token mergedEnd = + (thatStartsInThis && !contains(this, that.getEnd(), false)) ? that.getEnd() : this.end; + + // Repeat in the other direction. + Token mergedStart = thisStartsInThat ? that.getStart() : this.start; + + return newTokenRange(mergedStart, mergedEnd); + } + + private TokenRange fullRing() { + return newTokenRange(minToken, minToken); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof TokenRange) { + TokenRange that = (TokenRange) other; + return this.start.equals(that.getStart()) && this.end.equals(that.getEnd()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } + + @Override + public int compareTo(TokenRange that) { + if (this.equals(that)) { + return 0; + } else { + int compareStart = this.start.compareTo(that.getStart()); + return compareStart != 0 ? compareStart : this.end.compareTo(that.getEnd()); + } + } + + @Override + public String toString() { + return String.format("%s(%s, %s)", getClass().getSimpleName(), start, end); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index bc7ff91dc2f..e14b8471524 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -38,6 +38,7 @@ import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import io.netty.util.concurrent.EventExecutor; import java.nio.ByteBuffer; @@ -58,12 +59,18 @@ /** * The session implementation. * + *

      + * *

      It maintains a {@link ChannelPool} to each node that the {@link LoadBalancingPolicy} set to a * non-ignored distance. It listens for distance events and node state events, in order to adjust * the pools accordingly. * + *

      + * *

      It executes requests by: * + *

      + * *

        *
      • picking the appropriate processor to convert the request into a protocol message. *
      • getting a query plan from the load balancing policy @@ -128,26 +135,31 @@ public CqlIdentifier getKeyspace() { /** * INTERNAL USE ONLY -- switches the session to a new keyspace. * + *

        + * *

        This is called by the driver when a {@code USE} query is successfully executed through the * session. Calling it from anywhere else is highly discouraged, as an invalid keyspace would * wreak havoc (close all connections and make the session unusable). */ - public void setKeyspace(CqlIdentifier newKeyspace) { + public CompletionStage setKeyspace(CqlIdentifier newKeyspace) { CqlIdentifier oldKeyspace = this.keyspace; - if (!Objects.equals(oldKeyspace, newKeyspace)) { - if (config.getDefaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { - LOG.warn( - "[{}] Detected a keyspace change at runtime ({} => {}). " - + "This is an anti-pattern that should be avoided in production " - + "(see '{}' in the configuration).", - logPrefix, - (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), - newKeyspace.asInternal(), - CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); - } - this.keyspace = newKeyspace; - RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace)); + if (Objects.equals(oldKeyspace, newKeyspace)) { + return CompletableFuture.completedFuture(null); + } + if (config.getDefaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { + LOG.warn( + "[{}] Detected a keyspace change at runtime ({} => {}). " + + "This is an anti-pattern that should be avoided in production " + + "(see '{}' in the configuration).", + logPrefix, + (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), + newKeyspace.asInternal(), + CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); } + this.keyspace = newKeyspace; + CompletableFuture result = new CompletableFuture<>(); + RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace, result)); + return result; } public Map getPools() { @@ -474,15 +486,18 @@ private void onPoolReady(ChannelPool pool) { } } - private void setKeyspace(CqlIdentifier newKeyspace) { + private void setKeyspace(CqlIdentifier newKeyspace, CompletableFuture doneFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { + doneFuture.complete(null); return; } LOG.debug("[{}] Switching to keyspace {}", logPrefix, newKeyspace); + List> poolReadyFutures = Lists.newArrayListWithCapacity(pools.size()); for (ChannelPool pool : pools.values()) { - pool.setKeyspace(newKeyspace); + poolReadyFutures.add(pool.setKeyspace(newKeyspace)); } + CompletableFutures.completeFrom(CompletableFutures.allDone(poolReadyFutures), doneFuture); } private void close() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/RoutingKey.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/RoutingKey.java new file mode 100644 index 00000000000..73f3b24fee1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/RoutingKey.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import java.nio.ByteBuffer; + +public class RoutingKey { + + /** Assembles multiple routing key components into a single buffer. */ + public static ByteBuffer compose(ByteBuffer... components) { + if (components.length == 1) return components[0]; + + int totalLength = 0; + for (ByteBuffer bb : components) totalLength += 2 + bb.remaining() + 1; + + ByteBuffer out = ByteBuffer.allocate(totalLength); + for (ByteBuffer buffer : components) { + ByteBuffer bb = buffer.duplicate(); + putShortLength(out, bb.remaining()); + out.put(bb); + out.put((byte) 0); + } + out.flip(); + return out; + } + + private static void putShortLength(ByteBuffer bb, int length) { + bb.put((byte) ((length >> 8) & 0xFF)); + bb.put((byte) (length & 0xFF)); + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 21831bd34ca..e5549f40abf 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -416,14 +416,19 @@ datastax-java-driver { max-events = 20 } + # Options relating to schema metadata (Cluster.getMetadata.getKeyspaces). + # This metadata is exposed by the driver for informational purposes, and is also necessary for + # token-aware routing. schema { - # Whether schema and token metadata is enabled. If this is false, that metadata will remain - # empty, or to the value from the last update. - # Note that this option can be overridden programmatically with - # Cluster.setSchemaMetadataEnabled. + # Whether schema metadata is enabled. + # If this is false, the schema will remain empty, or to the last known value. + # This option can be changed at runtime, the new value will be used for refreshes issued after + # the change. It can also be overridden programmatically via Cluster.setSchemaMetadataEnabled. enabled = true # The list of keyspaces for which schema and token metadata should be maintained. If this # property is absent or empty, all existing keyspaces are processed. + # This option can be changed at runtime, the new value will be used for refreshes issued after + # the change. // refreshed-keyspaces = [ "ks1", "ks2" ] # The timeout for the requests to the schema tables. request-timeout = ${datastax-java-driver.request.timeout} @@ -442,6 +447,17 @@ datastax-java-driver { max-events = 20 } } + + # Whether token metadata (Cluster.getMetadata.getTokenMap) is enabled. + # This metadata is exposed by the driver for informational purposes, and is also necessary for + # token-aware routing. + # If this is false, it will remain empty, or to the last known value. Note that its computation + # requires information about the schema; therefore if schema metadata is disabled or filtered to + # a subset of keyspaces, the token map will be incomplete, regardless of the value of this + # property. + # This option can be changed at runtime, the new value will be used for refreshes issued after + # the change. + token-map.enabled = true } # The address translator to use to convert the addresses sent by Cassandra nodes into ones that @@ -476,13 +492,13 @@ datastax-java-driver { # The options to shut down the event loop group gracefully when the driver closes. If a task # gets submitted during the quiet period, it is accepted and the quiet period starts over. The # timeout limits the overall shutdown time. - shutdown { quiet-period = 2, timeout = 15, unit = SECONDS } + shutdown {quiet-period = 2, timeout = 15, unit = SECONDS} } # The event loop group used for admin tasks not related to request I/O (handle cluster events, # refresh metadata, schedule reconnections, etc.) admin-group { size = 2 - shutdown { quiet-period = 2, timeout = 15, unit = SECONDS } + shutdown {quiet-period = 2, timeout = 15, unit = SECONDS} } } diff --git a/core/src/test/java/com/datastax/oss/driver/Assertions.java b/core/src/test/java/com/datastax/oss/driver/Assertions.java index b9f98838e31..81075ed2d64 100644 --- a/core/src/test/java/com/datastax/oss/driver/Assertions.java +++ b/core/src/test/java/com/datastax/oss/driver/Assertions.java @@ -18,9 +18,11 @@ import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CassandraVersionAssert; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.datastax.oss.driver.internal.core.CompletionStageAssert; import com.datastax.oss.driver.internal.core.DriverConfigAssert; import com.datastax.oss.driver.internal.core.NettyFutureAssert; +import com.datastax.oss.driver.internal.core.metadata.token.TokenRangeAssert; import io.netty.buffer.ByteBuf; import io.netty.util.concurrent.Future; import java.util.concurrent.CompletionStage; @@ -45,4 +47,8 @@ public static CompletionStageAssert assertThat(CompletionStage actual) public static CassandraVersionAssert assertThat(CassandraVersion actual) { return new CassandraVersionAssert(actual); } + + public static TokenRangeAssert assertThat(TokenRange actual) { + return new TokenRangeAssert(actual); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index f0337519fc7..eb140db881e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import org.mockito.Mock; @@ -125,6 +126,9 @@ private RequestHandlerTestHarness(Builder builder) { return pools.get(node).next(); }); Mockito.when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); + + Mockito.when(session.setKeyspace(any(CqlIdentifier.class))) + .thenReturn(CompletableFuture.completedFuture(null)); } public DefaultSession getSession() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index fbf1c25c7b6..5a61be1416f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -32,7 +32,7 @@ public class AddNodeRefreshTest { @Test public void should_add_new_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS2) @@ -42,7 +42,7 @@ public void should_add_new_node() { AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then Map newNodes = result.newMetadata.getNodes(); @@ -56,7 +56,7 @@ public void should_add_new_node() { @Test public void should_not_add_existing_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS1) @@ -66,7 +66,7 @@ public void should_not_add_existing_node() { AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java new file mode 100644 index 00000000000..1f7a812df43 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3TokenFactory; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.net.InetSocketAddress; +import java.util.Map; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class DefaultMetadataTokenMapTest { + + // Simulate the simplest setup possible for a functional token map. We're not testing the token + // map itself, only how the metadata interacts with it. + private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + private static final String TOKEN1 = "-9000000000000000000"; + private static final String TOKEN2 = "9000000000000000000"; + private static final TokenFactory TOKEN_FACTORY = new Murmur3TokenFactory(); + private static final Node NODE1 = mockNode(TOKEN1); + private static final Node NODE2 = mockNode(TOKEN2); + private static final CqlIdentifier KEYSPACE_NAME = CqlIdentifier.fromInternal("ks"); + private static final KeyspaceMetadata KEYSPACE = + mockKeyspace( + KEYSPACE_NAME, + ImmutableMap.of( + "class", "org.apache.cassandra.locator.SimpleStrategy", "replication_factor", "1")); + + @Test + public void should_not_build_token_map_when_initializing_with_contact_points() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + assertThat(contactPointsMetadata.getTokenMap()).isNotPresent(); + } + + @Test + public void should_build_minimal_token_map_on_first_refresh() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata firstRefreshMetadata = + contactPointsMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + assertThat(firstRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(1); + } + + @Test + public void should_not_build_token_map_when_disabled() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata firstRefreshMetadata = + contactPointsMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1), false, true, new Murmur3TokenFactory()); + assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); + } + + @Test + public void should_stay_empty_on_first_refresh_if_partitioner_missing() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata firstRefreshMetadata = + contactPointsMetadata.withNodes(ImmutableMap.of(ADDRESS1, NODE1), true, true, null); + assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); + } + + @Test + public void should_update_minimal_token_map_if_new_node_and_still_no_schema() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata firstRefreshMetadata = + contactPointsMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + DefaultMetadata secondRefreshMetadata = + firstRefreshMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1, ADDRESS2, NODE2), true, false, null); + assertThat(secondRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(2); + } + + @Test + public void should_update_token_map_when_schema_changes() { + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata firstRefreshMetadata = + contactPointsMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + DefaultMetadata schemaRefreshMetadata = + firstRefreshMetadata.withSchema(ImmutableMap.of(KEYSPACE_NAME, KEYSPACE), true); + assertThat(schemaRefreshMetadata.getTokenMap().get().getTokenRanges(KEYSPACE_NAME, NODE1)) + .isNotEmpty(); + } + + private static DefaultNode mockNode(String token) { + DefaultNode node = Mockito.mock(DefaultNode.class); + Mockito.when(node.getRawTokens()).thenReturn(ImmutableSet.of(token)); + return node; + } + + private static KeyspaceMetadata mockKeyspace( + CqlIdentifier name, Map replicationConfig) { + KeyspaceMetadata keyspace = Mockito.mock(KeyspaceMetadata.class); + Mockito.when(keyspace.getName()).thenReturn(name); + Mockito.when(keyspace.getReplication()).thenReturn(replicationConfig); + return keyspace; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 3b5ca517a2b..d8ef535407e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -15,13 +15,18 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class FullNodeListRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); @@ -32,19 +37,21 @@ public class FullNodeListRefreshTest { private static final DefaultNode node2 = new DefaultNode(ADDRESS2); private static final DefaultNode node3 = new DefaultNode(ADDRESS3); + @Mock private InternalDriverContext context; + @Test public void should_add_and_remove_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, "test"); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, context); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); @@ -56,7 +63,7 @@ public void should_add_and_remove_nodes() { public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder() @@ -69,10 +76,10 @@ public void should_update_existing_nodes() { .withDatacenter("dc1") .withRack("rack2") .build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, "test"); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, context); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 0d59bbf3cc3..17783a44d70 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -33,7 +33,7 @@ public void should_create_nodes() { new InitContactPointsRefresh(ImmutableSet.of(ADDRESS1, ADDRESS2), "test"); // When - MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY); + MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 0334da5db82..712b7e09354 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -33,11 +33,11 @@ public class RemoveNodeRefreshTest { public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); @@ -47,11 +47,11 @@ public void should_remove_existing_node() { @Test public void should_not_remove_nonexistent_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java index 52268f93f54..81de8cb8389 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java @@ -45,7 +45,7 @@ public class SchemaRefreshTest { private static final DefaultKeyspaceMetadata OLD_KS1 = newKeyspace("ks1", true, OLD_T1, OLD_T2); private DefaultMetadata oldMetadata = - DefaultMetadata.EMPTY.withKeyspaces(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1)); + DefaultMetadata.EMPTY.withSchema(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1), false); private static DefaultKeyspaceMetadata newKeyspace( String name, boolean durableWrites, UserDefinedType... userTypes) { @@ -67,7 +67,7 @@ private static DefaultKeyspaceMetadata newKeyspace( @Test public void should_detect_dropped_keyspace() { SchemaRefresh refresh = new SchemaRefresh(Collections.emptyMap(), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); assertThat(result.newMetadata.getKeyspaces()).isEmpty(); assertThat(result.events).containsExactly(KeyspaceChangeEvent.dropped(OLD_KS1)); } @@ -77,7 +77,7 @@ public void should_detect_created_keyspace() { DefaultKeyspaceMetadata ks2 = newKeyspace("ks2", true); SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1, ks2.getName(), ks2), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); assertThat(result.newMetadata.getKeyspaces()).hasSize(2); assertThat(result.events).containsExactly(KeyspaceChangeEvent.created(ks2)); } @@ -87,7 +87,7 @@ public void should_detect_top_level_update_in_keyspace() { // Change only one top-level option (durable writes) DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, OLD_T1, OLD_T2); SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); assertThat(result.newMetadata.getKeyspaces()).hasSize(1); assertThat(result.events).containsExactly(KeyspaceChangeEvent.updated(OLD_KS1, newKs1)); } @@ -108,7 +108,7 @@ public void should_detect_updated_children_in_keyspace() { DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", true, newT2, t3); SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); assertThat(result.events) .containsExactly( @@ -134,7 +134,7 @@ public void should_detect_top_level_change_and_children_changes() { DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, newT2, t3); SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false); assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); assertThat(result.events) .containsExactly( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java new file mode 100644 index 00000000000..33c4b29fffa --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.protocol.internal.util.Bytes; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** @see TokenRangeTest */ +public class ByteOrderedTokenRangeTest { + + private static final String MIN = "0x"; + + @Test + public void should_split_range() { + assertThat(range("0x0a", "0x0d").splitEvenly(3)) + .containsExactly(range("0x0a", "0x0b"), range("0x0b", "0x0c"), range("0x0c", "0x0d")); + } + + @Test + public void should_split_range_producing_empty_splits_near_ring_end() { + // 0x00 is the first token following min. + // This is an edge case where we want to make sure we don't accidentally generate the ]min,min] + // range (which is the whole ring): + assertThat(range(MIN, "0x00").splitEvenly(3)) + .containsExactly(range(MIN, "0x00"), range("0x00", "0x00"), range("0x00", "0x00")); + } + + @Test + public void should_split_range_that_wraps_around_the_ring() { + assertThat(range("0x0d", "0x0a").splitEvenly(2)) + .containsExactly(range("0x0d", "0x8c"), range("0x8c", "0x0a")); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_split_whole_ring() { + range(MIN, MIN).splitEvenly(1); + } + + private ByteOrderedTokenRange range(String start, String end) { + return new ByteOrderedTokenRange( + new ByteOrderedToken(Bytes.fromHexString(start)), + new ByteOrderedToken(Bytes.fromHexString(end))); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java new file mode 100644 index 00000000000..abbd9104c0a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.mockito.Mockito; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class DefaultTokenMapTest { + + private static final String DC1 = "DC1"; + private static final String DC2 = "DC2"; + private static final String RACK1 = "RACK1"; + private static final String RACK2 = "RACK2"; + + private static final CqlIdentifier KS1 = CqlIdentifier.fromInternal("ks1"); + private static final CqlIdentifier KS2 = CqlIdentifier.fromInternal("ks2"); + + private static final TokenFactory TOKEN_FACTORY = new Murmur3TokenFactory(); + + private static final String TOKEN1 = "-9000000000000000000"; + private static final String TOKEN2 = "-6000000000000000000"; + private static final String TOKEN3 = "4000000000000000000"; + private static final String TOKEN4 = "9000000000000000000"; + private static final TokenRange RANGE12 = range(TOKEN1, TOKEN2); + private static final TokenRange RANGE23 = range(TOKEN2, TOKEN3); + private static final TokenRange RANGE34 = range(TOKEN3, TOKEN4); + private static final TokenRange RANGE41 = range(TOKEN4, TOKEN1); + private static final TokenRange FULL_RING = + range(TOKEN_FACTORY.minToken(), TOKEN_FACTORY.minToken()); + + // Some random routing keys that land in the ranges above (they were generated manually) + private static ByteBuffer ROUTING_KEY12 = TypeCodecs.BIGINT.encode(2L, CoreProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY23 = TypeCodecs.BIGINT.encode(0L, CoreProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY34 = TypeCodecs.BIGINT.encode(1L, CoreProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY41 = TypeCodecs.BIGINT.encode(99L, CoreProtocolVersion.V3); + + private static final ImmutableMap REPLICATE_ON_BOTH_DCS = + ImmutableMap.of( + "class", "org.apache.cassandra.locator.NetworkTopologyStrategy", DC1, "1", DC2, "1"); + private static final ImmutableMap REPLICATE_ON_DC1 = + ImmutableMap.of("class", "org.apache.cassandra.locator.NetworkTopologyStrategy", DC1, "1"); + + @Test + public void should_build_token_map() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List keyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + + // When + DefaultTokenMap tokenMap = DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, "test"); + + // Then + assertThat(tokenMap.getTokenRanges()).containsExactly(RANGE12, RANGE23, RANGE34, RANGE41); + + // For KS1, each node gets its primary range, plus the one of the previous node in the other DC + assertThat(tokenMap.getTokenRanges(KS1, node1)).containsOnly(RANGE41, RANGE34); + assertThat(tokenMap.getTokenRanges(KS1, node2)).containsOnly(RANGE12, RANGE41); + assertThat(tokenMap.getTokenRanges(KS1, node3)).containsOnly(RANGE23, RANGE12); + assertThat(tokenMap.getTokenRanges(KS1, node4)).containsOnly(RANGE34, RANGE23); + + assertThat(tokenMap.getReplicas(KS1, RANGE12)).containsOnly(node2, node3); + assertThat(tokenMap.getReplicas(KS1, RANGE23)).containsOnly(node3, node4); + assertThat(tokenMap.getReplicas(KS1, RANGE34)).containsOnly(node1, node4); + assertThat(tokenMap.getReplicas(KS1, RANGE41)).containsOnly(node1, node2); + + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY12)).containsOnly(node2, node3); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY23)).containsOnly(node3, node4); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY34)).containsOnly(node1, node4); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY41)).containsOnly(node1, node2); + + // KS2 is only replicated on DC1 + assertThat(tokenMap.getTokenRanges(KS2, node1)).containsOnly(RANGE41, RANGE34); + assertThat(tokenMap.getTokenRanges(KS2, node3)).containsOnly(RANGE23, RANGE12); + assertThat(tokenMap.getTokenRanges(KS2, node2)).isEmpty(); + assertThat(tokenMap.getTokenRanges(KS2, node4)).isEmpty(); + + assertThat(tokenMap.getReplicas(KS2, RANGE12)).containsOnly(node3); + assertThat(tokenMap.getReplicas(KS2, RANGE23)).containsOnly(node3); + assertThat(tokenMap.getReplicas(KS2, RANGE34)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, RANGE41)).containsOnly(node1); + + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY12)).containsOnly(node3); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY23)).containsOnly(node3); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY34)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY41)).containsOnly(node1); + } + + @Test + public void should_build_token_map_with_single_node() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + List nodes = ImmutableList.of(node1); + List keyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + + // When + DefaultTokenMap tokenMap = DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, "test"); + + // Then + assertThat(tokenMap.getTokenRanges()).containsExactly(FULL_RING); + + assertThat(tokenMap.getTokenRanges(KS1, node1)).containsOnly(FULL_RING); + assertThat(tokenMap.getReplicas(KS1, FULL_RING)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY12)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY23)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY34)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS1, ROUTING_KEY41)).containsOnly(node1); + + assertThat(tokenMap.getTokenRanges(KS2, node1)).containsOnly(FULL_RING); + assertThat(tokenMap.getReplicas(KS2, FULL_RING)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY12)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY23)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY34)).containsOnly(node1); + assertThat(tokenMap.getReplicas(KS2, ROUTING_KEY41)).containsOnly(node1); + } + + @Test + public void should_refresh_when_keyspace_replication_has_not_changed() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + + // When + // The schema gets refreshed, but no keyspaces are created or dropped, and the replication + // settings do not change (since we mock everything it looks the same here, but it could be a + // new table, etc). + List newKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + // Nothing was recomputed + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.replicationConfigs).isSameAs(oldTokenMap.replicationConfigs); + assertThat(newTokenMap.keyspaceMaps).isSameAs(oldTokenMap.keyspaceMaps); + } + + @Test + public void should_refresh_when_new_keyspace_with_existing_replication() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + + // When + List newKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.keyspaceMaps).isEqualTo(oldTokenMap.keyspaceMaps); + assertThat(newTokenMap.replicationConfigs) + .hasSize(2) + .containsEntry(KS1, REPLICATE_ON_BOTH_DCS) + .containsEntry(KS2, REPLICATE_ON_BOTH_DCS); + } + + @Test + public void should_refresh_when_new_keyspace_with_new_replication() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + + // When + List newKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS, REPLICATE_ON_DC1); + assertThat(newTokenMap.replicationConfigs) + .hasSize(2) + .containsEntry(KS1, REPLICATE_ON_BOTH_DCS) + .containsEntry(KS2, REPLICATE_ON_DC1); + } + + @Test + public void should_refresh_when_dropped_keyspace_with_replication_still_used() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + + // When + List newKeyspaces = + ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + assertThat(newTokenMap.replicationConfigs).hasSize(1).containsEntry(KS1, REPLICATE_ON_BOTH_DCS); + } + + @Test + public void should_refresh_when_dropped_keyspace_with_replication_not_used_anymore() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS, REPLICATE_ON_DC1); + + // When + List newKeyspaces = + ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + assertThat(newTokenMap.replicationConfigs).hasSize(1).containsEntry(KS1, REPLICATE_ON_BOTH_DCS); + } + + @Test + public void should_refresh_when_updated_keyspace_with_different_replication() { + // Given + Node node1 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN1)); + Node node2 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN2)); + Node node3 = mockNode(DC1, RACK1, ImmutableSet.of(TOKEN3)); + Node node4 = mockNode(DC2, RACK2, ImmutableSet.of(TOKEN4)); + List nodes = ImmutableList.of(node1, node2, node3, node4); + List oldKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); + DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS, REPLICATE_ON_DC1); + + // When + List newKeyspaces = + ImmutableList.of( + mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); + DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + + // Then + assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); + assertThat(newTokenMap.tokenRangesByPrimary).isSameAs(oldTokenMap.tokenRangesByPrimary); + assertThat(newTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); + assertThat(newTokenMap.replicationConfigs) + .hasSize(2) + .containsEntry(KS1, REPLICATE_ON_BOTH_DCS) + .containsEntry(KS2, REPLICATE_ON_BOTH_DCS); + } + + private DefaultNode mockNode(String dc, String rack, Set tokens) { + DefaultNode node = Mockito.mock(DefaultNode.class); + Mockito.when(node.getDatacenter()).thenReturn(dc); + Mockito.when(node.getRack()).thenReturn(rack); + Mockito.when(node.getRawTokens()).thenReturn(tokens); + return node; + } + + private KeyspaceMetadata mockKeyspace(CqlIdentifier name, Map replicationConfig) { + KeyspaceMetadata keyspace = Mockito.mock(KeyspaceMetadata.class); + Mockito.when(keyspace.getName()).thenReturn(name); + Mockito.when(keyspace.getReplication()).thenReturn(replicationConfig); + return keyspace; + } + + private static TokenRange range(String start, String end) { + return range(TOKEN_FACTORY.parse(start), TOKEN_FACTORY.parse(end)); + } + + private static TokenRange range(Token startToken, Token endToken) { + return new Murmur3TokenRange((Murmur3Token) startToken, (Murmur3Token) endToken); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java new file mode 100644 index 00000000000..2270723d3a5 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +/** @see TokenRangeTest */ +public class Murmur3TokenRangeTest { + + private static final long MIN = -9223372036854775808L; + private static final long MAX = 9223372036854775807L; + + @Test + public void should_split_range() { + assertThat(range(MIN, 4611686018427387904L).splitEvenly(3)) + .containsExactly( + range(MIN, -4611686018427387904L), + range(-4611686018427387904L, 0), + range(0, 4611686018427387904L)); + } + + @Test + public void should_split_range_that_wraps_around_the_ring() { + assertThat(range(4611686018427387904L, 0).splitEvenly(3)) + .containsExactly( + range(4611686018427387904L, -9223372036854775807L), + range(-9223372036854775807L, -4611686018427387903L), + range(-4611686018427387903L, 0)); + } + + @Test + public void should_split_range_when_division_not_integral() { + assertThat(range(0, 11).splitEvenly(3)).containsExactly(range(0, 4), range(4, 8), range(8, 11)); + } + + @Test + public void should_split_range_producing_empty_splits() { + assertThat(range(0, 2).splitEvenly(5)) + .containsExactly(range(0, 1), range(1, 2), range(2, 2), range(2, 2), range(2, 2)); + } + + @Test + public void should_split_range_producing_empty_splits_near_ring_end() { + // These are edge cases where we want to make sure we don't accidentally generate the ]min,min] + // range (which is the whole ring) + assertThat(range(MAX, MIN).splitEvenly(3)) + .containsExactly(range(MAX, MAX), range(MAX, MAX), range(MAX, MIN)); + + assertThat(range(MIN, MIN + 1).splitEvenly(3)) + .containsExactly(range(MIN, MIN + 1), range(MIN + 1, MIN + 1), range(MIN + 1, MIN + 1)); + } + + @Test + public void should_split_whole_ring() { + assertThat(range(MIN, MIN).splitEvenly(3)) + .containsExactly( + range(MIN, -3074457345618258603L), + range(-3074457345618258603L, 3074457345618258602L), + range(3074457345618258602L, MIN)); + } + + private Murmur3TokenRange range(long start, long end) { + return new Murmur3TokenRange(new Murmur3Token(start), new Murmur3Token(end)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java new file mode 100644 index 00000000000..688ceb15a56 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.SetMultimap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.LoggerFactory; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; + +@RunWith(MockitoJUnitRunner.class) +public class NetworkTopologyReplicationStrategyTest { + + private static final String DC1 = "DC1"; + private static final String DC2 = "DC2"; + private static final String DC3 = "DC3"; + private static final String RACK11 = "RACK11"; + private static final String RACK12 = "RACK12"; + private static final String RACK21 = "RACK21"; + private static final String RACK22 = "RACK22"; + private static final String RACK31 = "RACK31"; + + private static final Token TOKEN01 = new Murmur3Token(-9000000000000000000L); + private static final Token TOKEN02 = new Murmur3Token(-8000000000000000000L); + private static final Token TOKEN03 = new Murmur3Token(-7000000000000000000L); + private static final Token TOKEN04 = new Murmur3Token(-6000000000000000000L); + private static final Token TOKEN05 = new Murmur3Token(-5000000000000000000L); + private static final Token TOKEN06 = new Murmur3Token(-4000000000000000000L); + private static final Token TOKEN07 = new Murmur3Token(-3000000000000000000L); + private static final Token TOKEN08 = new Murmur3Token(-2000000000000000000L); + private static final Token TOKEN09 = new Murmur3Token(-1000000000000000000L); + private static final Token TOKEN10 = new Murmur3Token(0L); + private static final Token TOKEN11 = new Murmur3Token(1000000000000000000L); + private static final Token TOKEN12 = new Murmur3Token(2000000000000000000L); + private static final Token TOKEN13 = new Murmur3Token(3000000000000000000L); + private static final Token TOKEN14 = new Murmur3Token(4000000000000000000L); + private static final Token TOKEN15 = new Murmur3Token(5000000000000000000L); + private static final Token TOKEN16 = new Murmur3Token(6000000000000000000L); + private static final Token TOKEN17 = new Murmur3Token(7000000000000000000L); + private static final Token TOKEN18 = new Murmur3Token(8000000000000000000L); + private static final Token TOKEN19 = new Murmur3Token(9000000000000000000L); + + @Mock private Node node1, node2, node3, node4, node5, node6, node7, node8; + + @Mock private Appender appender; + @Captor private ArgumentCaptor loggingEventCaptor; + + /** 4 tokens, 2 nodes in 2 DCs, RF = 1 in each DC. */ + @Test + public void should_compute_for_simple_layout() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN04, TOKEN14, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN04, node2, TOKEN14, node1, TOKEN19, node2); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy(ImmutableMap.of(DC1, "1", DC2, "1"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + // Note: this also asserts the iteration order of the sets (unlike containsEntry(token, set)) + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN04)).containsExactly(node2, node1); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node2, node1); + } + + /** 8 tokens, 4 nodes in 2 DCs in the same racks, RF = 1 in each DC. */ + @Test + public void should_compute_for_simple_layout_with_multiple_nodes_per_rack() { + // Given + List ring = + ImmutableList.of(TOKEN01, TOKEN03, TOKEN05, TOKEN07, TOKEN13, TOKEN15, TOKEN17, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK11); + locate(node4, DC2, RACK21); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN03, node2) + .put(TOKEN05, node3) + .put(TOKEN07, node4) + .put(TOKEN13, node1) + .put(TOKEN15, node2) + .put(TOKEN17, node3) + .put(TOKEN19, node4) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy(ImmutableMap.of(DC1, "1", DC2, "1"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN03)).containsExactly(node2, node3); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node3, node4); + assertThat(replicasByToken.get(TOKEN07)).containsExactly(node4, node1); + assertThat(replicasByToken.get(TOKEN13)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node2, node3); + assertThat(replicasByToken.get(TOKEN17)).containsExactly(node3, node4); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node4, node1); + } + + /** 6 tokens, 3 nodes in 3 DCs, RF = 1 in each DC. */ + @Test + public void should_compute_for_simple_layout_with_3_dcs() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN05, TOKEN09, TOKEN11, TOKEN15, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC3, RACK31); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN05, node2) + .put(TOKEN09, node3) + .put(TOKEN11, node1) + .put(TOKEN15, node2) + .put(TOKEN19, node3) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy( + ImmutableMap.of(DC1, "1", DC2, "1", DC3, "1"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2, node3); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node2, node3, node1); + assertThat(replicasByToken.get(TOKEN09)).containsExactly(node3, node1, node2); + assertThat(replicasByToken.get(TOKEN11)).containsExactly(node1, node2, node3); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node2, node3, node1); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node3, node1, node2); + } + + /** 10 tokens, 4 nodes in 2 DCs, RF = 2 in each DC, 1 node owns 4 tokens, the others only 2. */ + @Test + public void should_compute_for_unbalanced_ring() { + // Given + List ring = + ImmutableList.of( + TOKEN01, TOKEN03, TOKEN05, TOKEN07, TOKEN09, TOKEN11, TOKEN13, TOKEN15, TOKEN17, + TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK11); + locate(node4, DC2, RACK21); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN03, node1) + .put(TOKEN05, node2) + .put(TOKEN07, node3) + .put(TOKEN09, node4) + .put(TOKEN11, node1) + .put(TOKEN13, node1) + .put(TOKEN15, node2) + .put(TOKEN17, node3) + .put(TOKEN19, node4) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy(ImmutableMap.of(DC1, "2", DC2, "2"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN03)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node2, node3, node4, node1); + assertThat(replicasByToken.get(TOKEN07)).containsExactly(node3, node4, node1, node2); + assertThat(replicasByToken.get(TOKEN09)).containsExactly(node4, node1, node2, node3); + assertThat(replicasByToken.get(TOKEN11)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN13)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node2, node3, node4, node1); + assertThat(replicasByToken.get(TOKEN17)).containsExactly(node3, node4, node1, node2); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node4, node1, node2, node3); + } + + /** 16 tokens, 8 nodes in 2 DCs with 2 per rack, RF = 2 in each DC. */ + @Test + public void should_compute_with_multiple_racks_per_dc() { + // Given + List ring = + ImmutableList.of( + TOKEN01, TOKEN02, TOKEN03, TOKEN04, TOKEN05, TOKEN06, TOKEN07, TOKEN08, TOKEN12, + TOKEN13, TOKEN14, TOKEN15, TOKEN16, TOKEN17, TOKEN18, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK12); + locate(node4, DC2, RACK22); + locate(node5, DC1, RACK11); + locate(node6, DC2, RACK21); + locate(node7, DC1, RACK12); + locate(node8, DC2, RACK22); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN02, node2) + .put(TOKEN03, node3) + .put(TOKEN04, node4) + .put(TOKEN05, node5) + .put(TOKEN06, node6) + .put(TOKEN07, node7) + .put(TOKEN08, node8) + .put(TOKEN12, node1) + .put(TOKEN13, node2) + .put(TOKEN14, node3) + .put(TOKEN15, node4) + .put(TOKEN16, node5) + .put(TOKEN17, node6) + .put(TOKEN18, node7) + .put(TOKEN19, node8) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy(ImmutableMap.of(DC1, "2", DC2, "2"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN02)).containsExactly(node2, node3, node4, node5); + assertThat(replicasByToken.get(TOKEN03)).containsExactly(node3, node4, node5, node6); + assertThat(replicasByToken.get(TOKEN04)).containsExactly(node4, node5, node6, node7); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node5, node6, node7, node8); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node6, node7, node8, node1); + assertThat(replicasByToken.get(TOKEN07)).containsExactly(node7, node8, node1, node2); + assertThat(replicasByToken.get(TOKEN08)).containsExactly(node8, node1, node2, node3); + assertThat(replicasByToken.get(TOKEN12)).containsExactly(node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN13)).containsExactly(node2, node3, node4, node5); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node3, node4, node5, node6); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node4, node5, node6, node7); + assertThat(replicasByToken.get(TOKEN16)).containsExactly(node5, node6, node7, node8); + assertThat(replicasByToken.get(TOKEN17)).containsExactly(node6, node7, node8, node1); + assertThat(replicasByToken.get(TOKEN18)).containsExactly(node7, node8, node1, node2); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node8, node1, node2, node3); + } + + /** + * 16 tokens, 8 nodes in 2 DCs with 2 per rack, RF = 3 in each DC. + * + *

        The nodes that are in the same rack occupy consecutive positions on the ring. We want to + * reproduce the case where we hit the same rack when we look for the second replica of a DC; the + * expected behavior is to skip the node and go to the next rack, and come back to the first rack + * for the third replica. + */ + @Test + public void should_pick_dc_replicas_in_different_racks_first() { + // Given + List ring = + ImmutableList.of( + TOKEN01, TOKEN02, TOKEN03, TOKEN04, TOKEN05, TOKEN06, TOKEN07, TOKEN08, TOKEN12, + TOKEN13, TOKEN14, TOKEN15, TOKEN16, TOKEN17, TOKEN18, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK11); + locate(node4, DC2, RACK21); + locate(node5, DC1, RACK12); + locate(node6, DC2, RACK22); + locate(node7, DC1, RACK12); + locate(node8, DC2, RACK22); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN02, node2) + .put(TOKEN03, node3) + .put(TOKEN04, node4) + .put(TOKEN05, node5) + .put(TOKEN06, node6) + .put(TOKEN07, node7) + .put(TOKEN08, node8) + .put(TOKEN12, node1) + .put(TOKEN13, node2) + .put(TOKEN14, node3) + .put(TOKEN15, node4) + .put(TOKEN16, node5) + .put(TOKEN17, node6) + .put(TOKEN18, node7) + .put(TOKEN19, node8) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy(ImmutableMap.of(DC1, "3", DC2, "3"), "test"); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)) + .containsExactly(node1, node2, node5, node3, node6, node4); + assertThat(replicasByToken.get(TOKEN02)) + .containsExactly(node2, node3, node5, node6, node4, node7); + assertThat(replicasByToken.get(TOKEN03)) + .containsExactly(node3, node4, node5, node6, node7, node8); + assertThat(replicasByToken.get(TOKEN04)) + .containsExactly(node4, node5, node6, node8, node1, node7); + assertThat(replicasByToken.get(TOKEN05)) + .containsExactly(node5, node6, node1, node7, node2, node8); + assertThat(replicasByToken.get(TOKEN06)) + .containsExactly(node6, node7, node1, node2, node8, node3); + assertThat(replicasByToken.get(TOKEN07)) + .containsExactly(node7, node8, node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN08)) + .containsExactly(node8, node1, node2, node4, node5, node3); + assertThat(replicasByToken.get(TOKEN12)) + .containsExactly(node1, node2, node5, node3, node6, node4); + assertThat(replicasByToken.get(TOKEN13)) + .containsExactly(node2, node3, node5, node6, node4, node7); + assertThat(replicasByToken.get(TOKEN14)) + .containsExactly(node3, node4, node5, node6, node7, node8); + assertThat(replicasByToken.get(TOKEN15)) + .containsExactly(node4, node5, node6, node8, node1, node7); + assertThat(replicasByToken.get(TOKEN16)) + .containsExactly(node5, node6, node1, node7, node2, node8); + assertThat(replicasByToken.get(TOKEN17)) + .containsExactly(node6, node7, node1, node2, node8, node3); + assertThat(replicasByToken.get(TOKEN18)) + .containsExactly(node7, node8, node1, node2, node3, node4); + assertThat(replicasByToken.get(TOKEN19)) + .containsExactly(node8, node1, node2, node4, node5, node3); + } + + /** + * 16 tokens, 8 nodes in 2 DCs with 2 per rack, RF = 3 in each DC. + * + *

        This is the same scenario as {@link #should_pick_dc_replicas_in_different_racks_first()}, + * except that each node owns consecutive tokens on the ring. + */ + @Test + public void should_pick_dc_replicas_in_different_racks_first_when_nodes_own_consecutive_tokens() { + // When + SetMultimap replicasByToken = computeWithDifferentRacksAndConsecutiveTokens(3); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(16); + assertThat(replicasByToken.get(TOKEN01)) + .containsExactly(node1, node5, node3, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN02)) + .containsExactly(node1, node5, node3, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN03)) + .containsExactly(node3, node5, node7, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN04)) + .containsExactly(node3, node5, node7, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN05)) + .containsExactly(node5, node2, node6, node4, node1, node7); + assertThat(replicasByToken.get(TOKEN06)) + .containsExactly(node5, node2, node6, node4, node1, node7); + assertThat(replicasByToken.get(TOKEN07)) + .containsExactly(node7, node2, node6, node4, node1, node3); + assertThat(replicasByToken.get(TOKEN08)) + .containsExactly(node7, node2, node6, node4, node1, node3); + assertThat(replicasByToken.get(TOKEN12)) + .containsExactly(node2, node6, node4, node1, node5, node3); + assertThat(replicasByToken.get(TOKEN13)) + .containsExactly(node2, node6, node4, node1, node5, node3); + assertThat(replicasByToken.get(TOKEN14)) + .containsExactly(node4, node6, node8, node1, node5, node3); + assertThat(replicasByToken.get(TOKEN15)) + .containsExactly(node4, node6, node8, node1, node5, node3); + assertThat(replicasByToken.get(TOKEN16)) + .containsExactly(node6, node1, node5, node3, node2, node8); + assertThat(replicasByToken.get(TOKEN17)) + .containsExactly(node6, node1, node5, node3, node2, node8); + assertThat(replicasByToken.get(TOKEN18)) + .containsExactly(node8, node1, node5, node3, node2, node4); + assertThat(replicasByToken.get(TOKEN19)) + .containsExactly(node8, node1, node5, node3, node2, node4); + } + + /** + * 16 tokens, 8 nodes in 2 DCs with 2 per rack, RF = 4 in each DC. + * + *

        This is the same test as {@link + * #should_pick_dc_replicas_in_different_racks_first_when_nodes_own_consecutive_tokens()}, except + * for the replication factors. + */ + @Test + public void should_pick_dc_replicas_in_different_racks_first_when_all_nodes_contain_all_data() { + // When + SetMultimap replicasByToken = computeWithDifferentRacksAndConsecutiveTokens(4); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(16); + assertThat(replicasByToken.get(TOKEN01)) + .containsExactly(node1, node5, node3, node7, node2, node6, node4, node8); + assertThat(replicasByToken.get(TOKEN02)) + .containsExactly(node1, node5, node3, node7, node2, node6, node4, node8); + assertThat(replicasByToken.get(TOKEN03)) + .containsExactly(node3, node5, node7, node2, node6, node4, node8, node1); + assertThat(replicasByToken.get(TOKEN04)) + .containsExactly(node3, node5, node7, node2, node6, node4, node8, node1); + assertThat(replicasByToken.get(TOKEN05)) + .containsExactly(node5, node2, node6, node4, node8, node1, node7, node3); + assertThat(replicasByToken.get(TOKEN06)) + .containsExactly(node5, node2, node6, node4, node8, node1, node7, node3); + assertThat(replicasByToken.get(TOKEN07)) + .containsExactly(node7, node2, node6, node4, node8, node1, node3, node5); + assertThat(replicasByToken.get(TOKEN08)) + .containsExactly(node7, node2, node6, node4, node8, node1, node3, node5); + assertThat(replicasByToken.get(TOKEN12)) + .containsExactly(node2, node6, node4, node8, node1, node5, node3, node7); + assertThat(replicasByToken.get(TOKEN13)) + .containsExactly(node2, node6, node4, node8, node1, node5, node3, node7); + assertThat(replicasByToken.get(TOKEN14)) + .containsExactly(node4, node6, node8, node1, node5, node3, node7, node2); + assertThat(replicasByToken.get(TOKEN15)) + .containsExactly(node4, node6, node8, node1, node5, node3, node7, node2); + assertThat(replicasByToken.get(TOKEN16)) + .containsExactly(node6, node1, node5, node3, node7, node2, node8, node4); + assertThat(replicasByToken.get(TOKEN17)) + .containsExactly(node6, node1, node5, node3, node7, node2, node8, node4); + assertThat(replicasByToken.get(TOKEN18)) + .containsExactly(node8, node1, node5, node3, node7, node2, node4, node6); + assertThat(replicasByToken.get(TOKEN19)) + .containsExactly(node8, node1, node5, node3, node7, node2, node4, node6); + } + + private SetMultimap computeWithDifferentRacksAndConsecutiveTokens( + int replicationFactor) { + List ring = + ImmutableList.of( + TOKEN01, TOKEN02, TOKEN03, TOKEN04, TOKEN05, TOKEN06, TOKEN07, TOKEN08, TOKEN12, + TOKEN13, TOKEN14, TOKEN15, TOKEN16, TOKEN17, TOKEN18, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK11); + locate(node4, DC2, RACK21); + locate(node5, DC1, RACK12); + locate(node6, DC2, RACK22); + locate(node7, DC1, RACK12); + locate(node8, DC2, RACK22); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN02, node1) + .put(TOKEN03, node3) + .put(TOKEN04, node3) + .put(TOKEN05, node5) + .put(TOKEN06, node5) + .put(TOKEN07, node7) + .put(TOKEN08, node7) + .put(TOKEN12, node2) + .put(TOKEN13, node2) + .put(TOKEN14, node4) + .put(TOKEN15, node4) + .put(TOKEN16, node6) + .put(TOKEN17, node6) + .put(TOKEN18, node8) + .put(TOKEN19, node8) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy( + ImmutableMap.of( + DC1, Integer.toString(replicationFactor), DC2, Integer.toString(replicationFactor)), + "test"); + + return strategy.computeReplicasByToken(tokenToPrimary, ring); + } + + /** + * 18 tokens, 6 nodes in 2 DCs with 2 in rack 1 and 1 in rack 2, RF = 2 in each DC. + * + *

        This is taken from a real-life cluster. + */ + @Test + public void should_compute_complex_layout() { + // When + SetMultimap replicasByToken = computeComplexLayout(2); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(18); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node5, node2, node6); + assertThat(replicasByToken.get(TOKEN02)).containsExactly(node1, node5, node2, node6); + assertThat(replicasByToken.get(TOKEN03)).containsExactly(node5, node3, node2, node6); + assertThat(replicasByToken.get(TOKEN04)).containsExactly(node3, node5, node2, node6); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node1, node5, node2, node6); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node5, node2, node6, node3); + assertThat(replicasByToken.get(TOKEN07)).containsExactly(node2, node6, node3, node5); + assertThat(replicasByToken.get(TOKEN08)).containsExactly(node6, node3, node4, node5); + assertThat(replicasByToken.get(TOKEN09)).containsExactly(node3, node4, node5, node6); + assertThat(replicasByToken.get(TOKEN10)).containsExactly(node4, node5, node6, node3); + assertThat(replicasByToken.get(TOKEN11)).containsExactly(node5, node4, node6, node3); + assertThat(replicasByToken.get(TOKEN12)).containsExactly(node4, node6, node3, node5); + assertThat(replicasByToken.get(TOKEN13)).containsExactly(node4, node6, node3, node5); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node2, node6, node3, node5); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node6, node3, node2, node5); + assertThat(replicasByToken.get(TOKEN16)).containsExactly(node3, node2, node6, node5); + assertThat(replicasByToken.get(TOKEN17)).containsExactly(node2, node6, node1, node5); + assertThat(replicasByToken.get(TOKEN18)).containsExactly(node6, node1, node5, node2); + } + + /** + * 18 tokens, 6 nodes in 2 DCs with 2 in rack 1 and 1 in rack 2, RF = 4 in each DC. + * + *

        This is the same test as {@link #should_compute_complex_layout()}, but with RF = 4, which is + * too high for this cluster (it would require 8 nodes). + */ + @Test + public void should_compute_complex_layout_with_rf_too_high() { + // When + SetMultimap replicasByToken = computeComplexLayout(4); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(18); + assertThat(replicasByToken.get(TOKEN01)) + .containsExactly(node1, node5, node3, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN02)) + .containsExactly(node1, node5, node3, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN03)) + .containsExactly(node5, node3, node1, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN04)) + .containsExactly(node3, node5, node1, node2, node6, node4); + assertThat(replicasByToken.get(TOKEN05)) + .containsExactly(node1, node5, node2, node6, node3, node4); + assertThat(replicasByToken.get(TOKEN06)) + .containsExactly(node5, node2, node6, node3, node4, node1); + assertThat(replicasByToken.get(TOKEN07)) + .containsExactly(node2, node6, node3, node4, node5, node1); + assertThat(replicasByToken.get(TOKEN08)) + .containsExactly(node6, node3, node4, node5, node2, node1); + assertThat(replicasByToken.get(TOKEN09)) + .containsExactly(node3, node4, node5, node6, node2, node1); + assertThat(replicasByToken.get(TOKEN10)) + .containsExactly(node4, node5, node6, node2, node3, node1); + assertThat(replicasByToken.get(TOKEN11)) + .containsExactly(node5, node4, node6, node2, node3, node1); + assertThat(replicasByToken.get(TOKEN12)) + .containsExactly(node4, node6, node2, node3, node5, node1); + assertThat(replicasByToken.get(TOKEN13)) + .containsExactly(node4, node6, node2, node3, node5, node1); + assertThat(replicasByToken.get(TOKEN14)) + .containsExactly(node2, node6, node3, node5, node1, node4); + assertThat(replicasByToken.get(TOKEN15)) + .containsExactly(node6, node3, node2, node5, node1, node4); + assertThat(replicasByToken.get(TOKEN16)) + .containsExactly(node3, node2, node6, node5, node1, node4); + assertThat(replicasByToken.get(TOKEN17)) + .containsExactly(node2, node6, node1, node5, node3, node4); + assertThat(replicasByToken.get(TOKEN18)) + .containsExactly(node6, node1, node5, node3, node2, node4); + } + + private SetMultimap computeComplexLayout(int replicationFactor) { + List ring = + ImmutableList.of( + TOKEN01, TOKEN02, TOKEN03, TOKEN04, TOKEN05, TOKEN06, TOKEN07, TOKEN08, TOKEN09, + TOKEN10, TOKEN11, TOKEN12, TOKEN13, TOKEN14, TOKEN15, TOKEN16, TOKEN17, TOKEN18); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + locate(node3, DC1, RACK11); + locate(node4, DC2, RACK21); + locate(node5, DC1, RACK12); + locate(node6, DC2, RACK22); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN02, node1) + .put(TOKEN03, node5) + .put(TOKEN04, node3) + .put(TOKEN05, node1) + .put(TOKEN06, node5) + .put(TOKEN07, node2) + .put(TOKEN08, node6) + .put(TOKEN09, node3) + .put(TOKEN10, node4) + .put(TOKEN11, node5) + .put(TOKEN12, node4) + .put(TOKEN13, node4) + .put(TOKEN14, node2) + .put(TOKEN15, node6) + .put(TOKEN16, node3) + .put(TOKEN17, node2) + .put(TOKEN18, node6) + .build(); + ReplicationStrategy strategy = + new NetworkTopologyReplicationStrategy( + ImmutableMap.of( + DC1, Integer.toString(replicationFactor), DC2, Integer.toString(replicationFactor)), + "test"); + + return strategy.computeReplicasByToken(tokenToPrimary, ring); + } + + /** + * When the replication factors are invalid (user error) and a datacenter has a replication factor + * that cannot be met, we want to quickly abort and move on to the next DC (instead of keeping + * scanning the ring in vain, which results in quadratic complexity). We also log a warning to + * give the user a chance to fix their settings. + * + * @see JAVA-702 + * @see JAVA-859 + */ + @Test + public void should_abort_early_and_log_when_bad_replication_factor_cannot_be_met() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN04, TOKEN14, TOKEN19); + locate(node1, DC1, RACK11); + locate(node2, DC2, RACK21); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN04, node2, TOKEN14, node1, TOKEN19, node2); + Logger logger = (Logger) LoggerFactory.getLogger(NetworkTopologyReplicationStrategy.class); + logger.addAppender(appender); + + try { + // When + int traversedTokensForValidSettings = + countTraversedTokens(ring, tokenToPrimary, ImmutableMap.of(DC1, "1", DC2, "1")); + + // Then + // No logs: + Mockito.verify(appender, never()).doAppend(any(ILoggingEvent.class)); + + // When + int traversedTokensForInvalidSettings = + countTraversedTokens(ring, tokenToPrimary, ImmutableMap.of(DC1, "1", DC2, "1", DC3, "1")); + // Did not take more steps than the valid settings + assertThat(traversedTokensForInvalidSettings).isEqualTo(traversedTokensForValidSettings); + // Did log: + Mockito.verify(appender).doAppend(loggingEventCaptor.capture()); + ILoggingEvent log = loggingEventCaptor.getValue(); + assertThat(log.getLevel()).isEqualTo(Level.WARN); + assertThat(log.getMessage()).contains("could not achieve replication factor"); + } finally { + logger.detachAppender(appender); + } + } + + // Counts the number of steps on the ring for a particular computation + private int countTraversedTokens( + List ring, + Map tokenToPrimary, + ImmutableMap replicationConfig) { + AtomicInteger count = new AtomicInteger(); + List ringSpy = Mockito.spy(ring); + Mockito.when(ringSpy.get(anyInt())) + .thenAnswer( + invocation -> { + count.incrementAndGet(); + return invocation.callRealMethod(); + }); + new NetworkTopologyReplicationStrategy(replicationConfig, "test") + .computeReplicasByToken(tokenToPrimary, ringSpy); + return count.get(); + } + + private void locate(Node node, String dc, String rack) { + Mockito.when(node.getDatacenter()).thenReturn(dc); + Mockito.when(node.getRack()).thenReturn(rack); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java new file mode 100644 index 00000000000..271741a299f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import java.math.BigInteger; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RandomTokenRangeTest { + + private static final String MIN = "-1"; + private static final String MAX = "170141183460469231731687303715884105728"; + + @Test + public void should_split_range() { + assertThat(range("0", "127605887595351923798765477786913079296").splitEvenly(3)) + .containsExactly( + range("0", "42535295865117307932921825928971026432"), + range( + "42535295865117307932921825928971026432", "85070591730234615865843651857942052864"), + range( + "85070591730234615865843651857942052864", + "127605887595351923798765477786913079296")); + } + + @Test + public void should_split_range_that_wraps_around_the_ring() { + assertThat( + range( + "127605887595351923798765477786913079296", + "85070591730234615865843651857942052864") + .splitEvenly(3)) + .containsExactly( + range("127605887595351923798765477786913079296", "0"), + range("0", "42535295865117307932921825928971026432"), + range( + "42535295865117307932921825928971026432", + "85070591730234615865843651857942052864")); + } + + @Test + public void should_split_range_producing_empty_splits_near_ring_end() { + // These are edge cases where we want to make sure we don't accidentally generate the ]min,min] + // range (which is the whole ring) + assertThat(range(MAX, MIN).splitEvenly(3)) + .containsExactly(range(MAX, MAX), range(MAX, MAX), range(MAX, MIN)); + + assertThat(range(MIN, "0").splitEvenly(3)) + .containsExactly(range(MIN, "0"), range("0", "0"), range("0", "0")); + } + + @Test + public void should_split_whole_ring() { + assertThat(range(MIN, MIN).splitEvenly(3)) + .containsExactly( + range(MIN, "56713727820156410577229101238628035242"), + range( + "56713727820156410577229101238628035242", + "113427455640312821154458202477256070485"), + range("113427455640312821154458202477256070485", MIN)); + } + + private RandomTokenRange range(String start, String end) { + return new RandomTokenRange( + new RandomToken(new BigInteger(start)), new RandomToken(new BigInteger(end))); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java new file mode 100644 index 00000000000..53f7cefaa0a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.SetMultimap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.datastax.oss.driver.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class SimpleReplicationStrategyTest { + + private static final Token TOKEN01 = new Murmur3Token(-9000000000000000000L); + private static final Token TOKEN02 = new Murmur3Token(-8000000000000000000L); + private static final Token TOKEN03 = new Murmur3Token(-7000000000000000000L); + private static final Token TOKEN04 = new Murmur3Token(-6000000000000000000L); + private static final Token TOKEN05 = new Murmur3Token(-5000000000000000000L); + private static final Token TOKEN06 = new Murmur3Token(-4000000000000000000L); + private static final Token TOKEN07 = new Murmur3Token(-3000000000000000000L); + private static final Token TOKEN08 = new Murmur3Token(-2000000000000000000L); + private static final Token TOKEN09 = new Murmur3Token(-1000000000000000000L); + private static final Token TOKEN10 = new Murmur3Token(0L); + private static final Token TOKEN11 = new Murmur3Token(1000000000000000000L); + private static final Token TOKEN12 = new Murmur3Token(2000000000000000000L); + private static final Token TOKEN13 = new Murmur3Token(3000000000000000000L); + private static final Token TOKEN14 = new Murmur3Token(4000000000000000000L); + private static final Token TOKEN15 = new Murmur3Token(5000000000000000000L); + private static final Token TOKEN16 = new Murmur3Token(6000000000000000000L); + private static final Token TOKEN17 = new Murmur3Token(7000000000000000000L); + private static final Token TOKEN18 = new Murmur3Token(8000000000000000000L); + private static final Token TOKEN19 = new Murmur3Token(9000000000000000000L); + + @Mock private Node node1, node2, node3, node4, node5, node6; + + /** 4 tokens, 2 nodes, RF = 2. */ + @Test + public void should_compute_for_simple_layout() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN06, TOKEN14, TOKEN19); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN06, node2, TOKEN14, node1, TOKEN19, node2); + SimpleReplicationStrategy strategy = new SimpleReplicationStrategy(2); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + // Note: this also asserts the iteration order of the sets (unlike containsEntry(token, set)) + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node2, node1); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node2, node1); + } + + /** 4 tokens, 2 nodes owning 2 consecutive tokens each, RF = 2. */ + @Test + public void should_compute_when_nodes_own_consecutive_tokens() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN06, TOKEN14, TOKEN19); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN06, node1, TOKEN14, node2, TOKEN19, node2); + SimpleReplicationStrategy strategy = new SimpleReplicationStrategy(2); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node2, node1); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node2, node1); + } + + /** 4 tokens, 1 node owns 3 of them, RF = 2. */ + @Test + public void should_compute_when_ring_unbalanced() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN06, TOKEN14, TOKEN19); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN06, node1, TOKEN14, node2, TOKEN19, node1); + SimpleReplicationStrategy strategy = new SimpleReplicationStrategy(2); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node2, node1); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node1, node2); + } + + /** 4 tokens, 2 nodes, RF = 6 (too large, should be <= number of nodes). */ + @Test + public void should_compute_when_replication_factor_is_larger_than_cluster_size() { + // Given + List ring = ImmutableList.of(TOKEN01, TOKEN06, TOKEN14, TOKEN19); + Map tokenToPrimary = + ImmutableMap.of(TOKEN01, node1, TOKEN06, node2, TOKEN14, node1, TOKEN19, node2); + SimpleReplicationStrategy strategy = new SimpleReplicationStrategy(6); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node2, node1); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node1, node2); + assertThat(replicasByToken.get(TOKEN19)).containsExactly(node2, node1); + } + + @Test + public void should_compute_for_complex_layout() { + // Given + List ring = + ImmutableList.builder() + .add(TOKEN01) + .add(TOKEN02) + .add(TOKEN03) + .add(TOKEN04) + .add(TOKEN05) + .add(TOKEN06) + .add(TOKEN07) + .add(TOKEN08) + .add(TOKEN09) + .add(TOKEN10) + .add(TOKEN11) + .add(TOKEN12) + .add(TOKEN13) + .add(TOKEN14) + .add(TOKEN15) + .add(TOKEN16) + .add(TOKEN17) + .add(TOKEN18) + .build(); + Map tokenToPrimary = + ImmutableMap.builder() + .put(TOKEN01, node1) + .put(TOKEN02, node1) + .put(TOKEN03, node5) + .put(TOKEN04, node3) + .put(TOKEN05, node1) + .put(TOKEN06, node5) + .put(TOKEN07, node2) + .put(TOKEN08, node6) + .put(TOKEN09, node3) + .put(TOKEN10, node4) + .put(TOKEN11, node5) + .put(TOKEN12, node4) + .put(TOKEN13, node4) + .put(TOKEN14, node2) + .put(TOKEN15, node6) + .put(TOKEN16, node3) + .put(TOKEN17, node2) + .put(TOKEN18, node6) + .build(); + + SimpleReplicationStrategy strategy = new SimpleReplicationStrategy(3); + + // When + SetMultimap replicasByToken = + strategy.computeReplicasByToken(tokenToPrimary, ring); + + // Then + assertThat(replicasByToken.keySet().size()).isEqualTo(ring.size()); + assertThat(replicasByToken.get(TOKEN01)).containsExactly(node1, node5, node3); + assertThat(replicasByToken.get(TOKEN02)).containsExactly(node1, node5, node3); + assertThat(replicasByToken.get(TOKEN03)).containsExactly(node5, node3, node1); + assertThat(replicasByToken.get(TOKEN04)).containsExactly(node3, node1, node5); + assertThat(replicasByToken.get(TOKEN05)).containsExactly(node1, node5, node2); + assertThat(replicasByToken.get(TOKEN06)).containsExactly(node5, node2, node6); + assertThat(replicasByToken.get(TOKEN07)).containsExactly(node2, node6, node3); + assertThat(replicasByToken.get(TOKEN08)).containsExactly(node6, node3, node4); + assertThat(replicasByToken.get(TOKEN09)).containsExactly(node3, node4, node5); + assertThat(replicasByToken.get(TOKEN10)).containsExactly(node4, node5, node2); + assertThat(replicasByToken.get(TOKEN11)).containsExactly(node5, node4, node2); + assertThat(replicasByToken.get(TOKEN12)).containsExactly(node4, node2, node6); + assertThat(replicasByToken.get(TOKEN13)).containsExactly(node4, node2, node6); + assertThat(replicasByToken.get(TOKEN14)).containsExactly(node2, node6, node3); + assertThat(replicasByToken.get(TOKEN15)).containsExactly(node6, node3, node2); + assertThat(replicasByToken.get(TOKEN16)).containsExactly(node3, node2, node6); + assertThat(replicasByToken.get(TOKEN17)).containsExactly(node2, node6, node1); + assertThat(replicasByToken.get(TOKEN18)).containsExactly(node6, node1, node5); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java new file mode 100644 index 00000000000..e8b0be2053d --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import java.util.List; +import org.assertj.core.api.AbstractAssert; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TokenRangeAssert extends AbstractAssert { + + public TokenRangeAssert(TokenRange actual) { + super(actual, TokenRangeAssert.class); + } + + public TokenRangeAssert startsWith(Token token) { + assertThat(actual.getStart()).isEqualTo(token); + return this; + } + + public TokenRangeAssert endsWith(Token token) { + assertThat(actual.getEnd()).isEqualTo(token); + return this; + } + + public TokenRangeAssert isEmpty() { + assertThat(actual.isEmpty()).isTrue(); + return this; + } + + public TokenRangeAssert isNotEmpty() { + assertThat(actual.isEmpty()).isFalse(); + return this; + } + + public TokenRangeAssert isWrappedAround() { + assertThat(actual.isWrappedAround()).isTrue(); + + List unwrapped = actual.unwrap(); + assertThat(unwrapped.size()) + .as("%s should unwrap to two ranges, but unwrapped to %s", actual, unwrapped) + .isEqualTo(2); + + return this; + } + + public TokenRangeAssert isNotWrappedAround() { + assertThat(actual.isWrappedAround()).isFalse(); + assertThat(actual.unwrap()).containsExactly(actual); + return this; + } + + public TokenRangeAssert unwrapsTo(TokenRange... subRanges) { + assertThat(actual.unwrap()).containsExactly(subRanges); + return this; + } + + public TokenRangeAssert intersects(TokenRange that) { + assertThat(actual.intersects(that)).as("%s should intersect %s", actual, that).isTrue(); + assertThat(that.intersects(actual)).as("%s should intersect %s", that, actual).isTrue(); + return this; + } + + public TokenRangeAssert doesNotIntersect(TokenRange... that) { + for (TokenRange thatRange : that) { + assertThat(actual.intersects(thatRange)) + .as("%s should not intersect %s", actual, thatRange) + .isFalse(); + assertThat(thatRange.intersects(actual)) + .as("%s should not intersect %s", thatRange, actual) + .isFalse(); + } + return this; + } + + public TokenRangeAssert contains(Token token, boolean isStart) { + assertThat(((TokenRangeBase) actual).contains(actual, token, isStart)).isTrue(); + return this; + } + + public TokenRangeAssert doesNotContain(Token token, boolean isStart) { + assertThat(((TokenRangeBase) actual).contains(actual, token, isStart)).isFalse(); + return this; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java new file mode 100644 index 00000000000..0ef4483e1ad --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Covers the methods that don't depend on the underlying factory (we use Murmur3 as the + * implementation here). + * + * @see Murmur3TokenRangeTest + * @see ByteOrderedTokenRangeTest + * @see RandomTokenRangeTest + */ +public class TokenRangeTest { + + private Murmur3Token min = Murmur3TokenFactory.MIN_TOKEN; + + @Test + public void should_check_intersection() { + // NB - to make the test more visual, we use watch face numbers + assertThat(range(3, 9)) + .doesNotIntersect(range(11, 1)) + .doesNotIntersect(range(1, 2)) + .doesNotIntersect(range(11, 3)) + .doesNotIntersect(range(2, 3)) + .doesNotIntersect(range(3, 3)) + .intersects(range(2, 6)) + .intersects(range(2, 10)) + .intersects(range(6, 10)) + .intersects(range(4, 8)) + .intersects(range(3, 9)) + .doesNotIntersect(range(9, 10)) + .doesNotIntersect(range(10, 11)); + assertThat(range(9, 3)) + .doesNotIntersect(range(5, 7)) + .doesNotIntersect(range(7, 8)) + .doesNotIntersect(range(5, 9)) + .doesNotIntersect(range(8, 9)) + .doesNotIntersect(range(9, 9)) + .intersects(range(8, 2)) + .intersects(range(8, 4)) + .intersects(range(2, 4)) + .intersects(range(10, 2)) + .intersects(range(9, 3)) + .doesNotIntersect(range(3, 4)) + .doesNotIntersect(range(4, 5)); + assertThat(range(3, 3)).doesNotIntersect(range(3, 3)); + + // Reminder: minToken serves as both lower and upper bound + assertThat(minTo(5)) + .doesNotIntersect(range(6, 7)) + .doesNotIntersect(toMax(6)) + .intersects(range(6, 4)) + .intersects(range(2, 4)) + .intersects(minTo(4)) + .intersects(minTo(5)); + + assertThat(toMax(5)) + .doesNotIntersect(range(3, 4)) + .doesNotIntersect(minTo(4)) + .intersects(range(6, 7)) + .intersects(range(4, 1)) + .intersects(toMax(6)) + .intersects(toMax(5)); + + assertThat(fullRing()) + .intersects(range(3, 4)) + .intersects(toMax(3)) + .intersects(minTo(3)) + .doesNotIntersect(range(3, 3)); + } + + @Test + public void should_compute_intersection() { + assertThat(range(3, 9).intersectWith(range(2, 4))).isEqualTo(ImmutableList.of(range(3, 4))); + assertThat(range(3, 9).intersectWith(range(3, 5))).isEqualTo(ImmutableList.of(range(3, 5))); + assertThat(range(3, 9).intersectWith(range(4, 6))).isEqualTo(ImmutableList.of(range(4, 6))); + assertThat(range(3, 9).intersectWith(range(7, 9))).isEqualTo(ImmutableList.of(range(7, 9))); + assertThat(range(3, 9).intersectWith(range(8, 10))).isEqualTo(ImmutableList.of(range(8, 9))); + } + + @Test + public void should_compute_intersection_with_ranges_around_ring() { + // If a range wraps the ring (like 10, -10 does) this will produce two separate intersected + // ranges. + assertThat(range(10, -10).intersectWith(range(-20, 20))) + .isEqualTo(ImmutableList.of(range(10, 20), range(-20, -10))); + assertThat(range(-20, 20).intersectWith(range(10, -10))) + .isEqualTo(ImmutableList.of(range(10, 20), range(-20, -10))); + + // If both ranges wrap the ring, they should be merged together wrapping across the range. + assertThat(range(10, -30).intersectWith(range(20, -20))) + .isEqualTo(ImmutableList.of(range(20, -30))); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_compute_intersection_when_ranges_dont_intersect() { + range(1, 2).intersectWith(range(2, 3)); + } + + @Test + public void should_merge_with_other_range() { + assertThat(range(3, 9).mergeWith(range(2, 3))).isEqualTo(range(2, 9)); + assertThat(range(3, 9).mergeWith(range(2, 4))).isEqualTo(range(2, 9)); + assertThat(range(3, 9).mergeWith(range(11, 3))).isEqualTo(range(11, 9)); + assertThat(range(3, 9).mergeWith(range(11, 4))).isEqualTo(range(11, 9)); + + assertThat(range(3, 9).mergeWith(range(4, 8))).isEqualTo(range(3, 9)); + assertThat(range(3, 9).mergeWith(range(3, 9))).isEqualTo(range(3, 9)); + assertThat(range(3, 9).mergeWith(range(3, 3))).isEqualTo(range(3, 9)); + assertThat(range(3, 3).mergeWith(range(3, 9))).isEqualTo(range(3, 9)); + + assertThat(range(3, 9).mergeWith(range(9, 11))).isEqualTo(range(3, 11)); + assertThat(range(3, 9).mergeWith(range(8, 11))).isEqualTo(range(3, 11)); + assertThat(range(3, 9).mergeWith(range(9, 1))).isEqualTo(range(3, 1)); + assertThat(range(3, 9).mergeWith(range(8, 1))).isEqualTo(range(3, 1)); + + assertThat(range(3, 9).mergeWith(range(9, 3))).isEqualTo(fullRing()); + assertThat(range(3, 9).mergeWith(range(9, 4))).isEqualTo(fullRing()); + assertThat(range(3, 10).mergeWith(range(9, 4))).isEqualTo(fullRing()); + + assertThat(range(9, 3).mergeWith(range(8, 9))).isEqualTo(range(8, 3)); + assertThat(range(9, 3).mergeWith(range(8, 10))).isEqualTo(range(8, 3)); + assertThat(range(9, 3).mergeWith(range(4, 9))).isEqualTo(range(4, 3)); + assertThat(range(9, 3).mergeWith(range(4, 10))).isEqualTo(range(4, 3)); + + assertThat(range(9, 3).mergeWith(range(10, 2))).isEqualTo(range(9, 3)); + assertThat(range(9, 3).mergeWith(range(9, 3))).isEqualTo(range(9, 3)); + assertThat(range(9, 3).mergeWith(range(9, 9))).isEqualTo(range(9, 3)); + assertThat(range(9, 9).mergeWith(range(9, 3))).isEqualTo(range(9, 3)); + + assertThat(range(9, 3).mergeWith(range(3, 5))).isEqualTo(range(9, 5)); + assertThat(range(9, 3).mergeWith(range(2, 5))).isEqualTo(range(9, 5)); + assertThat(range(9, 3).mergeWith(range(3, 7))).isEqualTo(range(9, 7)); + assertThat(range(9, 3).mergeWith(range(2, 7))).isEqualTo(range(9, 7)); + + assertThat(range(9, 3).mergeWith(range(3, 9))).isEqualTo(fullRing()); + assertThat(range(9, 3).mergeWith(range(3, 10))).isEqualTo(fullRing()); + + assertThat(range(3, 3).mergeWith(range(3, 3))).isEqualTo(range(3, 3)); + + assertThat(toMax(5).mergeWith(range(6, 7))).isEqualTo(toMax(5)); + assertThat(toMax(5).mergeWith(minTo(3))).isEqualTo(range(5, 3)); + assertThat(toMax(5).mergeWith(range(3, 5))).isEqualTo(toMax(3)); + + assertThat(minTo(5).mergeWith(range(2, 3))).isEqualTo(minTo(5)); + assertThat(minTo(5).mergeWith(toMax(7))).isEqualTo(range(7, 5)); + assertThat(minTo(5).mergeWith(range(5, 7))).isEqualTo(minTo(7)); + } + + @Test(expected = IllegalArgumentException.class) + public void should_not_merge_with_nonadjacent_and_disjoint_ranges() { + range(0, 5).mergeWith(range(7, 14)); + } + + @Test + public void should_return_non_empty_range_if_other_range_is_empty() { + assertThat(range(1, 5).mergeWith(range(5, 5))).isEqualTo(range(1, 5)); + } + + @Test + public void should_unwrap_to_non_wrapping_ranges() { + assertThat(range(9, 3)).unwrapsTo(toMax(9), minTo(3)); + assertThat(range(3, 9)).isNotWrappedAround(); + assertThat(toMax(3)).isNotWrappedAround(); + assertThat(minTo(3)).isNotWrappedAround(); + assertThat(range(3, 3)).isNotWrappedAround(); + assertThat(fullRing()).isNotWrappedAround(); + } + + @Test + public void should_split_evenly() { + // Simply exercise splitEvenly, split logic is exercised in the test of each TokenRange + // implementation + List splits = range(3, 9).splitEvenly(3); + + assertThat(splits).hasSize(3); + assertThat(splits).containsExactly(range(3, 5), range(5, 7), range(7, 9)); + } + + @Test + public void should_throw_error_with_less_than_1_splits() { + for (int i = -255; i < 1; i++) { + try { + range(0, 1).splitEvenly(i); + fail("Expected error when providing " + i + " splits."); + } catch (IllegalArgumentException e) { + // expected. + } + } + } + + @Test(expected = IllegalArgumentException.class) + public void should_not_split_empty_token_range() { + range(0, 0).splitEvenly(1); + } + + @Test + public void should_create_empty_token_ranges_if_too_many_splits() { + TokenRange range = range(0, 10); + + List ranges = range.splitEvenly(255); + assertThat(ranges).hasSize(255); + + for (int i = 0; i < ranges.size(); i++) { + TokenRange tr = ranges.get(i); + if (i < 10) { + assertThat(tr).isEqualTo(range(i, i + 1)); + } else { + assertThat(tr.isEmpty()); + } + } + } + + @Test + public void should_check_if_range_contains_token() { + // ]1,2] contains 2, but it does not contain the start of ]2,3] + assertThat(range(1, 2)) + .contains(new Murmur3Token(2), false) + .doesNotContain(new Murmur3Token(2), true); + // ]1,2] does not contain 1, but it contains the start of ]1,3] + assertThat(range(1, 2)) + .doesNotContain(new Murmur3Token(1), false) + .contains(new Murmur3Token(1), true); + + // ]2,1] contains the start of ]min,5] + assertThat(range(2, 1)).contains(min, true); + + // ]min, 1] does not contain min, but it contains the start of ]min, 2] + assertThat(minTo(1)).doesNotContain(min, false).contains(min, true); + // ]1, min] contains min, but not the start of ]min, 2] + assertThat(toMax(1)).contains(min, false).doesNotContain(min, true); + + // An empty range contains nothing + assertThat(range(1, 1)) + .doesNotContain(new Murmur3Token(1), true) + .doesNotContain(new Murmur3Token(1), false) + .doesNotContain(min, true) + .doesNotContain(min, false); + + // The whole ring contains everything + assertThat(fullRing()) + .contains(min, true) + .contains(min, false) + .contains(new Murmur3Token(1), true) + .contains(new Murmur3Token(1), false); + } + + private TokenRange range(long start, long end) { + return new Murmur3TokenRange(new Murmur3Token(start), new Murmur3Token(end)); + } + + private TokenRange minTo(long end) { + return new Murmur3TokenRange(min, new Murmur3Token(end)); + } + + private TokenRange toMax(long start) { + return new Murmur3TokenRange(new Murmur3Token(start), min); + } + + private TokenRange fullRing() { + return new Murmur3TokenRange(Murmur3TokenFactory.MIN_TOKEN, Murmur3TokenFactory.MIN_TOKEN); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java new file mode 100644 index 00000000000..6a1c0337eb1 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class ByteOrderedTokenIT extends TokenITBase { + + @ClassRule + public static CustomCcmRule ccmRule = + CustomCcmRule.builder().withNodes(3).withCreateOption("-p ByteOrderedPartitioner").build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public ByteOrderedTokenIT() { + super(ByteOrderedToken.class, false); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java new file mode 100644 index 00000000000..d0789b3f865 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class ByteOrderedTokenVnodesIT extends TokenITBase { + + @ClassRule + public static CustomCcmRule ccmRule = + CustomCcmRule.builder() + .withNodes(3) + .withCreateOption("-p ByteOrderedPartitioner") + .withCreateOption("--vnodes") + .build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public ByteOrderedTokenVnodesIT() { + super(ByteOrderedToken.class, true); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java new file mode 100644 index 00000000000..25f0669bc14 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class Murmur3TokenIT extends TokenITBase { + + @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public Murmur3TokenIT() { + super(Murmur3Token.class, false); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java new file mode 100644 index 00000000000..2bfdf9932ab --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class Murmur3TokenVnodesIT extends TokenITBase { + + @ClassRule + public static CustomCcmRule ccmRule = + CustomCcmRule.builder().withNodes(3).withCreateOption("--vnodes").build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public Murmur3TokenVnodesIT() { + super(Murmur3Token.class, true); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java new file mode 100644 index 00000000000..aec99df2fc8 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class RandomTokenIT extends TokenITBase { + + @ClassRule + public static CustomCcmRule ccmRule = + CustomCcmRule.builder().withNodes(3).withCreateOption("-p RandomPartitioner").build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public RandomTokenIT() { + super(RandomToken.class, false); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java new file mode 100644 index 00000000000..46eb5dfbbd2 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.experimental.categories.Category; + +@Category(LongTests.class) +public class RandomTokenVnodesIT extends TokenITBase { + + @ClassRule + public static CustomCcmRule ccmRule = + CustomCcmRule.builder() + .withNodes(3) + .withCreateOption("-p RandomPartitioner") + .withCreateOption("--vnodes") + .build(); + + @ClassRule + public static ClusterRule clusterRule = + new ClusterRule( + ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + + public RandomTokenVnodesIT() { + super(RandomToken.class, true); + } + + @Override + protected Cluster cluster() { + return clusterRule.cluster(); + } + + @Override + protected CqlSession session() { + return clusterRule.session(); + } + + @BeforeClass + public static void createSchema() { + TokenITBase.createSchema(clusterRule.session()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java new file mode 100644 index 00000000000..6314de38d5b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class TokenITBase { + + protected static final CqlIdentifier KS1 = ClusterUtils.uniqueKeyspaceId(); + protected static final CqlIdentifier KS2 = ClusterUtils.uniqueKeyspaceId(); + + // Must be called in a @BeforeClass method in each subclass (unfortunately we can't do this + // automatically because it requires the session, which is not available from a static context in + // this class). + protected static void createSchema(CqlSession session) { + for (String statement : + ImmutableList.of( + String.format( + "CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + KS1.asCql(false)), + String.format( + "CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}", + KS2.asCql(false)), + + // Shouldn't really do that, but it makes the rest of the tests a bit prettier. + String.format("USE %s", KS1.asCql(false)), + "CREATE TABLE foo(i int primary key)", + "INSERT INTO foo (i) VALUES (1)", + "INSERT INTO foo (i) VALUES (2)", + "INSERT INTO foo (i) VALUES (3)")) { + session.execute(statement); + } + } + + private final Class expectedTokenType; + private final boolean useVnodes; + private final int tokensPerNode; + + protected TokenITBase(Class expectedTokenType, boolean useVnodes) { + this.expectedTokenType = expectedTokenType; + this.useVnodes = useVnodes; + this.tokensPerNode = useVnodes ? 256 : 1; + } + + protected abstract Cluster cluster(); + + protected abstract CqlSession session(); + + /** + * Validates that the token metadata is consistent with server-side range queries. That is, + * querying the data in a range does return a PK that the driver thinks is in that range. + * + * @test_category metadata:token + * @expected_result token ranges are exposed and usable. + * @jira_ticket JAVA-312 + * @since 2.0.10, 2.1.5 + */ + @Test + public void should_be_consistent_with_range_queries() { + Metadata metadata = cluster().getMetadata(); + TokenMap tokenMap = metadata.getTokenMap().get(); + + // Find the replica for a given partition key of ks1.foo. + int key = 1; + ProtocolVersion protocolVersion = cluster().getContext().protocolVersion(); + ByteBuffer serializedKey = TypeCodecs.INT.encodePrimitive(key, protocolVersion); + Set replicas = tokenMap.getReplicas(KS1, serializedKey); + assertThat(replicas).hasSize(1); + Node replica = replicas.iterator().next(); + + // Iterate the cluster's token ranges. For each one, use a range query to get all the keys of + // ks1.foo that are in this range. + PreparedStatement rangeStatement = + session().prepare("SELECT i FROM foo WHERE token(i) > ? and token(i) <= ?"); + + TokenRange foundRange = null; + for (TokenRange range : tokenMap.getTokenRanges()) { + List rows = rangeQuery(rangeStatement, range); + for (Row row : rows) { + if (row.getInt("i") == key) { + // We should find our initial key exactly once + assertThat(foundRange) + .describedAs("found the same key in two ranges: " + foundRange + " and " + range) + .isNull(); + foundRange = range; + + // That range should be managed by the replica + assertThat(tokenMap.getReplicas(KS1, range)).contains(replica); + } + } + } + assertThat(foundRange).isNotNull(); + } + + private List rangeQuery(PreparedStatement rangeStatement, TokenRange range) { + List rows = Lists.newArrayList(); + for (TokenRange subRange : range.unwrap()) { + Statement statement = rangeStatement.bind(subRange.getStart(), subRange.getEnd()); + session().execute(statement).forEach(rows::add); + } + return rows; + } + + /** + * Validates that a {@link Token} can be retrieved and parsed by executing 'select token(name)' + * and then used to find data matching that token. + * + *

        This test does the following: retrieve the token for the key with value '1', get it by + * index, and ensure if is of the expected token type; select data by token with a BoundStatement; + * select data by token using setToken by index. + * + * @test_category token + * @expected_result tokens are selectable, properly parsed, and usable as input. + * @jira_ticket JAVA-312 + * @since 2.0.10, 2.1.5 + */ + @Test + public void should_get_token_from_row_and_set_token_in_query() { + ResultSet rs = session().execute("SELECT token(i) FROM foo WHERE i = 1"); + Row row = rs.one(); + + // Get by index: + Token token = row.getToken(0); + assertThat(token).isInstanceOf(expectedTokenType); + + // Get by name: the generated column name depends on the Cassandra version. + String tokenColumnName = + rs.getColumnDefinitions().contains("token(i)") ? "token(i)" : "system.token(i)"; + assertThat(row.getToken(tokenColumnName)).isEqualTo(token); + + PreparedStatement pst = session().prepare("SELECT * FROM foo WHERE token(i) = ?"); + // Bind with bind(...) + row = session().execute(pst.bind(token)).iterator().next(); + assertThat(row.getInt(0)).isEqualTo(1); + + // Bind with setToken by index + row = session().execute(pst.bind().setToken(0, token)).one(); + assertThat(row.getInt(0)).isEqualTo(1); + + // Bind with setToken by name + row = session().execute(pst.bind().setToken("partition key token", token)).one(); + assertThat(row.getInt(0)).isEqualTo(1); + } + + /** + * Validates that a {@link Token} can be retrieved and parsed by using bind variables and + * aliasing. + * + *

        This test does the following: retrieve the token by alias for the key '1', and ensure it + * matches the token by index; select data by token using setToken by name. + */ + @Test + public void should_get_token_from_row_and_set_token_in_query_with_binding_and_aliasing() { + Row row = session().execute("SELECT token(i) AS t FROM foo WHERE i = 1").one(); + Token token = row.getToken("t"); + assertThat(token).isInstanceOf(expectedTokenType); + + PreparedStatement pst = session().prepare("SELECT * FROM foo WHERE token(i) = :myToken"); + row = session().execute(pst.bind().setToken("myToken", token)).one(); + assertThat(row.getInt(0)).isEqualTo(1); + + row = + session() + .execute(SimpleStatement.newInstance("SELECT * FROM foo WHERE token(i) = ?", token)) + .one(); + assertThat(row.getInt(0)).isEqualTo(1); + } + + /** + * Ensures that an exception is raised when attempting to retrieve a token from a column that + * doesn't match the CQL type of any token type. + * + * @test_category token + * @expected_result an exception is raised. + * @jira_ticket JAVA-312 + * @since 2.0.10, 2.1.5 + */ + @Test(expected = IllegalArgumentException.class) + public void should_raise_exception_when_getting_token_on_non_token_column() { + Row row = session().execute("SELECT i FROM foo WHERE i = 1").one(); + row.getToken(0); + } + + /** + * Ensures that token ranges are exposed per node, the ranges are complete, the entire ring is + * represented, and that ranges do not overlap. + * + * @test_category metadata:token + * @expected_result The entire token range is represented collectively and the ranges do not + * overlap. + * @jira_ticket JAVA-312 + * @since 2.0.10, 2.1.5 + */ + @Test + public void should_expose_consistent_ranges() { + checkRanges(cluster()); + checkRanges(cluster(), KS1, 1); + checkRanges(cluster(), KS2, 2); + } + + private void checkRanges(Cluster cluster) { + TokenMap tokenMap = cluster.getMetadata().getTokenMap().get(); + checkRanges(tokenMap.getTokenRanges()); + } + + private void checkRanges(Cluster cluster, CqlIdentifier keyspace, int replicationFactor) { + TokenMap tokenMap = cluster.getMetadata().getTokenMap().get(); + List allRangesWithDuplicates = Lists.newArrayList(); + + // Get each host's ranges, the count should match the replication factor + for (Node node : cluster.getMetadata().getNodes().values()) { + Set hostRanges = tokenMap.getTokenRanges(keyspace, node); + // Special case: When using vnodes the tokens are not evenly assigned to each replica. + if (!useVnodes) { + assertThat(hostRanges).hasSize(replicationFactor * tokensPerNode); + } + allRangesWithDuplicates.addAll(hostRanges); + } + + // Special case check for vnodes to ensure that total number of replicated ranges is correct. + assertThat(allRangesWithDuplicates).hasSize(3 * tokensPerNode * replicationFactor); + + // Once we ignore duplicates, the number of ranges should match the number of nodes. + Set allRanges = new TreeSet<>(allRangesWithDuplicates); + assertThat(allRanges).hasSize(3 * tokensPerNode); + + // And the ranges should cover the whole ring and no ranges intersect. + checkRanges(allRanges); + } + + // Ensures that no ranges intersect and that they cover the entire ring. + private void checkRanges(Collection ranges) { + // Ensure no ranges intersect. + TokenRange[] rangesArray = ranges.toArray(new TokenRange[ranges.size()]); + for (int i = 0; i < rangesArray.length; i++) { + TokenRange rangeI = rangesArray[i]; + for (int j = i + 1; j < rangesArray.length; j++) { + TokenRange rangeJ = rangesArray[j]; + assertThat(rangeI.intersects(rangeJ)) + .as("Range " + rangeI + " intersects with " + rangeJ) + .isFalse(); + } + } + + // Ensure the defined ranges cover the entire ring. + Iterator it = ranges.iterator(); + TokenRange mergedRange = it.next(); + while (it.hasNext()) { + TokenRange next = it.next(); + mergedRange = mergedRange.mergeWith(next); + } + boolean isFullRing = + mergedRange.getStart().equals(mergedRange.getEnd()) && !mergedRange.isEmpty(); + assertThat(isFullRing).as("Ring is not fully defined for cluster.").isTrue(); + } + + /** + * Ensures that for there is at most one wrapped range in the ring, and check that unwrapping it + * produces two ranges. + * + * @test_category metadata:token + * @expected_result there is at most one wrapped range. + * @jira_ticket JAVA-312 + * @since 2.0.10, 2.1.5 + */ + @Test + public void should_have_only_one_wrapped_range() { + TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + TokenRange wrappedRange = null; + for (TokenRange range : tokenMap.getTokenRanges()) { + if (range.isWrappedAround()) { + assertThat(wrappedRange) + .as( + "Found a wrapped around TokenRange (%s) when one already exists (%s).", + range, wrappedRange) + .isNull(); + wrappedRange = range; + + assertThat(wrappedRange.unwrap()).hasSize(2); + } + } + } + + @Test + public void should_create_tokens_and_ranges() { + TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + + // Pick a random range + TokenRange range = tokenMap.getTokenRanges().iterator().next(); + + Token start = tokenMap.parse(tokenMap.format(range.getStart())); + Token end = tokenMap.parse(tokenMap.format(range.getEnd())); + + assertThat(tokenMap.newTokenRange(start, end)).isEqualTo(range); + } + + @Test + public void should_create_token_from_partition_key() { + TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + + Row row = session().execute("SELECT token(i) FROM foo WHERE i = 1").one(); + Token expected = row.getToken(0); + + ProtocolVersion protocolVersion = cluster().getContext().protocolVersion(); + assertThat(tokenMap.newToken(TypeCodecs.INT.encodePrimitive(1, protocolVersion))) + .isEqualTo(expected); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 39bce901765..4e441e12141 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -62,6 +62,7 @@ public class CcmBridge implements AutoCloseable { private final String ipPrefix; private final Map initialConfiguration; + private final List createOptions; private final String jvmArgs; @@ -99,15 +100,17 @@ public class CcmBridge implements AutoCloseable { private CcmBridge( Path directory, CassandraVersion cassandraVersion, - int nodes[], + int[] nodes, String ipPrefix, Map initialConfiguration, + List createOptions, Collection jvmArgs) { this.directory = directory; this.cassandraVersion = cassandraVersion; this.nodes = nodes; this.ipPrefix = ipPrefix; this.initialConfiguration = initialConfiguration; + this.createOptions = createOptions; StringBuilder allJvmArgs = new StringBuilder(""); String quote = isWindows() ? "\"" : ""; @@ -136,7 +139,8 @@ public void create() { "-i", ipPrefix, "-n", - Arrays.stream(nodes).mapToObj(n -> "" + n).collect(Collectors.joining(":"))); + Arrays.stream(nodes).mapToObj(n -> "" + n).collect(Collectors.joining(":")), + createOptions.stream().collect(Collectors.joining(" "))); for (Map.Entry conf : initialConfiguration.entrySet()) { execute("updateconf", String.format("%s:%s", conf.getKey(), conf.getValue())); @@ -258,6 +262,7 @@ public static class Builder { private final List jvmArgs = new ArrayList<>(); private String ipPrefix = "127.0.0."; private CassandraVersion cassandraVersion = CcmBridge.DEFAULT_CASSANDRA_VERSION; + private final List createOptions = new ArrayList<>(); private final Path directory; @@ -297,6 +302,12 @@ public Builder withIpPrefix(String ipPrefix) { return this; } + /** Adds an option to the {@code ccm create} command. */ + public Builder withCreateOption(String option) { + this.createOptions.add(option); + return this; + } + /** Enables SSL encryption. */ public Builder withSsl() { cassandraConfiguration.put("client_encryption_options.enabled", "true"); @@ -323,7 +334,13 @@ public CcmBridge build() { cassandraConfiguration.put("enable_user_defined_functions", "true"); } return new CcmBridge( - directory, cassandraVersion, nodes, ipPrefix, cassandraConfiguration, jvmArgs); + directory, + cassandraVersion, + nodes, + ipPrefix, + cassandraConfiguration, + createOptions, + jvmArgs); } } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index 3ca936d3c54..e3e344815c3 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -84,6 +84,11 @@ public Builder withCassandraVersion(CassandraVersion cassandraVersion) { return this; } + public Builder withCreateOption(String option) { + bridgeBuilder.withCreateOption(option); + return this; + } + public Builder withSsl() { bridgeBuilder.withSsl(); return this; From 0386c499ea762172205138a7753fd68e498d6d08 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Oct 2017 13:49:56 -0700 Subject: [PATCH 224/742] JAVA-1629: Add line break after each configuration option --- core/src/main/resources/reference.conf | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index e5549f40abf..39cb6e21ce1 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -60,6 +60,7 @@ datastax-java-driver { auth-provider { # This property is optional; if it is not present, no authentication will occur. // class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider + # Sample configuration for the plain-text provider: // username = cassandra // password = cassandra @@ -147,6 +148,7 @@ datastax-java-driver { # This option can be changed at runtime, the new value will be used for new connections # created after the change. interval = 30 seconds + # How long the driver waits for the response to a heartbeat. If this timeout fires, the # heartbeat is considered failed. # @@ -207,8 +209,10 @@ datastax-java-driver { # this. coalescer { class = com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer + # How many times the coalescer is allowed to reschedule itself when it did no work. max-runs-with-no-work = 5 + # The reschedule interval. reschedule-interval = 10 microseconds } @@ -303,6 +307,7 @@ datastax-java-driver { # How far in the future timestamps are allowed to drift before the warning is logged. # If it is undefined or set to 0, warnings are disabled. threshold = 1 second + # How often the warning will be logged if timestamps keep drifting above the threshold. interval = 10 seconds } @@ -324,9 +329,11 @@ datastax-java-driver { # Schedule a fixed number of executions, with a fixed delay // class = com.datastax.oss.driver.api.core.specex.ConstantSpeculativeExecutionPolicy + # The maximum number of executions (including the initial, non-speculative execution). # This must be at least one. // max-executions = 3 + # The delay between each execution. 0 is allowed, and will result in all executions being sent # simultaneously when the request starts. # Note that sub-millisecond precision is not supported, any excess precision information will @@ -377,6 +384,7 @@ datastax-java-driver { # performance penalty (one extra roundtrip to resend the query to prepare, and another to # retry the execution). enabled = true + # Whether to check `system.prepared_statements` on the target node before repreparing. # # This table exists since CASSANDRA-8831 (merged in 3.10). It stores the statements already @@ -388,11 +396,14 @@ datastax-java-driver { # If the table does not exist, or the query fails for any other reason, the error is ignored # and the driver proceeds to reprepare statements according to the other parameters. check-system-table = false + # The maximum number of statements that should be reprepared. 0 or a negative value means no # limit. max-statements = 0 + # The maximum number of concurrent requests when repreparing. max-parallelism = 100 + # The request timeout. This applies both to querying the system.prepared_statements table (if # relevant), and the prepare requests themselves. timeout = ${datastax-java-driver.connection.init-query-timeout} @@ -410,6 +421,7 @@ datastax-java-driver { # How long the driver waits to propagate an event. If another event is received within that # time, the window is reset and a batch of accumulated events will be delivered. window = 1 second + # The maximum number of events that can accumulate. If this count is reached, the events are # delivered immediately and the time window is reset. This avoids holding events indefinitely # if the window keeps getting reset. @@ -425,15 +437,19 @@ datastax-java-driver { # This option can be changed at runtime, the new value will be used for refreshes issued after # the change. It can also be overridden programmatically via Cluster.setSchemaMetadataEnabled. enabled = true + # The list of keyspaces for which schema and token metadata should be maintained. If this # property is absent or empty, all existing keyspaces are processed. # This option can be changed at runtime, the new value will be used for refreshes issued after # the change. // refreshed-keyspaces = [ "ks1", "ks2" ] + # The timeout for the requests to the schema tables. request-timeout = ${datastax-java-driver.request.timeout} + # The page size for the requests to the schema tables. request-page-size = ${datastax-java-driver.request.page-size} + # Protects against bursts of schema updates (for example when a client issues a sequence of # DDL queries), by coalescing them into a single update. # Debouncing may be disabled by setting the window to 0 or max-events to 1 (this is highly @@ -442,6 +458,7 @@ datastax-java-driver { # How long the driver waits to apply a refresh. If another refresh is requested within that # time, the window is reset and a single refresh will be triggered when it ends. window = 1 second + # The maximum number of refreshes that can accumulate. If this count is reached, a refresh # is done immediately and the window is reset. max-events = 20 @@ -474,6 +491,7 @@ datastax-java-driver { ssl-engine-factory { # This property is optional; if it is not present, SSL won't be activated. // class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory + # Sample configuration for the default SSL factory: # The cipher suites to enable when creating an SSLEngine for a connection. # This property is optional. If it is not present, the driver won't explicitly enable cipher @@ -489,6 +507,7 @@ datastax-java-driver { # The number of threads. # If this is set to 0, the driver will use `Runtime.getRuntime().availableProcessors() * 2`. size = 0 + # The options to shut down the event loop group gracefully when the driver closes. If a task # gets submitted during the quiet period, it is accepted and the quiet period starts over. The # timeout limits the overall shutdown time. @@ -498,6 +517,7 @@ datastax-java-driver { # refresh metadata, schedule reconnections, etc.) admin-group { size = 2 + shutdown {quiet-period = 2, timeout = 15, unit = SECONDS} } } From aa9a6773efd2eebaf630b2d11002acaa4669ea5c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Oct 2017 13:52:50 -0700 Subject: [PATCH 225/742] Organize imports --- .../oss/driver/internal/core/channel/WriteCoalescer.java | 1 - .../internal/core/config/typesafe/TypeSafeDriverConfig.java | 2 +- .../oss/driver/internal/core/control/ControlConnection.java | 1 - .../oss/driver/internal/core/cql/CqlRequestAsyncHandler.java | 1 - .../oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java | 1 - .../internal/core/metadata/schema/queries/SchemaRows.java | 1 - .../internal/core/metadata/token/ReplicationStrategy.java | 1 - .../oss/driver/internal/core/protocol/Lz4Compressor.java | 1 - .../oss/driver/internal/core/session/RequestHandlerBase.java | 2 -- .../datastax/oss/driver/internal/core/util/ImmutableMaps.java | 1 - .../oss/driver/internal/core/control/ControlConnectionTest.java | 1 - .../driver/internal/core/control/ControlConnectionTestBase.java | 1 - .../oss/driver/internal/core/metadata/MetadataManagerTest.java | 1 - .../oss/driver/api/core/compression/DirectCompressionIT.java | 1 - .../oss/driver/api/core/compression/HeapCompressionIT.java | 1 - 15 files changed, 1 insertion(+), 16 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java index 5cc54e18e32..ce5f3ad35fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/WriteCoalescer.java @@ -17,7 +17,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelOutboundInvoker; /** * Optimizes the write operations on Netty channels. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 4df9d226a06..66817166933 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -18,9 +18,9 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; -import com.google.common.collect.ImmutableMap; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValue; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 9f350a3a3d4..38814cf154b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java index 1d8848aab7f..37805a50ca4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class CqlRequestAsyncHandler extends CqlRequestHandlerBase diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java index 9d6d8be4b9e..062bcd9f927 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class CqlRequestAsyncProcessor diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java index 21edb17cbdd..6237b9ba1df 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java index 3a3dd4ae2cf..f24e0568210 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java @@ -21,7 +21,6 @@ import com.google.common.collect.SetMultimap; import java.util.List; import java.util.Map; -import java.util.Set; interface ReplicationStrategy { static ReplicationStrategy newInstance(Map replicationConfig, String logPrefix) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java index 871616d7a16..4d2bcf66d1a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.protocol.internal.Compressor; import io.netty.buffer.ByteBuf; import java.nio.ByteBuffer; import net.jpountz.lz4.LZ4Compressor; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java index fd1936e40dc..151ba82f802 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java @@ -21,9 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; import java.util.Queue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java index 00942d6cffb..9abb192ffb1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import java.util.Map; -import java.util.function.Function; public class ImmutableMaps { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 96efcc21bcb..8ce60d8e9c5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -35,7 +35,6 @@ import org.mockito.Mockito; import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @RunWith(DataProviderRunner.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index cb293642d0f..464de8ddf99 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -49,7 +49,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 4ef16252774..04386424a50 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -45,7 +45,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index af1370c8637..90fb15c9290 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.session.CqlSession; -import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index 906360b050a..12fac450bcc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.session.CqlSession; -import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; From d0b800b7b9741154575c0750ae0dae5f80dc7240 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Oct 2017 13:55:59 -0700 Subject: [PATCH 226/742] Remove obsolete class --- .../internal/core/util/ImmutableMaps.java | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java deleted file mode 100644 index 9abb192ffb1..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ImmutableMaps.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.util; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import java.util.Map; - -public class ImmutableMaps { - - /** Returns a new immutable map that overrides one mapping in the source map. */ - public static ImmutableMap replace(Map source, K key, V newValue) { - return ImmutableMap.builder() - .put(key, newValue) - .putAll(Maps.filterKeys(source, k -> !key.equals(k))) - .build(); - } -} From 36a5a7d25062ededfa631b79130dd13e68a2f6f8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Oct 2017 16:22:46 -0700 Subject: [PATCH 227/742] Upgrade native-protocol to 1.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 151a4b87ee8..a131a7b3b37 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ com.datastax.oss native-protocol - 1.4.1-SNAPSHOT + 1.4.1 io.netty From 5ca67fc1b70d7b3ab73935fa1883c4857d5606d7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 08:59:12 -0700 Subject: [PATCH 228/742] Clean up javadocs --- .../oss/driver/internal/core/session/DefaultSession.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index e14b8471524..6dce88afdb7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -59,18 +59,12 @@ /** * The session implementation. * - *

        - * *

        It maintains a {@link ChannelPool} to each node that the {@link LoadBalancingPolicy} set to a * non-ignored distance. It listens for distance events and node state events, in order to adjust * the pools accordingly. * - *

        - * *

        It executes requests by: * - *

        - * *

          *
        • picking the appropriate processor to convert the request into a protocol message. *
        • getting a query plan from the load balancing policy @@ -135,8 +129,6 @@ public CqlIdentifier getKeyspace() { /** * INTERNAL USE ONLY -- switches the session to a new keyspace. * - *

          - * *

          This is called by the driver when a {@code USE} query is successfully executed through the * session. Calling it from anywhere else is highly discouraged, as an invalid keyspace would * wreak havoc (close all connections and make the session unusable). From 296a53a05a4c1dbec38b24b428ec358ee75f2d58 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 08:57:38 -0700 Subject: [PATCH 229/742] Move CqlSession to api.core.cql --- core/console.scala | 3 ++- .../java/com/datastax/oss/driver/api/core/Cluster.java | 2 +- .../com/datastax/oss/driver/api/core/ClusterBuilder.java | 2 +- .../oss/driver/api/core/DefaultClusterBuilder.java | 2 +- .../oss/driver/api/core/{session => cql}/CqlSession.java | 9 ++------- .../datastax/oss/driver/api/core/session/Session.java | 1 + .../oss/driver/internal/core/DefaultCluster.java | 2 +- .../oss/driver/internal/core/cql/Conversions.java | 2 +- .../driver/internal/core/cql/DefaultAsyncResultSet.java | 2 +- .../oss/driver/internal/core/session/DefaultSession.java | 2 +- .../internal/core/cql/DefaultAsyncResultSetTest.java | 2 +- .../driver/internal/core/session/DefaultSessionTest.java | 2 +- .../api/core/ProtocolVersionInitialNegotiationIT.java | 2 +- .../driver/api/core/ProtocolVersionMixedClusterIT.java | 2 +- .../driver/api/core/auth/PlainTextAuthProviderIT.java | 2 +- .../driver/api/core/compression/DirectCompressionIT.java | 2 +- .../driver/api/core/compression/HeapCompressionIT.java | 2 +- .../driver/api/core/config/DriverConfigProfileIT.java | 2 +- .../api/core/config/DriverConfigProfileReloadIT.java | 2 +- .../oss/driver/api/core/cql/BoundStatementIT.java | 1 - .../oss/driver/api/core/heartbeat/HeartbeatIT.java | 2 +- .../oss/driver/api/core/metadata/ByteOrderedTokenIT.java | 2 +- .../api/core/metadata/ByteOrderedTokenVnodesIT.java | 2 +- .../oss/driver/api/core/metadata/DescribeIT.java | 2 +- .../oss/driver/api/core/metadata/Murmur3TokenIT.java | 2 +- .../driver/api/core/metadata/Murmur3TokenVnodesIT.java | 2 +- .../oss/driver/api/core/metadata/NodeStateIT.java | 2 +- .../oss/driver/api/core/metadata/RandomTokenIT.java | 2 +- .../driver/api/core/metadata/RandomTokenVnodesIT.java | 2 +- .../oss/driver/api/core/metadata/SchemaAgreementIT.java | 2 +- .../oss/driver/api/core/metadata/SchemaChangesIT.java | 2 +- .../datastax/oss/driver/api/core/metadata/SchemaIT.java | 2 +- .../oss/driver/api/core/metadata/TokenITBase.java | 2 +- .../oss/driver/api/core/session/GuavaCluster.java | 1 + .../oss/driver/api/core/session/GuavaClusterBuilder.java | 1 + .../oss/driver/api/core/session/RequestProcessorIT.java | 1 + .../driver/api/core/specex/SpeculativeExecutionIT.java | 2 +- .../driver/api/core/ssl/DefaultSslEngineFactoryIT.java | 2 +- .../ssl/DefaultSslEngineFactoryWithClientAuthIT.java | 2 +- ...faultSslEngineFactoryWithClientAuthNotProvidedIT.java | 2 +- ...faultSslEngineFactoryWithTruststoreNotProvidedIT.java | 2 +- .../api/core/type/codec/registry/CodecRegistryIT.java | 2 +- .../oss/driver/api/testinfra/cluster/ClusterRule.java | 2 +- .../oss/driver/api/testinfra/cluster/ClusterUtils.java | 2 +- 44 files changed, 45 insertions(+), 46 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/{session => cql}/CqlSession.java (88%) diff --git a/core/console.scala b/core/console.scala index 1490675b19f..cac890d08e6 100644 --- a/core/console.scala +++ b/core/console.scala @@ -11,11 +11,12 @@ * Use Ctrl+C instead. */ import com.datastax.oss.driver.api.core._ -import com.datastax.oss.driver.api.core.session.CqlSession import com.datastax.oss.driver.internal.core.metadata.TopologyEvent import com.datastax.oss.driver.internal.core.context.InternalDriverContext import java.net.InetSocketAddress +import com.datastax.oss.driver.api.core.cql.CqlSession + // Heartbeat logs every 30 seconds are annoying in the console, raise the interval System.setProperty("datastax-java-driver.connection.heartbeat.interval", "1 hour") diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java index bdbd0d3e1c4..6e2672a7fa2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 4d1250916a3..84c93b69301 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; import com.datastax.oss.driver.internal.core.DefaultCluster; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java index c842bb8ecc3..5a920f4a522 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; /** Helper class to build an instance of the default {@link Cluster} implementation. */ public class DefaultClusterBuilder diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java similarity index 88% rename from core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java rename to core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java index f278849361c..7ce974ac07c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java @@ -13,14 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; import java.util.concurrent.CompletionStage; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 77d8879dd34..8f75f99b6e0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.reflect.GenericType; /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index e782e4adb95..110ebba036d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -25,7 +25,7 @@ import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index fb5cfd57e51..ee6cc83554e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -50,7 +50,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index b154895e086..b0775b73d62 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 6dce88afdb7..f7fe1f5afc8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.channel.DriverChannel; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 23d4aa9e085..9f169b13eed 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 3a8c5d025a7..085aebe9a85 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 77bac43401b..e317bffa1f9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index fd218d3222d..cafc809d358 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index 41eff15b42d..ab9b929b7ac 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 90fb15c9290..c6cf7cea109 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index 12fac450bcc..0c404b09de9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index f42532f4b99..f061d7161fa 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index 7e340f2f93a..e2d799774ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 962d02e3fff..478ccace430 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.session.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index fadc5535289..040210cdea4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index 6a1c0337eb1..fb803d8391a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index d0789b3f865..04cb09103bc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 7fdd5d059ae..e1cd79c4684 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index 25f0669bc14..5ba470d6518 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index 2bfdf9932ab..e941561b0af 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 316e646bc39..bdcb8f83c97 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index aec99df2fc8..316f35ee8be 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index 46eb5dfbbd2..8565e459c21 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.LongTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index d896ff70df4..85874279274 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 17d36d8c620..a96e0f47bc8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index cf307ff687c..72480c88862 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index 6314de38d5b..79c5895b258 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -25,7 +25,7 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.google.common.collect.ImmutableList; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java index 0bd0212bd7a..127835535d7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java @@ -17,6 +17,7 @@ package com.datastax.oss.driver.api.core.session; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.internal.core.ClusterWrapper; public class GuavaCluster extends ClusterWrapper { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java index 5378193aba4..38bffbd84e3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import java.util.List; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 0cf3aff2e6f..524cb8796e1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.session; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index d68186ded97..51433bbf3ac 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index 9c42b1e5f95..7064fe30e9d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index f424bbe1466..872d61601af 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java index d04b763dee9..1fe145d7f60 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java index 4a924bd6a6e..158aa633efc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.IsolatedTests; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 142baf1ae1e..42f5cce44f6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 2611a98dbe0..5e4a584299b 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import org.junit.rules.ExternalResource; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index 776e5d2b5e6..9d400f46dbb 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.session.CqlSession; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import java.util.concurrent.atomic.AtomicInteger; From f75a9b56c4aa87da84a1d5f0a2b943043e717058 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Sep 2017 14:35:16 -0700 Subject: [PATCH 230/742] Port address translation docs from 3.x --- manual/core/address_resolution/README.md | 99 ++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 manual/core/address_resolution/README.md diff --git a/manual/core/address_resolution/README.md b/manual/core/address_resolution/README.md new file mode 100644 index 00000000000..d4d2efb96f1 --- /dev/null +++ b/manual/core/address_resolution/README.md @@ -0,0 +1,99 @@ +## Address resolution + +Each node in the Cassandra cluster is uniquely identified by an IP address that the driver will use +to establish connections. + +* for contact points, these are provided as part of configuring the `Cluster` object; +* for other nodes, addresses will be discovered dynamically, either by inspecting `system.peers` on + already connected nodes, or via push notifications received on the control connection when new + nodes are discovered by gossip. + + +### Cassandra-side configuration + +The address that each Cassandra node shares with clients is the **broadcast RPC address**; it is +controlled by various properties in [cassandra.yaml]: + +* [rpc_address] or [rpc_interface] is the address that the Cassandra process *binds to*. You must + set one or the other, not both (for more details, see the inline comments in the default + `cassandra.yaml` that came with your installation); +* [broadcast_rpc_address] \(introduced in Cassandra 2.1) is the address to share with clients, if it + is different than the previous one (the reason for having a separate property is if the bind + address is not public to clients, because there is a router in between). + +If `broadcast_rpc_address` is not set, it defaults to `rpc_address`/`rpc_interface`. If +`rpc_address`/`rpc_interface` is 0.0.0.0 (all interfaces), then `broadcast_rpc_address` *must* be +set. + +If you're not sure which address a Cassandra node is broadcasting, launch cqlsh locally on the node, +execute the following query and take node of the result: + +``` +cqlsh> select broadcast_address from system.local; + + broadcast_address +------------------- + 172.1.2.3 +``` + +Then connect to *another* node in the cluster and run the following query, injecting the previous +result: + +``` +cqlsh> select rpc_address from system.peers where peer = '172.1.2.3'; + + rpc_address +------------- + 1.2.3.4 +``` + +That last result is the broadcast RPC address. Ensure that it is accessible from the client machine +where the driver will run. + + +### Driver-side address translation + +Sometimes it's not possible for Cassandra nodes to broadcast addresses that will work for each and +every client; for instance, they might broadcast private IPs because most clients are in the same +network, but a particular client could be on another network and go through a router. + +For such cases, you can register a driver-side component that will perform additional address +translation. Write a class that implements [AddressTranslator] with the following constructor: + +```java +public class MyAddressTranslator implements AddressTranslator { + + public PassThroughAddressTranslator(DriverContext context, DriverOption configRoot) { + // retrieve any required dependency or extra configuration option, otherwise can stay empty + } + + @Override + public InetSocketAddress translate(InetSocketAddress address) { + // your custom translation logic + } + + @Override + public void close() { + // free any resources if needed, otherwise can stay empty + } +} +``` + +Then reference this class from the [configuration](../configuration/): + +``` +datastax-java-driver.address-translator.class = com.mycompany.MyAddressTranslator +``` + +Note: the contact points provided while creating the `Cluster` are not translated, only addresses +retrieved from or sent by Cassandra nodes are. + + + + +[AddressTranslator]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/addresstranslation/AddressTranslator.html + +[cassandra.yaml]: https://docs.datastax.com/en/cassandra/3.x/cassandra/configuration/configCassandra_yaml.html +[rpc_address]: https://docs.datastax.com/en/cassandra/3.x/cassandra/configuration/configCassandra_yaml.html?scroll=configCassandra_yaml__rpc_address +[rpc_interface]: https://docs.datastax.com/en/cassandra/3.x/cassandra/configuration/configCassandra_yaml.html?scroll=configCassandra_yaml__rpc_interface +[broadcast_rpc_address]: https://docs.datastax.com/en/cassandra/3.x/cassandra/configuration/configCassandra_yaml.html?scroll=configCassandra_yaml__broadcast_rpc_address From fd814f8f3104e4c8d9526e848f8df3ab0f0700c2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 26 Sep 2017 10:54:03 -0700 Subject: [PATCH 231/742] JAVA-1642: Document metadata --- .../oss/driver/api/core/metadata/Node.java | 10 +- manual/core/metadata/README.md | 47 ++++ manual/core/metadata/node/README.md | 84 +++++++ manual/core/metadata/schema/README.md | 219 ++++++++++++++++++ manual/core/metadata/token/README.md | 163 +++++++++++++ upgrade_guide/README.md | 12 +- 6 files changed, 532 insertions(+), 3 deletions(-) create mode 100644 manual/core/metadata/README.md create mode 100644 manual/core/metadata/node/README.md create mode 100644 manual/core/metadata/schema/README.md create mode 100644 manual/core/metadata/token/README.md diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index d0025c2a511..6ec304592ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -24,7 +24,12 @@ import java.util.Map; import java.util.Optional; -/** Metadata about a Cassandra node in the cluster. */ +/** + * Metadata about a Cassandra node in the cluster. + * + *

          This object is mutable, all of its properties may be updated at runtime to reflect the latest + * state of the node. + */ public interface Node { /** * The address that the driver uses to connect to the node. This is the node's broadcast RPC @@ -65,7 +70,8 @@ public interface Node { * are unspecified and may change at any point in time, always check for the existence of a key * before using it. * - *

          The returned map is immutable. + *

          Note that the returned map is immutable: if the properties change, this is reflected by + * publishing a new map instance, therefore you must call this method again to see the changes. */ Map getExtras(); diff --git a/manual/core/metadata/README.md b/manual/core/metadata/README.md new file mode 100644 index 00000000000..f203e3fdef4 --- /dev/null +++ b/manual/core/metadata/README.md @@ -0,0 +1,47 @@ +## Metadata + +The driver exposes metadata about the Cassandra cluster via the [Cluster#getMetadata] method. It +returns a [Metadata] object, which contains three types of information: + +* [node metadata](node/) +* [schema metadata](schema/) +* [token metadata](token/) + +Metadata is mostly **immutable** (except for the fields of the [Node] class, see the "node metadata" +link above for details). Each call to `getMetadata()` will return a **new copy** if something has +changed since the last call. Do not cache the result across usages: + +```java +Metadata metadata = cluster.getMetadata(); + +session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); + +// WRONG: the metadata was retrieved before the CREATE TABLE call, it does not reflect the new table +TableMetadata fooMetadata = + metadata + .getKeyspace(CqlIdentifier.fromCql("test")) + .getTable(CqlIdentifier.fromCql("foo")); +assert fooMetadata == null; +``` + +On the other hand, the advantage of immutability is that a `Metadata` instance provides a +**consistent view** of the cluster at a given point in time. In other words, the token map is +guaranteed to be in sync with the node and schema metadata: + +```java +Metadata metadata = cluster.getMetadata(); +// Pick up any node and keyspace: +Node node = metadata.getNodes().values().iterator().next(); +KeyspaceMetadata keyspace = metadata.getKeyspaces().values().iterator().next(); + +TokenMap tokenMap = metadata.getTokenMap().get(); +// The token map is guaranteed to have the corresponding data: +Set tokenRanges = tokenMap.getTokenRanges(keyspace.getName(), node); +``` + +This is a big improvement over previous versions of the driver, where it was possible to observe a +new keyspace in the schema metadata before the token metadata was updated. + +[Cluster#getMetadata]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#getMetadata-- +[Metadata]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html +[Node]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html \ No newline at end of file diff --git a/manual/core/metadata/node/README.md b/manual/core/metadata/node/README.md new file mode 100644 index 00000000000..40f9cb1e128 --- /dev/null +++ b/manual/core/metadata/node/README.md @@ -0,0 +1,84 @@ +## Node metadata + +[Metadata#getNodes] returns all the nodes known to the driver when the metadata was retrieved; this +includes down and ignored nodes (see below), so the fact that a node is in this list does not +necessarily mean that the driver is connected to it. + +```java +Map nodes = cluster.getMetadata().getNodes(); +System.out.println("Nodes in the cluster:"); +for (Node node : nodes.values()) { + System.out.printf( + " %s is %s and %s (%d connections)%n", + node.getConnectAddress().getAddress(), + node.getState(), + node.getDistance(), + node.getOpenConnections()); +} +``` + +The returned map is immutable: it does not reflect additions or removals since the metadata was +retrieved. On the other hand, the [Node] object is mutable; you can hold onto an instance across +metadata refreshes and see updates to the fields. + +A few notable fields are explained below; for the full details, refer to the Javadocs. + +[Node#getState()] indicates how the driver sees the node (see the Javadocs of [NodeState] for the +list of possible states with detailed explanations). In general, the driver tries to be resilient to +spurious DOWN notifications, and will try to use a node as long as it seems up, even if some events +seem to indicate otherwise: for example, if the Cassandra gossip detects a node as down because of +cross-node connectivity issues, but the driver still has active connections to that node, the node +will stay up. Two related properties are [Node#getOpenConnections()] and [Node#isReconnecting()]. + +[Node#getDatacenter()] and [Node#getRack()] represent the location of the node. This information is +used by some load balancing policies to prioritize coordinators that are physically close to the +client. + +[Node#getDistance()] is set by the load balancing policy. The driver does not connect to `IGNORED` +nodes. The exact definition of `LOCAL` and `REMOTE` is left to the interpretation of each policy, +but in general it represents the proximity to the client, and `LOCAL` nodes will be prioritized as +coordinators. They also influence pooling options. + +### Advanced topics + +#### Forcing a node down + +It is possible to temporarily or permanently close all connections to a node and disable +reconnection. The driver does that internally for certain unrecoverable errors (such as a protocol +version mismatch), but this could also be useful for maintenance, or for a custom component (load +balancing policy, etc). + +```java +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; + +InternalDriverContext context = (InternalDriverContext) cluster.getContext(); +context.eventBus().fire(TopologyEvent.forceDown(node1.getConnectAddress())); +context.eventBus().fire(TopologyEvent.forceUp(node1.getConnectAddress())); +``` + +As shown by the imports above, forcing a node down requires the *internal* driver API, which is +reserved for expert usage and subject to the disclaimers in +[API conventions](../../../api_conventions/). + +#### Using a custom topology monitor + +By default, the driver relies on Cassandra's gossip protocol to receive notifications about the +node states. It opens a control connection to one of the nodes, and registers for server-sent state +events. + +Some organizations have their own way of monitoring Cassandra nodes, and prefer to use it instead. +It is possible to completely override the default behavior to bypass gossip. The full details are +beyond the scope of this document; if you're interested, study the `TopologyMonitor` interface in +the source code. + + +[Metadata#getNodes]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html#getNodes-- +[Node]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html +[Node#getState()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#getState-- +[Node#getDatacenter()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#getDatacenter-- +[Node#getRack()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#getRack-- +[Node#getDistance()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#getDistance-- +[Node#getOpenConnections()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#getOpenConnections-- +[Node#isReconnecting()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html#isReconnecting-- +[NodeState]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/NodeState.html \ No newline at end of file diff --git a/manual/core/metadata/schema/README.md b/manual/core/metadata/schema/README.md new file mode 100644 index 00000000000..ba331d39867 --- /dev/null +++ b/manual/core/metadata/schema/README.md @@ -0,0 +1,219 @@ +## Schema metadata + +[Metadata#getKeyspaces] returns a client-side representation of the database schema: + +```java +Map keyspaces = cluster.getMetadata().getKeyspaces(); +KeyspaceMetadata system = keyspaces.get(CqlIdentifier.fromCql("system")); +System.out.println("The system keyspace contains the following tables:"); +for (TableMetadata table : system.getTables().values()) { + System.out.printf( + " %s (%d columns)%n", table.getName().asCql(true), table.getColumns().size()); +} +``` + +Schema metadata is fully immutable (both the map and all the objects it contains). It represents a +snapshot of the database at the time of the last metadata refresh, and is consistent with the +[token map](../token/) of its parent `Metadata` object. Keep in mind that `Metadata` is itself +immutable; if you need to get the latest schema, be sure to call +`cluster.getMetadata().getKeyspaces()` again (and not just `getKeyspaces()` on a stale `Metadata` +reference). + + +### Notifications + +If you need to follow schema changes, you don't need to poll the metadata manually; instead, +you can register a listener to get notified when changes occur: + +```java +SchemaChangeListener listener = + new SchemaChangeListenerBase() { + @Override + public void onTableCreated(TableMetadata table) { + System.out.println("New table: " + table.getName().asCql(true)); + } + }; +cluster.register(listener); + +session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); +``` + +See [SchemaChangeListener] for the list of available methods. [SchemaChangeListenerBase] is a +convenience implementation with empty methods, for when you only need to override a few of them. + + +### Configuration + +#### Enabling/disabling + +You can disable schema metadata globally from the configuration: + +``` +datastax-java-driver.metadata.schema.enabled = false +``` + +If it is disabled at startup, [Metadata#getKeyspaces] will stay empty. If you disable it at runtime, +it will keep the value of the last refresh. + +You can achieve the same thing programmatically with [Cluster#setSchemaMetadataEnabled]: if you call +it with `true` or `false`, it overrides the configuration; if you pass `null`, it reverts to the +value defined in the configuration. One case where that could come in handy is if you are sending a +large number of DDL statements from your code: + +```java +// Disable temporarily, we'll do a single refresh once we're done +cluster.setSchemaMetadataEnabled(false); + +for (int i = 0; i < 100; i++) { + session.execute(String.format("CREATE TABLE test.foo%d (k int PRIMARY KEY)", i)); +} + +cluster.setSchemaMetadataEnabled(null); +``` + +Whenever schema metadata was disabled and becomes enabled again (either through the configuration or +the API), a refresh is triggered immediately. + + +#### Filtering + +You can also limit the metadata to a subset of keyspaces: + +``` +datastax-java-driver.metadata.schema.refreshed-keyspaces = [ "users", "products" ] +``` + +If the property is absent or the list is empty, it is interpreted as "all keyspaces". + +Note that, if you change the list at runtime, `onKeyspaceAdded`/`onKeyspaceDropped` will be invoked +on your schema listeners for the newly included/excluded keyspaces. + + +#### Schema agreement + +Due to the distributed nature of Cassandra, schema changes made on one node might not be immediately +visible to others. If left unaddressed, this could create race conditions when successive queries +get routed to different coordinators: + +```ditaa + Application Driver Node 1 Node 2 +------+--------------------+------------------+------------------+--- + | | | | + | CREATE TABLE foo | | | + |------------------->| | | + | | send request | | + | |----------------->| | + | | | | + | | success | | + | |<-----------------| | + | complete query | | | + |<-------------------| | | + | | | | + | SELECT k FROM foo | | | + |------------------->| | | + | | send request | + | |------------------------------------>| schema changes not + | | | replicated yet + | | unconfigured table foo | + | |<------------------------------------| + | ERROR! | | | + |<-------------------| | | + | | | | +``` + +To avoid this issue, the driver waits until all nodes agree on a common schema version: + +```ditaa + Application Driver Node 1 +------+--------------------+------------------+----- + | | | + | CREATE TABLE... | | + |------------------->| | + | | send request | + | |----------------->| + | | | + | | success | + | |<-----------------| + | | | + | /--------------------\ | + | :Wait until all nodes+------>| + | :agree (or timeout) : | + | \--------------------/ | + | | ^ | + | | | | + | | +---------| + | | | + | complete query | | + |<-------------------| | + | | | +``` + +Schema agreement is checked: + +* before a schema refresh; +* before [repreparing all queries](../../statements/prepared#how-the-driver-prepares) on a newly up + node; +* before completing a successful schema-altering query (like in our example above). + +It is done by querying system tables to find out the schema version of all nodes that are currently +UP. If all the versions match, the check succeeds, otherwise it is retried periodically, until a +given timeout. This process is tunable in the driver's configuration: + +``` +datastax-java-driver.connection.control-connection.schema-agreement { + interval = 200 milliseconds + timeout = 10 seconds + warn-on-failure = true +} +``` + +After executing a statement, you can check whether schema agreement was successful or timed out with +[ExecutionInfo#isSchemaInAgreement]: + +```java +ResultSet rs = session.execute("CREATE TABLE..."); +if (rs.getExecutionInfo().isSchemaInAgreement()) { + ... +} +``` + +You can also perform an on-demand check at any time with [Cluster#checkSchemaAgreementAsync] (or its +synchronous counterpart): + +```java +if (cluster.checkSchemaAgreement()) { + ... +} +``` + +A schema agreement failure is not fatal, but it might produce unexpected results (as explained at +the beginning of this section). + + +##### Schema agreement in mixed-version clusters + +If you're operating a cluster with different major/minor server releases (for example, Cassandra 2.1 +and 2.2), schema agreement will never succeed. This is because the way the schema version is +computed changes across releases, so the nodes will report different versions even though they +actually agree (see [JAVA-750] for the technical details). + +This issue would be hard to fix in a reliable way, and shouldn't be that much of a problem in +practice anyway: if you're in the middle of a rolling upgrade, you're probably not applying schema +changes at the same time. + + +#### Relation to token metadata + +Some of the data in the [token map](../token/) relies on keyspace metadata (any method that takes a +`CqlIdentifier` argument). If schema metadata is disabled or filtered, token metadata will also be +unavailable for the excluded keyspaces. + + +[Metadata#getKeyspaces]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html#getKeyspaces-- +[SchemaChangeListener]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.html +[SchemaChangeListenerBase]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.html +[Cluster#setSchemaMetadataEnabled]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#setSchemaMetadataEnabled-java.lang.Boolean- +[Cluster#checkSchemaAgreementAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#checkSchemaAgreementAsync-- +[ExecutionInfo#isSchemaInAgreement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ExecutionInfo.html#isSchemaInAgreement-- + +[JAVA-750]: https://datastax-oss.atlassian.net/browse/JAVA-750 \ No newline at end of file diff --git a/manual/core/metadata/token/README.md b/manual/core/metadata/token/README.md new file mode 100644 index 00000000000..5546071253f --- /dev/null +++ b/manual/core/metadata/token/README.md @@ -0,0 +1,163 @@ +## Token metadata + +[Metadata#getTokenMap] returns information about the tokens used for data replication. It is used +internally by the driver to send requests to the optimal coordinator when token-aware routing is +enabled. Another typical use case is data analytics clients, for example fetching a large range of +keys in parallel by sending sub-queries to each replica. + +Because token metadata can be disabled, the resulting [TokenMap] object is wrapped in an `Optional`; +to access it, you can use either a functional pattern, or more traditionally test first with +`isPresent` and then unwrap: + +```java +Metadata metadata = cluster.getMetadata(); + +metadata.getTokenMap().ifPresent(tokenMap -> { + // do something with the map +}); + +if (metadata.getTokenMap().isPresent()) { + TokenMap tokenMap = metadata.getTokenMap().get(); + // do something with the map +} +``` + + +### `TokenMap` methods + +For illustration purposes, let's consider a fictitious ring with 6 tokens, and a cluster of 3 nodes +that each own two tokens: + +```ditaa + node1 + /---\ + /=---+ 12+---=\ + : \---/ : + | | + /-+-\ /-+-\ +node3 | 10| | 2 | node2 + \-+-/ \-+-/ + : : + | | + /---\ /-+-\ +node2 | 8 | | 4 | node3 + \-+-/ \-+-/ + : : + | /---\ | + \=---+ 6 +---=/ + \---/ + node1 +``` + +The first thing you can do is retrieve all the ranges, in other words describe the ring: + +```java +Set ring = tokenMap.getTokenRanges(); +// Returns [Murmur3TokenRange(Murmur3Token(12), Murmur3Token(2)), +// Murmur3TokenRange(Murmur3Token(2), Murmur3Token(4)), +// Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), +// Murmur3TokenRange(Murmur3Token(6), Murmur3Token(8)), +// Murmur3TokenRange(Murmur3Token(8), Murmur3Token(10)), +// Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))] +``` + +Note: `Murmur3Token` is an implementation detail. The actual class depends on the partitioner +you configured in Cassandra, but in general you don't need to worry about that. `TokenMap` provides +a few utility methods to parse tokens and create new instances: `parse`, `format`, `newToken` and +`newTokenRange`. + +You can also retrieve the ranges and tokens owned by a specific replica: + +```java +tokenMap.getTokenRanges(node1); +// [Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12)), +// Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6))] + +tokenMap.getTokens(node1); +// [Murmur3Token(12)), Murmur3Token(6))] +``` + +As shown here, the node owns the ranges that *end* with its tokens; this is because ranges are +start-exclusive and end-inclusive: `]10, 12]` and `]4, 6]`. + +Next, you can retrieve keyspace-specific information. To illustrate this, let's use two keyspaces +with different replication settings: + +``` +// RF = 1: each range is only stored on the primary replica +CREATE KEYSPACE ks1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; + +// RF = 2: each range is stored on the primary replica, and replicated on the next node in the ring +CREATE KEYSPACE ks2 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}; +``` + +`getReplicas` finds the nodes that have the data in a given range: + +```java +TokenRange firstRange = tokenMap.getTokenRanges().iterator().next(); +// Murmur3TokenRange(Murmur3Token(12), Murmur3Token(2)) + +Set nodes1 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks1"), firstRange); +// [node2] (only the primary replica) + +Set nodes2 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks2"), firstRange); +// [node2, node3] (the primary replica, and the next node on the ring) +``` + +There is a also a variant that takes a primary key, to find the replicas for a particular row. In +the following example, let's assume that the key hashes to the token "1" with the current +partitioner: + +```java +String pk = "johndoe@example.com"; +// You need to manually encode the key as binary: +ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, cluster.getContext().protocolVersion()); + +Set nodes1 = tokenMap.getReplicas(CqlIdentifier.fromInternal("ks1"), encodedPk); +// Assuming the key hashes to "1", it is in the ]12, 2] range +// => [node2] (only the primary replica) + +Set nodes2 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks2"), encodedPk); +// [node2, node3] (the primary replica, and the next node on the ring) +``` + +Finally, you can go the other way, and find the token ranges that a node stores for a given +keyspace: + +```java +Set ranges1 = tokenMap.getTokenRanges(CqlIdentifier.fromCql("ks1"), node1); +// [Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), +// Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))] +// (only its primary ranges) + +Set ranges2 = tokenMap.getTokenRanges(CqlIdentifier.fromCql("ks2"), node1); +// [Murmur3TokenRange(Murmur3Token(2), Murmur3Token(4)), +// Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), +// Murmur3TokenRange(Murmur3Token(8), Murmur3Token(10)), +// Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))] +// (its primary ranges, and a replica of the primary ranges of node3, the previous node on the ring) +``` + +### Configuration + +#### Enabling/disabling + +You can disable token metadata globally from the configuration: + +``` +datastax-java-driver.metadata.token-map.enabled = false +``` + +If it is disabled at startup, [Metadata#getTokenMap] will stay empty, and token-aware routing won't +work (requests will be sent to a non-optimal coordinator). If you disable it at runtime, it will +keep the value of the last refresh, and token-aware routing might operate on stale data. + +#### Relation to schema metadata + +The keyspace-specific information in `TokenMap` (all methods with a `CqlIdentifier` argument) relies +on [schema metadata](../schema/). If schema metadata is disabled or filtered, token metadata will +also be unavailable for the excluded keyspaces. + + +[Metadata#getTokenMap]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html#getTokenMap-- +[TokenMap]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/TokenMap.html \ No newline at end of file diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 58f084a5b05..e33d2892da2 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -116,4 +116,14 @@ sensitivity. For example, this type is used in schema metadata or when creating a session connected to a specific keyspace. When manipulating "data containers" such as rows, UDTs and tuples, columns can also be referenced by a `CqlIdentifier`; however, we've also kept a raw string variant for convenience, with -the same rules as in 3.x (see `GettableById` and `GettableByName` for details). \ No newline at end of file +the same rules as in 3.x (see `GettableById` and `GettableByName` for details). + +#### Atomic metadata updates + +`Cluster.getMetadata()` is now immutable and updated atomically. The node list, schema metadata and +token map exposed by a given `Metadata` instance are guaranteed to be in sync. + +On the other hand, this means you have to call `getMetadata()` again each time you need a fresh +copy; do not cache the result. + +See the [manual](../manual/core/metadata/) for all the details. \ No newline at end of file From 323517f8ddfe94a94dcfb9494ee57cf3d44f79d5 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 17 Oct 2017 11:52:26 -0500 Subject: [PATCH 232/742] Upgrade to simulacron 0.5.5 Also remove duplicate mockito-core callout in integration-tests/pom.xml --- integration-tests/pom.xml | 5 ----- pom.xml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 42dff931754..3f73bf99729 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -43,11 +43,6 @@ junit-dataprovider test - - org.mockito - mockito-core - test - ch.qos.logback logback-classic diff --git a/pom.xml b/pom.xml index a131a7b3b37..5c510e3a5db 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ com.datastax.oss.simulacron simulacron-native-server - 0.5.2 + 0.5.5 org.apache.commons From 29e13f9c63abc5489408b5f89949e52269dbf8cd Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Sep 2017 11:11:11 -0700 Subject: [PATCH 233/742] Update quick start example to latest Cluster API --- manual/core/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/manual/core/README.md b/manual/core/README.md index 135e789541c..1d4cc6855c4 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -16,29 +16,29 @@ following coordinates: Here's a short program that connects to Cassandra and executes a query: ```java -try (Cluster cluster = +try (Cluster cluster = Cluster.builder().addContactPoint(new InetSocketAddress("127.0.0.1", 9042)).build()) { // (1) - Session session = cluster.connect(); // (2) + CqlSession session = cluster.connect(); // (2) ResultSet rs = session.execute("select release_version from system.local"); // (3) - Row row = rs.iterator().next(); + Row row = rs.one(); System.out.println(row.getString("release_version")); // (4) } ``` -1. the [Cluster] is the main entry point of the driver. It holds the known state of the actual - Cassandra cluster. It is thread-safe, you should create a single instance (per target Cassandra - cluster), and share it throughout your application; -2. the [Session] is what you use to execute queries. Likewise, it is thread-safe and should be +1. [Cluster] is the main entry point of the driver. It holds the known state of the actual Cassandra + cluster. It is thread-safe, you should create a single instance (per target Cassandra cluster), + and share it throughout your application; +2. [CqlSession] is what you use to execute queries. Likewise, it is thread-safe and should be reused; 3. we use `execute` to send a query to Cassandra. This returns a [ResultSet], which is an iterable of [Row] objects. On the next line, we extract the first row (which is the only one in this case); 4. we extract the value of the first (and only) column from the row. Always close the `Cluster` once you're done with it, in order to free underlying resources (TCP -connections, thread pools...). Closing a `Cluster` also closes any `Session` that was created from -it. In this simple example, we can use a try-with-resources block because `Cluster` implements +connections, thread pools...). Closing a `Cluster` also closes any `CqlSession` that was created +from it. In this simple example, we can use a try-with-resources block because `Cluster` implements `java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods (`close`, `closeAsync`, `forceCloseAsync`) explicitly. @@ -82,7 +82,7 @@ the following choices: * `long`: `bigint` * `java.util.UUID`: `uuid` -[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html -[Session]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html -[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html -[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html +[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html +[CqlSession]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html +[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html +[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html From ecd201269e52d209c25c6b03aeadc09f7113ec40 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 10:43:59 -0700 Subject: [PATCH 234/742] Add Row.getColumnDefinitions --- .../main/java/com/datastax/oss/driver/api/core/cql/Row.java | 5 ++++- .../datastax/oss/driver/internal/core/cql/DefaultRow.java | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java index 164a3cf1c49..449fd3e629c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java @@ -23,4 +23,7 @@ /** A row from a CQL table. */ public interface Row - extends GettableByIndex, GettableByName, GettableById, Detachable, Serializable {} + extends GettableByIndex, GettableByName, GettableById, Detachable, Serializable { + + ColumnDefinitions getColumnDefinitions(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java index d87918e2de8..c2c51541dc6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java @@ -47,6 +47,11 @@ public DefaultRow(ColumnDefinitions definitions, List data) { this(definitions, data, AttachmentPoint.NONE); } + @Override + public ColumnDefinitions getColumnDefinitions() { + return definitions; + } + @Override public int size() { return definitions.size(); From 725eb6e55a92e4ff4313712db7c5acab11d233ab Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 09:13:23 -0700 Subject: [PATCH 235/742] Port remainder of core manual --- manual/core/README.md | 180 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 5 deletions(-) diff --git a/manual/core/README.md b/manual/core/README.md index 1d4cc6855c4..34cf090fe84 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -42,7 +42,125 @@ from it. In this simple example, we can use a try-with-resources block because ` `java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods (`close`, `closeAsync`, `forceCloseAsync`) explicitly. -### CQL to Java type mapping +Note: this example uses the synchronous API. Most methods have asynchronous equivalents (look for +`*Async` variants that return a `CompletionStage`). + + +### Setting up the driver + +#### [Cluster] + +[Cluster#builder()] provides a fluent API to create an instance programmatically. Most of the +customization is done through the driver configuration (refer to the +[corresponding section](configuration/) of this manual for full details). + +We recommend that you take a look at the `reference.conf` file bundled with the driver for the list +of available options, and cross-reference with the sub-sections in this manual for more +explanations. + +#### [CqlSession] + +By default, a session isn't tied to any specific keyspace. You'll need to prefix table names in your +queries: + +```java +CqlSession session = cluster.connect(); +session.execute("SELECT * FROM myKeyspace.myTable WHERE id = 1"); +``` + +You can also specify a keyspace name at construction time, it will be used as the default when table +names are not qualified: + +```java +CqlSession session = cluster.connect(CqlIdentifier.fromCql("myKeyspace")); +session.execute("SELECT * FROM myTable WHERE id = 1"); +session.execute("SELECT * FROM otherKeyspace.otherTable WHERE id = 1"); +``` + +You might be tempted to open a separate session for each keyspace used in your application; however, +connection pools are created at the session level, so each new session will consume additional +system resources: + +```java +// Warning: creating two sessions doubles the number of TCP connections opened by the driver +CqlSession session1 = cluster.connect(CqlIdentifier.fromCql("ks1")); +CqlSession session2 = cluster.connect(CqlIdentifier.fromCql("ks2")); +``` + +If you issue a `USE` statement, it will change the default keyspace on that session: + +```java +CqlSession session = cluster.connect(); +// No default keyspace set, need to prefix: +session.execute("SELECT * FROM myKeyspace.myTable WHERE id = 1"); + +session.execute("USE myKeyspace"); +// Now the keyspace is set, unqualified query works: +session.execute("SELECT * FROM myTable WHERE id = 1"); +``` + +Be very careful though: switching the keyspace at runtime is inherently thread-unsafe, so if the +session is shared by multiple threads (and is usually is), it could easily cause unexpected query +failures. + +Finally, [CASSANDRA-10145] (coming in Cassandra 4) will allow specifying the keyspace on a per query +basis instead of relying on session state, which should greatly simplify multiple keyspace handling. + +### Running queries + +You run queries with the session's `execute*` methods: + +```java +ResultSet rs = session.execute("SELECT release_version FROM system.local"); +``` + +As shown here, the simplest form is to pass a query string directly. You can also pass a +[Statement](statements/) instance. + +#### Processing rows + +Executing a query produces a [ResultSet], which is an iterable of [Row]. The basic way to process +all rows is to use Java's for-each loop: + +```java +for (Row row : rs) { + // process the row +} +``` + +This will return **all results** without limit (even though the driver might use multiple queries in +the background). To handle large result sets, you might want to use a `LIMIT` clause in your CQL +query, or use one of the techniques described in the [paging](paging/) documentation. + +When you know that there is only one row (or are only interested in the first one), the driver +provides a convenience method: + +```java +Row row = rs.one(); +``` + +#### Reading columns + +[Row] provides getters to extract column values; they can be either positional or named: + +```java +Row row = session.execute("SELECT first_name, last_name FROM users WHERE id = 1").one(); + +// The two are equivalent: +String firstName = row.getString(0); +String firstName = row.getString(CqlIdentifier.fromCql("first_name")); +``` + +[CqlIdentifier] is a string wrapper that deals with case-sensitivity. If you don't want to create an +instance for each getter call, the driver also provides convenience methods that take a raw string: + +```java +String firstName = row.getString("first_name"); +``` + +See [AccessibleByName] for an explanation of the conversion rules. + +##### CQL to Java type mapping @@ -82,7 +200,59 @@ the following choices: * `long`: `bigint` * `java.util.UUID`: `uuid` -[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html -[CqlSession]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html -[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html -[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html +In addition to these default mappings, you can register your own types with +[custom codecs](custom_codecs/). + +##### Primitive types + +For performance reasons, the driver uses primitive Java types wherever possible (`boolean`, +`int`...); the CQL value `NULL` is encoded as the type's default value (`false`, `0`...), which can +be ambiguous. To distinguish `NULL` from actual values, use `isNull`: + +```java +Integer age = row.isNull("age") ? null : row.getInt("age"); +``` +##### Collection types + +To ensure type safety, collection getters are generic. You need to provide type parameters matching +your CQL type when calling the methods: + +```java +// Assuming given_names is a list: +List givenNames = row.getList("given_names", String.class); +``` + +For nested collections, element types are generic and cannot be expressed as Java `Class` instances. +Use [GenericType] instead: + +```java +// Assuming teams is a set>: +GenericType>> listOfStrings = new GenericType>>() {}; +Set> teams = row.get("teams", listOfStrings); +``` + +Since generic types are anonymous inner classes, it's recommended to store them as constants in a +utility class instead of re-creating them each time. + +##### Row metadata + +[ResultSet] and [Row] expose an API to explore the column metadata at runtime: + +```java +for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) { + System.out.printf("Column %s has type %s%n", + definition.getName(), + definition.getType()); +} +``` + +[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html +[Cluster#builder()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#builder-- +[CqlSession]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html +[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html +[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html +[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html +[AccessibleByName]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html +[GenericType]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/reflect/GenericType.html + +[CASSANDRA-10145]: https://issues.apache.org/jira/browse/CASSANDRA-10145 \ No newline at end of file From 015779612f9669f4fae0f7e4d4022c255f314e02 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 13:45:06 -0700 Subject: [PATCH 236/742] Port paging docs from 3.x --- manual/core/paging/README.md | 180 +++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 manual/core/paging/README.md diff --git a/manual/core/paging/README.md b/manual/core/paging/README.md new file mode 100644 index 00000000000..bb46af77c92 --- /dev/null +++ b/manual/core/paging/README.md @@ -0,0 +1,180 @@ +## Paging + +When a query returns many rows, it would be inefficient to return them as a single response message. +Instead, the driver breaks the results into *pages* which get returned as they are needed. + + +### Setting the page size + +The page size specifies how many rows the server will return in each network frame. You can set it +in the configuration: + +``` +datastax-java-driver.request.page-size = 5000 +``` + +It can be changed at runtime ()the new value will be used for requests issued after the change). If +you have categories of queries that require different page sizes, use +[configuration profiles](../configuration#profiles). + +Note that the page size is merely a hint; the server will not always return the exact number of +rows, it might decide to return slightly more or less. + + +### Synchronous paging + +The fetch size limits the number of results that are returned in one page; if you iterate past that, +the driver uses background queries to fetch subsequent pages. Here's an example with a fetch size of +20: + +```java +ResultSet rs = session.execute("SELECT * FROM my_table WHERE k = 1"); +for (Row row : rs) { + // process the row +} +``` + +```ditaa + client Session Cassandra + --+--------------+---------------------------------+----- + |execute(query)| | + |------------->| | + | | query rows 1 to 20 | + | |-------------------------------->| + | | | + | |create | + | |------>ResultSet | + | | | ++-----+--------+-----------------+-+ | +|For i in 1..20| | | | ++--------------+ | | | +| | get next row | | | +| |------------------------->| | | +| | row i | | | +| |<-------------------------| | | +| | | | | ++-----+--------------------------+-+ | + | | | + | | | + | get next row | | + |------------------------->| | + | | query rows 21 to 40 | + | |-------------------->| + | row 21 | | + |<------------------------ | | +``` + +By default, the background fetch happens at the last moment, when there are no more "local" rows +available. If you need finer control, [ResultSet] provides the following methods: + +* `getAvailableWithoutFetching()` and `isFullyFetched()` to check the current state; +* `fetchMoreResults()` to force a page fetch. + +Here's how you could use these methods to pre-fetch the next page in advance, in order to avoid the +performance hit at the end of each page: + +```java +ResultSet rs = session.execute("your query"); +for (Row row : rs) { + // Fetch when there's only half a page left: + if (rs.getAvailableWithoutFetching() == 10 && !rs.isFullyFetched()) { + rs.fetchMoreResults(); // this is asynchronous + } + // Process the row... +} +``` + + +### Asynchronous paging + +In previous versions of the driver, the synchronous and asynchronous APIs returned the same +`ResultSet` type. This made asynchronous paging very tricky, because it was very easy to +accidentally trigger background synchronous queries (which would defeat the whole purpose of async, +or potentially introduce deadlocks). + +To avoid this problem, the driver's asynchronous API now returns a dedicated [AsyncResultSet]; +iteration only yields the current page, and the next page must be explicitly fetched. Here's how +that translates to our example: + +```java +CompletionStage futureRs = session.executeAsync("SELECT * FROM myTable WHERE id = 1"); +futureRs.whenComplete(this::processRows); + +void processRows(AsyncResultSet rs, Throwable error) { + if (error != null) { + // The query failed, process the error + } else { + for (Row row : rs.currentPage()) { + // Process the row... + } + if (rs.hasMorePages()) { + rs.fetchNextPage().whenComplete(this::processRows); + } + } +} +``` + + +### Saving and reusing the paging state + +Sometimes it is convenient to interrupt paging and resume it later. For example, this could be +used for a stateless web service that displays a list of results with a link to the next page. When +the user clicks that link, we want to run the exact same query, except that the iteration should +start where we stopped the last time. + +The driver exposes a *paging state* for that: + +```java +ResultSet rs = session.execute("your query"); +ByteBuffer pagingState = rs.getExecutionInfo().getPagingState(); + +// Later: +SimpleStatement statement = + SimpleStatement.builder("your query").withPagingState(pagingState).build(); +session.execute(statement); +``` + +The paging state can only be reused with the exact same statement (same query string, same +parameters). It is an opaque value that is only meant to be collected, stored and re-used. If you +try to modify its contents or reuse it with a different statement, the results are unpredictable. + + +### Offset queries + +Saving the paging state works well when you only let the user move from one page to the next. But it +doesn't allow random jumps (like "go directly to page 10"), because you can't fetch a page unless +you have the paging state of the previous one. Such a feature would require *offset queries*, but +they are not natively supported by Cassandra (see +[CASSANDRA-6511](https://issues.apache.org/jira/browse/CASSANDRA-6511)). The rationale is that +offset queries are inherently inefficient (the performance will always be linear in the number of +rows skipped), so the Cassandra team doesn't want to encourage their use. + +If you really want offset queries, you can emulate them client-side. You'll still get linear +performance, but maybe that's acceptable for your use case. For example, if each page holds 10 rows +and you show at most 20 pages, it means that in the worst case you'll fetch 190 extra rows, which is +probably not a big performance hit. + +For example, if the page size is 10, the fetch size is 50, and the user asks for page 12 (rows 110 +to 119): + +* execute the statement a first time (the result set contains rows 0 to 49, but you're not going to + use them, only the paging state); +* execute the statement a second time with the paging state from the first query; +* execute the statement a third time with the paging state from the second query. The result set now + contains rows 100 to 149; +* skip the first 10 rows of the iterator. Read the next 10 rows and discard the remaining ones. + +You'll want to experiment with the fetch size to find the best balance: too small means many +background queries; too big means bigger messages and too many unneeded rows returned (we picked 50 +above for the sake of example, but it's probably too small -- the default is 5000). + +Again, offset queries are inefficient by nature. Emulating them client-side is a compromise when you +think you can get away with the performance hit. We recommend that you: + +* test your code at scale with the expected query patterns, to make sure that your assumptions are + correct; +* set a hard limit on the highest possible page number, to prevent malicious clients from triggering + queries that would skip a huge amount of rows. + +[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html +[AsyncResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/AsyncResultSet.html From cfeaae4b9c1ed39cd9588078f25f3b0168a2b0c0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 16:22:04 -0700 Subject: [PATCH 237/742] Fix obsolete links in javadocs --- .../com/datastax/oss/driver/api/core/cql/AsyncResultSet.java | 5 ++--- .../java/com/datastax/oss/driver/api/core/cql/ResultSet.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 0ee8c8cb2bc..33323f2aeef 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,14 +15,13 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Session; import java.util.concurrent.CompletionStage; /** * The result of an asynchronous CQL query. * - * @see Session#executeAsync(Statement) - * @see Session#executeAsync(String) + * @see CqlSession#executeAsync(Statement) + * @see CqlSession#executeAsync(String) */ public interface AsyncResultSet { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 119d4f8543d..c66d7b80f47 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Session; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.util.Collections; @@ -36,8 +35,8 @@ *

          Implementations of this type are not thread-safe. They can only be iterated by the * thread that invoked {@code session.execute}. * - * @see Session#execute(Statement) - * @see Session#execute(String) + * @see CqlSession#execute(Statement) + * @see CqlSession#execute(String) */ public interface ResultSet extends Iterable { From 0b3561714ba84d1f2d896ef8831e8ef7eb9611f9 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 11:04:01 -0700 Subject: [PATCH 238/742] Reorganize integration tests - use parallelizable/non-parallelizable terminology instead of short/long. - make non-parallelizable the default. - run all tests in the default profile. - run tests by default in the build, only skip for release. --- CONTRIBUTING.md | 35 ++++++ build.yaml | 2 +- integration-tests/pom.xml | 96 ++++++----------- .../oss/driver/api/core/ConnectIT.java | 3 + .../ProtocolVersionInitialNegotiationIT.java | 3 + .../core/ProtocolVersionMixedClusterIT.java | 3 + .../core/auth/PlainTextAuthProviderIT.java | 3 - .../core/compression/DirectCompressionIT.java | 5 +- .../core/config/DriverConfigProfileIT.java | 3 + .../config/DriverConfigProfileReloadIT.java | 5 +- .../api/core/connection/FrameLengthIT.java | 3 + .../driver/api/core/cql/AsyncResultSetIT.java | 3 + .../driver/api/core/cql/BatchStatementIT.java | 3 + .../driver/api/core/cql/BoundStatementIT.java | 3 + .../api/core/cql/SimpleStatementIT.java | 3 + .../oss/driver/api/core/data/DataTypeIT.java | 3 + .../core/heartbeat/HeartbeatDisabledIT.java | 100 ++++++++++++++++++ .../api/core/heartbeat/HeartbeatIT.java | 22 +--- .../api/core/metadata/ByteOrderedTokenIT.java | 3 - .../metadata/ByteOrderedTokenVnodesIT.java | 3 - .../driver/api/core/metadata/DescribeIT.java | 5 +- .../api/core/metadata/Murmur3TokenIT.java | 3 - .../core/metadata/Murmur3TokenVnodesIT.java | 3 - .../driver/api/core/metadata/NodeStateIT.java | 5 +- .../api/core/metadata/RandomTokenIT.java | 3 - .../core/metadata/RandomTokenVnodesIT.java | 3 - .../api/core/metadata/SchemaAgreementIT.java | 5 +- .../api/core/metadata/SchemaChangesIT.java | 3 + .../driver/api/core/metadata/SchemaIT.java | 5 +- .../api/core/retry/DefaultRetryPolicyIT.java | 3 + .../api/core/session/RequestProcessorIT.java | 3 + .../core/specex/SpeculativeExecutionIT.java | 5 +- .../type/codec/registry/CodecRegistryIT.java | 3 + .../oss/driver/categories/IsolatedTests.java | 8 +- ...ongTests.java => ParallelizableTests.java} | 10 +- pom.xml | 10 +- 36 files changed, 257 insertions(+), 124 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java rename integration-tests/src/test/java/com/datastax/oss/driver/categories/{LongTests.java => ParallelizableTests.java} (65%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a32fd15249e..3f8fecd592e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,6 +205,41 @@ factor some common code in a parent abstract class named with "XxxTestBase", and different families of tests into separate child classes. For example, `CqlRequestHandlerTestBase`, `CqlRequestHandlerRetryTest`, `CqlRequestHandlerSpeculativeExecutionTest`... +Unit tests live in their respective production code's module. They should be fast and not start any +external process. They usually target one specific component and mock the rest of the driver +context. + +Integration tests live in the aptly-named `integration-tests` module. They exercise the whole driver +stack against an external process -- either simulated with +[Simulacron](https://github.com/datastax/simulacron), or a live Cassandra cluster run with +[CCM](https://github.com/pcmanus/ccm) (the `ccm` executable must be in the path). They are +classified into categories that determine how they will be run during the build: + +* `@Category(ParallelizableTests.class)`: for tests that use Simulacron or `CcmRule`. They will be + run in parallel. +* No annotation: for tests that use `CustomCcmRule`. They will be run one after the other. +* `@Category(IsolatedTests.class)`: for tests that require specific environment tweaks, typically + system properties that need to be set before initialization. They will be run one after the other, + each in their its JVM fork. + + +## Running the tests + +#### Unit tests + + mvn clean test + +This currently takes about 30 seconds. The goal is to keep it within a couple of minutes (it runs +for each commit if you enable the pre-commit hook -- see below). + +#### Integration tests + + mvn clean verify + +This currently takes about 9 minutes. We don't have a hard limit, but ideally it should stay within +30 minutes to 1 hour. + + ## License headers The build will fail if some license headers are missing. To update all files from the command line, diff --git a/build.yaml b/build.yaml index 4b2cc90c249..6afa93be9cf 100644 --- a/build.yaml +++ b/build.yaml @@ -10,7 +10,7 @@ cassandra: build: - type: maven version: 3.2.5 - goals: verify -Plong + goals: verify properties: | ccm.cassandraVersion=$CCM_CASSANDRA_VERSION - xunit: diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 3f73bf99729..218378df16c 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -99,82 +99,52 @@ maven-failsafe-plugin - short-tests + parallelizable-tests integration-test verify - - - com.datastax.oss.driver.categories.LongTests,com.datastax.oss.driver.categories.IsolatedTests - - short + com.datastax.oss.driver.categories.ParallelizableTests classes 8 true + parallelized + + + + serial-tests + + integration-test + verify + + + + com.datastax.oss.driver.categories.ParallelizableTests, + com.datastax.oss.driver.categories.IsolatedTests + + true + serial + + + + isolated-tests + + integration-test + verify + + + com.datastax.oss.driver.categories.IsolatedTests + + 1 + false + true + isolated - - - - short - - - false - - - - long - - - false - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - long-tests - - integration-test - verify - - - com.datastax.oss.driver.categories.LongTests - long - true - - - - - isolated-tests - - integration-test - verify - - - com.datastax.oss.driver.categories.IsolatedTests - isolated - - 1 - false - true - - - - - - - - - diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 391f878af8b..51f26bf1f75 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -18,11 +18,14 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class ConnectIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index e317bffa1f9..51b4815374d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -19,12 +19,15 @@ import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; /** Covers protocol negotiation for the initial connection to the first contact point. */ +@Category(ParallelizableTests.class) public class ProtocolVersionInitialNegotiationIT { @Rule public CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index cafc809d358..ed2c049dde2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import com.datastax.oss.protocol.internal.request.Query; @@ -30,6 +31,7 @@ import java.util.stream.Stream; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import static com.datastax.oss.driver.assertions.Assertions.assertThat; @@ -40,6 +42,7 @@ * first node list refresh, we find out that some nodes only support a lower version, reconnect the * control connection immediately. */ +@Category(ParallelizableTests.class) public class ProtocolVersionMixedClusterIT { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index ab9b929b7ac..b3a82789a32 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -21,15 +21,12 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; -import com.datastax.oss.driver.categories.LongTests; import com.google.common.util.concurrent.Uninterruptibles; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class PlainTextAuthProviderIT { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index c6cf7cea109..8e887a0fbb4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -16,20 +16,23 @@ package com.datastax.oss.driver.api.core.compression; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.offset; +@Category(ParallelizableTests.class) public class DirectCompressionIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index f061d7161fa..fedb6469c32 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -30,12 +30,14 @@ import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; @@ -44,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +@Category(ParallelizableTests.class) public class DriverConfigProfileIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index e2d799774ec..7aa783997a6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -17,10 +17,9 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.DriverTimeoutException; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; @@ -32,7 +31,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import static com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER; @@ -41,7 +39,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; -@Category(LongTests.class) public class DriverConfigProfileReloadIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 896e626b3bb..e69415df30f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -33,6 +34,7 @@ import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; @@ -40,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +@Category(ParallelizableTests.class) public class FrameLengthIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 23a2d35cdeb..d9876a83ca2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -24,9 +25,11 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class AsyncResultSetIT { private static final int PAGE_SIZE = 100; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 71cf327b2be..b6960380358 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -19,14 +19,17 @@ import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class BatchStatementIT { @Rule public CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 478ccace430..132a2a82906 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -22,14 +22,17 @@ import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class BoundStatementIT { @Rule public CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index d80c2a63cea..75864008c23 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -18,16 +18,19 @@ import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class SimpleStatementIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 8ab712e4ff1..7e82f586a22 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -36,6 +36,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.DefaultListType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.datastax.oss.driver.internal.core.type.DefaultSetType; @@ -68,6 +69,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import org.junit.runner.RunWith; @@ -76,6 +78,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; +@Category(ParallelizableTests.class) @RunWith(DataProviderRunner.class) public class DataTypeIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java new file mode 100644 index 00000000000..71cef5995d1 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.heartbeat; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.server.BoundNode; +import java.net.SocketAddress; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** This test is separate from {@link HeartbeatIT} because it can't be parallelized. */ +public class HeartbeatDisabledIT { + + @ClassRule + public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + + @Rule + public ClusterRule cluster = + ClusterRule.builder(simulacron) + .withDefaultSession(false) + .withOptions( + "connection.heartbeat.interval = 1 second", + "connection.heartbeat.timeout = 500 milliseconds", + "connection.init-query-timeout = 2 seconds", + "connection.reconnection-policy.max-delay = 1 second") + .build(); + + private SocketAddress controlConnection; + + @Before + public void setUp() { + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + + Optional controlNodeOption = + simulacron + .cluster() + .getNodes() + .stream() + .filter(n -> n.getActiveConnections() == 1) + .findFirst(); + + if (controlNodeOption.isPresent()) { + BoundNode controlNode = controlNodeOption.get(); + controlConnection = controlNode.getConnections().getConnections().get(0); + } else { + fail("Control node not found"); + } + } + + @Test + public void should_not_send_heartbeat_when_disabled() throws InterruptedException { + // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any + try (Cluster cluster = + ClusterUtils.newCluster(simulacron, "connection.heartbeat.interval = 0 second")) { + cluster.connect(); + AtomicInteger heartbeats = registerHeartbeatListener(); + SECONDS.sleep(35); + + assertThat(heartbeats.get()).isZero(); + } + } + + private AtomicInteger registerHeartbeatListener() { + AtomicInteger nonControlHeartbeats = new AtomicInteger(); + simulacron + .cluster() + .registerQueryListener( + (n, l) -> nonControlHeartbeats.incrementAndGet(), + false, + (l) -> l.getQuery().equals("OPTIONS") && !l.getConnection().equals(controlConnection)); + return nonControlHeartbeats; + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index 040210cdea4..80001079b6d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -15,14 +15,12 @@ */ package com.datastax.oss.driver.api.core.heartbeat; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -import com.datastax.oss.driver.categories.LongTests; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import com.datastax.oss.simulacron.common.request.Options; @@ -50,10 +48,10 @@ import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noResult; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +@Category(ParallelizableTests.class) public class HeartbeatIT { @ClassRule @@ -277,20 +275,6 @@ public void should_close_connection_when_heartbeat_times_out() { assertThat(node.getState()).isEqualTo(NodeState.UP); } - @Test - @Category(LongTests.class) - public void should_not_send_heartbeat_when_disabled() throws InterruptedException { - // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any - try (Cluster cluster = - ClusterUtils.newCluster(simulacron, "connection.heartbeat.interval = 0 second")) { - cluster.connect(); - AtomicInteger heartbeats = registerHeartbeatListener(); - SECONDS.sleep(35); - - assertThat(heartbeats.get()).isZero(); - } - } - /** * Registers a query listener that increments the returned {@link AtomicInteger} whenever a * heartbeat is received on the non-control connection. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index fb803d8391a..9e09767609f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class ByteOrderedTokenIT extends TokenITBase { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index 04cb09103bc..67aaf8ea69a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class ByteOrderedTokenVnodesIT extends TokenITBase { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index e1cd79c4684..9545ab0de39 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -18,11 +18,12 @@ import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.io.ByteStreams; import com.google.common.io.Closer; import java.io.ByteArrayOutputStream; @@ -31,12 +32,14 @@ import java.io.PrintStream; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +@Category(ParallelizableTests.class) public class DescribeIT { private static final Logger logger = LoggerFactory.getLogger(DescribeIT.class); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index 5ba470d6518..4ceef5675bf 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class Murmur3TokenIT extends TokenITBase { @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index e941561b0af..709368002da 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class Murmur3TokenVnodesIT extends TokenITBase { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index bdcb8f83c97..303363b2b3c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -16,12 +16,13 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; @@ -45,6 +46,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.mockito.InOrder; import org.mockito.Mockito; @@ -53,6 +55,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; +@Category(ParallelizableTests.class) public class NodeStateIT { public @Rule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index 316f35ee8be..6872224db73 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class RandomTokenIT extends TokenITBase { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index 8565e459c21..f30445621e6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -19,13 +19,10 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.categories.LongTests; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.experimental.categories.Category; -@Category(LongTests.class) public class RandomTokenVnodesIT extends TokenITBase { @ClassRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index 85874279274..b1555304959 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -16,23 +16,20 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; -import com.datastax.oss.driver.categories.LongTests; import java.util.concurrent.atomic.AtomicInteger; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; import org.junit.rules.TestName; import static org.assertj.core.api.Assertions.assertThat; -@Category(LongTests.class) public class SchemaAgreementIT { private static CustomCcmRule ccm = CustomCcmRule.builder().withNodes(3).build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index a96e0f47bc8..f7266413f75 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.function.BiConsumer; @@ -35,10 +36,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class SchemaChangesIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 72480c88862..23767564a5e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -18,19 +18,22 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Map; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class SchemaIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index c7f2c189ea0..1f40cebcd9e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.CloseType; import com.datastax.oss.simulacron.common.stubbing.DisconnectAction; @@ -39,6 +40,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import static com.datastax.oss.simulacron.common.codec.ConsistencyLevel.LOCAL_QUORUM; @@ -52,6 +54,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +@Category(ParallelizableTests.class) @RunWith(DataProviderRunner.class) public class DefaultRetryPolicyIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 524cb8796e1..09e37d80d07 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import com.google.common.collect.Iterables; @@ -30,6 +31,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; @@ -51,6 +53,7 @@ *

          {@link KeyRequestProcessor} is also registered for handling {@link KeyRequest}s which * simplifies a certain query down to 1 parameter. */ +@Category(ParallelizableTests.class) public class RequestProcessorIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 51433bbf3ac..7e1047cc330 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -17,23 +17,26 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.isBootstrapping; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class SpeculativeExecutionIT { // Note: it looks like shorter delays cause precision issues with Netty timers diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 42f5cce44f6..ba46d925a72 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -32,6 +32,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericTypeParameter; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.codec.IntCodec; import java.nio.ByteBuffer; import java.util.Collection; @@ -44,11 +45,13 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import org.junit.rules.TestName; import static org.assertj.core.api.Assertions.assertThat; +@Category(ParallelizableTests.class) public class CodecRegistryIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java b/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java index d7970ba4b78..957c54b5001 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java @@ -15,5 +15,9 @@ */ package com.datastax.oss.driver.categories; -/** Defines a classification of tests that should be run in their own jvm fork. */ -public class IsolatedTests {} +/** + * Defines a classification of tests that should be run in their own jvm fork. + * + *

          This is generally because they need to set system properties. + */ +public interface IsolatedTests {} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java b/integration-tests/src/test/java/com/datastax/oss/driver/categories/ParallelizableTests.java similarity index 65% rename from integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java rename to integration-tests/src/test/java/com/datastax/oss/driver/categories/ParallelizableTests.java index c0c2c73a79e..2a51eb25077 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/categories/LongTests.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/categories/ParallelizableTests.java @@ -15,5 +15,11 @@ */ package com.datastax.oss.driver.categories; -/** Defines a classification of tests that are slow. */ -public interface LongTests {} +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; + +/** + * Defines a classification of tests that can be run in parallel, namely: tests that use {@link + * CcmRule} (not {@link CustomCcmRule}), and tests that use Simulacron. + */ +public interface ParallelizableTests {} diff --git a/pom.xml b/pom.xml index 5c510e3a5db..568e0aa7ac2 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss @@ -41,8 +43,6 @@ true UTF-8 UTF-8 - - true @@ -375,6 +375,10 @@ limitations under the License.]]> + + + true + From 0c6114b18ad5219e7bf0de120ade73eb25cfece0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 11:21:05 -0700 Subject: [PATCH 239/742] Raise timeout in future assertions 100 ms seems to be too low for Travis builds. --- .../oss/driver/internal/core/CompletionStageAssert.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java index b0dcf3c5613..59659c44f94 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -34,7 +34,7 @@ public CompletionStageAssert(CompletionStage actual) { public CompletionStageAssert isSuccess(Consumer valueAssertions) { try { - V value = actual.toCompletableFuture().get(100, TimeUnit.MILLISECONDS); + V value = actual.toCompletableFuture().get(2, TimeUnit.SECONDS); valueAssertions.accept(value); } catch (TimeoutException e) { fail("Future did not complete within the timeout"); @@ -50,7 +50,7 @@ public CompletionStageAssert isSuccess() { public CompletionStageAssert isFailed(Consumer failureAssertions) { try { - actual.toCompletableFuture().get(100, TimeUnit.MILLISECONDS); + actual.toCompletableFuture().get(2, TimeUnit.SECONDS); fail("Expected completion stage to fail"); } catch (TimeoutException e) { fail("Future did not complete within the timeout"); From 135fee22e8ae694077cf6d2b1d4a858910e6a500 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 15:01:41 -0700 Subject: [PATCH 240/742] Update upgrade guide --- upgrade_guide/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index e33d2892da2..fd928699cdd 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -1,6 +1,6 @@ ## Upgrade guide -### 4.0.0-alpha1 +### 4.0.0 Java driver 4 is **not binary compatible** with previous versions. However, most of the concepts remain unchanged, and the new API will look very familiar to 2.x and 3.x users. @@ -126,4 +126,10 @@ token map exposed by a given `Metadata` instance are guaranteed to be in sync. On the other hand, this means you have to call `getMetadata()` again each time you need a fresh copy; do not cache the result. -See the [manual](../manual/core/metadata/) for all the details. \ No newline at end of file +See the [manual](../manual/core/metadata/) for all the details. + +#### Improved protocol version negotiation + +You no longer need to force the protocol version in a mixed cluster: upon connecting to the first +node, the driver will read the release version of all the nodes in the cluster and infer the best +protocol version that works with all of them. \ No newline at end of file From 679107939fbb00eccb8e0a9a659bd1152dc7a26c Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 15:02:27 -0700 Subject: [PATCH 241/742] Update version in docs --- README.md | 2 +- changelog/README.md | 2 +- manual/core/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 04edbbc62b3..ab27b0469a4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-alpha1](https://github.com/datastax/java-driver/tree/4.0.0-alpha1).* +[4.0.0-alpha2](https://github.com/datastax/java-driver/tree/4.0.0-alpha2).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] (2.1+) and [DataStax Enterprise] (4.7+), using exclusively Cassandra's binary protocol and Cassandra Query diff --git a/changelog/README.md b/changelog/README.md index a28bd115ce7..18f2828ab35 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-alpha2 (in progress) +### 4.0.0-alpha2 - [new feature] JAVA-1525: Handle token metadata - [new feature] JAVA-1638: Check schema agreement diff --git a/manual/core/README.md b/manual/core/README.md index 34cf090fe84..8170bf4e3b9 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-alpha1 + 4.0.0-alpha2 ``` From 38b009fd892b66922b70d7f55fc065bc0cf0594e Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 15:23:49 -0700 Subject: [PATCH 242/742] Pass argument to skip ITs in release:prepare --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index 568e0aa7ac2..5371bf522b0 100644 --- a/pom.xml +++ b/pom.xml @@ -352,6 +352,8 @@ limitations under the License.]]> false release deploy + + -DskipITs From 1bf93dcc221aa36b09fbb898dd171250d06fa6d2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 15:27:44 -0700 Subject: [PATCH 243/742] [maven-release-plugin] prepare release 4.0.0-alpha2 --- core/pom.xml | 6 ++---- integration-tests/pom.xml | 6 ++---- pom.xml | 8 +++----- test-infra/pom.xml | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 1967f08c9bd..abf2595d346 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-alpha2-SNAPSHOT + 4.0.0-alpha2 java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 218378df16c..11f6d4d2944 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-alpha2-SNAPSHOT + 4.0.0-alpha2 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 5371bf522b0..4c3cb57693d 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,12 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-alpha2-SNAPSHOT + 4.0.0-alpha2 pom DataStax Java driver for Apache Cassandra® @@ -401,7 +399,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-alpha2 diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 371b548d179..cc83b7e6a24 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha2-SNAPSHOT + 4.0.0-alpha2 java-driver-test-infra From 8dcea3fcbff21bd0ee31861bc04d84b8a59f62c2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 18 Oct 2017 15:28:25 -0700 Subject: [PATCH 244/742] [maven-release-plugin] prepare for next development iteration --- core/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- test-infra/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index abf2595d346..93b996ef460 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha2 + 4.0.0-alpha3-SNAPSHOT java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 11f6d4d2944..cb431ec2e18 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha2 + 4.0.0-alpha3-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 4c3cb57693d..4df9a0f7bc7 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha2 + 4.0.0-alpha3-SNAPSHOT pom DataStax Java driver for Apache Cassandra® @@ -399,7 +399,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-alpha2 + HEAD diff --git a/test-infra/pom.xml b/test-infra/pom.xml index cc83b7e6a24..fb6dcc234bb 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha2 + 4.0.0-alpha3-SNAPSHOT java-driver-test-infra From 7d6b98e6659acaf771a499fb66f5248e70fb137b Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 19 Oct 2017 08:12:08 -0700 Subject: [PATCH 245/742] Prepare changelog for next version --- changelog/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 18f2828ab35..e5eab14a2a1 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,6 +2,8 @@ +### 4.0.0-alpha3 (in progress) + ### 4.0.0-alpha2 - [new feature] JAVA-1525: Handle token metadata From 9f0edeba2bb0e5e3859e72cd8edcebae80b3d055 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 19 Oct 2017 11:18:17 -0700 Subject: [PATCH 246/742] Fix broken links in manual --- README.md | 4 ++-- manual/core/README.md | 5 +++-- manual/core/configuration/README.md | 2 +- manual/core/custom_codecs/README.md | 3 +++ manual/core/metadata/schema/README.md | 4 ++-- manual/core/statements/README.md | 4 ++-- 6 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 manual/core/custom_codecs/README.md diff --git a/README.md b/README.md index ab27b0469a4..a5b60d97183 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ version and that some features described here might not yet have been released. documentation for latest version through [DataStax Docs] or via the release tags, e.g. [4.0.0-alpha2](https://github.com/datastax/java-driver/tree/4.0.0-alpha2).* -A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] (2.1+) and -[DataStax Enterprise] (4.7+), using exclusively Cassandra's binary protocol and Cassandra Query +A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and +[DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query Language v3. [DataStax Docs]: http://docs.datastax.com/en/developer/java-driver/ diff --git a/manual/core/README.md b/manual/core/README.md index 8170bf4e3b9..7a49eb17f1b 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -103,8 +103,9 @@ Be very careful though: switching the keyspace at runtime is inherently thread-u session is shared by multiple threads (and is usually is), it could easily cause unexpected query failures. -Finally, [CASSANDRA-10145] (coming in Cassandra 4) will allow specifying the keyspace on a per query -basis instead of relying on session state, which should greatly simplify multiple keyspace handling. +Finally, [CASSANDRA-10145] \(coming in Cassandra 4) will allow specifying the keyspace on a per +query basis instead of relying on session state, which should greatly simplify multiple keyspace +handling. ### Running queries diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 6c67c900d05..dfa98a5b533 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -110,7 +110,7 @@ all the configuration in one place will make it easier to manage and review. As shown so far, all options live under a `datastax-java-driver` prefix. This can be changed, for example if you need multiple driver instances in the same VM with different configurations. See the -[Advanced topics](changing-the-config-prefix) section. +[Advanced topics](#changing-the-config-prefix) section. ### The configuration API diff --git a/manual/core/custom_codecs/README.md b/manual/core/custom_codecs/README.md new file mode 100644 index 00000000000..a24a1f21e06 --- /dev/null +++ b/manual/core/custom_codecs/README.md @@ -0,0 +1,3 @@ +## Custom codecs + +*Work in progress: this section will be ported from the 3.x docs soon* \ No newline at end of file diff --git a/manual/core/metadata/schema/README.md b/manual/core/metadata/schema/README.md index ba331d39867..e4e95cf80f1 100644 --- a/manual/core/metadata/schema/README.md +++ b/manual/core/metadata/schema/README.md @@ -177,8 +177,8 @@ if (rs.getExecutionInfo().isSchemaInAgreement()) { } ``` -You can also perform an on-demand check at any time with [Cluster#checkSchemaAgreementAsync] (or its -synchronous counterpart): +You can also perform an on-demand check at any time with [Cluster#checkSchemaAgreementAsync] \(or +its synchronous counterpart): ```java if (cluster.checkSchemaAgreement()) { diff --git a/manual/core/statements/README.md b/manual/core/statements/README.md index 3b9ac19e769..f5922735ece 100644 --- a/manual/core/statements/README.md +++ b/manual/core/statements/README.md @@ -41,5 +41,5 @@ statement = statement.setConfigProfileName("oltp").setIdempotent(true); [Statement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Statement.html [StatementBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/StatementBuilder.html -[execute]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Session.html#execute-com.datastax.oss.driver.api.core.cql.Statement- -[executeAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Session.html#executeAsync-com.datastax.oss.driver.api.core.cql.Statement- +[execute]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#execute-com.datastax.oss.driver.api.core.cql.Statement- +[executeAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#executeAsync-com.datastax.oss.driver.api.core.cql.Statement- From 2bfe49409ed0bec601d6d1247c00961087213ab4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Oct 2017 14:18:01 -0700 Subject: [PATCH 247/742] JAVA-1584: Validate that no bound values are unset in protocol v3 --- changelog/README.md | 2 ++ .../CassandraProtocolVersionRegistry.java | 10 ++++++ .../driver/internal/core/ProtocolFeature.java | 24 ++++++++++++++ .../core/ProtocolVersionRegistry.java | 3 ++ .../driver/internal/core/cql/Conversions.java | 31 ++++++++++++++++++- .../core/cql/RequestHandlerTestHarness.java | 10 ++++++ .../driver/api/core/cql/BatchStatementIT.java | 27 ++++++++++++++++ .../driver/api/core/cql/BoundStatementIT.java | 21 +++---------- 8 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java diff --git a/changelog/README.md b/changelog/README.md index e5eab14a2a1..46d01275a75 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0-alpha3 (in progress) +- [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 + ### 4.0.0-alpha2 - [new feature] JAVA-1525: Handle token metadata diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 96a1e081bf4..363e4d41ec4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -165,6 +165,16 @@ public ProtocolVersion highestCommon(Collection nodes) { } } + @Override + public boolean supports(ProtocolVersion version, ProtocolFeature feature) { + switch (feature) { + case UNSET_BOUND_VALUES: + return version.getCode() >= 4; + default: + throw new IllegalArgumentException("Unhandled protocol feature: " + feature); + } + } + private NavigableMap byCode(ProtocolVersion[][] versionRanges) { NavigableMap map = new TreeMap<>(); for (ProtocolVersion[] versionRange : versionRanges) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java new file mode 100644 index 00000000000..b5fcc9b1aaf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +/** Features of the native protocol that are only supported in specific versions. */ +public enum ProtocolFeature { + + /** The ability to leave variables unset in prepared statements. */ + UNSET_BOUND_VALUES, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java index d55120f7045..07d5015ddbc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java @@ -66,4 +66,7 @@ public interface ProtocolVersionRegistry { * the driver initialization to fail. */ ProtocolVersion highestCommon(Collection nodes); + + /** Whether a given version supports a given feature. */ + boolean supports(ProtocolVersion version, ProtocolFeature feature); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index ee6cc83554e..e8d3c492b16 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -50,9 +51,10 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; @@ -104,6 +106,7 @@ static Message toMessage( } CodecRegistry codecRegistry = context.codecRegistry(); ProtocolVersion protocolVersion = context.protocolVersion(); + ProtocolVersionRegistry registry = context.protocolVersionRegistry(); if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; @@ -126,6 +129,9 @@ static Message toMessage( return new Query(simpleStatement.getQuery(), queryOptions); } else if (statement instanceof BoundStatement) { BoundStatement boundStatement = (BoundStatement) statement; + if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { + ensureAllSet(boundStatement); + } QueryOptions queryOptions = new QueryOptions( consistency, @@ -141,6 +147,9 @@ static Message toMessage( return new Execute(Bytes.getArray(id), queryOptions); } else if (statement instanceof BatchStatement) { BatchStatement batchStatement = (BatchStatement) statement; + if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { + ensureAllSet(batchStatement); + } List queriesOrIds = new ArrayList<>(batchStatement.size()); List> values = new ArrayList<>(batchStatement.size()); for (BatchableStatement child : batchStatement) { @@ -221,6 +230,26 @@ private static ByteBuffer encode( } } + private static void ensureAllSet(BoundStatement boundStatement) { + for (int i = 0; i < boundStatement.size(); i++) { + if (!boundStatement.isSet(i)) { + throw new IllegalStateException( + "Unset value at index " + + i + + ". " + + "If you want this value to be null, please set it to null explicitly."); + } + } + } + + private static void ensureAllSet(BatchStatement batchStatement) { + for (BatchableStatement batchableStatement : batchStatement) { + if (batchableStatement instanceof BoundStatement) { + ensureAllSet(((BoundStatement) batchableStatement)); + } + } + } + static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index eb140db881e..56dd5feaca7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -25,6 +26,8 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -75,6 +78,7 @@ public static Builder builder() { @Mock private RetryPolicy retryPolicy; @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; @Mock private TimestampGenerator timestampGenerator; + @Mock private ProtocolVersionRegistry protocolVersionRegistry; private RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); @@ -129,6 +133,12 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(session.setKeyspace(any(CqlIdentifier.class))) .thenReturn(CompletableFuture.completedFuture(null)); + + Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when( + protocolVersionRegistry.supports( + any(ProtocolVersion.class), any(ProtocolFeature.class))) + .thenReturn(true); } public DefaultSession getSession() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index b6960380358..6059006e73a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -15,10 +15,13 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import org.junit.Before; @@ -314,6 +317,30 @@ public void should_fail_counter_batch_with_non_counter_increment() { cluster.session().execute(batchStatement); } + @Test(expected = IllegalStateException.class) + public void should_not_allow_unset_value_when_protocol_less_than_v4() { + // CREATE TABLE test (k0 text, k1 int, v int, PRIMARY KEY (k0, k1)) + try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { + CqlIdentifier keyspace = cluster.keyspace(); + CqlSession session = v3Cluster.connect(keyspace); + PreparedStatement prepared = session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); + + BatchStatementBuilder builder = BatchStatement.builder(BatchType.LOGGED); + builder.addStatements( + // All set => OK + prepared.bind(name.getMethodName(), 1, 1), + // One variable unset => should fail + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, 2) + .unset(2) + .build()); + + session.execute(builder.build()); + } + } + private void verifyBatchInsert() { // validate data inserted by the batch. Statement select = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 132a2a82906..d42f7f8c856 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -17,14 +17,12 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -55,27 +53,16 @@ public void setupSchema() { } @Test(expected = IllegalStateException.class) - @Ignore - public void should_not_allow_unset_value_on_bound_statement_when_protocol_less_than_v4() { - // TODO reenable this if JAVA-1584 is fixed. + public void should_not_allow_unset_value_when_protocol_less_than_v4() { try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { - CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); - DriverConfigProfile slowProfile = ClusterUtils.slowProfile(v3Cluster); - ClusterUtils.createKeyspace(v3Cluster, keyspace, slowProfile); + CqlIdentifier keyspace = cluster.keyspace(); CqlSession session = v3Cluster.connect(keyspace); - PreparedStatement prepared = - session.prepare("INSERT INTO test2 (k, v0, v1) values (?, ?, ?)"); + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); BoundStatement boundStatement = - prepared - .boundStatementBuilder() - .setString(0, name.getMethodName()) - .unset(1) - .setString(2, name.getMethodName()) - .build(); + prepared.boundStatementBuilder().setString(0, name.getMethodName()).unset(1).build(); session.execute(boundStatement); - ClusterUtils.dropKeyspace(v3Cluster, keyspace, slowProfile); } } From 1ce22a9d737547fefd5b05c4d5708863d44a5a30 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 31 Oct 2017 11:06:50 -0700 Subject: [PATCH 248/742] Add server ticket reference in ProtocolFeature --- .../datastax/oss/driver/internal/core/ProtocolFeature.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java index b5fcc9b1aaf..519f587373a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java @@ -18,7 +18,11 @@ /** Features of the native protocol that are only supported in specific versions. */ public enum ProtocolFeature { - /** The ability to leave variables unset in prepared statements. */ + /** + * The ability to leave variables unset in prepared statements. + * + * @see CASSANDRA-7304 + */ UNSET_BOUND_VALUES, ; } From c027b2063c9f7cc96a1946bcbed72f192ae1540c Mon Sep 17 00:00:00 2001 From: olim7t Date: Sat, 7 Oct 2017 16:42:41 -0700 Subject: [PATCH 249/742] JAVA-1566: Enforce API rules automatically --- changelog/README.md | 1 + .../api/core/cql/BatchStatementBuilder.java | 2 +- .../api/core/cql/BoundStatementBuilder.java | 2 +- .../api/core/cql/SimpleStatementBuilder.java | 2 +- .../driver/api/core/cql/StatementBuilder.java | 7 +++- .../api/core/type/reflect/GenericType.java | 2 ++ pom.xml | 35 ++++++++++++++++--- .../testinfra/ccm/BaseCcmRule.java | 2 +- .../oss/driver/api/testinfra/ccm/CcmRule.java | 2 -- .../api/testinfra/ccm/CustomCcmRule.java | 1 - 10 files changed, 43 insertions(+), 13 deletions(-) rename test-infra/src/main/java/com/datastax/oss/driver/{internal => api}/testinfra/ccm/BaseCcmRule.java (98%) diff --git a/changelog/README.md b/changelog/README.md index 46d01275a75..e7ec05d647e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1566: Enforce API rules automatically - [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 ### 4.0.0-alpha2 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index d8d060953dc..decc3ab2451 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -81,7 +81,7 @@ public BatchStatement build() { configProfileName, configProfile, null, - (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), + buildCustomPayload(), idempotent, tracing, timestamp, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index d5ee0347f26..266c9f862a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -107,7 +107,7 @@ public BoundStatement build() { configProfileName, configProfile, keyspace, - (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), + buildCustomPayload(), idempotent, tracing, timestamp, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 56133eb22cc..3d50e315541 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -121,7 +121,7 @@ public SimpleStatement build() { (namedValuesBuilder == null) ? Collections.emptyMap() : namedValuesBuilder.build(), configProfileName, configProfile, - (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(), + buildCustomPayload(), idempotent, tracing, timestamp, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 4497c7c2151..40517637a46 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.Map; /** @@ -35,7 +36,7 @@ public abstract class StatementBuilder, S exten protected String configProfileName; protected DriverConfigProfile configProfile; protected String keyspace; - protected ImmutableMap.Builder customPayloadBuilder; + private ImmutableMap.Builder customPayloadBuilder; protected Boolean idempotent; protected boolean tracing; protected long timestamp = Long.MIN_VALUE; @@ -108,5 +109,9 @@ public T withPagingState(ByteBuffer pagingState) { return self; } + protected Map buildCustomPayload() { + return (customPayloadBuilder == null) ? Collections.emptyMap() : customPayloadBuilder.build(); + } + public abstract S build(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index 3b9277e5f7f..2304a1b9e02 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -181,6 +181,8 @@ public final GenericType where(GenericTypeParameter freeVariable, Clas * *

          It leaks a shaded type. This should be part of the internal API, but due to internal * implementation details it has to be exposed here. + * + * @leaks-private-api */ public TypeToken __getToken() { return token; diff --git a/pom.xml b/pom.xml index 4df9a0f7bc7..ac64db57cc3 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss @@ -176,7 +178,7 @@ maven-javadoc-plugin - 2.10.4 + 3.0.0-M1 org.sonatype.plugins @@ -330,15 +332,38 @@ limitations under the License.]]> maven-javadoc-plugin - - -Xdoclint:none - attach-javadocs jar + + -Xdoclint:none + + + + check-api-leaks + + javadoc + + process-classes + + com.datastax.oss.doclet.ApiPlumber + + com.datastax.oss + api-plumber-doclet + 1.0.0 + + com.datastax.oss.driver.internal.* + + -preventleak + com.datastax.oss.driver.internal + -preventleak + com.google.common + + false + diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java similarity index 98% rename from test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java index 09768029f50..2874ae560b8 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.testinfra.ccm; +package com.datastax.oss.driver.api.testinfra.ccm; import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CoreProtocolVersion; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index eef178ec551..a68b74328f5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -15,8 +15,6 @@ */ package com.datastax.oss.driver.api.testinfra.ccm; -import com.datastax.oss.driver.internal.testinfra.ccm.BaseCcmRule; - /** * A rule that creates a globally shared single node Ccm cluster that is only shut down after the * JVM exists. diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index e3e344815c3..e171c279043 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.testinfra.ccm; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.internal.testinfra.ccm.BaseCcmRule; import java.util.concurrent.atomic.AtomicReference; /** From a932bf0a91802efd5e72350da63da1084073d2e4 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 9 Nov 2017 13:17:47 -0600 Subject: [PATCH 250/742] Upgrade to simulacron 0.6.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac64db57cc3..88b755b9da1 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ com.datastax.oss.simulacron simulacron-native-server - 0.5.5 + 0.6.0 org.apache.commons From 046368bdd8be2ae93b8c2b45628f11f7f80e9763 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 3 Nov 2017 16:19:49 -0700 Subject: [PATCH 251/742] JAVA-1662: Raise default request timeout --- changelog/README.md | 1 + core/src/main/resources/reference.conf | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index e7ec05d647e..92722f948b2 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1662: Raise default request timeout - [improvement] JAVA-1566: Enforce API rules automatically - [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 39cb6e21ce1..edc6c5d2864 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -239,7 +239,13 @@ datastax-java-driver { # # This option can be changed at runtime, the new value will be used for requests issued after # the change. It can be overridden in a profile. - timeout = 500 milliseconds + # + # By default, this value is set pretty high to ensure that DDL queries don't time out, in order + # to provide the best experience for new users trying the driver with the out-of-the-box + # configuration. + # For any serious deployment, we recommend that you use separate configuration profiles for DDL + # and DML; you can then set the DML timeout much lower (down to a few milliseconds if needed). + timeout = 2 seconds # The consistency level. # From 7f39cb63b4cb381ac44fd07075b0b5d25fd2e52f Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Oct 2017 16:27:44 -0700 Subject: [PATCH 252/742] JAVA-1646: Provide a more readable error when connecting to Cassandra 2.0 or lower --- changelog/README.md | 1 + .../UnsupportedProtocolVersionException.java | 3 +- .../internal/core/protocol/FrameDecoder.java | 33 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 92722f948b2..e6d13d34599 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1646: Provide a more readable error when connecting to Cassandra 2.0 or lower - [improvement] JAVA-1662: Raise default request timeout - [improvement] JAVA-1566: Enforce API rules automatically - [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index cfd5616590a..acdc9218e7c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -46,7 +46,8 @@ public static UnsupportedProtocolVersionException forNegotiation( SocketAddress address, List attemptedVersions) { String message = String.format( - "[%s] Protocol negotiation failed: could not find a common version (attempted: %s)", + "[%s] Protocol negotiation failed: could not find a common version (attempted: %s). " + + "Note that the driver does not support Cassandra 2.0 or lower.", address, attemptedVersions); return new UnsupportedProtocolVersionException( address, message, ImmutableList.copyOf(attemptedVersions)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java index 1fb2869cffd..9c9825d06dd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -16,11 +16,15 @@ package com.datastax.oss.driver.internal.core.protocol; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; +import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.TooLongFrameException; +import java.util.Collections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +44,36 @@ public FrameDecoder(FrameCodec frameCodec, int maxFrameLengthInBytes) { @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + // Must read at least the protocol v1/v2 header (see below) + if (in.readableBytes() < 8) { + return null; + } int startIndex = in.readerIndex(); + + // Special case for obsolete protocol versions (< v3): the length field is at a different + // position, so we can't delegate to super.decode() which would read the wrong length. + int protocolVersion = (int) in.getByte(startIndex) & 0b0111_1111; + if (protocolVersion < 3) { + int streamId = in.getByte(startIndex + 2); + int length = in.getInt(startIndex + 4); + // We don't need a full-blown decoder, just to signal the protocol error. So discard the + // incoming data and spoof a server-side protocol error. + if (in.readableBytes() < 8 + length) { + return null; // keep reading until we can discard the whole message at once + } else { + in.readerIndex(startIndex + 8 + length); + } + return Frame.forResponse( + protocolVersion, + streamId, + null, + Frame.NO_PAYLOAD, + Collections.emptyList(), + new Error( + ProtocolConstants.ErrorCode.PROTOCOL_ERROR, + "Invalid or unsupported protocol version")); + } + try { ByteBuf buffer = (ByteBuf) super.decode(ctx, in); return (buffer == null) From 5cc7c9732e01ff940e349a540bea6cfcacaf599f Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Oct 2017 16:52:38 -0700 Subject: [PATCH 253/742] Close cluster if an error occurs during init --- .../com/datastax/oss/driver/internal/core/DefaultCluster.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 110ebba036d..90d28417893 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -218,6 +218,7 @@ private void init() { .thenAccept(this::afterInitialNodeListRefresh) .exceptionally( error -> { + close(); initFuture.completeExceptionally(error); return null; }); From 6dd01db9d8a9c7b40ebbb6d7d501547f716d7954 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 21 Nov 2017 13:52:59 -0800 Subject: [PATCH 254/742] Raise timeout in channel factory test harness Trying to fix a flaky test in Travis. --- .../internal/core/channel/MockChannelFactoryHelper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index 5244a63d7b6..2b48c634519 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -49,6 +49,8 @@ */ public class MockChannelFactoryHelper { + private static final int CONNECT_TIMEOUT_MILLIS = 500; + public static Builder builder(ChannelFactory channelFactory) { return new Builder(channelFactory); } @@ -87,7 +89,7 @@ public void waitForCalls(SocketAddress address, int expected) { ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); inOrder - .verify(channelFactory, timeout(100).atLeast(expected)) + .verify(channelFactory, timeout(CONNECT_TIMEOUT_MILLIS).atLeast(expected)) .connect(eq(address), optionsCaptor.capture()); int actual = optionsCaptor.getAllValues().size(); @@ -99,7 +101,7 @@ public void waitForCalls(SocketAddress address, int expected) { public void verifyNoMoreCalls() { inOrder - .verify(channelFactory, timeout(100).times(0)) + .verify(channelFactory, timeout(CONNECT_TIMEOUT_MILLIS).times(0)) .connect(any(SocketAddress.class), any(DriverChannelOptions.class)); Set counts = Sets.newHashSet(previous.values()); From af75775ce6c4db98b84730bca7858aa708670af1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 17 Oct 2017 16:21:31 -0700 Subject: [PATCH 255/742] JAVA-1524: Add query trace API --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 3 + .../driver/api/core/cql/AsyncResultSet.java | 12 + .../driver/api/core/cql/ExecutionInfo.java | 35 ++ .../oss/driver/api/core/cql/QueryTrace.java | 58 +++ .../oss/driver/api/core/cql/TraceEvent.java | 40 ++ .../core/cql/CqlRequestHandlerBase.java | 11 +- .../core/cql/DefaultExecutionInfo.java | 32 +- .../internal/core/cql/DefaultQueryTrace.java | 91 +++++ .../internal/core/cql/DefaultTraceEvent.java | 72 ++++ .../internal/core/cql/QueryTraceFetcher.java | 156 ++++++++ core/src/main/resources/reference.conf | 17 + .../core/cql/QueryTraceFetcherTest.java | 367 ++++++++++++++++++ .../oss/driver/api/core/cql/QueryTraceIT.java | 79 ++++ 14 files changed, 969 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/TraceEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultQueryTrace.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java diff --git a/changelog/README.md b/changelog/README.md index e6d13d34599..73fb568247c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [new feature] JAVA-1524: Add query trace API - [improvement] JAVA-1646: Provide a more readable error when connecting to Cassandra 2.0 or lower - [improvement] JAVA-1662: Raise default request timeout - [improvement] JAVA-1566: Enforce API rules automatically diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index cb33305782b..d05f3d7f289 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -49,6 +49,9 @@ public enum CoreDriverOption implements DriverOption { SPECULATIVE_EXECUTION_POLICY_ROOT("request.speculative-execution-policy", true), RELATIVE_SPECULATIVE_EXECUTION_MAX("max-executions", false), RELATIVE_SPECULATIVE_EXECUTION_DELAY("delay", false), + REQUEST_TRACE_ATTEMPTS("request.trace.attempts", true), + REQUEST_TRACE_INTERVAL("request.trace.interval", true), + REQUEST_TRACE_CONSISTENCY("request.trace.consistency", true), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_AGREEMENT_INTERVAL( diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 33323f2aeef..4f320238e58 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import java.util.Iterator; import java.util.concurrent.CompletionStage; /** @@ -41,6 +42,17 @@ public interface AsyncResultSet { */ Iterable currentPage(); + /** + * Returns the next row, or {@code null} if the result set is exhausted. + * + *

          This is convenient for queries that are known to return exactly one row, for example count + * queries. + */ + default Row one() { + Iterator iterator = currentPage().iterator(); + return iterator.hasNext() ? iterator.next() : null; + } + /** * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next * one asynchronously. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 9f7395a10d3..8d3c4dc8ea4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -19,10 +19,15 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletionStage; /** Information about the execution of a query. */ public interface ExecutionInfo { @@ -112,4 +117,34 @@ public interface ExecutionInfo { * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT */ boolean isSchemaInAgreement(); + + /** + * The tracing identifier if tracing was {@link Request#isTracing() enabled} for this query, + * otherwise {@code null}. + */ + UUID getTracingId(); + + /** + * Fetches the query trace asynchronously, if tracing was enabled for this query. + * + *

          Note that each call to this method triggers a new fetch, even if the previous call was + * successful (this allows fetching the trace again if the list of {@link QueryTrace#getEvents() + * events} was incomplete). + * + *

          This method will return a failed future if tracing was disabled for the query (that is, if + * {@link #getTracingId()} is null). + */ + CompletionStage getQueryTraceAsync(); + + /** + * Convenience method to call {@link #getQueryTraceAsync()} and block for the result. + * + *

          This must not be called on a driver thread. + * + * @throws IllegalStateException if {@link #getTracingId()} is null. + */ + default QueryTrace getQueryTrace() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(getQueryTraceAsync()); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java new file mode 100644 index 00000000000..7b5fd1fab3c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.session.Request; +import java.net.InetAddress; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Tracing information for a query. + * + *

          When {@link Request#isTracing() tracing} is enabled for a query, Cassandra generates rows in + * the {@code sessions} and {@code events} table of the {@code system_traces} keyspace. This class + * is a client-side representation of that information. + */ +public interface QueryTrace { + + UUID getTracingId(); + + String getRequestType(); + + /** The server-side duration of the query in microseconds. */ + int getDurationMicros(); + + /** The IP of the node that coordinated the query. */ + InetAddress getCoordinator(); + + /** The parameters attached to this trace. */ + Map getParameters(); + + /** The server-side timestamp of the start of this query. */ + long getStartedAt(); + + /** + * The events contained in this trace. + * + *

          Query tracing is asynchronous in Cassandra. Hence, it is possible for the list returned to + * be missing some events for some of the replicas involved in the query if the query trace is + * requested just after the return of the query (the only guarantee being that the list will + * contain the events pertaining to the coordinator). + */ + List getEvents(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/TraceEvent.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/TraceEvent.java new file mode 100644 index 00000000000..904c3a9afed --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/TraceEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import java.net.InetAddress; + +/** An event in a {@link QueryTrace}. */ +public interface TraceEvent { + + /** Which activity this event corresponds to. */ + String getActivity(); + + /** The server-side timestamp of the event. */ + long getTimestamp(); + + /** The IP of the host having generated this event. */ + InetAddress getSource(); + + /** + * The number of microseconds elapsed on the source when this event occurred since the moment when + * the source started handling the query. + */ + int getSourceElapsedMicros(); + + /** The name of the thread on which this event occurred. */ + String getThreadName(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 988cbaf8592..6f757fbbe77 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -84,6 +84,7 @@ public abstract class CqlRequestHandlerBase { private final CqlIdentifier keyspace; private final InternalDriverContext context; private final Queue queryPlan; + private final DriverConfigProfile configProfile; private final boolean isIdempotent; protected final CompletableFuture result; private final Message message; @@ -127,13 +128,12 @@ protected CqlRequestHandlerBase( this.context = context; this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); - DriverConfigProfile configProfile; if (statement.getConfigProfile() != null) { - configProfile = statement.getConfigProfile(); + this.configProfile = statement.getConfigProfile(); } else { DriverConfig config = context.config(); String profileName = statement.getConfigProfileName(); - configProfile = + this.configProfile = (profileName == null || profileName.isEmpty()) ? config.getDefaultProfile() : config.getNamedProfile(profileName); @@ -323,7 +323,10 @@ private ExecutionInfo buildExecutionInfo( errors, pagingState, responseFrame, - schemaInAgreement); + schemaInAgreement, + session, + context, + configProfile); } private void setFinalError(Throwable error) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index 278afc6ac49..a25c7b2736e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -15,15 +15,21 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Frame; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletionStage; public class DefaultExecutionInfo implements ExecutionInfo { @@ -37,6 +43,9 @@ public class DefaultExecutionInfo implements ExecutionInfo { private final List warnings; private final Map customPayload; private final boolean schemaInAgreement; + private final DefaultSession session; + private final InternalDriverContext context; + private final DriverConfigProfile configProfile; public DefaultExecutionInfo( Statement statement, @@ -46,7 +55,10 @@ public DefaultExecutionInfo( List> errors, ByteBuffer pagingState, Frame frame, - boolean schemaInAgreement) { + boolean schemaInAgreement, + DefaultSession session, + InternalDriverContext context, + DriverConfigProfile configProfile) { this.statement = statement; this.coordinator = coordinator; this.speculativeExecutionCount = speculativeExecutionCount; @@ -59,6 +71,9 @@ public DefaultExecutionInfo( this.warnings = (frame == null) ? Collections.emptyList() : frame.warnings; this.customPayload = (frame == null) ? Collections.emptyMap() : frame.customPayload; this.schemaInAgreement = schemaInAgreement; + this.session = session; + this.context = context; + this.configProfile = configProfile; } @Override @@ -107,4 +122,19 @@ public Map getIncomingPayload() { public boolean isSchemaInAgreement() { return schemaInAgreement; } + + @Override + public UUID getTracingId() { + return tracingId; + } + + @Override + public CompletionStage getQueryTraceAsync() { + if (tracingId == null) { + return CompletableFutures.failedFuture( + new IllegalStateException("Tracing was disabled for this request")); + } else { + return new QueryTraceFetcher(tracingId, session, context, configProfile).fetch(); + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultQueryTrace.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultQueryTrace.java new file mode 100644 index 00000000000..4b6bdac344f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultQueryTrace.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.QueryTrace; +import com.datastax.oss.driver.api.core.cql.TraceEvent; +import java.net.InetAddress; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class DefaultQueryTrace implements QueryTrace { + + private final UUID tracingId; + private final String requestType; + private final int durationMicros; + private final InetAddress coordinator; + private final Map parameters; + private final long startedAt; + private final List events; + + public DefaultQueryTrace( + UUID tracingId, + String requestType, + int durationMicros, + InetAddress coordinator, + Map parameters, + long startedAt, + List events) { + this.tracingId = tracingId; + this.requestType = requestType; + this.durationMicros = durationMicros; + this.coordinator = coordinator; + this.parameters = parameters; + this.startedAt = startedAt; + this.events = events; + } + + @Override + public UUID getTracingId() { + return tracingId; + } + + @Override + public String getRequestType() { + return requestType; + } + + @Override + public int getDurationMicros() { + return durationMicros; + } + + @Override + public InetAddress getCoordinator() { + return coordinator; + } + + @Override + public Map getParameters() { + return parameters; + } + + @Override + public long getStartedAt() { + return startedAt; + } + + @Override + public List getEvents() { + return events; + } + + @Override + public String toString() { + return String.format("%s [%s] - %dµs", requestType, tracingId, durationMicros); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java new file mode 100644 index 00000000000..1089b16952f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.cql.TraceEvent; +import java.net.InetAddress; +import java.util.Date; + +public class DefaultTraceEvent implements TraceEvent { + + private final String activity; + private final long timestamp; + private final InetAddress source; + private final int sourceElapsedMicros; + private final String threadName; + + public DefaultTraceEvent( + String activity, + long timestamp, + InetAddress source, + int sourceElapsedMicros, + String threadName) { + this.activity = activity; + // Convert the UUID timestamp to an epoch timestamp + this.timestamp = (timestamp - 0x01b21dd213814000L) / 10000; + this.source = source; + this.sourceElapsedMicros = sourceElapsedMicros; + this.threadName = threadName; + } + + @Override + public String getActivity() { + return activity; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public InetAddress getSource() { + return source; + } + + public int getSourceElapsedMicros() { + return sourceElapsedMicros; + } + + @Override + public String getThreadName() { + return threadName; + } + + @Override + public String toString() { + return String.format("%s on %s[%s] at %s", activity, source, threadName, new Date(timestamp)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java new file mode 100644 index 00000000000..dad0f4c4428 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.QueryTrace; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.TraceEvent; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import io.netty.util.concurrent.EventExecutor; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +class QueryTraceFetcher { + + private final UUID tracingId; + private final CqlSession session; + private final DriverConfigProfile configProfile; + private final int maxAttempts; + private final long intervalNanos; + private final EventExecutor scheduler; + private final CompletableFuture resultFuture = new CompletableFuture<>(); + + QueryTraceFetcher( + UUID tracingId, + CqlSession session, + InternalDriverContext context, + DriverConfigProfile configProfile) { + this.tracingId = tracingId; + this.session = session; + + ConsistencyLevel regularConsistency = + configProfile.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY); + ConsistencyLevel traceConsistency = + configProfile.getConsistencyLevel(CoreDriverOption.REQUEST_TRACE_CONSISTENCY); + this.configProfile = + (traceConsistency == regularConsistency) + ? configProfile + : configProfile.withConsistencyLevel( + CoreDriverOption.REQUEST_CONSISTENCY, traceConsistency); + + this.maxAttempts = configProfile.getInt(CoreDriverOption.REQUEST_TRACE_ATTEMPTS); + this.intervalNanos = + configProfile.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); + this.scheduler = context.nettyOptions().adminEventExecutorGroup().next(); + + querySession(maxAttempts); + } + + CompletionStage fetch() { + return resultFuture; + } + + private void querySession(int remainingAttempts) { + session + .executeAsync( + SimpleStatement.builder("SELECT * FROM system_traces.sessions WHERE session_id = ?") + .addPositionalValue(tracingId) + .withConfigProfile(configProfile) + .build()) + .whenComplete( + (rs, error) -> { + if (error != null) { + resultFuture.completeExceptionally(error); + } else { + Row row = rs.one(); + if (row == null || row.isNull("duration") || row.isNull("started_at")) { + // Trace is incomplete => fail if last try, or schedule retry + if (remainingAttempts == 1) { + resultFuture.completeExceptionally( + new IllegalStateException( + String.format( + "Trace %s still not complete after %d attempts", + tracingId, maxAttempts))); + } else { + scheduler.schedule( + () -> querySession(remainingAttempts - 1), + intervalNanos, + TimeUnit.NANOSECONDS); + } + } else { + queryEvents(row, new ArrayList<>(), null); + } + } + }); + } + + private void queryEvents(Row sessionRow, List events, ByteBuffer pagingState) { + session + .executeAsync( + SimpleStatement.builder("SELECT * FROM system_traces.events WHERE session_id = ?") + .addPositionalValue(tracingId) + .withPagingState(pagingState) + .withConfigProfile(configProfile) + .build()) + .whenComplete( + (rs, error) -> { + if (error != null) { + resultFuture.completeExceptionally(error); + } else { + Iterables.addAll(events, rs.currentPage()); + ByteBuffer nextPagingState = rs.getExecutionInfo().getPagingState(); + if (nextPagingState == null) { + resultFuture.complete(buildTrace(sessionRow, events)); + } else { + queryEvents(sessionRow, events, nextPagingState); + } + } + }); + } + + private QueryTrace buildTrace(Row sessionRow, Iterable eventRows) { + ImmutableList.Builder eventsBuilder = ImmutableList.builder(); + for (Row eventRow : eventRows) { + eventsBuilder.add( + new DefaultTraceEvent( + eventRow.getString("activity"), + eventRow.getUuid("event_id").timestamp(), + eventRow.getInetAddress("source"), + eventRow.getInt("source_elapsed"), + eventRow.getString("thread"))); + } + return new DefaultQueryTrace( + tracingId, + sessionRow.getString("request"), + sessionRow.getInt("duration"), + sessionRow.getInetAddress("coordinator"), + sessionRow.getMap("parameters", String.class, String.class), + sessionRow.getInstant("started_at").toEpochMilli(), + eventsBuilder.build()); + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index edc6c5d2864..bff7c04244e 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -347,6 +347,23 @@ datastax-java-driver { # This must be positive or 0. // delay = 100 milliseconds } + + # If tracing is enabled for a query, this controls how the trace is fetched. + # These options can be changed at runtime, the new values will be used for requests issued after + # the change. They can be overridden in a profile. + trace { + # How many times the driver will attempt to fetch the query if it is not ready yet. + attempts = 5 + + # The interval between each attempt. + interval = 3 milliseconds + + # The consistency level to use for trace queries. + # Note that the default replication strategy for the system_traces keyspace is SimpleStrategy + # with RF=2, therefore LOCAL_ONE might not work if the local DC has no replicas for a given + # trace id. + consistency = ONE + } } prepared-statements { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java new file mode 100644 index 00000000000..0d424de0d19 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.QueryTrace; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.TraceEvent; +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.EventExecutorGroup; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; + +@RunWith(MockitoJUnitRunner.class) +public class QueryTraceFetcherTest { + + private static final UUID TRACING_ID = UUID.randomUUID(); + private static final ByteBuffer PAGING_STATE = Bytes.fromHexString("0xdeadbeef"); + + @Mock private CqlSession session; + @Mock private InternalDriverContext context; + @Mock private DriverConfigProfile config; + @Mock private DriverConfigProfile traceConfig; + @Mock private NettyOptions nettyOptions; + @Mock private EventExecutorGroup adminEventExecutorGroup; + @Mock private EventExecutor eventExecutor; + @Mock private InetAddress address; + + @Captor private ArgumentCaptor statementCaptor; + + @Before + public void setup() { + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); + Mockito.when(adminEventExecutorGroup.next()).thenReturn(eventExecutor); + // Always execute scheduled tasks immediately: + Mockito.when(eventExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + .thenAnswer( + invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + // OK because the production code doesn't use the result: + return null; + }); + + Mockito.when(config.getInt(CoreDriverOption.REQUEST_TRACE_ATTEMPTS)).thenReturn(3); + // Doesn't really matter since we mock the scheduler + Mockito.when(config.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL)) + .thenReturn(Duration.ZERO); + Mockito.when(config.getConsistencyLevel(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)) + .thenReturn(ConsistencyLevel.ONE); + + Mockito.when( + config.withConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.ONE)) + .thenReturn(traceConfig); + } + + @Test + public void should_succeed_when_both_queries_succeed_immediately() { + // Given + CompletionStage sessionRow = completeSessionRow(); + CompletionStage eventRows = singlePageEventRows(); + Mockito.when(session.executeAsync(any(SimpleStatement.class))) + .thenReturn(sessionRow, eventRows); + + // When + QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); + CompletionStage traceFuture = fetcher.fetch(); + + // Then + Mockito.verify(session, times(2)).executeAsync(statementCaptor.capture()); + List statements = statementCaptor.getAllValues(); + assertSessionQuery(statements.get(0)); + SimpleStatement statement = statements.get(1); + assertEventsQuery(statement); + Mockito.verifyNoMoreInteractions(session); + + assertThat(traceFuture) + .isSuccess( + trace -> { + assertThat(trace.getTracingId()).isEqualTo(TRACING_ID); + assertThat(trace.getRequestType()).isEqualTo("mock request"); + assertThat(trace.getDurationMicros()).isEqualTo(42); + assertThat(trace.getCoordinator()).isEqualTo(address); + assertThat(trace.getParameters()) + .hasSize(2) + .containsEntry("key1", "value1") + .containsEntry("key2", "value2"); + assertThat(trace.getStartedAt()).isEqualTo(0); + + List events = trace.getEvents(); + assertThat(events).hasSize(3); + for (int i = 0; i < events.size(); i++) { + TraceEvent event = events.get(i); + assertThat(event.getActivity()).isEqualTo("mock activity " + i); + assertThat(event.getTimestamp()).isEqualTo(i); + assertThat(event.getSource()).isEqualTo(address); + assertThat(event.getSourceElapsedMicros()).isEqualTo(i); + assertThat(event.getThreadName()).isEqualTo("mock thread " + i); + } + }); + } + + /** + * This should not happen with a sane configuration, but we need to handle it in case {@link + * CoreDriverOption#REQUEST_PAGE_SIZE} is set ridiculously low. + */ + @Test + public void should_succeed_when_events_query_is_paged() { + // Given + CompletionStage sessionRow = completeSessionRow(); + CompletionStage eventRows1 = multiPageEventRows1(); + CompletionStage eventRows2 = multiPageEventRows2(); + Mockito.when(session.executeAsync(any(SimpleStatement.class))) + .thenReturn(sessionRow, eventRows1, eventRows2); + + // When + QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); + CompletionStage traceFuture = fetcher.fetch(); + + // Then + Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + List statements = statementCaptor.getAllValues(); + assertSessionQuery(statements.get(0)); + assertEventsQuery(statements.get(1)); + assertEventsQuery(statements.get(2)); + assertThat(statements.get(2).getPagingState()).isEqualTo(PAGING_STATE); + Mockito.verifyNoMoreInteractions(session); + + assertThat(traceFuture).isSuccess(trace -> assertThat(trace.getEvents()).hasSize(2)); + } + + @Test + public void should_retry_when_session_row_is_incomplete() { + // Given + CompletionStage sessionRow1 = incompleteSessionRow(); + CompletionStage sessionRow2 = completeSessionRow(); + CompletionStage eventRows = singlePageEventRows(); + Mockito.when(session.executeAsync(any(SimpleStatement.class))) + .thenReturn(sessionRow1, sessionRow2, eventRows); + + // When + QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); + CompletionStage traceFuture = fetcher.fetch(); + + // Then + Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + List statements = statementCaptor.getAllValues(); + assertSessionQuery(statements.get(0)); + assertSessionQuery(statements.get(1)); + assertEventsQuery(statements.get(2)); + Mockito.verifyNoMoreInteractions(session); + + assertThat(traceFuture) + .isSuccess( + trace -> { + assertThat(trace.getTracingId()).isEqualTo(TRACING_ID); + assertThat(trace.getRequestType()).isEqualTo("mock request"); + assertThat(trace.getDurationMicros()).isEqualTo(42); + assertThat(trace.getCoordinator()).isEqualTo(address); + assertThat(trace.getParameters()) + .hasSize(2) + .containsEntry("key1", "value1") + .containsEntry("key2", "value2"); + assertThat(trace.getStartedAt()).isEqualTo(0); + + List events = trace.getEvents(); + assertThat(events).hasSize(3); + for (int i = 0; i < events.size(); i++) { + TraceEvent event = events.get(i); + assertThat(event.getActivity()).isEqualTo("mock activity " + i); + assertThat(event.getTimestamp()).isEqualTo(i); + assertThat(event.getSource()).isEqualTo(address); + assertThat(event.getSourceElapsedMicros()).isEqualTo(i); + assertThat(event.getThreadName()).isEqualTo("mock thread " + i); + } + }); + } + + @Test + public void should_fail_when_session_query_fails() { + // Given + RuntimeException mockError = new RuntimeException("mock error"); + Mockito.when(session.executeAsync(any(SimpleStatement.class))) + .thenReturn(CompletableFutures.failedFuture(mockError)); + + // When + QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); + CompletionStage traceFuture = fetcher.fetch(); + + // Then + Mockito.verify(session).executeAsync(statementCaptor.capture()); + SimpleStatement statement = statementCaptor.getValue(); + assertSessionQuery(statement); + Mockito.verifyNoMoreInteractions(session); + + assertThat(traceFuture).isFailed(error -> assertThat(error).isSameAs(mockError)); + } + + @Test + public void should_fail_when_session_query_still_incomplete_after_max_tries() { + // Given + CompletionStage sessionRow1 = incompleteSessionRow(); + CompletionStage sessionRow2 = incompleteSessionRow(); + CompletionStage sessionRow3 = incompleteSessionRow(); + Mockito.when(session.executeAsync(any(SimpleStatement.class))) + .thenReturn(sessionRow1, sessionRow2, sessionRow3); + + // When + QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); + CompletionStage traceFuture = fetcher.fetch(); + + // Then + Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + List statements = statementCaptor.getAllValues(); + for (int i = 0; i < 3; i++) { + assertSessionQuery(statements.get(i)); + } + + assertThat(traceFuture) + .isFailed( + error -> + assertThat(error.getMessage()) + .isEqualTo( + String.format("Trace %s still not complete after 3 attempts", TRACING_ID))); + } + + private CompletionStage completeSessionRow() { + return sessionRow(42); + } + + private CompletionStage incompleteSessionRow() { + return sessionRow(null); + } + + private CompletionStage sessionRow(Integer duration) { + Row row = Mockito.mock(Row.class); + Mockito.when(row.getString("request")).thenReturn("mock request"); + if (duration == null) { + Mockito.when(row.isNull("duration")).thenReturn(true); + } else { + Mockito.when(row.getInt("duration")).thenReturn(duration); + } + Mockito.when(row.getInetAddress("coordinator")).thenReturn(address); + Mockito.when(row.getMap("parameters", String.class, String.class)) + .thenReturn(ImmutableMap.of("key1", "value1", "key2", "value2")); + Mockito.when(row.isNull("started_at")).thenReturn(false); + Mockito.when(row.getInstant("started_at")).thenReturn(Instant.EPOCH); + + AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + Mockito.when(rs.one()).thenReturn(row); + return CompletableFuture.completedFuture(rs); + } + + private CompletionStage singlePageEventRows() { + List rows = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + rows.add(eventRow(i)); + } + + AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + Mockito.when(rs.currentPage()).thenReturn(rows); + + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(executionInfo.getPagingState()).thenReturn(null); + Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + + return CompletableFuture.completedFuture(rs); + } + + private CompletionStage multiPageEventRows1() { + AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + + ImmutableList rows = ImmutableList.of(eventRow(0)); + Mockito.when(rs.currentPage()).thenReturn(rows); + + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(executionInfo.getPagingState()).thenReturn(PAGING_STATE); + Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + + return CompletableFuture.completedFuture(rs); + } + + private CompletionStage multiPageEventRows2() { + AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + + ImmutableList rows = ImmutableList.of(eventRow(1)); + Mockito.when(rs.currentPage()).thenReturn(rows); + + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(executionInfo.getPagingState()).thenReturn(null); + Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + + return CompletableFuture.completedFuture(rs); + } + + private Row eventRow(int i) { + Row row = Mockito.mock(Row.class); + Mockito.when(row.getString("activity")).thenReturn("mock activity " + i); + Mockito.when(row.getUuid("event_id")).thenReturn(Uuids.startOf(i)); + Mockito.when(row.getInetAddress("source")).thenReturn(address); + Mockito.when(row.getInt("source_elapsed")).thenReturn(i); + Mockito.when(row.getString("thread")).thenReturn("mock thread " + i); + return row; + } + + private void assertSessionQuery(SimpleStatement statement) { + assertThat(statement.getQuery()) + .isEqualTo("SELECT * FROM system_traces.sessions WHERE session_id = ?"); + assertThat(statement.getPositionalValues()).containsOnly(TRACING_ID); + assertThat(statement.getConfigProfile()).isEqualTo(traceConfig); + } + + private void assertEventsQuery(SimpleStatement statement) { + assertThat(statement.getQuery()) + .isEqualTo("SELECT * FROM system_traces.events WHERE session_id = ?"); + assertThat(statement.getPositionalValues()).containsOnly(TRACING_ID); + assertThat(statement.getConfigProfile()).isEqualTo(traceConfig); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java new file mode 100644 index 00000000000..46397dc9d5c --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +@Category(ParallelizableTests.class) +public class QueryTraceIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + @ClassRule public static ClusterRule clusterRule = new ClusterRule(ccmRule); + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_not_have_tracing_id_when_tracing_disabled() { + ExecutionInfo executionInfo = + clusterRule + .session() + .execute("SELECT release_version FROM system.local") + .getExecutionInfo(); + + assertThat(executionInfo.getTracingId()).isNull(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Tracing was disabled for this request"); + executionInfo.getQueryTrace(); + } + + @Test + public void should_fetch_trace_when_tracing_enabled() { + ExecutionInfo executionInfo = + clusterRule + .session() + .execute( + SimpleStatement.builder("SELECT release_version FROM system.local") + .withTracing() + .build()) + .getExecutionInfo(); + + assertThat(executionInfo.getTracingId()).isNotNull(); + + QueryTrace queryTrace = executionInfo.getQueryTrace(); + assertThat(queryTrace.getTracingId()).isEqualTo(executionInfo.getTracingId()); + assertThat(queryTrace.getRequestType()).isEqualTo("Execute CQL3 query"); + assertThat(queryTrace.getDurationMicros()).isPositive(); + assertThat(queryTrace.getCoordinator()) + .isEqualTo(ccmRule.getContactPoints().iterator().next().getAddress()); + assertThat(queryTrace.getParameters()) + .containsEntry("consistency_level", "LOCAL_ONE") + .containsEntry("page_size", "5000") + .containsEntry("query", "SELECT release_version FROM system.local") + .containsEntry("serial_consistency_level", "SERIAL"); + assertThat(queryTrace.getStartedAt()).isPositive(); + // Don't want to get too deep into event testing because that could change across versions + assertThat(queryTrace.getEvents()).isNotEmpty(); + } +} From 2a24edac331169feffca899d9b2bc19146390bf8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 30 Oct 2017 09:17:10 -0700 Subject: [PATCH 256/742] JAVA-1645: Don't log stack traces at WARN level --- changelog/README.md | 1 + .../oss/driver/api/core/uuid/Uuids.java | 5 ++- .../driver/internal/core/DefaultCluster.java | 12 ++++-- .../core/channel/InFlightHandler.java | 16 +++++--- .../config/typesafe/TypeSafeDriverConfig.java | 3 +- .../core/control/ControlConnection.java | 7 +++- .../core/cql/CqlPrepareHandlerBase.java | 5 ++- .../core/cql/CqlRequestHandlerBase.java | 5 ++- .../core/metadata/DefaultMetadata.java | 4 +- .../core/metadata/MetadataManager.java | 4 +- .../core/metadata/NodeStateManager.java | 3 +- .../metadata/schema/parsing/TableParser.java | 4 +- .../metadata/schema/parsing/ViewParser.java | 4 +- .../internal/core/pool/ChannelPool.java | 3 +- .../internal/core/protocol/FrameDecoder.java | 3 +- .../internal/core/session/DefaultSession.java | 10 +++-- .../driver/internal/core/util/Loggers.java | 40 +++++++++++++++++++ .../core/util/concurrent/Reconnection.java | 14 +++++-- .../util/concurrent/UncaughtExceptions.java | 5 ++- 19 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java diff --git a/changelog/README.md b/changelog/README.md index 73fb568247c..22c7eab5df7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1645: Don't log stack traces at WARN level - [new feature] JAVA-1524: Add query trace API - [improvement] JAVA-1646: Provide a more readable error when connecting to Cassandra 2.0 or lower - [improvement] JAVA-1662: Raise default request timeout diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java index c42188a7340..3cea62389e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.uuid; import com.datastax.oss.driver.internal.core.os.Native; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import java.lang.management.ManagementFactory; @@ -159,7 +160,7 @@ private static String getProcessPiece() { pid = Native.getProcessId(); LOG.info("PID obtained through native call to getpid(): {}", pid); } catch (Exception e) { - LOG.warn("Native call to getpid() failed", e); + Loggers.warnWithException(LOG, "Native call to getpid() failed", e); } } if (pid == null) { @@ -168,7 +169,7 @@ private static String getProcessPiece() { pid = Integer.parseInt(pidJmx); LOG.info("PID obtained through JMX: {}", pid); } catch (Exception e) { - LOG.warn("Failed to obtain PID from JMX", e); + Loggers.warnWithException(LOG, "Failed to obtain PID from JMX", e); } } if (pid == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 90d28417893..ac0972a34a1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -21,11 +21,11 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; @@ -33,6 +33,7 @@ import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.collect.ImmutableList; @@ -432,8 +433,11 @@ private void warnIfFailed(CompletionStage stage) { CompletableFuture future = stage.toCompletableFuture(); assert future.isDone(); if (future.isCompletedExceptionally()) { - LOG.warn( - "[{}] Unexpected error while closing", logPrefix, CompletableFutures.getFailed(future)); + Loggers.warnWithException( + LOG, + "[{}] Unexpected error while closing", + logPrefix, + CompletableFutures.getFailed(future)); } } @@ -449,7 +453,7 @@ private void closePolicies() { try { closeable.close(); } catch (Throwable t) { - LOG.warn("[{}] Error while closing {}", logPrefix, closeable, t); + Loggers.warnWithException(LOG, "[{}] Error while closing {}", logPrefix, closeable, t); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 031cc6c9018..60578bdbc61 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; @@ -213,7 +214,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception try { eventCallback.onEvent(event); } catch (Throwable t) { - LOG.warn("[{}] Unexpected error while invoking event handler", logPrefix, t); + Loggers.warnWithException( + LOG, "[{}] Unexpected error while invoking event handler", logPrefix, t); } } } else { @@ -234,7 +236,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception try { responseCallback.onResponse(responseFrame); } catch (Throwable t) { - LOG.warn("[{}] Unexpected error while invoking response handler", logPrefix, t); + Loggers.warnWithException( + LOG, "[{}] Unexpected error while invoking response handler", logPrefix, t); } } } @@ -252,10 +255,12 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable exception) thro try { responseCallback.onFailure(exception.getCause()); } catch (Throwable t) { - LOG.warn("[{}] Unexpected error while invoking failure handler", logPrefix, t); + Loggers.warnWithException( + LOG, "[{}] Unexpected error while invoking failure handler", logPrefix, t); } } else { - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Unexpected error while decoding incoming event frame", logPrefix, exception.getCause()); @@ -384,7 +389,8 @@ void fail(String message, Throwable cause) { // keyspace switch fails, this could be due to a schema disagreement or a more serious // error. Rescheduling the switch is impractical, we can't do much better than closing the // channel and letting it reconnect. - LOG.warn("[{}] Unexpected error while switching keyspace", logPrefix, setKeyspaceException); + Loggers.warnWithException( + LOG, "[{}] Unexpected error while switching keyspace", logPrefix, setKeyspaceException); abortAllInFlight(setKeyspaceException, this); ctx.channel().close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 66817166933..1ecf94d7ccf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -94,7 +95,7 @@ public boolean reload(Config config) { } return true; } catch (Throwable t) { - LOG.warn("Error reloading configuration, keeping previous one", t); + Loggers.warnWithException(LOG, "Error reloading configuration, keeping previous one", t); return false; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 38814cf154b..7f7f4ac2c40 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; @@ -340,7 +341,8 @@ private void connect( onSuccess.run(); } } catch (Exception e) { - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Unexpected exception while processing channel init result", logPrefix, e); @@ -366,7 +368,8 @@ private void onSuccessfulReconnect() { context.metadataManager().refreshSchema(null, false, true); // TODO avoid refreshing the token map twice } catch (Throwable t) { - LOG.warn("[{}] Unexpected error on control connection reconnect", logPrefix, t); + Loggers.warnWithException( + LOG, "[{}] Unexpected error on control connection reconnect", logPrefix, t); } } }); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 8f795006ee7..73bfba57631 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -35,6 +35,7 @@ import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -122,7 +123,7 @@ protected CqlPrepareHandlerBase( cancelTimeout(); } } catch (Throwable t2) { - LOG.warn("[{}] Uncaught exception", logPrefix, t2); + Loggers.warnWithException(LOG, "[{}] Uncaught exception", logPrefix, t2); } return null; }); @@ -419,7 +420,7 @@ public void cancel() { this.channel.cancel(this); } } catch (Throwable t) { - LOG.warn("[{}] Error cancelling", logPrefix, t); + Loggers.warnWithException(LOG, "[{}] Error cancelling", logPrefix, t); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 6f757fbbe77..286c8d8a6a4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -43,6 +43,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -150,7 +151,7 @@ protected CqlRequestHandlerBase( cancelScheduledTasks(); } } catch (Throwable t2) { - LOG.warn("[{}] Uncaught exception", logPrefix, t2); + Loggers.warnWithException(LOG, "[{}] Uncaught exception", logPrefix, t2); } return null; }); @@ -559,7 +560,7 @@ public void cancel() { this.channel.cancel(this); } } catch (Throwable t) { - LOG.warn("[{}] Error cancelling", logPrefix, t); + Loggers.warnWithException(LOG, "[{}] Error cancelling", logPrefix, t); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 69a0e607a07..b6a0f192e6d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenMap; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.NanoTime; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; @@ -148,7 +149,8 @@ private Optional rebuildTokenMap( return Optional.of(oldTokenMap.refresh(newNodes.values(), newKeyspaces.values())); } } catch (Throwable t) { - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Unexpected error while refreshing token map, keeping previous version", logPrefix, t); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 96a9cc272e4..bb11ea22314 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.NanoTime; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.Debouncer; @@ -370,7 +371,8 @@ private void startSchemaRequest(CompletableFuture future) { .whenComplete( (v, error) -> { if (error != null) { - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Unexpected error while refreshing schema, skipping", logPrefix, error); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 317dc8f095b..a6ad6635618 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.Debouncer; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.collect.Maps; @@ -311,7 +312,7 @@ private void setState(DefaultNode node, NodeState newState, String reason) { // Fire the event whether the refresh succeeded or not eventBus.fire(NodeStateEvent.changed(oldState, newState, node)); } catch (Throwable t) { - LOG.warn("[{}] Unexpected exception", logPrefix, t); + Loggers.warnWithException(LOG, "[{}] Unexpected exception", logPrefix, t); } }); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java index 30e5ab30644..aa080f467e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -32,6 +32,7 @@ import com.datastax.oss.driver.internal.core.metadata.schema.DefaultIndexMetadata; import com.datastax.oss.driver.internal.core.metadata.schema.DefaultTableMetadata; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; @@ -192,7 +193,8 @@ TableMetadata parseTable( options = parseOptions(tableRow); } catch (Exception e) { // Options change the most often, so be especially lenient if anything goes wrong. - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Error while parsing options for {}.{}, getOptions() will be empty", logPrefix, keyspaceId, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java index 52e1f0471c9..2646679219f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.internal.core.metadata.schema.DefaultColumnMetadata; import com.datastax.oss.driver.internal.core.metadata.schema.DefaultViewMetadata; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -124,7 +125,8 @@ ViewMetadata parseView( options = parseOptions(viewRow); } catch (Exception e) { // Options change the most often, so be especially lenient if anything goes wrong. - LOG.warn( + Loggers.warnWithException( + LOG, "[{}] Error while parsing options for {}.{}, getOptions() will be empty", logPrefix, keyspaceId, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index e88682a2f09..b77d315f1c0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; @@ -473,7 +474,7 @@ private void close() { GenericFutureListener> channelCloseListener = f -> { if (!f.isSuccess()) { - LOG.warn("[{}] Error closing channel", logPrefix, f.cause()); + Loggers.warnWithException(LOG, "[{}] Error closing channel", logPrefix, f.cause()); } if (remaining.decrementAndGet() == 0) { closeFuture.complete(null); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java index 9c9825d06dd..aa8002495a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoder.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.protocol; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.FrameCodec; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -89,7 +90,7 @@ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception } catch (Exception e1) { // Should never happen, super.decode does not return a non-null buffer until the length // field has been read, and the stream id comes before - LOG.warn("Unexpected error while reading stream id", e1); + Loggers.warnWithException(LOG, "Unexpected error while reading stream id", e1); streamId = -1; } if (e instanceof TooLongFrameException) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f7fe1f5afc8..6bc5f4f6bbc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -19,11 +19,11 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -34,6 +34,7 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; @@ -332,7 +333,7 @@ private void processDistanceEvent(DistanceEvent event) { pool.closeAsync() .exceptionally( error -> { - LOG.warn("[{}] Error closing pool", logPrefix, error); + Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); return null; }); } @@ -375,7 +376,7 @@ private void processStateEvent(NodeStateEvent event) { pool.closeAsync() .exceptionally( error -> { - LOG.warn("[{}] Error closing pool", logPrefix, error); + Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); return null; }); } @@ -430,7 +431,8 @@ private void onPoolInitialized(ChannelPool pool) { .handleAsync( (result, error) -> { if (error != null) { - LOG.warn("Error while switching keyspace to " + keyspace, error); + Loggers.warnWithException( + LOG, "Error while switching keyspace to " + keyspace, error); } reprepareStatements(pool); return null; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java new file mode 100644 index 00000000000..f2716a408da --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2017 DataStax Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import org.slf4j.Logger; + +public class Loggers { + + /** + * Emits a warning log that includes an exception. If the current level is debug, the full stack + * trace is included, otherwise only the exception's message. + */ + public static void warnWithException(Logger logger, String format, Object... arguments) { + if (logger.isDebugEnabled()) { + logger.warn(format, arguments); + } else { + Object last = arguments[arguments.length - 1]; + if (last instanceof Throwable) { + arguments[arguments.length - 1] = ((Throwable) last).getMessage(); + logger.warn(format + " ({})", arguments); + } else { + // Should only be called with an exception as last argument, but handle gracefully anyway + logger.warn(format, arguments); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index 58780ae7eee..c9b4ddd8c52 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.util.concurrent; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.internal.core.util.Loggers; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ScheduledFuture; @@ -140,7 +141,8 @@ public void reconnectNow(boolean forceIfStopped) { try { onNextAttemptStarted(reconnectionTask.call()); } catch (Exception e) { - LOG.warn("[{}] Uncaught error while starting reconnection attempt", logPrefix, e); + Loggers.warnWithException( + LOG, "[{}] Uncaught error while starting reconnection attempt", logPrefix, e); scheduleNextAttempt(); } } @@ -186,8 +188,11 @@ private void scheduleNextAttempt() { if (f.isSuccess()) { onNextAttemptStarted(f.getNow()); } else if (!f.isCancelled()) { - LOG.warn( - "[{}] Uncaught error while starting reconnection attempt", logPrefix, f.cause()); + Loggers.warnWithException( + LOG, + "[{}] Uncaught error while starting reconnection attempt", + logPrefix, + f.cause()); scheduleNextAttempt(); } }); @@ -210,7 +215,8 @@ private void onNextAttemptCompleted(Boolean success, Throwable error) { reallyStop(); } else { if (error != null && !(error instanceof CancellationException)) { - LOG.warn("[{}] Uncaught error while starting reconnection attempt", logPrefix, error); + Loggers.warnWithException( + LOG, "[{}] Uncaught error while starting reconnection attempt", logPrefix, error); } if (state == State.STOP_AFTER_CURRENT) { reallyStop(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java index 05c0d992e6d..4c0b6925608 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import com.datastax.oss.driver.internal.core.util.Loggers; import io.netty.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,12 +46,12 @@ public class UncaughtExceptions { public static void log(Future future) { if (!future.isSuccess() && !future.isCancelled()) { - LOG.warn("Uncaught exception in scheduled task", future.cause()); + Loggers.warnWithException(LOG, "Uncaught exception in scheduled task", future.cause()); } } public static T log(Throwable t) { - LOG.warn("Uncaught exception in scheduled task", t); + Loggers.warnWithException(LOG, "Uncaught exception in scheduled task", t); return null; } } From 7dc612b78479e30a54de29aac69d59b7bc10c1ab Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 21 Nov 2017 14:22:13 -0800 Subject: [PATCH 257/742] JAVA-1675: Remove dates from copyright headers --- changelog/README.md | 1 + core/pom.xml | 2 +- .../driver/api/core/AllNodesFailedException.java | 2 +- .../oss/driver/api/core/AsyncAutoCloseable.java | 2 +- .../oss/driver/api/core/CassandraVersion.java | 2 +- .../datastax/oss/driver/api/core/Cluster.java | 2 +- .../oss/driver/api/core/ClusterBuilder.java | 2 +- .../oss/driver/api/core/ConsistencyLevel.java | 2 +- .../oss/driver/api/core/CoreProtocolVersion.java | 2 +- .../oss/driver/api/core/CqlIdentifier.java | 2 +- .../driver/api/core/DefaultClusterBuilder.java | 2 +- .../oss/driver/api/core/DriverException.java | 2 +- .../api/core/DriverExecutionException.java | 2 +- .../driver/api/core/DriverTimeoutException.java | 2 +- .../api/core/InvalidKeyspaceException.java | 2 +- .../api/core/NoNodeAvailableException.java | 2 +- .../oss/driver/api/core/ProtocolVersion.java | 2 +- .../UnsupportedProtocolVersionException.java | 2 +- .../addresstranslation/AddressTranslator.java | 2 +- .../PassThroughAddressTranslator.java | 2 +- .../oss/driver/api/core/auth/AuthProvider.java | 2 +- .../api/core/auth/AuthenticationException.java | 2 +- .../oss/driver/api/core/auth/Authenticator.java | 2 +- .../api/core/auth/PlainTextAuthProvider.java | 2 +- .../driver/api/core/auth/SyncAuthenticator.java | 2 +- .../oss/driver/api/core/auth/package-info.java | 2 +- .../driver/api/core/config/CoreDriverOption.java | 2 +- .../oss/driver/api/core/config/DriverConfig.java | 2 +- .../api/core/config/DriverConfigLoader.java | 2 +- .../api/core/config/DriverConfigProfile.java | 2 +- .../oss/driver/api/core/config/DriverOption.java | 2 +- .../oss/driver/api/core/config/package-info.java | 2 +- .../core/connection/BusyConnectionException.java | 2 +- .../connection/ClosedConnectionException.java | 2 +- .../core/connection/ConnectionInitException.java | 2 +- .../ExponentialReconnectionPolicy.java | 2 +- .../core/connection/FrameTooLongException.java | 2 +- .../api/core/connection/HeartbeatException.java | 2 +- .../api/core/connection/ReconnectionPolicy.java | 2 +- .../driver/api/core/connection/package-info.java | 2 +- .../driver/api/core/context/DriverContext.java | 2 +- .../oss/driver/api/core/cql/AsyncResultSet.java | 2 +- .../oss/driver/api/core/cql/BatchStatement.java | 2 +- .../api/core/cql/BatchStatementBuilder.java | 2 +- .../oss/driver/api/core/cql/BatchType.java | 2 +- .../driver/api/core/cql/BatchableStatement.java | 2 +- .../oss/driver/api/core/cql/Bindable.java | 2 +- .../oss/driver/api/core/cql/BoundStatement.java | 2 +- .../api/core/cql/BoundStatementBuilder.java | 2 +- .../driver/api/core/cql/ColumnDefinition.java | 2 +- .../driver/api/core/cql/ColumnDefinitions.java | 2 +- .../oss/driver/api/core/cql/CqlSession.java | 2 +- .../oss/driver/api/core/cql/ExecutionInfo.java | 2 +- .../oss/driver/api/core/cql/PrepareRequest.java | 2 +- .../driver/api/core/cql/PreparedStatement.java | 2 +- .../oss/driver/api/core/cql/QueryTrace.java | 2 +- .../oss/driver/api/core/cql/ResultSet.java | 2 +- .../datastax/oss/driver/api/core/cql/Row.java | 2 +- .../oss/driver/api/core/cql/SimpleStatement.java | 2 +- .../api/core/cql/SimpleStatementBuilder.java | 2 +- .../oss/driver/api/core/cql/Statement.java | 2 +- .../driver/api/core/cql/StatementBuilder.java | 2 +- .../oss/driver/api/core/cql/TraceEvent.java | 2 +- .../oss/driver/api/core/data/AccessibleById.java | 2 +- .../driver/api/core/data/AccessibleByIndex.java | 2 +- .../driver/api/core/data/AccessibleByName.java | 2 +- .../oss/driver/api/core/data/CqlDuration.java | 2 +- .../datastax/oss/driver/api/core/data/Data.java | 2 +- .../oss/driver/api/core/data/GettableById.java | 2 +- .../driver/api/core/data/GettableByIndex.java | 2 +- .../oss/driver/api/core/data/GettableByName.java | 2 +- .../oss/driver/api/core/data/SettableById.java | 2 +- .../driver/api/core/data/SettableByIndex.java | 2 +- .../oss/driver/api/core/data/SettableByName.java | 2 +- .../oss/driver/api/core/data/TupleValue.java | 2 +- .../oss/driver/api/core/data/UdtValue.java | 2 +- .../driver/api/core/detach/AttachmentPoint.java | 2 +- .../oss/driver/api/core/detach/Detachable.java | 2 +- .../core/loadbalancing/LoadBalancingPolicy.java | 2 +- .../api/core/loadbalancing/NodeDistance.java | 2 +- .../RoundRobinLoadBalancingPolicy.java | 2 +- .../oss/driver/api/core/metadata/Metadata.java | 2 +- .../oss/driver/api/core/metadata/Node.java | 2 +- .../oss/driver/api/core/metadata/NodeState.java | 2 +- .../api/core/metadata/NodeStateListener.java | 2 +- .../oss/driver/api/core/metadata/TokenMap.java | 2 +- .../core/metadata/schema/AggregateMetadata.java | 2 +- .../core/metadata/schema/ClusteringOrder.java | 2 +- .../api/core/metadata/schema/ColumnMetadata.java | 2 +- .../api/core/metadata/schema/Describable.java | 2 +- .../core/metadata/schema/FunctionMetadata.java | 2 +- .../core/metadata/schema/FunctionSignature.java | 2 +- .../api/core/metadata/schema/IndexKind.java | 2 +- .../api/core/metadata/schema/IndexMetadata.java | 2 +- .../core/metadata/schema/KeyspaceMetadata.java | 2 +- .../core/metadata/schema/RelationMetadata.java | 2 +- .../metadata/schema/SchemaChangeListener.java | 2 +- .../schema/SchemaChangeListenerBase.java | 2 +- .../api/core/metadata/schema/TableMetadata.java | 2 +- .../api/core/metadata/schema/ViewMetadata.java | 2 +- .../driver/api/core/metadata/token/Token.java | 2 +- .../api/core/metadata/token/TokenRange.java | 2 +- .../oss/driver/api/core/package-info.java | 2 +- .../api/core/retry/DefaultRetryPolicy.java | 2 +- .../oss/driver/api/core/retry/RetryDecision.java | 2 +- .../oss/driver/api/core/retry/RetryPolicy.java | 2 +- .../oss/driver/api/core/retry/WriteType.java | 2 +- .../servererrors/AlreadyExistsException.java | 2 +- .../servererrors/BootstrappingException.java | 2 +- .../core/servererrors/CoordinatorException.java | 2 +- .../servererrors/FunctionFailureException.java | 2 +- .../InvalidConfigurationInQueryException.java | 2 +- .../core/servererrors/InvalidQueryException.java | 2 +- .../core/servererrors/OverloadedException.java | 2 +- .../api/core/servererrors/ProtocolError.java | 2 +- .../servererrors/QueryConsistencyException.java | 2 +- .../servererrors/QueryExecutionException.java | 2 +- .../servererrors/QueryValidationException.java | 2 +- .../core/servererrors/ReadFailureException.java | 2 +- .../core/servererrors/ReadTimeoutException.java | 2 +- .../api/core/servererrors/ServerError.java | 2 +- .../api/core/servererrors/SyntaxError.java | 2 +- .../api/core/servererrors/TruncateException.java | 2 +- .../core/servererrors/UnauthorizedException.java | 2 +- .../core/servererrors/UnavailableException.java | 2 +- .../core/servererrors/WriteFailureException.java | 2 +- .../core/servererrors/WriteTimeoutException.java | 2 +- .../oss/driver/api/core/session/Request.java | 2 +- .../oss/driver/api/core/session/Session.java | 2 +- .../ConstantSpeculativeExecutionPolicy.java | 2 +- .../specex/NoSpeculativeExecutionPolicy.java | 2 +- .../core/specex/SpeculativeExecutionPolicy.java | 2 +- .../api/core/ssl/DefaultSslEngineFactory.java | 2 +- .../driver/api/core/ssl/SslEngineFactory.java | 2 +- .../oss/driver/api/core/ssl/package-info.java | 2 +- .../api/core/time/AtomicTimestampGenerator.java | 2 +- .../core/time/MonotonicTimestampGenerator.java | 2 +- .../core/time/ServerSideTimestampGenerator.java | 2 +- .../core/time/ThreadLocalTimestampGenerator.java | 2 +- .../driver/api/core/time/TimestampGenerator.java | 2 +- .../oss/driver/api/core/type/CustomType.java | 2 +- .../oss/driver/api/core/type/DataType.java | 2 +- .../oss/driver/api/core/type/DataTypes.java | 2 +- .../oss/driver/api/core/type/ListType.java | 2 +- .../oss/driver/api/core/type/MapType.java | 2 +- .../oss/driver/api/core/type/SetType.java | 2 +- .../oss/driver/api/core/type/TupleType.java | 2 +- .../driver/api/core/type/UserDefinedType.java | 2 +- .../core/type/codec/CodecNotFoundException.java | 2 +- .../core/type/codec/PrimitiveBooleanCodec.java | 2 +- .../api/core/type/codec/PrimitiveByteCodec.java | 2 +- .../core/type/codec/PrimitiveDoubleCodec.java | 2 +- .../api/core/type/codec/PrimitiveFloatCodec.java | 2 +- .../api/core/type/codec/PrimitiveIntCodec.java | 2 +- .../api/core/type/codec/PrimitiveLongCodec.java | 2 +- .../api/core/type/codec/PrimitiveShortCodec.java | 2 +- .../driver/api/core/type/codec/TypeCodec.java | 2 +- .../driver/api/core/type/codec/TypeCodecs.java | 2 +- .../core/type/codec/registry/CodecRegistry.java | 2 +- .../api/core/type/reflect/GenericType.java | 2 +- .../core/type/reflect/GenericTypeParameter.java | 2 +- .../datastax/oss/driver/api/core/uuid/Uuids.java | 2 +- .../datastax/oss/driver/api/package-info.java | 2 +- .../core/CassandraProtocolVersionRegistry.java | 2 +- .../oss/driver/internal/core/ClusterWrapper.java | 2 +- .../oss/driver/internal/core/ContactPoints.java | 2 +- .../oss/driver/internal/core/DefaultCluster.java | 2 +- .../driver/internal/core/ProtocolFeature.java | 2 +- .../internal/core/ProtocolVersionRegistry.java | 2 +- .../internal/core/SchemaListenerNotifier.java | 2 +- .../core/adminrequest/AdminRequestHandler.java | 2 +- .../internal/core/adminrequest/AdminResult.java | 2 +- .../internal/core/adminrequest/AdminRow.java | 2 +- .../internal/core/adminrequest/package-info.java | 2 +- .../core/channel/AvailableIdsHolder.java | 2 +- .../internal/core/channel/ChannelEvent.java | 2 +- .../internal/core/channel/ChannelFactory.java | 2 +- .../core/channel/ChannelHandlerRequest.java | 2 +- .../channel/ClusterNameMismatchException.java | 2 +- .../core/channel/ConnectInitHandler.java | 2 +- .../core/channel/DefaultWriteCoalescer.java | 2 +- .../internal/core/channel/DriverChannel.java | 2 +- .../core/channel/DriverChannelOptions.java | 2 +- .../internal/core/channel/EventCallback.java | 2 +- .../internal/core/channel/HeartbeatHandler.java | 2 +- .../internal/core/channel/InFlightHandler.java | 2 +- .../core/channel/PassThroughWriteCoalescer.java | 2 +- .../core/channel/ProtocolInitHandler.java | 2 +- .../internal/core/channel/ResponseCallback.java | 2 +- .../internal/core/channel/StreamIdGenerator.java | 2 +- .../internal/core/channel/WriteCoalescer.java | 2 +- .../internal/core/channel/package-info.java | 2 +- .../internal/core/config/ConfigChangeEvent.java | 2 +- .../core/config/ForceReloadConfigEvent.java | 2 +- .../typesafe/DefaultDriverConfigLoader.java | 2 +- .../config/typesafe/TypeSafeDriverConfig.java | 2 +- .../typesafe/TypesafeDriverConfigProfile.java | 2 +- .../core/config/typesafe/package-info.java | 2 +- .../core/context/DefaultDriverContext.java | 2 +- .../core/context/DefaultNettyOptions.java | 2 +- .../driver/internal/core/context/EventBus.java | 2 +- .../core/context/InternalDriverContext.java | 2 +- .../internal/core/context/NettyOptions.java | 2 +- .../internal/core/control/ControlConnection.java | 2 +- .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/CqlPrepareAsyncHandler.java | 2 +- .../core/cql/CqlPrepareAsyncProcessor.java | 2 +- .../internal/core/cql/CqlPrepareHandlerBase.java | 2 +- .../internal/core/cql/CqlPrepareSyncHandler.java | 2 +- .../core/cql/CqlPrepareSyncProcessor.java | 2 +- .../core/cql/CqlRequestAsyncHandler.java | 2 +- .../core/cql/CqlRequestAsyncProcessor.java | 2 +- .../internal/core/cql/CqlRequestHandlerBase.java | 2 +- .../internal/core/cql/CqlRequestSyncHandler.java | 2 +- .../core/cql/CqlRequestSyncProcessor.java | 2 +- .../internal/core/cql/DefaultAsyncResultSet.java | 2 +- .../internal/core/cql/DefaultBatchStatement.java | 2 +- .../internal/core/cql/DefaultBoundStatement.java | 2 +- .../core/cql/DefaultColumnDefinition.java | 2 +- .../core/cql/DefaultColumnDefinitions.java | 2 +- .../internal/core/cql/DefaultExecutionInfo.java | 2 +- .../internal/core/cql/DefaultPrepareRequest.java | 2 +- .../core/cql/DefaultPreparedStatement.java | 2 +- .../internal/core/cql/DefaultQueryTrace.java | 2 +- .../oss/driver/internal/core/cql/DefaultRow.java | 2 +- .../core/cql/DefaultSimpleStatement.java | 2 +- .../internal/core/cql/DefaultTraceEvent.java | 2 +- .../core/cql/EmptyColumnDefinitions.java | 2 +- .../internal/core/cql/MultiPageResultSet.java | 2 +- .../internal/core/cql/QueryTraceFetcher.java | 2 +- .../oss/driver/internal/core/cql/ResultSets.java | 2 +- .../internal/core/cql/SinglePageResultSet.java | 2 +- .../internal/core/data/DefaultTupleValue.java | 2 +- .../internal/core/data/DefaultUdtValue.java | 2 +- .../internal/core/data/IdentifierIndex.java | 2 +- .../internal/core/metadata/AddNodeRefresh.java | 2 +- .../internal/core/metadata/DefaultMetadata.java | 2 +- .../internal/core/metadata/DefaultNode.java | 2 +- .../internal/core/metadata/DefaultNodeInfo.java | 2 +- .../core/metadata/DefaultTopologyMonitor.java | 2 +- .../internal/core/metadata/DistanceEvent.java | 2 +- .../core/metadata/FullNodeListRefresh.java | 2 +- .../core/metadata/InitContactPointsRefresh.java | 2 +- .../metadata/LoadBalancingPolicyWrapper.java | 2 +- .../internal/core/metadata/MetadataManager.java | 2 +- .../internal/core/metadata/MetadataRefresh.java | 2 +- .../driver/internal/core/metadata/NodeInfo.java | 2 +- .../internal/core/metadata/NodeStateEvent.java | 2 +- .../internal/core/metadata/NodeStateManager.java | 2 +- .../internal/core/metadata/NodesRefresh.java | 2 +- .../core/metadata/RemoveNodeRefresh.java | 2 +- .../core/metadata/SchemaAgreementChecker.java | 2 +- .../core/metadata/TokensChangedRefresh.java | 2 +- .../internal/core/metadata/TopologyEvent.java | 2 +- .../internal/core/metadata/TopologyMonitor.java | 2 +- .../schema/DefaultAggregateMetadata.java | 2 +- .../metadata/schema/DefaultColumnMetadata.java | 2 +- .../metadata/schema/DefaultFunctionMetadata.java | 2 +- .../metadata/schema/DefaultIndexMetadata.java | 2 +- .../metadata/schema/DefaultKeyspaceMetadata.java | 2 +- .../metadata/schema/DefaultTableMetadata.java | 2 +- .../metadata/schema/DefaultViewMetadata.java | 2 +- .../core/metadata/schema/SchemaChangeType.java | 2 +- .../core/metadata/schema/ScriptBuilder.java | 2 +- .../metadata/schema/ShallowUserDefinedType.java | 2 +- .../schema/events/AggregateChangeEvent.java | 2 +- .../schema/events/FunctionChangeEvent.java | 2 +- .../schema/events/KeyspaceChangeEvent.java | 2 +- .../metadata/schema/events/TableChangeEvent.java | 2 +- .../metadata/schema/events/TypeChangeEvent.java | 2 +- .../metadata/schema/events/ViewChangeEvent.java | 2 +- .../metadata/schema/parsing/AggregateParser.java | 2 +- .../DataTypeClassNameCompositeParser.java | 2 +- .../schema/parsing/DataTypeClassNameParser.java | 2 +- .../schema/parsing/DataTypeCqlNameParser.java | 2 +- .../metadata/schema/parsing/DataTypeParser.java | 2 +- .../parsing/DefaultSchemaParserFactory.java | 2 +- .../metadata/schema/parsing/FunctionParser.java | 2 +- .../core/metadata/schema/parsing/RawColumn.java | 2 +- .../metadata/schema/parsing/RelationParser.java | 2 +- .../metadata/schema/parsing/SchemaParser.java | 2 +- .../schema/parsing/SchemaParserFactory.java | 2 +- .../schema/parsing/SimpleJsonParser.java | 2 +- .../metadata/schema/parsing/TableParser.java | 2 +- .../schema/parsing/UserDefinedTypeParser.java | 2 +- .../core/metadata/schema/parsing/ViewParser.java | 2 +- .../schema/queries/Cassandra21SchemaQueries.java | 2 +- .../schema/queries/Cassandra22SchemaQueries.java | 2 +- .../schema/queries/Cassandra3SchemaQueries.java | 2 +- .../queries/DefaultSchemaQueriesFactory.java | 2 +- .../metadata/schema/queries/SchemaQueries.java | 2 +- .../schema/queries/SchemaQueriesFactory.java | 2 +- .../core/metadata/schema/queries/SchemaRows.java | 2 +- .../metadata/schema/refresh/SchemaRefresh.java | 2 +- .../core/metadata/token/ByteOrderedToken.java | 2 +- .../metadata/token/ByteOrderedTokenFactory.java | 2 +- .../metadata/token/ByteOrderedTokenRange.java | 2 +- .../token/DefaultTokenFactoryRegistry.java | 2 +- .../core/metadata/token/DefaultTokenMap.java | 2 +- .../core/metadata/token/KeyspaceTokenMap.java | 2 +- .../metadata/token/LocalReplicationStrategy.java | 2 +- .../core/metadata/token/Murmur3Token.java | 2 +- .../core/metadata/token/Murmur3TokenFactory.java | 2 +- .../core/metadata/token/Murmur3TokenRange.java | 2 +- .../NetworkTopologyReplicationStrategy.java | 2 +- .../core/metadata/token/RandomToken.java | 2 +- .../core/metadata/token/RandomTokenFactory.java | 2 +- .../core/metadata/token/RandomTokenRange.java | 2 +- .../core/metadata/token/ReplicationStrategy.java | 2 +- .../token/SimpleReplicationStrategy.java | 2 +- .../core/metadata/token/TokenFactory.java | 2 +- .../metadata/token/TokenFactoryRegistry.java | 2 +- .../core/metadata/token/TokenRangeBase.java | 2 +- .../oss/driver/internal/core/os/Native.java | 2 +- .../driver/internal/core/pool/ChannelPool.java | 2 +- .../internal/core/pool/ChannelPoolFactory.java | 2 +- .../driver/internal/core/pool/ChannelSet.java | 2 +- .../core/protocol/ByteBufCompressor.java | 2 +- .../core/protocol/ByteBufPrimitiveCodec.java | 2 +- .../internal/core/protocol/FrameDecoder.java | 2 +- .../core/protocol/FrameDecodingException.java | 2 +- .../internal/core/protocol/FrameEncoder.java | 2 +- .../internal/core/protocol/Lz4Compressor.java | 2 +- .../internal/core/protocol/SnappyCompressor.java | 2 +- .../internal/core/protocol/package-info.java | 2 +- .../internal/core/session/DefaultSession.java | 2 +- .../internal/core/session/ReprepareOnUp.java | 2 +- .../internal/core/session/RepreparePayload.java | 2 +- .../internal/core/session/RequestHandler.java | 2 +- .../core/session/RequestHandlerBase.java | 2 +- .../internal/core/session/RequestProcessor.java | 2 +- .../core/session/RequestProcessorRegistry.java | 2 +- .../internal/core/session/SessionWrapper.java | 2 +- .../internal/core/ssl/JdkSslHandlerFactory.java | 2 +- .../internal/core/ssl/SslHandlerFactory.java | 2 +- .../oss/driver/internal/core/time/Clock.java | 2 +- .../oss/driver/internal/core/time/JavaClock.java | 2 +- .../driver/internal/core/time/NativeClock.java | 2 +- .../internal/core/type/DataTypeHelper.java | 2 +- .../internal/core/type/DefaultCustomType.java | 2 +- .../internal/core/type/DefaultListType.java | 2 +- .../internal/core/type/DefaultMapType.java | 2 +- .../internal/core/type/DefaultSetType.java | 2 +- .../internal/core/type/DefaultTupleType.java | 2 +- .../core/type/DefaultUserDefinedType.java | 2 +- .../driver/internal/core/type/PrimitiveType.java | 2 +- .../core/type/UserDefinedTypeBuilder.java | 2 +- .../internal/core/type/codec/BigIntCodec.java | 2 +- .../internal/core/type/codec/BlobCodec.java | 2 +- .../internal/core/type/codec/BooleanCodec.java | 2 +- .../internal/core/type/codec/CounterCodec.java | 2 +- .../core/type/codec/CqlDurationCodec.java | 2 +- .../internal/core/type/codec/CustomCodec.java | 2 +- .../internal/core/type/codec/DateCodec.java | 2 +- .../internal/core/type/codec/DecimalCodec.java | 2 +- .../internal/core/type/codec/DoubleCodec.java | 2 +- .../internal/core/type/codec/FloatCodec.java | 2 +- .../internal/core/type/codec/InetCodec.java | 2 +- .../internal/core/type/codec/IntCodec.java | 2 +- .../internal/core/type/codec/ListCodec.java | 2 +- .../internal/core/type/codec/MapCodec.java | 2 +- .../internal/core/type/codec/ParseUtils.java | 2 +- .../internal/core/type/codec/SetCodec.java | 2 +- .../internal/core/type/codec/SmallIntCodec.java | 2 +- .../internal/core/type/codec/StringCodec.java | 2 +- .../internal/core/type/codec/TimeCodec.java | 2 +- .../internal/core/type/codec/TimeUuidCodec.java | 2 +- .../internal/core/type/codec/TimestampCodec.java | 2 +- .../internal/core/type/codec/TinyIntCodec.java | 2 +- .../internal/core/type/codec/TupleCodec.java | 2 +- .../internal/core/type/codec/UdtCodec.java | 2 +- .../internal/core/type/codec/UuidCodec.java | 2 +- .../internal/core/type/codec/VarIntCodec.java | 2 +- .../codec/registry/CachingCodecRegistry.java | 2 +- .../codec/registry/DefaultCodecRegistry.java | 2 +- .../internal/core/type/util/VIntCoding.java | 2 +- .../internal/core/util/CountingIterator.java | 2 +- .../driver/internal/core/util/DirectedGraph.java | 2 +- .../oss/driver/internal/core/util/Loggers.java | 2 +- .../oss/driver/internal/core/util/NanoTime.java | 2 +- .../driver/internal/core/util/ProtocolUtils.java | 2 +- .../driver/internal/core/util/Reflection.java | 2 +- .../driver/internal/core/util/RoutingKey.java | 2 +- .../oss/driver/internal/core/util/Strings.java | 2 +- .../core/util/concurrent/BlockingOperation.java | 2 +- .../core/util/concurrent/CompletableFutures.java | 2 +- .../core/util/concurrent/CycleDetector.java | 2 +- .../internal/core/util/concurrent/Debouncer.java | 2 +- .../core/util/concurrent/LazyReference.java | 2 +- .../core/util/concurrent/Reconnection.java | 2 +- .../util/concurrent/ReplayingEventFilter.java | 2 +- .../core/util/concurrent/RunOrSchedule.java | 2 +- .../core/util/concurrent/UncaughtExceptions.java | 2 +- .../driver/internal/core/util/package-info.java | 2 +- .../oss/driver/internal/package-info.java | 2 +- .../com/datastax/oss/driver/Driver.properties | 2 +- .../java/com/datastax/oss/driver/Assertions.java | 2 +- .../com/datastax/oss/driver/ByteBufAssert.java | 2 +- .../datastax/oss/driver/DriverRunListener.java | 2 +- .../datastax/oss/driver/TestDataProviders.java | 2 +- .../driver/api/core/CassandraVersionAssert.java | 2 +- .../driver/api/core/CassandraVersionTest.java | 2 +- .../oss/driver/api/core/CqlIdentifierTest.java | 2 +- .../driver/api/core/data/CqlDurationTest.java | 2 +- .../RoundRobinLoadBalancingPolicyTest.java | 2 +- .../api/core/retry/DefaultRetryPolicyTest.java | 2 +- .../api/core/retry/RetryPolicyTestBase.java | 2 +- .../ConstantSpeculativeExecutionPolicyTest.java | 2 +- .../core/time/AtomicTimestampGeneratorTest.java | 2 +- .../MonotonicTimestampGeneratorTestBase.java | 2 +- .../time/ThreadLocalTimestampGeneratorTest.java | 2 +- .../api/core/type/UserDefinedTypeTest.java | 2 +- .../api/core/type/reflect/GenericTypeTest.java | 2 +- .../oss/driver/api/core/uuid/UuidsTest.java | 2 +- .../oss/driver/internal/SerializationHelper.java | 2 +- ...ProtocolVersionRegistryHighestCommonTest.java | 2 +- .../CassandraProtocolVersionRegistryTest.java | 2 +- .../internal/core/CompletionStageAssert.java | 2 +- .../driver/internal/core/DriverConfigAssert.java | 2 +- .../driver/internal/core/NettyFutureAssert.java | 2 +- .../oss/driver/internal/core/TestResponses.java | 2 +- .../channel/ChannelFactoryAvailableIdsTest.java | 2 +- .../channel/ChannelFactoryClusterNameTest.java | 2 +- .../ChannelFactoryProtocolNegotiationTest.java | 2 +- .../core/channel/ChannelFactoryTestBase.java | 2 +- .../core/channel/ChannelHandlerTestBase.java | 2 +- .../core/channel/ConnectInitHandlerTest.java | 2 +- .../internal/core/channel/DriverChannelTest.java | 2 +- .../core/channel/InFlightHandlerTest.java | 2 +- .../internal/core/channel/MockAuthenticator.java | 2 +- .../core/channel/MockChannelFactoryHelper.java | 2 +- .../core/channel/MockResponseCallback.java | 2 +- .../core/channel/ProtocolInitHandlerTest.java | 2 +- .../core/channel/StreamIdGeneratorTest.java | 2 +- .../typesafe/DefaultDriverConfigLoaderTest.java | 2 +- .../core/config/typesafe/MockOptions.java | 2 +- .../typesafe/TypeSafeDriverConfigTest.java | 2 +- .../internal/core/context/bus/EventBusTest.java | 2 +- .../control/ControlConnectionEventsTest.java | 2 +- .../core/control/ControlConnectionTest.java | 2 +- .../core/control/ControlConnectionTestBase.java | 2 +- .../internal/core/cql/CqlPrepareHandlerTest.java | 2 +- .../core/cql/CqlRequestHandlerRetryTest.java | 2 +- ...qlRequestHandlerSpeculativeExecutionTest.java | 2 +- .../internal/core/cql/CqlRequestHandlerTest.java | 2 +- .../core/cql/CqlRequestHandlerTestBase.java | 2 +- .../core/cql/DefaultAsyncResultSetTest.java | 2 +- .../driver/internal/core/cql/PoolBehavior.java | 2 +- .../internal/core/cql/QueryTraceFetcherTest.java | 2 +- .../core/cql/RequestHandlerTestHarness.java | 2 +- .../driver/internal/core/cql/ResultSetsTest.java | 2 +- .../core/data/AccessibleByIdTestBase.java | 2 +- .../core/data/AccessibleByIndexTestBase.java | 2 +- .../core/data/DefaultTupleValueTest.java | 2 +- .../internal/core/data/DefaultUdtValueTest.java | 2 +- .../internal/core/data/IdentifierIndexTest.java | 2 +- .../core/metadata/AddNodeRefreshTest.java | 2 +- .../metadata/DefaultMetadataTokenMapTest.java | 2 +- .../metadata/DefaultTopologyMonitorTest.java | 2 +- .../core/metadata/FullNodeListRefreshTest.java | 2 +- .../metadata/InitContactPointsRefreshTest.java | 2 +- .../metadata/LoadBalancingPolicyWrapperTest.java | 2 +- .../core/metadata/MetadataManagerTest.java | 2 +- .../core/metadata/NodeStateManagerTest.java | 2 +- .../core/metadata/RemoveNodeRefreshTest.java | 2 +- .../metadata/SchemaAgreementCheckerTest.java | 2 +- .../schema/parsing/AggregateParserTest.java | 2 +- .../parsing/DataTypeClassNameParserTest.java | 2 +- .../parsing/DataTypeCqlNameParserTest.java | 2 +- .../schema/parsing/FunctionParserTest.java | 2 +- .../schema/parsing/SchemaParserTest.java | 2 +- .../schema/parsing/SchemaParserTestBase.java | 2 +- .../metadata/schema/parsing/TableParserTest.java | 2 +- .../parsing/UserDefinedTypeListParserTest.java | 2 +- .../metadata/schema/parsing/ViewParserTest.java | 2 +- .../queries/Cassandra21SchemaQueriesTest.java | 2 +- .../queries/Cassandra22SchemaQueriesTest.java | 2 +- .../queries/Cassandra3SchemaQueriesTest.java | 2 +- .../schema/queries/SchemaQueriesTest.java | 2 +- .../schema/refresh/SchemaRefreshTest.java | 2 +- .../token/ByteOrderedTokenRangeTest.java | 2 +- .../core/metadata/token/DefaultTokenMapTest.java | 2 +- .../metadata/token/Murmur3TokenRangeTest.java | 2 +- .../NetworkTopologyReplicationStrategyTest.java | 2 +- .../metadata/token/RandomTokenRangeTest.java | 2 +- .../token/SimpleReplicationStrategyTest.java | 2 +- .../core/metadata/token/TokenRangeAssert.java | 2 +- .../core/metadata/token/TokenRangeTest.java | 2 +- .../internal/core/pool/ChannelPoolInitTest.java | 2 +- .../core/pool/ChannelPoolKeyspaceTest.java | 2 +- .../core/pool/ChannelPoolReconnectTest.java | 2 +- .../core/pool/ChannelPoolResizeTest.java | 2 +- .../core/pool/ChannelPoolShutdownTest.java | 2 +- .../internal/core/pool/ChannelPoolTestBase.java | 2 +- .../internal/core/pool/ChannelSetTest.java | 2 +- .../core/protocol/ByteBufPrimitiveCodecTest.java | 2 +- .../internal/core/protocol/FrameDecoderTest.java | 2 +- .../core/session/DefaultSessionTest.java | 2 +- .../session/MockChannelPoolFactoryHelper.java | 2 +- .../internal/core/session/ReprepareOnUpTest.java | 2 +- .../core/type/DataTypeDetachableTest.java | 2 +- .../core/type/DataTypeSerializationTest.java | 2 +- .../core/type/codec/BigIntCodecTest.java | 2 +- .../internal/core/type/codec/BlobCodecTest.java | 2 +- .../core/type/codec/BooleanCodecTest.java | 2 +- .../internal/core/type/codec/CodecTestBase.java | 2 +- .../core/type/codec/CounterCodecTest.java | 2 +- .../core/type/codec/CqlDurationCodecTest.java | 2 +- .../core/type/codec/CqlIntToStringCodec.java | 2 +- .../core/type/codec/CustomCodecTest.java | 2 +- .../internal/core/type/codec/DateCodecTest.java | 2 +- .../core/type/codec/DecimalCodecTest.java | 2 +- .../core/type/codec/DoubleCodecTest.java | 2 +- .../internal/core/type/codec/FloatCodecTest.java | 2 +- .../internal/core/type/codec/InetCodecTest.java | 2 +- .../internal/core/type/codec/IntCodecTest.java | 2 +- .../internal/core/type/codec/ListCodecTest.java | 2 +- .../internal/core/type/codec/MapCodecTest.java | 2 +- .../internal/core/type/codec/SetCodecTest.java | 2 +- .../core/type/codec/SmallIntCodecTest.java | 2 +- .../core/type/codec/StringCodecTest.java | 2 +- .../internal/core/type/codec/TimeCodecTest.java | 2 +- .../core/type/codec/TimeUuidCodecTest.java | 2 +- .../core/type/codec/TimestampCodecTest.java | 2 +- .../core/type/codec/TinyIntCodecTest.java | 2 +- .../internal/core/type/codec/TupleCodecTest.java | 2 +- .../internal/core/type/codec/UdtCodecTest.java | 2 +- .../internal/core/type/codec/UuidCodecTest.java | 2 +- .../core/type/codec/VarintCodecTest.java | 2 +- .../codec/registry/CachingCodecRegistryTest.java | 2 +- .../oss/driver/internal/core/util/ByteBufs.java | 2 +- .../internal/core/util/DirectedGraphTest.java | 2 +- .../core/util/concurrent/CycleDetectorTest.java | 2 +- .../core/util/concurrent/DebouncerTest.java | 2 +- .../core/util/concurrent/ReconnectionTest.java | 2 +- .../concurrent/ReplayingEventFilterTest.java | 2 +- .../ScheduledTaskCapturingEventLoop.java | 2 +- .../ScheduledTaskCapturingEventLoopTest.java | 2 +- core/src/test/resources/logback-test.xml | 2 +- integration-tests/pom.xml | 2 +- .../datastax/oss/driver/api/core/ConnectIT.java | 2 +- .../ProtocolVersionInitialNegotiationIT.java | 2 +- .../api/core/ProtocolVersionMixedClusterIT.java | 2 +- .../api/core/auth/PlainTextAuthProviderIT.java | 2 +- .../core/compression/DirectCompressionIT.java | 2 +- .../api/core/compression/HeapCompressionIT.java | 2 +- .../api/core/config/DriverConfigProfileIT.java | 2 +- .../core/config/DriverConfigProfileReloadIT.java | 2 +- .../api/core/connection/FrameLengthIT.java | 2 +- .../driver/api/core/cql/AsyncResultSetIT.java | 2 +- .../driver/api/core/cql/BatchStatementIT.java | 2 +- .../driver/api/core/cql/BoundStatementIT.java | 2 +- .../oss/driver/api/core/cql/QueryTraceIT.java | 2 +- .../driver/api/core/cql/SimpleStatementIT.java | 2 +- .../oss/driver/api/core/data/DataTypeIT.java | 2 +- .../api/core/heartbeat/HeartbeatDisabledIT.java | 2 +- .../driver/api/core/heartbeat/HeartbeatIT.java | 2 +- .../api/core/metadata/ByteOrderedTokenIT.java | 2 +- .../core/metadata/ByteOrderedTokenVnodesIT.java | 2 +- .../oss/driver/api/core/metadata/DescribeIT.java | 2 +- .../driver/api/core/metadata/Murmur3TokenIT.java | 2 +- .../api/core/metadata/Murmur3TokenVnodesIT.java | 2 +- .../driver/api/core/metadata/NodeStateIT.java | 2 +- .../driver/api/core/metadata/RandomTokenIT.java | 2 +- .../api/core/metadata/RandomTokenVnodesIT.java | 2 +- .../api/core/metadata/SchemaAgreementIT.java | 2 +- .../api/core/metadata/SchemaChangesIT.java | 2 +- .../oss/driver/api/core/metadata/SchemaIT.java | 2 +- .../driver/api/core/metadata/TokenITBase.java | 2 +- .../api/core/retry/DefaultRetryPolicyIT.java | 2 +- .../driver/api/core/session/GuavaCluster.java | 16 +++++++++++++++- .../api/core/session/GuavaClusterBuilder.java | 2 +- .../api/core/session/GuavaDriverContext.java | 2 +- .../core/session/GuavaRequestAsyncProcessor.java | 3 +-- .../driver/api/core/session/GuavaSession.java | 2 +- .../oss/driver/api/core/session/KeyRequest.java | 2 +- .../api/core/session/KeyRequestProcessor.java | 2 +- .../api/core/session/RequestProcessorIT.java | 2 +- .../api/core/specex/SpeculativeExecutionIT.java | 2 +- .../api/core/ssl/DefaultSslEngineFactoryIT.java | 2 +- .../DefaultSslEngineFactoryWithClientAuthIT.java | 2 +- ...EngineFactoryWithClientAuthNotProvidedIT.java | 2 +- ...EngineFactoryWithTruststoreNotProvidedIT.java | 2 +- .../type/codec/registry/CodecRegistryIT.java | 2 +- .../oss/driver/categories/IsolatedTests.java | 2 +- .../driver/categories/ParallelizableTests.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- pom.xml | 4 ++-- test-infra/pom.xml | 2 +- .../api/testinfra/CassandraRequirement.java | 2 +- .../api/testinfra/CassandraResourceRule.java | 2 +- .../driver/api/testinfra/ccm/BaseCcmRule.java | 2 +- .../oss/driver/api/testinfra/ccm/CcmBridge.java | 2 +- .../oss/driver/api/testinfra/ccm/CcmRule.java | 2 +- .../driver/api/testinfra/ccm/CustomCcmRule.java | 2 +- .../api/testinfra/cluster/ClusterRule.java | 2 +- .../testinfra/cluster/ClusterRuleBuilder.java | 2 +- .../api/testinfra/cluster/ClusterUtils.java | 2 +- .../SortingLoadBalancingPolicy.java | 2 +- .../api/testinfra/simulacron/SimulacronRule.java | 2 +- .../api/testinfra/utils/ConditionChecker.java | 2 +- .../driver/api/testinfra/utils/NodeUtils.java | 2 +- .../oss/driver/assertions/Assertions.java | 2 +- .../driver/assertions/NodeMetadataAssert.java | 2 +- .../testinfra/cluster/TestConfigLoader.java | 2 +- 605 files changed, 620 insertions(+), 606 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 22c7eab5df7..16abf4699e6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1675: Remove dates from copyright headers - [improvement] JAVA-1645: Don't log stack traces at WARN level - [new feature] JAVA-1524: Add query trace API - [improvement] JAVA-1646: Provide a more readable error when connecting to Cassandra 2.0 or lower diff --git a/core/pom.xml b/core/pom.xml index 93b996ef460..37799aa22fc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,6 +1,6 @@ true parallelized + ${skipParallelizableITs} @@ -124,6 +131,7 @@ true serial + ${skipSerialITs} @@ -139,6 +147,7 @@ false true isolated + ${skipIsolatedITs} From 6999f9a725b41ebf90b403bc38a6d46525416fe0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 11:12:05 -0800 Subject: [PATCH 266/742] Fix random failure in HeartBeatIT `nonControlNode.acceptConnections()` is the actual fix, but also switching SimulacronRule to class-level as a generic good practice. --- .../datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index 93384c0f475..b3ba3d8fe14 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -36,7 +36,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.junit.Before; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -54,8 +53,7 @@ @Category(ParallelizableTests.class) public class HeartbeatIT { - @ClassRule - public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); @Rule public ClusterRule cluster = @@ -132,6 +130,8 @@ public void node_should_go_down_gracefully_when_connection_closed_during_heartbe // Should have been a heartbeat received since that's what caused the disconnect. assertThat(getHeartbeatsForNode(nonControlNode).size()).isGreaterThan(heartbeatCount); + + nonControlNode.acceptConnections(); } private static final Predicate optionsRequest = (l) -> l.getQuery().equals("OPTIONS"); From c0e3b53a268d6843e1053bd54a53ad3a44c2c242 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 11:33:50 -0800 Subject: [PATCH 267/742] Fix parallel execution bug in NodeStateIT When tests are executed in parallel, the method to find an "unused" address would occasionnally generate the address of a node in the cluster of another test. --- .../driver/api/core/metadata/NodeStateIT.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index d4e580ae055..9d5d67c7599 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -33,10 +33,10 @@ import com.datastax.oss.simulacron.common.stubbing.CloseType; import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.RejectScope; -import java.net.InetAddress; +import java.io.IOException; import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.SocketAddress; -import java.net.UnknownHostException; import java.util.Iterator; import java.util.Map; import java.util.concurrent.BlockingQueue; @@ -438,14 +438,14 @@ public void should_signal_non_contact_points_as_added() { @Test public void should_remove_invalid_contact_point() { - // Initialize the driver with 1 wrong address and 1 valid address - InetSocketAddress wrongContactPoint = unusedAddress(); Iterator contactPoints = simulacron.getContactPoints().iterator(); InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + // Initialize the driver with 1 wrong address and 1 valid address + InetSocketAddress wrongContactPoint = withUnusedPort(address1); try (Cluster localCluster = Cluster.builder() .addContactPoint(address1) @@ -568,20 +568,31 @@ private void expect(NodeStateEvent... expectedEvents) { } // Generates a socket address that is not the connect address of one of the nodes in the cluster - private InetSocketAddress unusedAddress() { + private InetSocketAddress withUnusedPort(InetSocketAddress address) { + return new InetSocketAddress(address.getAddress(), findAvailablePort()); + } + + /** + * Finds an available port in the ephemeral range. This is loosely inspired by Apache MINA's + * AvailablePortFinder. + */ + private static synchronized int findAvailablePort() throws RuntimeException { + ServerSocket ss = null; try { - byte[] bytes = new byte[] {127, 0, 1, 2}; - for (int i = 0; i < 100; i++) { - bytes[3] += 1; - InetSocketAddress address = new InetSocketAddress(InetAddress.getByAddress(bytes), 9043); - if (!simulacron.getContactPoints().contains(address)) { - return address; + // let the system pick an ephemeral port + ss = new ServerSocket(0); + ss.setReuseAddress(true); + return ss.getLocalPort(); + } catch (IOException e) { + throw new AssertionError(e); + } finally { + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + throw new AssertionError(e); } } - } catch (UnknownHostException e) { - fail("unexpected error", e); } - fail("Could not find unused address"); - return null; // never reached } } From f5caaa696eaddc5a7707d344f03514ffd1c050c8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 13:22:51 -0800 Subject: [PATCH 268/742] Increase timeouts in DefaultSessionTest --- .../driver/internal/core/session/DefaultSessionTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 4d718ea199b..7aceff1ec83 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -335,7 +335,7 @@ public void should_resize_pool_if_distance_changes() { assertThat(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); - Mockito.verify(pool2, timeout(100)).resize(NodeDistance.REMOTE); + Mockito.verify(pool2, timeout(500)).resize(NodeDistance.REMOTE); } @Test @@ -359,7 +359,7 @@ public void should_remove_pool_if_node_becomes_ignored() { assertThat(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); - Mockito.verify(pool2, timeout(100)).closeAsync(); + Mockito.verify(pool2, timeout(500)).closeAsync(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -418,7 +418,7 @@ public void should_remove_pool_if_node_is_forced_down() { assertThat(initFuture).isSuccess(); eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); - Mockito.verify(pool2, timeout(100)).closeAsync(); + Mockito.verify(pool2, timeout(500)).closeAsync(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); From 51666fbf3ddda1935b3cb1e93a2bdb847219458b Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 14:01:40 -0800 Subject: [PATCH 269/742] Increase admin timeouts in integration tests --- integration-tests/src/test/resources/application.conf | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 integration-tests/src/test/resources/application.conf diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf new file mode 100644 index 00000000000..51275634afa --- /dev/null +++ b/integration-tests/src/test/resources/application.conf @@ -0,0 +1,9 @@ +datastax-java-driver { + connection { + init-query-timeout = 5 seconds + set-keyspace-timeout = 5 seconds + heartbeat.timeout = 5 seconds + control-connection.timeout = 5 seconds + } + request.trace.interval = 1 second +} \ No newline at end of file From 66c7dde972b3a96554ef2f23f09eb41a2368b0e7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 14:14:41 -0800 Subject: [PATCH 270/742] Raise all verification timeouts --- .../ChannelFactoryAvailableIdsTest.java | 4 +- .../LoadBalancingPolicyWrapperTest.java | 10 ++-- .../core/metadata/MetadataManagerTest.java | 2 +- .../session/MockChannelPoolFactoryHelper.java | 4 +- .../driver/api/core/metadata/NodeStateIT.java | 56 +++++++++---------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 29ceae07a73..efdf5b29bf2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -75,7 +75,7 @@ public void should_report_available_ids_if_requested() { // Complete the request, should increase again writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + Mockito.verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); assertThat(channel.availableIds()).isEqualTo(128); }); }); @@ -106,7 +106,7 @@ public void should_not_report_available_ids_if_not_requested() { assertThat(channel.availableIds()).isEqualTo(-1); writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(100)).onResponse(any(Frame.class)); + Mockito.verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); assertThat(channel.availableIds()).isEqualTo(-1); }); }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 8bea839dfc9..9f9d4be9e45 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -183,7 +183,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted Answer mockInit = i -> { eventLatch.countDown(); - initLatch.await(100, TimeUnit.MILLISECONDS); + initLatch.await(500, TimeUnit.MILLISECONDS); return null; }; Mockito.doAnswer(mockInit) @@ -194,7 +194,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted Runnable runnable = () -> { try { - eventLatch.await(100, TimeUnit.MILLISECONDS); + eventLatch.await(500, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -207,11 +207,11 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted // Then // wait for init launch to signal that runnable is complete. - initLatch.await(100, TimeUnit.MILLISECONDS); + initLatch.await(500, TimeUnit.MILLISECONDS); Mockito.verify(loadBalancingPolicy).onDown(node1); if (thread.isAlive()) { - // thread still completing - sleep for 100ms to allow thread to complete. - Thread.sleep(100); + // thread still completing - sleep to allow thread to complete. + Thread.sleep(500); } assertThat(thread.isAlive()).isFalse(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 2c5642b11ef..c1f03180804 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -146,7 +146,7 @@ public void should_refresh_single_node() { // Then // the info should have been copied to the node assertThat(refreshNodeFuture).isSuccess(); - Mockito.verify(info, timeout(100)).getDatacenter(); + Mockito.verify(info, timeout(500)).getDatacenter(); assertThat(node.getDatacenter()).isEqualTo("dc1"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 2522e41b360..3b28cf35c3f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -86,7 +86,7 @@ public void waitForCalls(Node node, CqlIdentifier keyspace, NodeDistance distanc ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(InternalDriverContext.class); inOrder - .verify(channelPoolFactory, timeout(100).atLeast(expected)) + .verify(channelPoolFactory, timeout(500).atLeast(expected)) .init(eq(node), eq(keyspace), eq(distance), contextCaptor.capture(), eq("test")); int actual = contextCaptor.getAllValues().size(); @@ -98,7 +98,7 @@ public void waitForCalls(Node node, CqlIdentifier keyspace, NodeDistance distanc public void verifyNoMoreCalls() { inOrder - .verify(channelPoolFactory, timeout(100).times(0)) + .verify(channelPoolFactory, timeout(500).times(0)) .init( any(Node.class), any(CqlIdentifier.class), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 9d5d67c7599..c9782600115 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -116,8 +116,8 @@ public void setup() { // ClusterRule uses all nodes as contact points, so we only get onUp notifications for them (no // onAdd) - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataControlNode); - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataControlNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); } @After @@ -162,7 +162,7 @@ public void should_mark_regular_node_down_when_no_more_connections() { .becomesTrue(); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode)); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); } @Test @@ -190,7 +190,7 @@ public void should_mark_control_node_down_when_control_connection_is_last_connec .as("Control node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataControlNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataControlNode); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataControlNode)); } @@ -204,7 +204,7 @@ public void should_bring_node_back_up_when_reconnection_succeeds() { .as("Node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); simulacronRegularNode.acceptConnections(); @@ -213,7 +213,7 @@ public void should_bring_node_back_up_when_reconnection_succeeds() { .as("Connections re-established") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); expect( NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, metadataRegularNode), @@ -249,7 +249,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .as("SUGGEST_DOWN event applied") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( @@ -262,7 +262,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .as("SUGGEST_UP event applied") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); } @Test @@ -270,7 +270,7 @@ public void should_ignore_down_topology_event_when_still_connected() throws Inte driverContext .eventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); - TimeUnit.MILLISECONDS.sleep(200); + TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting(); } @@ -302,7 +302,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - Mockito.verify(localNodeStateListener, timeout(100)).onDown(localMetadataNode); + Mockito.verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, localMetadataNode)); @@ -315,7 +315,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node coming back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode); expect(NodeStateEvent.changed(NodeState.DOWN, NodeState.UP, localMetadataNode)); } @@ -333,17 +333,17 @@ public void should_force_down_when_not_ignored() throws InterruptedException { .as("Node forced down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); - TimeUnit.MILLISECONDS.sleep(200); + TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext .eventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); - TimeUnit.MILLISECONDS.sleep(200); + TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); // Should only come back up on a FORCE_UP event @@ -353,7 +353,7 @@ public void should_force_down_when_not_ignored() throws InterruptedException { .as("Node forced back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); } @Test @@ -371,17 +371,17 @@ public void should_force_down_when_ignored() throws InterruptedException { .as("Node forced down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onDown(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); - TimeUnit.MILLISECONDS.sleep(200); + TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext .eventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); - TimeUnit.MILLISECONDS.sleep(200); + TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); // Should only come back up on a FORCE_UP event, will not reopen connections since it is still @@ -397,7 +397,7 @@ public void should_force_down_when_ignored() throws InterruptedException { .as("Node forced back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); - inOrder.verify(nodeStateListener, timeout(100)).onUp(metadataRegularNode); + inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); driverContext.loadBalancingPolicyWrapper().setDistance(metadataRegularNode, NodeDistance.LOCAL); } @@ -425,14 +425,14 @@ public void should_signal_non_contact_points_as_added() { Node localMetadataNode2 = nodes.get(address2); // Successful contact point goes to up directly - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); // Non-contact point only added since we don't have a connection or events for it yet - Mockito.verify(localNodeStateListener, timeout(100)).onAdd(localMetadataNode2); + Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); localCluster.connect(); // Non-contact point now has a connection opened => up - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode2); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode2); } } @@ -464,10 +464,10 @@ public void should_remove_invalid_contact_point() { // The order of the calls is not deterministic because contact points are shuffled, but it // does not matter here since Mockito.verify does not enforce order. - Mockito.verify(localNodeStateListener, timeout(100)) + Mockito.verify(localNodeStateListener, timeout(500)) .onRemove(new DefaultNode(wrongContactPoint)); - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); - Mockito.verify(localNodeStateListener, timeout(100)).onAdd(localMetadataNode2); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); + Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); // Note: there might be an additional onDown for wrongContactPoint if it was hit first at // init. This is hard to test since the node was removed later, so we simply don't call @@ -511,14 +511,14 @@ public void should_mark_unreachable_contact_point_down() { Node localMetadataNode2 = nodes.get(address2); if (localMetadataNode2.getState() == NodeState.DOWN) { // Stopped node was tried first and marked down, that's our target scenario - Mockito.verify(localNodeStateListener, timeout(100)).onDown(localMetadataNode2); - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode2); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); Mockito.verifyNoMoreInteractions(localNodeStateListener); return; } else { // Stopped node was not tried assertThat(localMetadataNode2).isUnknown(); - Mockito.verify(localNodeStateListener, timeout(100)).onUp(localMetadataNode1); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); Mockito.verifyNoMoreInteractions(localNodeStateListener); } } From ace2004ba53ce0f6262cd7243be7ed9f1a7eb33a Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 22 Nov 2017 14:27:59 -0800 Subject: [PATCH 271/742] Increase delays in SpeculativeExecutionIT --- .../core/session/RequestHandlerBase.java | 64 ------------------- .../core/specex/SpeculativeExecutionIT.java | 2 +- 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java deleted file mode 100644 index f717f261f63..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandlerBase.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.session; - -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import java.util.Queue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** Factors code that should be common to most request handler implementations. */ -public abstract class RequestHandlerBase - implements RequestHandler { - - private static final Logger LOG = LoggerFactory.getLogger(RequestHandlerBase.class); - - protected final boolean isIdempotent; - protected final DefaultSession session; - protected final CqlIdentifier keyspace; - protected final InternalDriverContext context; - protected final Queue queryPlan; - protected final DriverConfigProfile configProfile; - - protected RequestHandlerBase( - RequestT request, DefaultSession session, InternalDriverContext context) { - this.session = session; - this.keyspace = session.getKeyspace(); - this.context = context; - this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); - - if (request.getConfigProfile() != null) { - this.configProfile = request.getConfigProfile(); - } else { - DriverConfig config = context.config(); - String profileName = request.getConfigProfileName(); - this.configProfile = - (profileName == null || profileName.isEmpty()) - ? config.getDefaultProfile() - : config.getNamedProfile(profileName); - } - this.isIdempotent = - (request.isIdempotent() == null) - ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) - : request.isIdempotent(); - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index fa526e6041c..aca41a4db54 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -40,7 +40,7 @@ public class SpeculativeExecutionIT { // Note: it looks like shorter delays cause precision issues with Netty timers - private static final long SPECULATIVE_DELAY = 100; + private static final long SPECULATIVE_DELAY = 500; private static String QUERY_STRING = "select * from foo"; private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); From 530a5c5b72906c566d4c3e755274966cc3db3595 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 26 Oct 2017 10:35:21 -0700 Subject: [PATCH 272/742] Improve comment --- .../oss/driver/internal/core/control/ControlConnection.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 4bfbf776e31..dc82b2fcd2e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -363,10 +363,11 @@ private void onSuccessfulReconnect() { LOG.debug("[{}] Error while refreshing node list", logPrefix, error); } else { try { - // This does nothing if the LBP is initialized already + // A failed node list refresh at startup is not fatal, so this might be the + // first successful refresh; make sure the LBP gets initialized (this is a no-op + // if it was initialized already). context.loadBalancingPolicyWrapper().init(); context.metadataManager().refreshSchema(null, false, true); - // TODO avoid refreshing the token map twice } catch (Throwable t) { Loggers.warnWithException( LOG, "[{}] Unexpected error on control connection reconnect", logPrefix, t); From 4c6b5acf2408b32c7d6b27e4e7b6dd4561c54fa0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 2 Oct 2017 13:03:45 -0700 Subject: [PATCH 273/742] Remove DriverOption#concat Since we're moving away from the "chainable policy" pattern for load balancing policies, relative options are not necessary anymore. --- .../PassThroughAddressTranslator.java | 5 +- .../api/core/auth/PlainTextAuthProvider.java | 11 ++-- .../api/core/config/CoreDriverOption.java | 49 +++++++++-------- .../driver/api/core/config/DriverOption.java | 44 --------------- .../ExponentialReconnectionPolicy.java | 22 ++++---- .../RoundRobinLoadBalancingPolicy.java | 5 +- .../api/core/retry/DefaultRetryPolicy.java | 5 +- .../ConstantSpeculativeExecutionPolicy.java | 10 ++-- .../specex/NoSpeculativeExecutionPolicy.java | 5 +- .../api/core/ssl/DefaultSslEngineFactory.java | 9 ++-- .../core/time/AtomicTimestampGenerator.java | 9 ++-- .../time/MonotonicTimestampGenerator.java | 31 ++++++----- .../time/ServerSideTimestampGenerator.java | 5 +- .../time/ThreadLocalTimestampGenerator.java | 9 ++-- .../core/channel/DefaultWriteCoalescer.java | 13 ++--- .../channel/PassThroughWriteCoalescer.java | 5 +- .../core/context/DefaultDriverContext.java | 54 ++++++++++--------- .../internal/core/protocol/Lz4Compressor.java | 3 +- .../core/protocol/SnappyCompressor.java | 5 +- .../driver/internal/core/util/Reflection.java | 19 +++---- .../RoundRobinLoadBalancingPolicyTest.java | 4 +- .../core/retry/DefaultRetryPolicyTest.java | 2 +- ...onstantSpeculativeExecutionPolicyTest.java | 20 ++----- .../time/AtomicTimestampGeneratorTest.java | 3 +- .../MonotonicTimestampGeneratorTestBase.java | 25 ++++----- .../ThreadLocalTimestampGeneratorTest.java | 4 +- .../core/channel/ChannelFactoryTestBase.java | 6 +-- .../control/ControlConnectionTestBase.java | 5 +- .../metadata/DefaultTopologyMonitorTest.java | 4 +- .../metadata/SchemaAgreementCheckerTest.java | 4 +- .../api/core/connection/FrameLengthIT.java | 4 +- manual/core/configuration/README.md | 9 ++-- .../SortingLoadBalancingPolicy.java | 2 +- 33 files changed, 147 insertions(+), 263 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java index 97f3e59691d..ca66d79f1e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/addresstranslation/PassThroughAddressTranslator.java @@ -15,16 +15,13 @@ */ package com.datastax.oss.driver.api.core.addresstranslation; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; /** An address translator that always returns the same address unchanged. */ public class PassThroughAddressTranslator implements AddressTranslator { - public PassThroughAddressTranslator( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public PassThroughAddressTranslator(@SuppressWarnings("unused") DriverContext context) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index 486a54f27f4..ce6cfbd4bbf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Charsets; import java.net.SocketAddress; @@ -45,20 +44,16 @@ public class PlainTextAuthProvider implements AuthProvider { private final DriverConfigProfile config; - private final DriverOption configRoot; /** Builds a new instance. */ - public PlainTextAuthProvider(DriverContext context, DriverOption configRoot) { + public PlainTextAuthProvider(DriverContext context) { this.config = context.config().getDefaultProfile(); - this.configRoot = configRoot; } @Override public Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) { - String username = - config.getString(configRoot.concat(CoreDriverOption.RELATIVE_PLAIN_TEXT_AUTH_USERNAME)); - String password = - config.getString(configRoot.concat(CoreDriverOption.RELATIVE_PLAIN_TEXT_AUTH_PASSWORD)); + String username = config.getString(CoreDriverOption.AUTH_PROVIDER_USER_NAME); + String password = config.getString(CoreDriverOption.AUTH_PROVIDER_PASSWORD); return new PlainTextAuthenticator(username, password); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 57128174a3a..0ce2000bfa3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -25,7 +25,7 @@ public enum CoreDriverOption implements DriverOption { PROTOCOL_VERSION("protocol.version", false), PROTOCOL_MAX_FRAME_LENGTH("protocol.max-frame-length", true), - PROTOCOL_COMPRESSOR("protocol.compressor", false), + PROTOCOL_COMPRESSOR_CLASS("protocol.compressor.class", false), CLUSTER_NAME("cluster-name", false), CONFIG_RELOAD_INTERVAL("config-reload-interval", false), @@ -45,10 +45,10 @@ public enum CoreDriverOption implements DriverOption { REQUEST_SERIAL_CONSISTENCY("request.serial-consistency", true), REQUEST_WARN_IF_SET_KEYSPACE("request.warn-if-set-keyspace", true), REQUEST_DEFAULT_IDEMPOTENCE("request.default-idempotence", true), - RETRY_POLICY_ROOT("request.retry-policy", true), - SPECULATIVE_EXECUTION_POLICY_ROOT("request.speculative-execution-policy", true), - RELATIVE_SPECULATIVE_EXECUTION_MAX("max-executions", false), - RELATIVE_SPECULATIVE_EXECUTION_DELAY("delay", false), + RETRY_POLICY_CLASS("request.retry-policy.class", true), + SPECULATIVE_EXECUTION_POLICY_CLASS("request.speculative-execution-policy.class", true), + SPECULATIVE_EXECUTION_MAX("request.speculative-execution-policy.max-executions", false), + SPECULATIVE_EXECUTION_DELAY("request.speculative-execution-policy.delay", false), REQUEST_TRACE_ATTEMPTS("request.trace.attempts", true), REQUEST_TRACE_INTERVAL("request.trace.interval", true), REQUEST_TRACE_CONSISTENCY("request.trace.consistency", true), @@ -61,18 +61,15 @@ public enum CoreDriverOption implements DriverOption { CONTROL_CONNECTION_AGREEMENT_WARN( "connection.control-connection.schema-agreement.warn-on-failure", true), - COALESCER_ROOT("connection.coalescer", true), - RELATIVE_COALESCER_MAX_RUNS("max-runs-with-no-work", false), - RELATIVE_COALESCER_INTERVAL("reschedule-interval", false), + COALESCER_CLASS("connection.coalescer.class", true), + COALESCER_MAX_RUNS("connection.coalescer.max-runs-with-no-work", false), + COALESCER_INTERVAL("connection.coalescer.reschedule-interval", false), - // "Sub-option" for all the policies, etc. - RELATIVE_POLICY_CLASS("class", false), + LOAD_BALANCING_POLICY_CLASS("load-balancing-policy.class", true), - LOAD_BALANCING_POLICY_ROOT("load-balancing-policy", true), - - RECONNECTION_POLICY_ROOT("connection.reconnection-policy", true), - RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY("base-delay", false), - RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY("max-delay", false), + RECONNECTION_POLICY_CLASS("connection.reconnection-policy.class", true), + RECONNECTION_BASE_DELAY("connection.reconnection-policy.base-delay", false), + RECONNECTION_MAX_DELAY("connection.reconnection-policy.max-delay", false), PREPARE_ON_ALL_NODES("prepared-statements.prepare-on-all-nodes", true), REPREPARE_ENABLED("prepared-statements.reprepare-on-up.enabled", true), @@ -81,14 +78,14 @@ public enum CoreDriverOption implements DriverOption { REPREPARE_MAX_PARALLELISM("prepared-statements.reprepare-on-up.max-parallelism", false), REPREPARE_TIMEOUT("prepared-statements.reprepare-on-up.timeout", false), - ADDRESS_TRANSLATOR_ROOT("address-translator", true), + ADDRESS_TRANSLATOR_CLASS("address-translator.class", true), - AUTH_PROVIDER_ROOT("protocol.auth-provider", false), - RELATIVE_PLAIN_TEXT_AUTH_USERNAME("username", false), - RELATIVE_PLAIN_TEXT_AUTH_PASSWORD("password", false), + AUTH_PROVIDER_CLASS("protocol.auth-provider.class", false), + AUTH_PROVIDER_USER_NAME("protocol.auth-provider.username", false), + AUTH_PROVIDER_PASSWORD("protocol.auth-provider.password", false), - SSL_ENGINE_FACTORY_ROOT("ssl-engine-factory", false), - RELATIVE_DEFAULT_SSL_CIPHER_SUITES("cipher-suites", false), + SSL_ENGINE_FACTORY_CLASS("ssl-engine-factory.class", false), + SSL_CIPHER_SUITES("ssl-engine-factory.cipher-suites", false), METADATA_TOPOLOGY_WINDOW("metadata.topology-event-debouncer.window", true), METADATA_TOPOLOGY_MAX_EVENTS("metadata.topology-event-debouncer.max-events", true), @@ -100,10 +97,12 @@ public enum CoreDriverOption implements DriverOption { METADATA_SCHEMA_MAX_EVENTS("metadata.schema.debouncer.max-events", true), METADATA_TOKEN_MAP_ENABLED("metadata.token-map.enabled", true), - TIMESTAMP_GENERATOR_ROOT("request.timestamp-generator", true), - RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("force-java-clock", false), - RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD("drift-warning.threshold", false), - RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL("drift-warning.interval", false), + TIMESTAMP_GENERATOR_CLASS("request.timestamp-generator.class", true), + TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("request.timestamp-generator.force-java-clock", false), + TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD( + "request.timestamp-generator.drift-warning.threshold", false), + TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL( + "request.timestamp-generator.drift-warning.interval", false), NETTY_IO_SIZE("netty.io-group.size", false), NETTY_IO_SHUTDOWN_QUIET_PERIOD("netty.io-group.shutdown.quiet-period", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java index 5df573c263f..c41f15e444d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverOption.java @@ -20,48 +20,4 @@ public interface DriverOption { String getPath(); boolean required(); - - /** - * Concatenates two options to build a longer path. - * - *

          This is intended for options that can appear at different levels, for example arguments of - * policies that can be nested. - */ - default DriverOption concat(DriverOption child) { - DriverOption parent = this; - // Not particularly efficient, but this will mainly be used for policies, which initialize at - // startup, so it should be good enough. - return new DriverOption() { - @Override - public String getPath() { - return parent.getPath() + "." + child.getPath(); - } - - @Override - public boolean required() { - // This property is only for initial validation of the configuration, and we can't validate - // nested fields because they are by definition not known in advance. - return false; - } - - // Only needed for unit tests: comparing options has no meaningful purpose, but it is needed - // to mock them - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } else if (other instanceof DriverOption) { - DriverOption that = (DriverOption) other; - return this.getPath().equals(that.getPath()); - } else { - return false; - } - } - - @Override - public int hashCode() { - return getPath().hashCode(); - } - }; - } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index 270f4e4121b..c65688706e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; import java.time.Duration; @@ -33,28 +32,27 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { private final long maxAttempts; /** Builds a new instance. */ - public ExponentialReconnectionPolicy(DriverContext context, DriverOption configRoot) { + public ExponentialReconnectionPolicy(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - DriverOption baseDelayOption = - configRoot.concat(CoreDriverOption.RELATIVE_EXPONENTIAL_RECONNECTION_BASE_DELAY); - DriverOption maxDelayOption = - configRoot.concat(CoreDriverOption.RELATIVE_EXPONENTIAL_RECONNECTION_MAX_DELAY); - this.baseDelayMs = config.getDuration(baseDelayOption).toMillis(); - this.maxDelayMs = config.getDuration(maxDelayOption).toMillis(); + this.baseDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_BASE_DELAY).toMillis(); + this.maxDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_MAX_DELAY).toMillis(); Preconditions.checkArgument( baseDelayMs > 0, "%s must be strictly positive (got %s)", - baseDelayOption.getPath(), + CoreDriverOption.RECONNECTION_BASE_DELAY.getPath(), baseDelayMs); Preconditions.checkArgument( - maxDelayMs >= 0, "%s must be positive (got %s)", maxDelayOption.getPath(), maxDelayMs); + maxDelayMs >= 0, + "%s must be positive (got %s)", + CoreDriverOption.RECONNECTION_MAX_DELAY.getPath(), + maxDelayMs); Preconditions.checkArgument( maxDelayMs >= baseDelayMs, "%s must be bigger than %s (got %s, %s)", - maxDelayOption.getPath(), - baseDelayOption.getPath(), + CoreDriverOption.RECONNECTION_MAX_DELAY.getPath(), + CoreDriverOption.RECONNECTION_BASE_DELAY.getPath(), maxDelayMs, baseDelayMs); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java index 80809c94f53..b12dce6fc8c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -44,9 +43,7 @@ public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); private volatile DistanceReporter distanceReporter; - public RoundRobinLoadBalancingPolicy( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public RoundRobinLoadBalancingPolicy(DriverContext context) { this.logPrefix = context.clusterName(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index d12f6fb3e14..80591c4b376 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core.retry; import com.datastax.oss.driver.api.core.ConsistencyLevel; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -32,9 +31,7 @@ */ public class DefaultRetryPolicy implements RetryPolicy { - public DefaultRetryPolicy( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public DefaultRetryPolicy(@SuppressWarnings("unused") DriverContext context) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java index 2fcff6a62a7..3d7050e2be7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; @@ -34,17 +33,14 @@ public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionP private final int maxExecutions; private final long constantDelayMillis; - public ConstantSpeculativeExecutionPolicy(DriverContext context, DriverOption configRoot) { + public ConstantSpeculativeExecutionPolicy(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - this.maxExecutions = - config.getInt(configRoot.concat(CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_MAX)); + this.maxExecutions = config.getInt(CoreDriverOption.SPECULATIVE_EXECUTION_MAX); if (this.maxExecutions < 1) { throw new IllegalArgumentException("Max must be at least 1"); } this.constantDelayMillis = - config - .getDuration(configRoot.concat(CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_DELAY)) - .toMillis(); + config.getDuration(CoreDriverOption.SPECULATIVE_EXECUTION_DELAY).toMillis(); if (this.constantDelayMillis < 0) { throw new IllegalArgumentException("Delay must be positive or 0"); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java index 6a5f102a2a7..6b277de7a25 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -16,16 +16,13 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; /** A policy that never triggers speculative executions. */ public class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { - public NoSpeculativeExecutionPolicy( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public NoSpeculativeExecutionPolicy(@SuppressWarnings("unused") DriverContext context) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java index 921964d8b74..c6444ff0ec0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -49,17 +48,15 @@ public class DefaultSslEngineFactory implements SslEngineFactory { private final String[] cipherSuites; /** Builds a new instance from the driver configuration. */ - public DefaultSslEngineFactory(DriverContext driverContext, DriverOption configRoot) { + public DefaultSslEngineFactory(DriverContext driverContext) { try { this.sslContext = SSLContext.getDefault(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Cannot initialize SSL Context", e); } DriverConfigProfile config = driverContext.config().getDefaultProfile(); - DriverOption cipherSuiteOption = - configRoot.concat(CoreDriverOption.RELATIVE_DEFAULT_SSL_CIPHER_SUITES); - if (config.isDefined(cipherSuiteOption)) { - List list = config.getStringList(cipherSuiteOption); + if (config.isDefined(CoreDriverOption.SSL_CIPHER_SUITES)) { + List list = config.getStringList(CoreDriverOption.SSL_CIPHER_SUITES); String tmp[] = new String[list.size()]; this.cipherSuites = list.toArray(tmp); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java index 57509efcc1b..f76756d00f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGenerator.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.time.Clock; @@ -30,13 +29,13 @@ public class AtomicTimestampGenerator extends MonotonicTimestampGenerator { private AtomicLong lastRef = new AtomicLong(0); - public AtomicTimestampGenerator(DriverContext context, DriverOption configRoot) { - super(context, configRoot); + public AtomicTimestampGenerator(DriverContext context) { + super(context); } @VisibleForTesting - AtomicTimestampGenerator(Clock clock, InternalDriverContext context, DriverOption configRoot) { - super(clock, context, configRoot); + AtomicTimestampGenerator(Clock clock, InternalDriverContext context) { + super(clock, context); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java index ddeb8d3ce20..8cafeded3ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.time.Clock; import com.google.common.annotations.VisibleForTesting; @@ -38,29 +37,30 @@ abstract class MonotonicTimestampGenerator implements TimestampGenerator { private final long warningIntervalMillis; private final AtomicLong lastDriftWarning = new AtomicLong(Long.MIN_VALUE); - protected MonotonicTimestampGenerator(DriverContext context, DriverOption configRoot) { - this(buildClock(context, configRoot), context, configRoot); + protected MonotonicTimestampGenerator(DriverContext context) { + this(buildClock(context), context); } @VisibleForTesting - protected MonotonicTimestampGenerator( - Clock clock, DriverContext context, DriverOption configRoot) { + protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { this.clock = clock; - DriverOption warningThresholdOption = - configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD); DriverConfigProfile config = context.config().getDefaultProfile(); this.warningThresholdMicros = - (config.isDefined(warningThresholdOption)) - ? config.getDuration(warningThresholdOption).toNanos() / 1000 + (config.isDefined(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + ? config + .getDuration(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD) + .toNanos() + / 1000 : 0; if (this.warningThresholdMicros == 0) { this.warningIntervalMillis = 0; } else { - DriverOption warningIntervalOption = - configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL); - this.warningIntervalMillis = config.getDuration(warningIntervalOption).toMillis(); + this.warningIntervalMillis = + config + .getDuration(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL) + .toMillis(); } } @@ -97,12 +97,11 @@ private void maybeLog(long currentTick, long last) { } } - private static Clock buildClock(DriverContext context, DriverOption configRoot) { - DriverOption forceJavaClockOption = - configRoot.concat(CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); + private static Clock buildClock(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); boolean forceJavaClock = - config.isDefined(forceJavaClockOption) && config.getBoolean(forceJavaClockOption); + config.isDefined(CoreDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK) + && config.getBoolean(CoreDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); return Clock.getInstance(forceJavaClock); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java index 28bf89a8c63..207601c7114 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/ServerSideTimestampGenerator.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; /** @@ -24,9 +23,7 @@ */ public class ServerSideTimestampGenerator implements TimestampGenerator { - public ServerSideTimestampGenerator( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public ServerSideTimestampGenerator(@SuppressWarnings("unused") DriverContext context) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java index 665340ff14e..f6796d77faa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGenerator.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.time.Clock; import com.google.common.annotations.VisibleForTesting; @@ -32,13 +31,13 @@ public class ThreadLocalTimestampGenerator extends MonotonicTimestampGenerator { private final ThreadLocal lastRef = ThreadLocal.withInitial(() -> 0L); - public ThreadLocalTimestampGenerator(DriverContext context, DriverOption configRoot) { - super(context, configRoot); + public ThreadLocalTimestampGenerator(DriverContext context) { + super(context); } @VisibleForTesting - ThreadLocalTimestampGenerator(Clock clock, DriverContext context, DriverOption configRoot) { - super(clock, context, configRoot); + ThreadLocalTimestampGenerator(Clock clock, DriverContext context) { + super(clock, context); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index 9cc5e296938..e2f21d50411 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -52,17 +51,11 @@ public class DefaultWriteCoalescer implements WriteCoalescer { private final long rescheduleIntervalNanos; private final ConcurrentMap flushers = new ConcurrentHashMap<>(); - public DefaultWriteCoalescer( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { - + public DefaultWriteCoalescer(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - this.maxRunsWithNoWork = - config.getInt(configRoot.concat(CoreDriverOption.RELATIVE_COALESCER_MAX_RUNS)); + this.maxRunsWithNoWork = config.getInt(CoreDriverOption.COALESCER_MAX_RUNS); this.rescheduleIntervalNanos = - config - .getDuration(configRoot.concat(CoreDriverOption.RELATIVE_COALESCER_INTERVAL)) - .toNanos(); + config.getDuration(CoreDriverOption.COALESCER_INTERVAL).toNanos(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java index 22956bac894..61415e87cb1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/PassThroughWriteCoalescer.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -23,9 +22,7 @@ /** No-op implementation of the write coalescer: each write is flushed immediately. */ public class PassThroughWriteCoalescer implements WriteCoalescer { - public PassThroughWriteCoalescer( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public PassThroughWriteCoalescer(@SuppressWarnings("unused") DriverContext context) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 9ad831b020e..45b57da9a5e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -156,65 +156,69 @@ public DefaultDriverContext(DriverConfigLoader configLoader, List> } protected LoadBalancingPolicy buildLoadBalancingPolicy() { - DriverOption rootOption = CoreDriverOption.LOAD_BALANCING_POLICY_ROOT; - return Reflection.buildFromConfig(this, rootOption, LoadBalancingPolicy.class) + return Reflection.buildFromConfig( + this, CoreDriverOption.LOAD_BALANCING_POLICY_CLASS, LoadBalancingPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing load balancing policy, check your configuration (%s)", - rootOption))); + (DriverOption) CoreDriverOption.LOAD_BALANCING_POLICY_CLASS))); } protected ReconnectionPolicy buildReconnectionPolicy() { - CoreDriverOption rootOption = CoreDriverOption.RECONNECTION_POLICY_ROOT; - return Reflection.buildFromConfig(this, rootOption, ReconnectionPolicy.class) + return Reflection.buildFromConfig( + this, CoreDriverOption.RECONNECTION_POLICY_CLASS, ReconnectionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing reconnection policy, check your configuration (%s)", rootOption))); + "Missing reconnection policy, check your configuration (%s)", + CoreDriverOption.RECONNECTION_POLICY_CLASS))); } protected RetryPolicy buildRetryPolicy() { - CoreDriverOption rootOption = CoreDriverOption.RETRY_POLICY_ROOT; - return Reflection.buildFromConfig(this, rootOption, RetryPolicy.class) + return Reflection.buildFromConfig(this, CoreDriverOption.RETRY_POLICY_CLASS, RetryPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing retry policy, check your configuration (%s)", rootOption))); + "Missing retry policy, check your configuration (%s)", + CoreDriverOption.RETRY_POLICY_CLASS))); } protected SpeculativeExecutionPolicy buildSpeculativeExecutionPolicy() { - CoreDriverOption rootOption = CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT; - return Reflection.buildFromConfig(this, rootOption, SpeculativeExecutionPolicy.class) + return Reflection.buildFromConfig( + this, + CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + SpeculativeExecutionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing speculative execution policy, check your configuration (%s)", - rootOption))); + CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS))); } protected AddressTranslator buildAddressTranslator() { - CoreDriverOption rootOption = CoreDriverOption.ADDRESS_TRANSLATOR_ROOT; - return Reflection.buildFromConfig(this, rootOption, AddressTranslator.class) + return Reflection.buildFromConfig( + this, CoreDriverOption.ADDRESS_TRANSLATOR_CLASS, AddressTranslator.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing address translator, check your configuration (%s)", rootOption))); + "Missing address translator, check your configuration (%s)", + CoreDriverOption.ADDRESS_TRANSLATOR_CLASS))); } protected Optional buildAuthProvider() { return Reflection.buildFromConfig( - this, CoreDriverOption.AUTH_PROVIDER_ROOT, AuthProvider.class); + this, CoreDriverOption.AUTH_PROVIDER_CLASS, AuthProvider.class); } protected Optional buildSslEngineFactory() { return Reflection.buildFromConfig( - this, CoreDriverOption.SSL_ENGINE_FACTORY_ROOT, SslEngineFactory.class); + this, CoreDriverOption.SSL_ENGINE_FACTORY_CLASS, SslEngineFactory.class); } protected EventBus buildEventBus() { @@ -224,7 +228,8 @@ protected EventBus buildEventBus() { @SuppressWarnings("unchecked") protected Compressor buildCompressor() { return (Compressor) - Reflection.buildFromConfig(this, CoreDriverOption.PROTOCOL_COMPRESSOR, Compressor.class) + Reflection.buildFromConfig( + this, CoreDriverOption.PROTOCOL_COMPRESSOR_CLASS, Compressor.class) .orElse(Compressor.none()); } @@ -250,13 +255,13 @@ protected Optional buildSslHandlerFactory() { } protected WriteCoalescer buildWriteCoalescer() { - CoreDriverOption rootOption = CoreDriverOption.COALESCER_ROOT; - return Reflection.buildFromConfig(this, rootOption, WriteCoalescer.class) + return Reflection.buildFromConfig(this, CoreDriverOption.COALESCER_CLASS, WriteCoalescer.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing write coalescer, check your configuration (%s)", rootOption))); + "Missing write coalescer, check your configuration (%s)", + CoreDriverOption.COALESCER_CLASS))); } protected ChannelFactory buildChannelFactory() { @@ -285,13 +290,14 @@ protected CodecRegistry buildCodecRegistry(String logPrefix, List> } protected TimestampGenerator buildTimestampGenerator() { - CoreDriverOption rootOption = CoreDriverOption.TIMESTAMP_GENERATOR_ROOT; - return Reflection.buildFromConfig(this, rootOption, TimestampGenerator.class) + return Reflection.buildFromConfig( + this, CoreDriverOption.TIMESTAMP_GENERATOR_CLASS, TimestampGenerator.class) .orElseThrow( () -> new IllegalArgumentException( String.format( - "Missing timestamp generator, check your configuration (%s)", rootOption))); + "Missing timestamp generator, check your configuration (%s)", + CoreDriverOption.TIMESTAMP_GENERATOR_CLASS))); } protected SchemaQueriesFactory buildSchemaQueriesFactory() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java index b0533402e8e..8cc9b99f4a2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.protocol; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.buffer.ByteBuf; import java.nio.ByteBuffer; @@ -32,7 +31,7 @@ public class Lz4Compressor extends ByteBufCompressor { private final LZ4Compressor compressor; private final LZ4FastDecompressor decompressor; - public Lz4Compressor(DriverContext context, @SuppressWarnings("unused") DriverOption configRoot) { + public Lz4Compressor(DriverContext context) { try { LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); LOG.info("[{}] Using {}", context.clusterName(), lz4Factory.toString()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java index e1219ea77c8..4b3a5c64b31 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/SnappyCompressor.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.protocol; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.buffer.ByteBuf; import java.io.IOException; @@ -24,9 +23,7 @@ public class SnappyCompressor extends ByteBufCompressor { - public SnappyCompressor( - @SuppressWarnings("unused") DriverContext context, - @SuppressWarnings("unused") DriverOption configRoot) { + public SnappyCompressor(@SuppressWarnings("unused") DriverContext context) { try { Snappy.getNativeLibraryVersion(); } catch (NoClassDefFoundError e) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index bd7ac762e52..cd137149503 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.util; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -66,18 +65,17 @@ public static Class loadClass(String className, String source) { * above). * * @param context the driver context. - * @param rootOption the root of the set of options that configures the class. It will be looked - * up in the default profile of the configuration stored in the context. + * @param classNameOption the option that indicates the class. It will be looked up in the default + * profile of the configuration stored in the context. * @param expectedSuperType a super-type that the class is expected to implement/extend. * @return the new instance, or empty if {@code rootOption} or the class sub-option is not defined * in the configuration. */ public static Optional buildFromConfig( - DriverContext context, DriverOption rootOption, Class expectedSuperType) { + DriverContext context, DriverOption classNameOption, Class expectedSuperType) { DriverConfigProfile config = context.config().getDefaultProfile(); - DriverOption classNameOption = rootOption.concat(CoreDriverOption.RELATIVE_POLICY_CLASS); if (!config.isDefined(classNameOption)) { return Optional.empty(); } @@ -94,19 +92,16 @@ public static Optional buildFromConfig( Constructor constructor; try { - constructor = clazz.getConstructor(DriverContext.class, DriverOption.class); + constructor = clazz.getConstructor(DriverContext.class); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format( "Expected class %s (specified by %s) " - + "to have an accessible constructor with arguments (%s, %s)", - className, - configPath, - DriverContext.class.getSimpleName(), - DriverOption.class.getSimpleName())); + + "to have an accessible constructor with argument (%s)", + className, configPath, DriverContext.class.getSimpleName())); } try { - Object instance = constructor.newInstance(context, rootOption); + Object instance = constructor.newInstance(context); return Optional.of(expectedSuperType.cast(instance)); } catch (Exception e) { // ITE just wraps an exception thrown by the constructor, get rid of it: diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java index 88464fccb56..717ec57816b 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; import com.datastax.oss.driver.api.core.metadata.Node; @@ -44,8 +43,7 @@ public void setup() { Mockito.when(node2.getState()).thenReturn(NodeState.UP); Mockito.when(node3.getState()).thenReturn(NodeState.UP); - policy = - new RoundRobinLoadBalancingPolicy(context, CoreDriverOption.LOAD_BALANCING_POLICY_ROOT); + policy = new RoundRobinLoadBalancingPolicy(context); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index d9e3517001c..65e936db338 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -34,7 +34,7 @@ public class DefaultRetryPolicyTest extends RetryPolicyTestBase { public DefaultRetryPolicyTest() { - super(new DefaultRetryPolicy(null, null)); + super(new DefaultRetryPolicy(null)); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index e2f67417e15..69e1abb410b 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -44,38 +44,28 @@ public void setup() { } private void mockOptions(int maxExecutions, long constantDelayMillis) { - Mockito.when( - defaultProfile.getInt( - CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT.concat( - CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_MAX))) + Mockito.when(defaultProfile.getInt(CoreDriverOption.SPECULATIVE_EXECUTION_MAX)) .thenReturn(maxExecutions); - Mockito.when( - defaultProfile.getDuration( - CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT.concat( - CoreDriverOption.RELATIVE_SPECULATIVE_EXECUTION_DELAY))) + Mockito.when(defaultProfile.getDuration(CoreDriverOption.SPECULATIVE_EXECUTION_DELAY)) .thenReturn(Duration.ofMillis(constantDelayMillis)); } @Test(expected = IllegalArgumentException.class) public void should_fail_if_delay_negative() { mockOptions(1, -10); - new ConstantSpeculativeExecutionPolicy( - context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + new ConstantSpeculativeExecutionPolicy(context); } @Test(expected = IllegalArgumentException.class) public void should_fail_if_max_less_than_one() { mockOptions(0, 10); - new ConstantSpeculativeExecutionPolicy( - context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + new ConstantSpeculativeExecutionPolicy(context); } @Test public void should_return_delay_until_max() { mockOptions(3, 10); - SpeculativeExecutionPolicy policy = - new ConstantSpeculativeExecutionPolicy( - context, CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_ROOT); + SpeculativeExecutionPolicy policy = new ConstantSpeculativeExecutionPolicy(context); // Initial execution starts, schedule first speculative execution assertThat(policy.nextExecution(null, request, 1)).isEqualTo(10); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java index c97b8b4fbaa..ac91b8da502 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.internal.core.time.Clock; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; @@ -32,7 +31,7 @@ public class AtomicTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @Override protected MonotonicTimestampGenerator newInstance(Clock clock) { - return new AtomicTimestampGenerator(clock, context, CoreDriverOption.TIMESTAMP_GENERATOR_ROOT); + return new AtomicTimestampGenerator(clock, context); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java index ada9866dd58..a5d4aa77770 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -22,7 +22,6 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.time.Clock; import java.time.Duration; @@ -41,13 +40,6 @@ abstract class MonotonicTimestampGeneratorTestBase { - protected static final DriverOption WARNING_THRESHOLD_OPTION = - CoreDriverOption.TIMESTAMP_GENERATOR_ROOT.concat( - CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD); - protected static final DriverOption WARNING_INTERVAL_OPTION = - CoreDriverOption.TIMESTAMP_GENERATOR_ROOT.concat( - CoreDriverOption.RELATIVE_TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL); - @Mock protected Clock clock; @Mock protected InternalDriverContext context; @Mock private DriverConfig config; @@ -66,11 +58,18 @@ public void setup() { Mockito.when(context.config()).thenReturn(config); // Disable warnings by default - Mockito.when(defaultConfigProfile.isDefined(WARNING_THRESHOLD_OPTION)).thenReturn(true); - Mockito.when(defaultConfigProfile.getDuration(WARNING_THRESHOLD_OPTION)) + Mockito.when( + defaultConfigProfile.isDefined( + CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + .thenReturn(true); + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) .thenReturn(Duration.ofNanos(0)); // Actual value doesn't really matter since we only test the first warning - Mockito.when(defaultConfigProfile.getDuration(WARNING_INTERVAL_OPTION)) + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) .thenReturn(Duration.ofSeconds(10)); logger = (Logger) LoggerFactory.getLogger(MonotonicTimestampGenerator.class); @@ -112,7 +111,9 @@ public void should_increment_if_clock_does_not_increase() { @Test public void should_warn_if_timestamps_drift() { - Mockito.when(defaultConfigProfile.getDuration(WARNING_THRESHOLD_OPTION)) + Mockito.when( + defaultConfigProfile.getDuration( + CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) .thenReturn(Duration.ofNanos(2 * 1000)); Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java index a6981e40d35..6165d23d055 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.internal.core.time.Clock; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.List; @@ -35,8 +34,7 @@ public class ThreadLocalTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @Override protected MonotonicTimestampGenerator newInstance(Clock clock) { - return new ThreadLocalTimestampGenerator( - clock, context, CoreDriverOption.TIMESTAMP_GENERATOR_ROOT); + return new ThreadLocalTimestampGenerator(clock, context); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 721dedfe810..9dcaaa6c64c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -112,9 +112,7 @@ public void setup() throws InterruptedException { Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when( - defaultConfigProfile.isDefined( - CoreDriverOption.AUTH_PROVIDER_ROOT.concat(CoreDriverOption.RELATIVE_POLICY_CLASS))) + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTH_PROVIDER_CLASS)) .thenReturn(false); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); @@ -136,7 +134,7 @@ public void setup() throws InterruptedException { new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); Mockito.when(context.sslHandlerFactory()).thenReturn(Optional.empty()); Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.writeCoalescer()).thenReturn(new PassThroughWriteCoalescer(null, null)); + Mockito.when(context.writeCoalescer()).thenReturn(new PassThroughWriteCoalescer(null)); Mockito.when(context.compressor()).thenReturn(compressor); // Start local server diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 60dfd5d55b3..58f9e6a4529 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -106,9 +105,7 @@ public void setup() { .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(context.metadataManager()).thenReturn(metadataManager); - addressTranslator = - Mockito.spy( - new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); + addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); controlConnection = new ControlConnection(context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 127dbf8df09..c3f500b3a4b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -77,9 +77,7 @@ public void setup() { Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); - addressTranslator = - Mockito.spy( - new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); + addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); Mockito.when(channel.remoteAddress()).thenReturn(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index b4380d48dda..da645b5205c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -85,9 +85,7 @@ public void setup() { Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); - addressTranslator = - Mockito.spy( - new PassThroughAddressTranslator(context, CoreDriverOption.ADDRESS_TRANSLATOR_ROOT)); + addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); Map nodes = ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 016f6c5c448..770f452e759 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -102,8 +102,8 @@ public void should_fail_if_response_exceeds_max_frame_length() { * {@link AllNodesFailedException}). */ public static class AlwaysRetryAbortedPolicy extends DefaultRetryPolicy { - public AlwaysRetryAbortedPolicy(DriverContext context, DriverOption configRoot) { - super(context, configRoot); + public AlwaysRetryAbortedPolicy(DriverContext context) { + super(context); } @Override diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index dfa98a5b533..ba9ba219eb3 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -340,14 +340,11 @@ When the driver encounters such a declaration, it will load the class and look f with the following signature: ```java -ExponentialReconnectionPolicy(DriverContext context, DriverOption configRoot) +ExponentialReconnectionPolicy(DriverContext context) ``` -* `context` argument allows the policy to access other driver components (for example the - configuration); -* `configRoot` represents the root of the configuration element (`reconnection-policy` in the - example above), and can be used to build the path of the other options, in particular for nestable - policies that can appear at arbitrary paths in the configuration tree. +Where `context` is the object returned by `Cluster.getContext()`, which allows the policy to access +other driver components (for example the configuration). If you write custom policy implementations, you should follow that same pattern; it provides an elegant way to switch policies without having to recompile the application (if your policy needs diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index 06e925e85ba..d7bb54ec06c 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -29,7 +29,7 @@ public class SortingLoadBalancingPolicy implements LoadBalancingPolicy { @SuppressWarnings("unused") - public SortingLoadBalancingPolicy(DriverContext context, DriverOption option) { + public SortingLoadBalancingPolicy(DriverContext context) { // constructor needed for loading via config. } From 43e855ab8c60a952c1f56038b396c82185836006 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Oct 2017 10:54:36 -0700 Subject: [PATCH 274/742] Fix bug in DefaultSession If a node's distance was set to IGNORED but it was already IGNORED, the session would open a new pool. --- .../internal/core/session/DefaultSession.java | 2 +- .../core/session/DefaultSessionTest.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index fc9363c15bd..23c3d7d301c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -326,7 +326,7 @@ private void processDistanceEvent(DistanceEvent event) { NodeDistance newDistance = event.distance; if (pending.containsKey(node)) { pendingDistanceEvents.put(node, event); - } else if (newDistance == NodeDistance.IGNORED && pools.containsKey(node)) { + } else if (newDistance == NodeDistance.IGNORED) { ChannelPool pool = pools.remove(node); if (pool != null) { LOG.debug("[{}] {} became IGNORED, destroying pool", logPrefix, node); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java index 7aceff1ec83..d6240d99bd8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java @@ -365,6 +365,38 @@ public void should_remove_pool_if_node_becomes_ignored() { assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } + @Test + public void should_do_nothing_if_node_becomes_ignored_but_was_already_ignored() { + ChannelPool pool1 = mockPool(node1); + ChannelPool pool2 = mockPool(node2); + ChannelPool pool3 = mockPool(node3); + MockChannelPoolFactoryHelper factoryHelper = + MockChannelPoolFactoryHelper.builder(channelPoolFactory) + .success(node1, KEYSPACE, NodeDistance.LOCAL, pool1) + .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) + .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) + .build(); + + CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + + factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); + factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); + waitForPendingAdminTasks(); + assertThat(initFuture).isSuccess(); + + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); + Mockito.verify(pool2, timeout(100)).closeAsync(); + + Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); + assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); + + // Fire the same event again, nothing should happen + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); + waitForPendingAdminTasks(); + factoryHelper.verifyNoMoreCalls(); + } + @Test public void should_recreate_pool_if_node_becomes_not_ignored() { Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); From 873bf867fd293268326d6e68d38c1d7aa31c847c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 23 Oct 2017 16:15:28 -0700 Subject: [PATCH 275/742] Always report available stream ids --- .../core/channel/AvailableIdsHolder.java | 25 ----------- .../internal/core/channel/ChannelFactory.java | 30 ++----------- .../internal/core/channel/DriverChannel.java | 22 ++++------ .../core/channel/DriverChannelOptions.java | 14 +------ .../core/channel/InFlightHandler.java | 13 +----- .../core/channel/StreamIdGenerator.java | 10 ++++- .../internal/core/pool/ChannelPool.java | 1 - .../driver/internal/core/pool/ChannelSet.java | 2 +- .../ChannelFactoryAvailableIdsTest.java | 42 +++---------------- .../core/channel/ChannelFactoryTestBase.java | 2 - .../core/channel/DriverChannelTest.java | 3 +- .../core/channel/InFlightHandlerTest.java | 1 - .../core/channel/ProtocolInitHandlerTest.java | 1 - .../internal/core/pool/ChannelSetTest.java | 22 +++++----- 14 files changed, 41 insertions(+), 147 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java deleted file mode 100644 index a469243aeaa..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/AvailableIdsHolder.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.channel; - -/** - * Hack to expose the number of available ids on a channel. We want to access it on the {@link - * DriverChannel} instance, but it's updated from {@link InFlightHandler}, and we want volatile for - * efficiency. - */ -class AvailableIdsHolder { - volatile int value; -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index b85359b4c56..86344f341e5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -85,9 +85,6 @@ public CompletionStage connect( final SocketAddress address, DriverChannelOptions options) { CompletableFuture resultFuture = new CompletableFuture<>(); - AvailableIdsHolder availableIdsHolder = - options.reportAvailableIds ? new AvailableIdsHolder() : null; - ProtocolVersion currentVersion; boolean isNegotiating; List attemptedVersions = new CopyOnWriteArrayList<>(); @@ -99,21 +96,13 @@ public CompletionStage connect( isNegotiating = true; } - connect( - address, - options, - availableIdsHolder, - currentVersion, - isNegotiating, - attemptedVersions, - resultFuture); + connect(address, options, currentVersion, isNegotiating, attemptedVersions, resultFuture); return resultFuture; } private void connect( SocketAddress address, DriverChannelOptions options, - AvailableIdsHolder availableIdsHolder, final ProtocolVersion currentVersion, boolean isNegotiating, List attemptedVersions, @@ -126,8 +115,7 @@ private void connect( .group(nettyOptions.ioEventLoopGroup()) .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) - .handler( - initializer(address, currentVersion, options, availableIdsHolder, resultFuture)); + .handler(initializer(address, currentVersion, options, resultFuture)); nettyOptions.afterBootstrapInitialized(bootstrap); @@ -138,8 +126,7 @@ private void connect( if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); DriverChannel driverChannel = - new DriverChannel( - channel, context.writeCoalescer(), availableIdsHolder, currentVersion); + new DriverChannel(channel, context.writeCoalescer(), currentVersion); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -160,14 +147,7 @@ private void connect( "Failed to connect with protocol {}, retrying with {}", currentVersion, downgraded.get()); - connect( - address, - options, - availableIdsHolder, - downgraded.get(), - true, - attemptedVersions, - resultFuture); + connect(address, options, downgraded.get(), true, attemptedVersions, resultFuture); } else { resultFuture.completeExceptionally( UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); @@ -186,7 +166,6 @@ ChannelInitializer initializer( SocketAddress address, final ProtocolVersion protocolVersion, final DriverChannelOptions options, - AvailableIdsHolder availableIdsHolder, CompletableFuture resultFuture) { return new ChannelInitializer() { @Override @@ -211,7 +190,6 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), maxOrphanRequests, setKeyspaceTimeoutMillis, - availableIdsHolder, channel.newPromise(), options.eventCallback, options.ownerLogPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 42e76356218..da5e9a9e175 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -43,22 +43,16 @@ public class DriverChannel { static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); private final Channel channel; - private final ChannelFuture closeStartedFuture; + private final InFlightHandler inFlightHandler; private final WriteCoalescer writeCoalescer; - private final AvailableIdsHolder availableIdsHolder; private final ProtocolVersion protocolVersion; private final AtomicBoolean closing = new AtomicBoolean(); private final AtomicBoolean forceClosing = new AtomicBoolean(); - DriverChannel( - Channel channel, - WriteCoalescer writeCoalescer, - AvailableIdsHolder availableIdsHolder, - ProtocolVersion protocolVersion) { + DriverChannel(Channel channel, WriteCoalescer writeCoalescer, ProtocolVersion protocolVersion) { this.channel = channel; - this.closeStartedFuture = channel.pipeline().get(InFlightHandler.class).closeStartedFuture; + this.inFlightHandler = channel.pipeline().get(InFlightHandler.class); this.writeCoalescer = writeCoalescer; - this.availableIdsHolder = availableIdsHolder; this.protocolVersion = protocolVersion; } @@ -127,11 +121,11 @@ public String getClusterName() { /** * @return the number of available stream ids on the channel. This is used to weigh channels in - * the pool. Note that for performance reasons this is only maintained if the channel is part - * of a pool that has a size bigger than 1, otherwise it will always return -1. + * pools that have a size bigger than 1, in the load balancing policy, and for monitoring + * purposes. */ - public int availableIds() { - return (availableIdsHolder == null) ? -1 : availableIdsHolder.value; + public int getAvailableIds() { + return inFlightHandler.getAvailableIds(); } public EventLoop eventLoop() { @@ -186,7 +180,7 @@ public Future forceClose() { * #forceClose()} is called first, this future will never complete. */ public ChannelFuture closeStartedFuture() { - return this.closeStartedFuture; + return this.inFlightHandler.closeStartedFuture; } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java index 82c0f234c8b..2415f62cafe 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java @@ -32,9 +32,6 @@ public static Builder builder() { public final CqlIdentifier keyspace; - /** Whether {@link DriverChannel#availableIds()} should be maintained */ - public final boolean reportAvailableIds; - /** * What kind of protocol events to listen for. * @@ -48,12 +45,10 @@ public static Builder builder() { private DriverChannelOptions( CqlIdentifier keyspace, - boolean reportAvailableIds, List eventTypes, EventCallback eventCallback, String ownerLogPrefix) { this.keyspace = keyspace; - this.reportAvailableIds = reportAvailableIds; this.eventTypes = eventTypes; this.eventCallback = eventCallback; this.ownerLogPrefix = ownerLogPrefix; @@ -61,7 +56,6 @@ private DriverChannelOptions( public static class Builder { private CqlIdentifier keyspace = null; - private boolean reportAvailableIds = false; private List eventTypes = Collections.emptyList(); private EventCallback eventCallback = null; private String ownerLogPrefix = null; @@ -71,11 +65,6 @@ public Builder withKeyspace(CqlIdentifier keyspace) { return this; } - public Builder reportAvailableIds(boolean reportAvailableIds) { - this.reportAvailableIds = reportAvailableIds; - return this; - } - public Builder withEvents(List eventTypes, EventCallback eventCallback) { Preconditions.checkArgument(eventTypes != null && !eventTypes.isEmpty()); Preconditions.checkNotNull(eventCallback); @@ -90,8 +79,7 @@ public Builder withOwnerLogPrefix(String ownerLogPrefix) { } public DriverChannelOptions build() { - return new DriverChannelOptions( - keyspace, reportAvailableIds, eventTypes, eventCallback, ownerLogPrefix); + return new DriverChannelOptions(keyspace, eventTypes, eventCallback, ownerLogPrefix); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 59e3fed6bb9..4a34ec9254b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -51,7 +51,6 @@ public class InFlightHandler extends ChannelDuplexHandler { private final String ownerLogPrefix; private final BiMap inFlight; private final long setKeyspaceTimeoutMillis; - private final AvailableIdsHolder availableIdsHolder; private final EventCallback eventCallback; private final int maxOrphanStreamIds; private boolean closingGracefully; @@ -64,7 +63,6 @@ public class InFlightHandler extends ChannelDuplexHandler { StreamIdGenerator streamIds, int maxOrphanStreamIds, long setKeyspaceTimeoutMillis, - AvailableIdsHolder availableIdsHolder, ChannelPromise closeStartedFuture, EventCallback eventCallback, String ownerLogPrefix) { @@ -74,10 +72,8 @@ public class InFlightHandler extends ChannelDuplexHandler { this.closeStartedFuture = closeStartedFuture; this.ownerLogPrefix = ownerLogPrefix; this.logPrefix = ownerLogPrefix + "|connecting..."; - reportAvailableIds(); this.inFlight = HashBiMap.create(streamIds.getMaxAvailableIds()); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; - this.availableIdsHolder = availableIdsHolder; this.eventCallback = eventCallback; } @@ -128,8 +124,6 @@ private void write(ChannelHandlerContext ctx, RequestMessage message, ChannelPro return; } - reportAvailableIds(); - LOG.debug("[{}] Writing {} on stream id {}", logPrefix, message.responseCallback, streamId); Frame frame = Frame.forRequest( @@ -311,7 +305,6 @@ private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); ResponseCallback responseCallback = inFlight.remove(streamId); streamIds.release(streamId); - reportAvailableIds(); // If we're in the middle of an orderly close and this was the last request, actually close // the channel now if (closingGracefully && inFlight.isEmpty()) { @@ -340,10 +333,8 @@ private void abortAllInFlight(DriverException cause, ResponseCallback ignore) { // closing the channel } - private void reportAvailableIds() { - if (availableIdsHolder != null) { - availableIdsHolder.value = streamIds.getAvailableIds(); - } + int getAvailableIds() { + return streamIds.getAvailableIds(); } private class SetKeyspaceRequest extends ChannelHandlerRequest { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java index 0dc17a235cb..05a07d9f537 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java @@ -17,13 +17,19 @@ import java.util.BitSet; -/** Manages the set of stream ids used to distinguish multiplexed requests on a channel. */ +/** + * Manages the set of identifiers used to distinguish multiplexed requests on a channel. + * + *

          This class is not thread safe: calls to {@link #acquire()} and {@link #release(int)} must be + * properly synchronized (in practice this is done by only calling them from the I/O thread). + * However, {@link #getAvailableIds()} has volatile semantics. + */ class StreamIdGenerator { private final int maxAvailableIds; // unset = available, set = borrowed (note that this is the opposite of the 3.x implementation) private final BitSet ids; - private int availableIds; + private volatile int availableIds; StreamIdGenerator(int maxAvailableIds) { this.maxAvailableIds = maxAvailableIds; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 7300ef3aa9d..85ac81922ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -248,7 +248,6 @@ private CompletionStage addMissingChannels() { DriverChannelOptions options = DriverChannelOptions.builder() .withKeyspace(keyspaceName) - .reportAvailableIds(wantedCount > 1) .withOwnerLogPrefix(sessionLogPrefix) .build(); for (int i = 0; i < missing; i++) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java index e82241a05eb..6f6653f7289 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -85,7 +85,7 @@ DriverChannel next() { DriverChannel best = null; int bestScore = 0; for (DriverChannel channel : snapshot) { - int score = channel.availableIds(); + int score = channel.getAvailableIds(); if (score > bestScore) { bestScore = score; best = channel; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index efdf5b29bf2..c356305cf8d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -49,21 +49,20 @@ public void setup() throws InterruptedException { } @Test - public void should_report_available_ids_if_requested() { + public void should_report_available_ids() { // Given ChannelFactory factory = newChannelFactory(); // When CompletionStage channelFuture = - factory.connect( - SERVER_ADDRESS, DriverChannelOptions.builder().reportAvailableIds(true).build()); + factory.connect(SERVER_ADDRESS, DriverChannelOptions.builder().build()); completeSimpleChannelInit(); // Then assertThat(channelFuture) .isSuccess( channel -> { - assertThat(channel.availableIds()).isEqualTo(128); + assertThat(channel.getAvailableIds()).isEqualTo(128); // Write a request, should decrease the count Future writeFuture = @@ -71,43 +70,12 @@ public void should_report_available_ids_if_requested() { assertThat(writeFuture) .isSuccess( v -> { - assertThat(channel.availableIds()).isEqualTo(127); + assertThat(channel.getAvailableIds()).isEqualTo(127); // Complete the request, should increase again writeInboundFrame(readOutboundFrame(), Void.INSTANCE); Mockito.verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); - assertThat(channel.availableIds()).isEqualTo(128); - }); - }); - } - - @Test - public void should_not_report_available_ids_if_not_requested() { - // Given - ChannelFactory factory = newChannelFactory(); - - // When - CompletionStage channelFuture = - factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); - completeSimpleChannelInit(); - - // Then - assertThat(channelFuture) - .isSuccess( - channel -> { - assertThat(channel.availableIds()).isEqualTo(-1); - - // Write a request, complete it, count should never be updated - Future writeFuture = - channel.write(new Query("test"), false, Frame.NO_PAYLOAD, responseCallback); - assertThat(writeFuture) - .isSuccess( - v -> { - assertThat(channel.availableIds()).isEqualTo(-1); - - writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); - assertThat(channel.availableIds()).isEqualTo(-1); + assertThat(channel.getAvailableIds()).isEqualTo(128); }); }); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 9dcaaa6c64c..bc25095b5df 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -226,7 +226,6 @@ ChannelInitializer initializer( SocketAddress address, ProtocolVersion protocolVersion, DriverChannelOptions options, - AvailableIdsHolder availableIdsHolder, CompletableFuture resultFuture) { return new ChannelInitializer() { @Override @@ -247,7 +246,6 @@ protected void initChannel(Channel channel) throws Exception { new StreamIdGenerator(maxRequestsPerConnection), Integer.MAX_VALUE, setKeyspaceTimeoutMillis, - availableIdsHolder, channel.newPromise(), null, "test"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 50d65d315c2..6d3c4d2ee3a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -56,12 +56,11 @@ public void setup() { streamIds, Integer.MAX_VALUE, SET_KEYSPACE_TIMEOUT_MILLIS, - null, channel.newPromise(), null, "test")); writeCoalescer = new MockWriteCoalescer(); - driverChannel = new DriverChannel(channel, writeCoalescer, null, CoreProtocolVersion.V3); + driverChannel = new DriverChannel(channel, writeCoalescer, CoreProtocolVersion.V3); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 743ba20b827..81976872c93 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -512,7 +512,6 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { streamIds, MAX_ORPHAN_IDS, SET_KEYSPACE_TIMEOUT_MILLIS, - null, channel.newPromise(), eventCallback, "test")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index bba981812ac..b9690999ddf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -94,7 +94,6 @@ public void setup() { new StreamIdGenerator(100), Integer.MAX_VALUE, 100, - null, channel.newPromise(), null, "test")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java index d034f11882a..633c328c296 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java @@ -49,15 +49,15 @@ public void should_return_element_when_single() { // Then assertThat(set.size()).isEqualTo(1); assertThat(set.next()).isEqualTo(channel1); - Mockito.verify(channel1, never()).availableIds(); + Mockito.verify(channel1, never()).getAvailableIds(); } @Test public void should_return_most_available_when_multiple() { // Given - Mockito.when(channel1.availableIds()).thenReturn(2); - Mockito.when(channel2.availableIds()).thenReturn(12); - Mockito.when(channel3.availableIds()).thenReturn(8); + Mockito.when(channel1.getAvailableIds()).thenReturn(2); + Mockito.when(channel2.getAvailableIds()).thenReturn(12); + Mockito.when(channel3.getAvailableIds()).thenReturn(8); // When set.add(channel1); @@ -67,12 +67,12 @@ public void should_return_most_available_when_multiple() { // Then assertThat(set.size()).isEqualTo(3); assertThat(set.next()).isEqualTo(channel2); - Mockito.verify(channel1).availableIds(); - Mockito.verify(channel2).availableIds(); - Mockito.verify(channel3).availableIds(); + Mockito.verify(channel1).getAvailableIds(); + Mockito.verify(channel2).getAvailableIds(); + Mockito.verify(channel3).getAvailableIds(); // When - Mockito.when(channel1.availableIds()).thenReturn(15); + Mockito.when(channel1.getAvailableIds()).thenReturn(15); // Then assertThat(set.next()).isEqualTo(channel1); @@ -81,9 +81,9 @@ public void should_return_most_available_when_multiple() { @Test public void should_remove_channels() { // Given - Mockito.when(channel1.availableIds()).thenReturn(2); - Mockito.when(channel2.availableIds()).thenReturn(12); - Mockito.when(channel3.availableIds()).thenReturn(8); + Mockito.when(channel1.getAvailableIds()).thenReturn(2); + Mockito.when(channel2.getAvailableIds()).thenReturn(12); + Mockito.when(channel3.getAvailableIds()).thenReturn(8); set.add(channel1); set.add(channel2); From f7256a07d7a7ffdf64a5a39cc29fa4374ddc704e Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 25 Oct 2017 11:44:29 -0700 Subject: [PATCH 276/742] Default to 127.0.0.1 if no contact points are provided --- .../oss/driver/api/core/ClusterBuilder.java | 2 +- .../driver/internal/core/DefaultCluster.java | 1 + .../core/metadata/MetadataManager.java | 23 ++++++++++++------- core/src/main/resources/reference.conf | 3 ++- .../core/metadata/MetadataManagerTest.java | 16 +++++++++++++ manual/core/README.md | 3 +-- manual/core/configuration/README.md | 8 +------ 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java index 76651dc93c3..f639313b182 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java @@ -102,7 +102,7 @@ protected DriverConfigLoader defaultConfigLoader() { * correctly. * *

          Contact points can also be provided statically in the configuration. If both are specified, - * they will be merged. + * they will be merged. If both are absent, the driver will default to 127.0.0.1:9042. * *

          Contrary to the configuration, DNS names with multiple A-records will not be handled here. * If you need that, call {@link java.net.InetAddress#getAllByName(String)} before calling this diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index ae46796fa85..13e96c4b2fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -37,6 +37,7 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.ArrayList; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index d9b31fa532f..49ece98359d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.internal.core.util.concurrent.Debouncer; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.Collections; @@ -48,6 +49,9 @@ public class MetadataManager implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class); + public static final InetSocketAddress DEFAULT_CONTACT_POINT = + new InetSocketAddress("127.0.0.1", 9042); + private final InternalDriverContext context; private final String logPrefix; private final EventExecutor adminExecutor; @@ -103,16 +107,19 @@ public Metadata getMetadata() { return this.metadata; } - public CompletionStage addContactPoints(Set contactPoints) { - if (contactPoints == null || contactPoints.isEmpty()) { - return CompletableFuture.completedFuture(null); + public CompletionStage addContactPoints(Set providedContactPoints) { + Set contactPoints; + if (providedContactPoints == null || providedContactPoints.isEmpty()) { + LOG.info( + "[{}] No contact points provided, defaulting to {}", logPrefix, DEFAULT_CONTACT_POINT); + contactPoints = ImmutableSet.of(DEFAULT_CONTACT_POINT); } else { - LOG.debug("[{}] Adding initial contact points {}", logPrefix, contactPoints); - CompletableFuture initNodesFuture = new CompletableFuture<>(); - RunOrSchedule.on( - adminExecutor, () -> singleThreaded.initNodes(contactPoints, initNodesFuture)); - return initNodesFuture; + contactPoints = providedContactPoints; } + LOG.debug("[{}] Adding initial contact points {}", logPrefix, contactPoints); + CompletableFuture initNodesFuture = new CompletableFuture<>(); + RunOrSchedule.on(adminExecutor, () -> singleThreaded.initNodes(contactPoints, initNodesFuture)); + return initNodesFuture; } public CompletionStage refreshNodes() { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index bff7c04244e..83cddae86eb 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -26,7 +26,8 @@ datastax-java-driver { # same port. # # This option is not required. Contact points can also be provided programmatically when you build - # a cluster instance. If both are specified, they will be merged. + # a cluster instance. If both are specified, they will be merged. If both are absent, the driver + # will default to 127.0.0.1:9042. // contact-points = [ "127.0.0.1:9042", "127.0.0.2:9042" ] protocol { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index c1f03180804..af03857003f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -31,6 +31,7 @@ import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -111,6 +112,21 @@ public void should_add_contact_points() { assertThat(refresh.contactPoints).containsExactlyInAnyOrder(ADDRESS1); } + @Test + public void should_use_default_if_no_contact_points_provided() { + // When + CompletionStage addContactPointsFuture = + metadataManager.addContactPoints(Collections.emptySet()); + waitForPendingAdminTasks(); + + // Then + assertThat(addContactPointsFuture).isSuccess(); + assertThat(metadataManager.refreshes).hasSize(1); + InitContactPointsRefresh refresh = + ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); + assertThat(refresh.contactPoints).containsExactly(MetadataManager.DEFAULT_CONTACT_POINT); + } + @Test public void should_refresh_all_nodes() { // Given diff --git a/manual/core/README.md b/manual/core/README.md index 7a49eb17f1b..0701dc9e80d 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -16,8 +16,7 @@ following coordinates: Here's a short program that connects to Cassandra and executes a query: ```java -try (Cluster cluster = - Cluster.builder().addContactPoint(new InetSocketAddress("127.0.0.1", 9042)).build()) { // (1) +try (Cluster cluster = Cluster.builder().build()) { // (1) CqlSession session = cluster.connect(); // (2) diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index ba9ba219eb3..5da82c1e520 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -243,7 +243,6 @@ Finally, pass the config loader when building the driver: ```java Cluster cluster1 = Cluster.builder() - .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) .withConfigLoader(cluster1ConfigLoader) .build(); ``` @@ -268,11 +267,7 @@ DriverConfigLoader loader = }, CoreDriverOption.values()); -Cluster cluster = - Cluster.builder() - .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) - .withConfigLoader(loader) - .build(); +Cluster cluster = Cluster.builder().withConfigLoader(loader).build(); ``` #### Bypassing TypeSafe Config @@ -411,7 +406,6 @@ Pass the options to the config loader: ```java Cluster cluster = Cluster.builder() - .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) .withConfigLoader(new DefaultDriverConfigLoader( DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values(), // don't forget to keep the core options From a97e4843e6bf3519f5a833d18cc121774e9f90f2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 24 Oct 2017 22:23:25 -0700 Subject: [PATCH 277/742] JAVA-1526: Provide a single load balancing policy implementation --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 2 + .../driver/api/core/cql/BatchStatement.java | 6 + .../api/core/cql/BatchStatementBuilder.java | 3 + .../api/core/cql/BoundStatementBuilder.java | 4 +- .../driver/api/core/cql/PrepareRequest.java | 5 +- .../api/core/cql/PreparedStatement.java | 30 +- .../driver/api/core/cql/SimpleStatement.java | 15 +- .../api/core/cql/SimpleStatementBuilder.java | 4 + .../oss/driver/api/core/cql/Statement.java | 25 +- .../driver/api/core/cql/StatementBuilder.java | 25 +- .../loadbalancing/LoadBalancingPolicy.java | 24 +- .../RoundRobinLoadBalancingPolicy.java | 104 ------ .../oss/driver/api/core/session/Request.java | 63 +++- .../driver/internal/core/ContactPoints.java | 3 +- .../driver/internal/core/DefaultCluster.java | 7 +- .../driver/internal/core/cql/Conversions.java | 13 + .../core/cql/CqlPrepareHandlerBase.java | 2 +- .../core/cql/CqlRequestHandlerBase.java | 2 +- .../core/cql/DefaultBatchStatement.java | 120 ++++++- .../core/cql/DefaultBoundStatement.java | 146 ++++++++- .../core/cql/DefaultPrepareRequest.java | 21 +- .../core/cql/DefaultPreparedStatement.java | 15 +- .../core/cql/DefaultSimpleStatement.java | 132 +++++++- .../DefaultLoadBalancingPolicy.java | 302 ++++++++++++++++++ .../metadata/LoadBalancingPolicyWrapper.java | 26 +- .../core/metadata/MetadataManager.java | 6 + .../internal/core/pool/ChannelPool.java | 4 + .../driver/internal/core/pool/ChannelSet.java | 9 + .../core/session/RepreparePayload.java | 5 +- .../driver/internal/core/util/ArrayUtils.java | 56 ++++ core/src/main/resources/reference.conf | 26 +- .../RoundRobinLoadBalancingPolicyTest.java | 115 ------- .../core/cql/RequestHandlerTestHarness.java | 4 +- .../DefaultLoadBalancingPolicyEventsTest.java | 155 +++++++++ .../DefaultLoadBalancingPolicyInitTest.java | 143 +++++++++ ...faultLoadBalancingPolicyQueryPlanTest.java | 238 ++++++++++++++ .../DefaultLoadBalancingPolicyTestBase.java | 81 +++++ .../LoadBalancingPolicyWrapperTest.java | 28 +- .../internal/core/util/ArrayUtilsTest.java | 90 ++++++ .../DefaultLoadBalancingPolicyIT.java | 216 +++++++++++++ .../driver/api/core/metadata/NodeStateIT.java | 31 +- .../driver/api/core/session/KeyRequest.java | 19 +- .../src/test/resources/application.conf | 8 + .../driver/api/testinfra/ccm/CcmBridge.java | 12 +- .../SortingLoadBalancingPolicy.java | 13 +- 46 files changed, 2069 insertions(+), 290 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java delete mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java diff --git a/changelog/README.md b/changelog/README.md index cb7bc056230..366f498ece1 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [new feature] JAVA-1526: Provide a single load balancing policy implementation - [improvement] JAVA-1680: Improve error message on batch log write timeout - [improvement] JAVA-1675: Remove dates from copyright headers - [improvement] JAVA-1645: Don't log stack traces at WARN level diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 0ce2000bfa3..0bb0a7484b1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -66,6 +66,8 @@ public enum CoreDriverOption implements DriverOption { COALESCER_INTERVAL("connection.coalescer.reschedule-interval", false), LOAD_BALANCING_POLICY_CLASS("load-balancing-policy.class", true), + LOAD_BALANCING_LOCAL_DATACENTER("load-balancing-policy.local-datacenter", false), + LOAD_BALANCING_FILTER_CLASS("load-balancing-policy.filter.class", false), RECONNECTION_POLICY_CLASS("connection.reconnection-policy.class", true), RECONNECTION_BASE_DELAY("connection.reconnection-policy.base-delay", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 5faaf66b48a..55519c25383 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -39,6 +39,9 @@ static BatchStatement newInstance(BatchType batchType) { null, null, null, + null, + null, + null, Collections.emptyMap(), false, false, @@ -57,6 +60,9 @@ static BatchStatement newInstance(BatchType batchType, BatchableStatement... null, null, null, + null, + null, + null, Collections.emptyMap(), false, false, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index 861f2c4c566..c7e73274845 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -81,6 +81,9 @@ public BatchStatement build() { configProfileName, configProfile, null, + routingKeyspace, + routingKey, + routingToken, buildCustomPayload(), idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index 59b405db7c8..c13ff6265cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -22,7 +22,6 @@ import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; import com.datastax.oss.protocol.internal.ProtocolConstants; import java.nio.ByteBuffer; -import java.util.Collections; public class BoundStatementBuilder extends StatementBuilder implements Bindable { @@ -107,6 +106,9 @@ public BoundStatement build() { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, buildCustomPayload(), idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index d14c3e1cd8c..e994eebb8b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -38,7 +38,7 @@ public interface PrepareRequest extends Request { * *

          Most users won't use this explicitly. It is needed for the generic execute method ({@link * Session#execute(Request, GenericType)}), but CQL statements will generally be prepared with one - * of the driver's built-in helper methods (such as {@link Session#prepare(SimpleStatement)}). + * of the driver's built-in helper methods (such as {@link CqlSession#prepare(SimpleStatement)}). */ GenericType SYNC = GenericType.of(PreparedStatement.class); @@ -47,7 +47,8 @@ public interface PrepareRequest extends Request { * *

          Most users won't use this explicitly. It is needed for the generic execute method ({@link * Session#execute(Request, GenericType)}), but CQL statements will generally be prepared with one - * of the driver's built-in helper methods (such as {@link Session#prepareAsync(SimpleStatement)}. + * of the driver's built-in helper methods (such as {@link + * CqlSession#prepareAsync(SimpleStatement)}. */ GenericType> ASYNC = new GenericType>() {}; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 23a444b9745..a40d0afe45d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Session; import java.nio.ByteBuffer; +import java.util.List; /** * A query with bind variables that has been pre-parsed by the database. * - *

          Client applications create instances with {@link Session#prepare(SimpleStatement)}. Then they - * use {@link #bind(Object...)} to obtain an executable {@link BoundStatement}. + *

          Client applications create instances with {@link CqlSession#prepare(SimpleStatement)}. Then + * they use {@link #bind(Object...)} to obtain an executable {@link BoundStatement}. * *

          The default prepared statement implementation returned by the driver is thread-safe. * Client applications can -- and are expected to -- prepare each query once and store the result in @@ -44,6 +44,30 @@ public interface PreparedStatement { /** A description of the bind variables of this prepared statement. */ ColumnDefinitions getVariableDefinitions(); + /** + * The indices of the variables in {@link #getVariableDefinitions()} that correspond to the target + * table's partition key. + * + *

          This is only present if all the partition key columns are expressed as bind variables. + * Otherwise, the list will be empty. For example, given the following schema: + * + *

          +   *   CREATE TABLE foo (pk1 int, pk2 int, cc int, v int, PRIMARY KEY ((pk1, pk2), cc));
          +   * 
          + * + * And the following definitions: + * + *
          +   * PreparedStatement ps1 = session.prepare("UPDATE foo SET v = ? WHERE pk1 = ? AND pk2 = ? AND v = ?");
          +   * PreparedStatement ps2 = session.prepare("UPDATE foo SET v = ? WHERE pk1 = 1 AND pk2 = ? AND v = ?");
          +   * 
          + * + * Then {@code ps1.getPrimaryKeyIndices()} contains 1 and 2, and {@code + * ps2.getPrimaryKeyIndices()} is empty (because one of the partition key components is hard-coded + * in the query string). + */ + List getPrimaryKeyIndices(); + /** * A description of the result set that will be returned when this prepared statement is bound and * executed. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 35ac4826869..8765a25bbbe 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import java.util.Arrays; import java.util.Collections; @@ -28,7 +27,7 @@ * *

          To create instances, client applications can use the {@code newInstance} factory methods on * this interface for common cases, or {@link #builder(String)} for more control over the - * parameters. They can then be passed to {@link Session#execute(Statement)}. + * parameters. They can then be passed to {@link CqlSession#execute(Statement)}. * *

          Simple statements should be reserved for queries that will only be executed a few times by an * application. For more frequent queries, {@link PreparedStatement} provides many advantages: it is @@ -56,6 +55,10 @@ static SimpleStatement newInstance(String cqlQuery) { Collections.emptyMap(), null, null, + null, + null, + null, + null, Collections.emptyMap(), null, false, @@ -74,6 +77,10 @@ static SimpleStatement newInstance(String cqlQuery, Object... positionalValues) Collections.emptyMap(), null, null, + null, + null, + null, + null, Collections.emptyMap(), null, false, @@ -92,6 +99,10 @@ static SimpleStatement newInstance(String cqlQuery, Map namedVal namedValues, null, null, + null, + null, + null, + null, Collections.emptyMap(), null, false, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 5ebe3ee3217..1ca810883f4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -121,6 +121,10 @@ public SimpleStatement build() { (namedValuesBuilder == null) ? Collections.emptyMap() : namedValuesBuilder.build(), configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, buildCustomPayload(), idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 6e952a4e924..23e411bf802 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.time.TimestampGenerator; @@ -79,10 +81,31 @@ public interface Statement> extends Request { * stable server release. In the meantime, this method always throws {@link * UnsupportedOperationException}. */ - default T setKeyspace(String newKeyspace) { + default T setKeyspace(CqlIdentifier newKeyspace) { throw new UnsupportedOperationException("Per-query keyspaces are not yet supported"); } + /** + * Sets the keyspace to use for token-aware routing. + * + *

          See {@link Request#getRoutingKey()} for a description of the token-aware routing algorithm. + */ + T setRoutingKeyspace(CqlIdentifier newRoutingKeyspace); + + /** + * Sets the key to use for token-aware routing. + * + *

          See {@link Request#getRoutingKey()} for a description of the token-aware routing algorithm. + */ + T setRoutingKey(ByteBuffer newRoutingKey); + + /** + * Sets the token to use for token-aware routing. + * + *

          See {@link Request#getRoutingKey()} for a description of the token-aware routing algorithm. + */ + T setRoutingToken(Token newRoutingToken); + /** * Sets the custom payload to use for execution. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 314f50cdb61..8669b3fbb02 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; import java.util.Collections; @@ -35,7 +37,10 @@ public abstract class StatementBuilder, S exten protected String configProfileName; protected DriverConfigProfile configProfile; - protected String keyspace; + protected CqlIdentifier keyspace; + protected CqlIdentifier routingKeyspace; + protected ByteBuffer routingKey; + protected Token routingToken; private ImmutableMap.Builder customPayloadBuilder; protected Boolean idempotent; protected boolean tracing; @@ -70,6 +75,24 @@ public T withConfigProfile(DriverConfigProfile configProfile) { return self; } + /** @see Statement#setRoutingKeyspace(CqlIdentifier) */ + public T withRoutingKeyspace(CqlIdentifier routingKeyspace) { + this.routingKeyspace = routingKeyspace; + return self; + } + + /** @see Statement#setRoutingKey(ByteBuffer) */ + public T withRoutingKey(ByteBuffer routingKey) { + this.routingKey = routingKey; + return self; + } + + /** @see Statement#setRoutingToken(Token) */ + public T withRoutingToken(Token routingToken) { + this.routingToken = routingToken; + return self; + } + /** @see Statement#setCustomPayload(Map) */ public T addCustomPayload(String key, ByteBuffer value) { if (customPayloadBuilder == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index ee98e1ef52e..53cf0fa7aad 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -15,8 +15,14 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; +import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Map; import java.util.Queue; import java.util.Set; @@ -33,18 +39,32 @@ public interface LoadBalancingPolicy extends AutoCloseable { * method is invoked, their state is guaranteed to be either {@link NodeState#UP} or {@link * NodeState#UNKNOWN}. Node states may be updated concurrently while this method executes, but * if so you will receive a notification + * @param distanceReporter an object that will be used by the policy to signal distance changes. + * @param contactPoints the set of contact points that the driver was initialized with (see {@link + * ClusterBuilder#addContactPoints(Collection)}). This is provided for reference, in case the + * policy needs to handle those nodes in a particular way. Each address in this set should + * normally have a corresponding entry in {@code nodes}, except for contact points that were + * down or invalid. If no contact points were provided, the driver defaults to 127.0.0.1:9042, + * but the set will be empty. */ - void init(Set nodes, DistanceReporter distanceReporter); + void init( + Map nodes, + DistanceReporter distanceReporter, + Set contactPoints); /** * Returns the coordinators to use for a new query. * *

          Each new query will call this method, and try the returned nodes sequentially. * + * @param request the request that is being routed. Note that this can be null for some internal + * uses. + * @param session the session that is executing the request. Note that this can be null for some + * internal uses. * @return the list of coordinators to try. This must be a concurrent queue; {@link * java.util.concurrent.ConcurrentLinkedQueue} is a good choice. */ - Queue newQueryPlan(/*TODO keyspace, statement*/ ); + Queue newQueryPlan(Request request, Session session); /** * Called when a node is added to the cluster. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java deleted file mode 100644 index b12dce6fc8c..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicy.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.api.core.loadbalancing; - -import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metadata.NodeState; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.IntUnaryOperator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A round-robin load balancing policy. - * - *

          It assigns distance {@link NodeDistance#LOCAL} to all up nodes. Each query plan returns all - * the nodes, starting at an incrementing index and traversing the list in a circular fashion. - */ -public class RoundRobinLoadBalancingPolicy implements LoadBalancingPolicy { - private static final Logger LOG = LoggerFactory.getLogger(RoundRobinLoadBalancingPolicy.class); - - private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; - - private final String logPrefix; - private final AtomicInteger startIndex = new AtomicInteger(); - private final CopyOnWriteArraySet liveNodes = new CopyOnWriteArraySet<>(); - private volatile DistanceReporter distanceReporter; - - public RoundRobinLoadBalancingPolicy(DriverContext context) { - this.logPrefix = context.clusterName(); - } - - @Override - public void init(Set nodes, DistanceReporter distanceReporter) { - LOG.debug("[{}] Initializing with {}", logPrefix, nodes); - this.distanceReporter = distanceReporter; - for (Node node : nodes) { - distanceReporter.setDistance(node, NodeDistance.LOCAL); - if (node.getState() == NodeState.UNKNOWN || node.getState() == NodeState.UP) { - this.liveNodes.add(node); - } - } - } - - @Override - public Queue newQueryPlan() { - Object[] snapshot = liveNodes.toArray(); - int myStartIndex = startIndex.getAndUpdate(INCREMENT); - ConcurrentLinkedQueue plan = new ConcurrentLinkedQueue<>(); - for (int i = 0; i < snapshot.length; i++) { - Node node = (Node) snapshot[(myStartIndex + i) % liveNodes.size()]; - plan.offer(node); - } - return plan; - } - - @Override - public void onAdd(Node node) { - LOG.debug("[{}] {} was added, setting distance to LOCAL", logPrefix, node); - // Setting to a non-ignored distance triggers the session to open a pool, which will in turn set - // the node UP when the first channel gets opened. - distanceReporter.setDistance(node, NodeDistance.LOCAL); - } - - @Override - public void onUp(Node node) { - LOG.debug("[{}] {} came back UP, adding to live set", logPrefix, node); - liveNodes.add(node); - } - - @Override - public void onDown(Node node) { - LOG.debug("[{}] {} went DOWN, removing from live set", logPrefix, node); - liveNodes.remove(node); - } - - @Override - public void onRemove(Node node) { - LOG.debug("[{}] {} was removed, removing from live set", logPrefix, node); - liveNodes.remove(node); - } - - @Override - public void close() { - // nothing to do - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 83f654c011e..921434b2d8b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -15,9 +15,12 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import java.nio.ByteBuffer; import java.util.Map; @@ -51,14 +54,62 @@ public interface Request { DriverConfigProfile getConfigProfile(); /** - * NOT YET SUPPORTED -- the CQL keyspace to associate with the query. + * NOT IMPLEMENTED YET (method added for binary compatibility, implementation coming soon) + * -- The CQL keyspace to execute this request in. * - *

          This will be available when CASSANDRA-10145 is merged in a - * stable server release. In the meantime, the method is present to avoid breaking the API later, - * but returning any value other than {@code null} will cause a runtime exception. + *

          This overrides {@link Session#getKeyspace()} for this particular request, providing a way to + * specify the keyspace without forcing it globally on the session, or hard-coding it in the query + * string. + * + *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime + * error. + * + * @see CASSANDRA-10145 + */ + CqlIdentifier getKeyspace(); + + /** + * The keyspace to use for token-aware routing, if no {@link #getKeyspace() per-request keyspace + * is defined}. + * + *

          See {@link #getRoutingKey()} for a detailed explanation of token-aware routing. + * + *

          Note that this is the only way to define a routing keyspace for protocol v4 or lower. + */ + CqlIdentifier getRoutingKeyspace(); + + /** + * The (encoded) partition key to use for token-aware routing. + * + *

          When the driver picks a coordinator to execute a request, it prioritizes the replicas of the + * partition that this query operates on, in order to avoid an extra network jump on the server + * side. To find these replicas, it needs a keyspace (which is where the replication settings are + * defined) and a key, that are computed the following way: + * + *

            + *
          • if a per-request keyspace is specified with {@link #getKeyspace()}, it is used as the + * keyspace; + *
          • otherwise, if {@link #getRoutingKeyspace()} is specified, it is used as the keyspace; + *
          • otherwise, if {@link Session#getKeyspace()} is not null, it is used as the keyspace; + *
          • if a routing token is defined with {@link #getRoutingToken()}, it is used as the key; + *
          • otherwise, the result of this method is used as the key. + *
          + * + * If either keyspace or key is null at the end of this process, then token-aware routing is + * disabled. + */ + ByteBuffer getRoutingKey(); + + /** + * The token to use for token-aware routing. + * + *

          This is the same information as {@link #getRoutingKey()}, but already hashed in a token. It + * is probably more useful for analytics tools that "shard" a query on a set of token ranges. + * + *

          See {@link #getRoutingKey()} for a detailed explanation of token-aware routing. */ - String getKeyspace(); + Token getRoutingToken(); /** * Returns the custom payload to send alongside the request. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java index 2c1aefa80b8..11bf82fc640 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -44,7 +45,7 @@ public static Set merge( } } } - return result; + return ImmutableSet.copyOf(result); } private static Set extract(String spec) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index 13e96c4b2fa..ec675f25e4f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -37,7 +37,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; import java.util.ArrayList; @@ -208,12 +207,14 @@ private void init() { initWasCalled = true; LOG.debug("[{}] Starting initialization", logPrefix); + // TODO fail if LBP=default, initialContactPoints not empty and local DC missing + nodeStateListeners.forEach(l -> l.onRegister(DefaultCluster.this)); - // If any contact points were provided, store them in the metadata right away (the - // control connection will need them if it has to initialize) MetadataManager metadataManager = context.metadataManager(); metadataManager + // Store contact points in the metadata right away, the control connection will need them + // if it has to initialize (if the set is empty, 127.0.0.1 is used as a default). .addContactPoints(initialContactPoints) .thenCompose(v -> context.topologyMonitor().init()) .thenCompose(v -> metadataManager.refreshNodes()) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index c2f3062afcf..c9dff8aad32 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -279,6 +279,7 @@ static DefaultPreparedStatement toPreparedStatement( ByteBuffer.wrap(response.preparedQueryId).asReadOnlyBuffer(), request.getQuery(), toColumnDefinitions(response.variablesMetadata, context), + asList(response.variablesMetadata.pkIndices), toColumnDefinitions(response.resultMetadata, context), request.getConfigProfileNameForBoundStatements(), request.getConfigProfileForBoundStatements(), @@ -299,6 +300,18 @@ private static ColumnDefinitions toColumnDefinitions( return DefaultColumnDefinitions.valueOf(definitions.build()); } + private static List asList(int[] pkIndices) { + if (pkIndices == null || pkIndices.length == 0) { + return Collections.emptyList(); + } else { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int pkIndex : pkIndices) { + builder.add(pkIndex); + } + return builder.build(); + } + } + static CoordinatorException toThrowable(Node node, Error errorMessage) { switch (errorMessage.code) { case ProtocolConstants.ErrorCode.UNPREPARED: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 566b9f3bc29..e6bd5f5685f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -101,7 +101,7 @@ protected CqlPrepareHandlerBase( this.preparedStatementsCache = preparedStatementsCache; this.session = session; this.context = context; - this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); + this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(request, session); DriverConfigProfile configProfile; if (request.getConfigProfile() != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index dfa4c12fdb6..34d47f2ebdb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -127,7 +127,7 @@ protected CqlRequestHandlerBase( this.session = session; this.keyspace = session.getKeyspace(); this.context = context; - this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(); + this.queryPlan = context.loadBalancingPolicyWrapper().newQueryPlan(statement, session); if (statement.getConfigProfile() != null) { this.configProfile = statement.getConfigProfile(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index ab2a5b4bc46..07bad0ed8c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -15,10 +15,12 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchType; import com.datastax.oss.driver.api.core.cql.BatchableStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.nio.ByteBuffer; @@ -32,7 +34,10 @@ public class DefaultBatchStatement implements BatchStatement { private final List> statements; private final String configProfileName; private final DriverConfigProfile configProfile; - private final String keyspace; + private final CqlIdentifier keyspace; + private final CqlIdentifier routingKeyspace; + private final ByteBuffer routingKey; + private final Token routingToken; private final Map customPayload; private final Boolean idempotent; private final boolean tracing; @@ -44,7 +49,10 @@ public DefaultBatchStatement( List> statements, String configProfileName, DriverConfigProfile configProfile, - String keyspace, + CqlIdentifier keyspace, + CqlIdentifier routingKeyspace, + ByteBuffer routingKey, + Token routingToken, Map customPayload, Boolean idempotent, boolean tracing, @@ -55,6 +63,9 @@ public DefaultBatchStatement( this.configProfileName = configProfileName; this.configProfile = configProfile; this.keyspace = keyspace; + this.routingKeyspace = routingKeyspace; + this.routingKey = routingKey; + this.routingToken = routingToken; this.customPayload = customPayload; this.idempotent = idempotent; this.tracing = tracing; @@ -75,6 +86,9 @@ public BatchStatement setBatchType(BatchType newBatchType) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -94,6 +108,9 @@ public BatchStatement add(BatchableStatement statement) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -117,6 +134,9 @@ public BatchStatement addAll(Iterable> newStatem configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -138,6 +158,9 @@ public BatchStatement clear() { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -163,6 +186,9 @@ public BatchStatement setPagingState(ByteBuffer newPagingState) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -183,6 +209,9 @@ public BatchStatement setConfigProfileName(String newConfigProfileName) { newConfigProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -203,6 +232,9 @@ public DefaultBatchStatement setConfigProfile(DriverConfigProfile newProfile) { configProfileName, newProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -211,15 +243,77 @@ public DefaultBatchStatement setConfigProfile(DriverConfigProfile newProfile) { } @Override - public String getKeyspace() { + public CqlIdentifier getKeyspace() { return keyspace; } @Override - public DefaultBatchStatement setKeyspace(@SuppressWarnings("unused") String keyspace) { - throw new UnsupportedOperationException( - "Per-statement keyspaces are not supported currently, " - + "this feature will be available in Cassandra 4"); + public CqlIdentifier getRoutingKeyspace() { + return routingKeyspace; + } + + @Override + public BatchStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + newRoutingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + + @Override + public ByteBuffer getRoutingKey() { + return routingKey; + } + + @Override + public BatchStatement setRoutingKey(ByteBuffer newRoutingKey) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + newRoutingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + + @Override + public Token getRoutingToken() { + return routingToken; + } + + @Override + public BatchStatement setRoutingToken(Token newRoutingToken) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + newRoutingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override @@ -235,6 +329,9 @@ public DefaultBatchStatement setCustomPayload(Map newCustomP configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, newCustomPayload, idempotent, tracing, @@ -255,6 +352,9 @@ public DefaultBatchStatement setIdempotent(Boolean newIdempotence) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, newIdempotence, tracing, @@ -275,6 +375,9 @@ public BatchStatement setTracing(boolean newTracing) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, newTracing, @@ -295,6 +398,9 @@ public BatchStatement setTimestamp(long newTimestamp) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index a0e87155ec2..4209013114e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -21,8 +21,10 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.util.RoutingKey; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; @@ -35,7 +37,10 @@ public class DefaultBoundStatement implements BoundStatement { private final ByteBuffer[] values; private final String configProfileName; private final DriverConfigProfile configProfile; - private final String keyspace; + private final CqlIdentifier keyspace; + private final CqlIdentifier routingKeyspace; + private final ByteBuffer routingKey; + private final Token routingToken; private final Map customPayload; private final Boolean idempotent; private final boolean tracing; @@ -50,7 +55,10 @@ public DefaultBoundStatement( ByteBuffer[] values, String configProfileName, DriverConfigProfile configProfile, - String keyspace, + CqlIdentifier keyspace, + CqlIdentifier routingKeyspace, + ByteBuffer routingKey, + Token routingToken, Map customPayload, Boolean idempotent, boolean tracing, @@ -64,6 +72,9 @@ public DefaultBoundStatement( this.configProfileName = configProfileName; this.configProfile = configProfile; this.keyspace = keyspace; + this.routingKeyspace = routingKeyspace; + this.routingKey = routingKey; + this.routingToken = routingToken; this.customPayload = customPayload; this.idempotent = idempotent; this.tracing = tracing; @@ -119,6 +130,9 @@ public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -152,6 +166,9 @@ public BoundStatement setConfigProfileName(String newConfigProfileName) { newConfigProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -175,6 +192,9 @@ public BoundStatement setConfigProfile(DriverConfigProfile newConfigProfile) { configProfileName, newConfigProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -185,10 +205,115 @@ public BoundStatement setConfigProfile(DriverConfigProfile newConfigProfile) { } @Override - public String getKeyspace() { + public CqlIdentifier getKeyspace() { return keyspace; } + @Override + public CqlIdentifier getRoutingKeyspace() { + // If it was set explicitly, use that value, else try to infer it from the prepared statement's + // metadata + if (routingKeyspace != null) { + return routingKeyspace; + } else { + ColumnDefinitions definitions = preparedStatement.getResultSetDefinitions(); + return (definitions.size() == 0) ? null : definitions.get(0).getKeyspace(); + } + } + + @Override + public BoundStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + newRoutingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); + } + + @Override + public ByteBuffer getRoutingKey() { + if (routingKey != null) { + return routingKey; + } else { + List indices = preparedStatement.getPrimaryKeyIndices(); + if (indices.isEmpty()) { + return null; + } else if (indices.size() == 1) { + return getBytesUnsafe(indices.get(0)); + } else { + ByteBuffer[] components = new ByteBuffer[indices.size()]; + for (Integer index : indices) { + ByteBuffer value; + if (!isSet(index) || (value = getBytesUnsafe(index)) == null) { + return null; + } else { + components[index] = value; + } + } + return RoutingKey.compose(components); + } + } + } + + @Override + public BoundStatement setRoutingKey(ByteBuffer newRoutingKey) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + newRoutingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); + } + + @Override + public Token getRoutingToken() { + return routingToken; + } + + @Override + public BoundStatement setRoutingToken(Token newRoutingToken) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + newRoutingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + codecRegistry, + protocolVersion); + } + @Override public Map getCustomPayload() { return customPayload; @@ -203,6 +328,9 @@ public BoundStatement setCustomPayload(Map newCustomPayload) configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, newCustomPayload, idempotent, tracing, @@ -226,6 +354,9 @@ public BoundStatement setIdempotent(Boolean newIdempotence) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, newIdempotence, tracing, @@ -249,6 +380,9 @@ public BoundStatement setTracing(boolean newTracing) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, newTracing, @@ -272,6 +406,9 @@ public BoundStatement setTimestamp(long newTimestamp) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -295,6 +432,9 @@ public BoundStatement setPagingState(ByteBuffer newPagingState) { configProfileName, configProfile, keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index 58c4528ee26..4cbda0156fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import java.nio.ByteBuffer; import java.util.Map; @@ -56,8 +58,23 @@ public DriverConfigProfile getConfigProfile() { } @Override - public String getKeyspace() { - // Not implemented yet, waiting for CASSANDRA-10145 to land in a release + public CqlIdentifier getKeyspace() { + return statement.getKeyspace(); + } + + @Override + public CqlIdentifier getRoutingKeyspace() { + // Prepare requests do not operate on a particular partition, token-aware routing doesn't apply. + return null; + } + + @Override + public ByteBuffer getRoutingKey() { + return null; + } + + @Override + public Token getRoutingToken() { return null; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 89fa8a4b9e9..04bb0356181 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.BoundStatement; @@ -31,6 +32,7 @@ import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.protocol.internal.ProtocolConstants; import java.nio.ByteBuffer; +import java.util.List; import java.util.Map; public class DefaultPreparedStatement implements PreparedStatement { @@ -38,6 +40,7 @@ public class DefaultPreparedStatement implements PreparedStatement { private final ByteBuffer id; private final RepreparePayload repreparePayload; private final ColumnDefinitions variableDefinitions; + private final List primaryKeyIndices; private final ColumnDefinitions resultSetDefinitions; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; @@ -51,16 +54,18 @@ public DefaultPreparedStatement( ByteBuffer id, String query, ColumnDefinitions variableDefinitions, + List primaryKeyIndices, ColumnDefinitions resultSetDefinitions, String configProfileName, DriverConfigProfile configProfile, - String keyspace, + CqlIdentifier keyspace, Map customPayloadForBoundStatements, Boolean idempotent, CodecRegistry codecRegistry, ProtocolVersion protocolVersion, Map customPayloadForPrepare) { this.id = id; + this.primaryKeyIndices = primaryKeyIndices; // It's important that we keep a reference to this object, so that it only gets evicted from // the map in DefaultSession if no client reference the PreparedStatement anymore. this.repreparePayload = new RepreparePayload(id, query, keyspace, customPayloadForPrepare); @@ -89,6 +94,11 @@ public ColumnDefinitions getVariableDefinitions() { return variableDefinitions; } + @Override + public List getPrimaryKeyIndices() { + return primaryKeyIndices; + } + @Override public ColumnDefinitions getResultSetDefinitions() { return resultSetDefinitions; @@ -136,6 +146,9 @@ public BoundStatement bind(Object... values) { configProfileName, configProfile, repreparePayload.keyspace, + null, + null, + null, customPayloadForBoundStatements, idempotent, false, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index e741a8dbd6e..71b102ad059 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -15,8 +15,10 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; @@ -30,6 +32,11 @@ public class DefaultSimpleStatement implements SimpleStatement { private final Map namedValues; private final String configProfileName; private final DriverConfigProfile configProfile; + private final CqlIdentifier keyspace; + private final CqlIdentifier routingKeyspace; + private final ByteBuffer routingKey; + private final Token routingToken; + private final Map customPayload; private final Boolean idempotent; private final boolean tracing; @@ -43,6 +50,10 @@ public DefaultSimpleStatement( Map namedValues, String configProfileName, DriverConfigProfile configProfile, + CqlIdentifier keyspace, + CqlIdentifier routingKeyspace, + ByteBuffer routingKey, + Token routingToken, Map customPayload, Boolean idempotent, boolean tracing, @@ -56,6 +67,10 @@ public DefaultSimpleStatement( this.namedValues = ImmutableMap.copyOf(namedValues); this.configProfileName = configProfileName; this.configProfile = configProfile; + this.keyspace = keyspace; + this.routingKeyspace = routingKeyspace; + this.routingKey = routingKey; + this.routingToken = routingToken; this.customPayload = customPayload; this.idempotent = idempotent; this.tracing = tracing; @@ -76,6 +91,10 @@ public SimpleStatement setQuery(String newQuery) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -96,6 +115,10 @@ public SimpleStatement setPositionalValues(List newPositionalValues) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -116,6 +139,10 @@ public SimpleStatement setNamedValues(Map newNamedValues) { newNamedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -136,6 +163,10 @@ public SimpleStatement setConfigProfileName(String newConfigProfileName) { namedValues, newConfigProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -156,6 +187,63 @@ public SimpleStatement setConfigProfile(DriverConfigProfile newProfile) { namedValues, null, newProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + + @Override + public CqlIdentifier getKeyspace() { + return keyspace; + } + + @Override + public CqlIdentifier getRoutingKeyspace() { + return routingKeyspace; + } + + @Override + public SimpleStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + newRoutingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + + @Override + public ByteBuffer getRoutingKey() { + return routingKey; + } + + @Override + public SimpleStatement setRoutingKey(ByteBuffer newRoutingKey) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + newRoutingKey, + routingToken, customPayload, idempotent, tracing, @@ -164,9 +252,27 @@ public SimpleStatement setConfigProfile(DriverConfigProfile newProfile) { } @Override - public String getKeyspace() { - // Not implemented yet, waiting for CASSANDRA-10145 to land in a release - return null; + public Token getRoutingToken() { + return routingToken; + } + + @Override + public SimpleStatement setRoutingToken(Token newRoutingToken) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + newRoutingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); } @Override @@ -182,6 +288,10 @@ public SimpleStatement setCustomPayload(Map newCustomPayload namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, newCustomPayload, idempotent, tracing, @@ -202,6 +312,10 @@ public SimpleStatement setIdempotent(Boolean newIdempotence) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, newIdempotence, tracing, @@ -222,6 +336,10 @@ public SimpleStatement setTracing(boolean newTracing) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, newTracing, @@ -242,6 +360,10 @@ public SimpleStatement setTimestamp(long newTimestamp) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, @@ -262,6 +384,10 @@ public SimpleStatement setPagingState(ByteBuffer newPagingState) { namedValues, configProfileName, configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, customPayload, idempotent, tracing, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java new file mode 100644 index 00000000000..7fd24ebebbb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -0,0 +1,302 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.loadbalancing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.TokenMap; +import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.util.ArrayUtils; +import com.datastax.oss.driver.internal.core.util.Reflection; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntUnaryOperator; +import java.util.function.Predicate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultLoadBalancingPolicy implements LoadBalancingPolicy { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); + private static final Predicate INCLUDE_ALL_NODES = n -> true; + private static final IntUnaryOperator INCREMENT = i -> (i == Integer.MAX_VALUE) ? 0 : i + 1; + + private final String logPrefix; + private final MetadataManager metadataManager; + private final Predicate filter; + private final AtomicInteger startIndex = new AtomicInteger(); + @VisibleForTesting final CopyOnWriteArraySet localDcLiveNodes = new CopyOnWriteArraySet<>(); + + private volatile DistanceReporter distanceReporter; + @VisibleForTesting volatile String localDc; + + public DefaultLoadBalancingPolicy(DriverContext context) { + this(getLocalDcFromConfig(context), getFilterFromConfig(context), context); + } + + @VisibleForTesting + DefaultLoadBalancingPolicy( + String localDcFromConfig, Predicate filterFromConfig, DriverContext context) { + this.logPrefix = context.clusterName(); + this.metadataManager = ((InternalDriverContext) context).metadataManager(); + if (localDcFromConfig != null) { + LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcFromConfig); + this.localDc = localDcFromConfig; + } + + this.filter = + node -> { + String localDc = this.localDc; + if (localDc != null && !localDc.equals(node.getDatacenter())) { + LOG.debug( + "[{}] Ignoring {} because it doesn't belong to the local DC {}", + logPrefix, + node, + localDc); + return false; + } else if (!filterFromConfig.test(node)) { + LOG.debug( + "[{}] Ignoring {} because it doesn't match the user-provided predicate", + logPrefix, + node); + return false; + } else { + return true; + } + }; + } + + @Override + public void init( + Map nodes, + DistanceReporter distanceReporter, + Set contactPoints) { + this.distanceReporter = distanceReporter; + + if (contactPoints.isEmpty()) { + // No explicit contact points provided => the driver used the default one, and we allow + // inferring the local DC in this case + Node contactPoint = nodes.get(MetadataManager.DEFAULT_CONTACT_POINT); + localDc = contactPoint.getDatacenter(); + LOG.debug("[{}] Local DC set from contact point {}: {}", logPrefix, contactPoint, localDc); + } else if (localDc == null) { + throw new IllegalStateException( + "You provided explicit contact points, the local DC must be specified (see " + + CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER.getPath() + + " in the config)"); + } else { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (InetSocketAddress address : contactPoints) { + Node node = nodes.get(address); + if (node != null && !localDc.equals(node.getDatacenter())) { + builder.put(address, node.getDatacenter()); + } + } + ImmutableMap badContactPoints = builder.build(); + if (!badContactPoints.isEmpty()) { + LOG.warn( + "[{}] You specified {} as the local DC, but some contact points are from a different DC ({})", + logPrefix, + localDc, + badContactPoints); + } + } + + for (Node node : nodes.values()) { + if (filter.test(node)) { + distanceReporter.setDistance(node, NodeDistance.LOCAL); + localDcLiveNodes.add(node); + } else { + distanceReporter.setDistance(node, NodeDistance.IGNORED); + } + } + } + + @Override + public Queue newQueryPlan(Request request, Session session) { + // Take a snapshot since the set is concurrent: + Object[] currentNodes = localDcLiveNodes.toArray(); + + Set allReplicas = getReplicas(request, session); + int replicaCount = 0; // in currentNodes + + if (!allReplicas.isEmpty()) { + // Move replicas to the beginning + for (int i = 0; i < currentNodes.length; i++) { + Node node = (Node) currentNodes[i]; + if (allReplicas.contains(node)) { + ArrayUtils.bubbleUp(currentNodes, i, replicaCount); + replicaCount += 1; + } + } + + if (replicaCount > 1) { + shuffleHead(currentNodes, replicaCount); + } + + // Power of 2 choices: order the first two nodes by increasing load + if (replicaCount > 2 + && getAvailableIds((Node) currentNodes[1], session) + > getAvailableIds((Node) currentNodes[0], session)) { + ArrayUtils.swap(currentNodes, 0, 1); + } + } + + LOG.trace("[{}] Prioritizing {} local replicas", logPrefix, replicaCount); + + ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); + // Copy the replicas as-is (we've already shuffled/ordered them) + for (int i = 0; i < replicaCount; i++) { + queryPlan.offer((Node) currentNodes[i]); + } + // Round-robin the remaining nodes + int remaining = currentNodes.length - replicaCount; + int myStartIndex = startIndex.getAndUpdate(INCREMENT); + for (int i = 0; i < remaining; i++) { + Node node = (Node) currentNodes[replicaCount + (myStartIndex + i) % remaining]; + queryPlan.offer(node); + } + return queryPlan; + } + + private Set getReplicas(Request request, Session session) { + if (request == null || session == null) { + return Collections.emptySet(); + } + + // Note: we're on the hot path and the getXxx methods are potentially more than simple getters, + // so we only call each method when strictly necessary (which is why the code below looks a bit + // weird). + CqlIdentifier keyspace = request.getKeyspace(); + if (keyspace == null) { + keyspace = request.getRoutingKeyspace(); + } + if (keyspace == null) { + keyspace = session.getKeyspace(); + } + if (keyspace == null) { + return Collections.emptySet(); + } + + Token token = request.getRoutingToken(); + ByteBuffer key = (token == null) ? request.getRoutingKey() : null; + if (token == null && key == null) { + return Collections.emptySet(); + } + + Optional maybeTokenMap = metadataManager.getMetadata().getTokenMap(); + if (maybeTokenMap.isPresent()) { + TokenMap tokenMap = maybeTokenMap.get(); + return (token != null) + ? tokenMap.getReplicas(keyspace, token) + : tokenMap.getReplicas(keyspace, key); + } else { + return Collections.emptySet(); + } + } + + @VisibleForTesting + protected void shuffleHead(Object[] currentNodes, int replicaCount) { + ArrayUtils.shuffleHead(currentNodes, replicaCount); + } + + private int getAvailableIds(Node node, Session session) { + // The cast will always succeed because there's no way to replace the internal session impl + ChannelPool pool = ((DefaultSession) session).getPools().get(node); + return (pool == null) ? 0 : pool.getAvailableIds(); + } + + @Override + public void onAdd(Node node) { + if (filter.test(node)) { + LOG.debug("[{}] {} was added, setting distance to LOCAL", logPrefix, node); + // Setting to a non-ignored distance triggers the session to open a pool, which will in turn + // set the node UP when the first channel gets opened. + distanceReporter.setDistance(node, NodeDistance.LOCAL); + } else { + distanceReporter.setDistance(node, NodeDistance.IGNORED); + } + } + + @Override + public void onUp(Node node) { + if (filter.test(node)) { + // Normally this is already the case, but the filter could be dynamic and have ignored the + // node previously. + distanceReporter.setDistance(node, NodeDistance.LOCAL); + if (localDcLiveNodes.add(node)) { + LOG.debug("[{}] {} came back UP, added to live set", logPrefix, node); + } + } else { + distanceReporter.setDistance(node, NodeDistance.IGNORED); + } + } + + @Override + public void onDown(Node node) { + if (localDcLiveNodes.remove(node)) { + LOG.debug("[{}] {} went DOWN, removed from live set", logPrefix, node); + } + } + + @Override + public void onRemove(Node node) { + if (localDcLiveNodes.remove(node)) { + LOG.debug("[{}] {} was removed, removed from live set", logPrefix, node); + } + } + + @Override + public void close() { + // nothing to do + } + + private static String getLocalDcFromConfig(DriverContext context) { + DriverConfigProfile config = context.config().getDefaultProfile(); + return (config.isDefined(CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) + ? config.getString(CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) + : null; + } + + @SuppressWarnings("unchecked") + private static Predicate getFilterFromConfig(DriverContext context) { + return (Predicate) + Reflection.buildFromConfig( + context, CoreDriverOption.LOAD_BALANCING_FILTER_CLASS, Predicate.class) + .orElse(INCLUDE_ALL_NODES); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index a8bb6f98c8c..e1547ce81e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -20,12 +20,16 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableMap; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; @@ -75,8 +79,9 @@ public void init() { // State events can happen concurrently with init, so we must record them and replay once the // policy is initialized. eventFilter.start(); - Metadata metadata = context.metadataManager().getMetadata(); - policy.init(excludeDownHosts(metadata), this); + MetadataManager metadataManager = context.metadataManager(); + Metadata metadata = metadataManager.getMetadata(); + policy.init(excludeDownHosts(metadata), this, metadataManager.getContactPoints()); if (stateRef.compareAndSet(State.DURING_INIT, State.RUNNING)) { eventFilter.markReady(); } else { // closed during init @@ -86,7 +91,8 @@ public void init() { } } - public Queue newQueryPlan() { + /** @see LoadBalancingPolicy#newQueryPlan(Request, Session) */ + public Queue newQueryPlan(Request request, Session session) { switch (stateRef.get()) { case BEFORE_INIT: case DURING_INIT: @@ -97,12 +103,16 @@ public Queue newQueryPlan() { Collections.shuffle(nodes); return new ConcurrentLinkedQueue<>(nodes); case RUNNING: - return policy.newQueryPlan(); + return policy.newQueryPlan(request, session); default: return new ConcurrentLinkedQueue<>(); } } + public Queue newQueryPlan() { + return newQueryPlan(null, null); + } + @Override public void setDistance(Node node, NodeDistance distance) { LOG.debug("[{}] LBP changed distance of {} to {}", logPrefix, node, distance); @@ -140,11 +150,11 @@ private void processNodeStateEvent(NodeStateEvent event) { } } - private static ImmutableSet excludeDownHosts(Metadata metadata) { - ImmutableSet.Builder nodes = ImmutableSet.builder(); + private static Map excludeDownHosts(Metadata metadata) { + ImmutableMap.Builder nodes = ImmutableMap.builder(); for (Node node : metadata.getNodes().values()) { if (node.getState() == NodeState.UP || node.getState() == NodeState.UNKNOWN) { - nodes.add(node); + nodes.put(node.getConnectAddress(), node); } } return nodes.build(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 49ece98359d..76c1da1ee31 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -64,6 +64,7 @@ public class MetadataManager implements AsyncAutoCloseable { private volatile List refreshedKeyspaces; private volatile Boolean schemaEnabledProgrammatically; private volatile boolean tokenMapEnabled; + private volatile Set providedContactPoints; public MetadataManager(InternalDriverContext context) { this.context = context; @@ -108,6 +109,7 @@ public Metadata getMetadata() { } public CompletionStage addContactPoints(Set providedContactPoints) { + this.providedContactPoints = providedContactPoints; Set contactPoints; if (providedContactPoints == null || providedContactPoints.isEmpty()) { LOG.info( @@ -122,6 +124,10 @@ public CompletionStage addContactPoints(Set providedCon return initNodesFuture; } + public Set getContactPoints() { + return providedContactPoints; + } + public CompletionStage refreshNodes() { return context .topologyMonitor() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 85ac81922ac..73366abfd15 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -141,6 +141,10 @@ public DriverChannel next() { return channels.next(); } + public int getAvailableIds() { + return channels.getAvailableIds(); + } + public void resize(NodeDistance newDistance) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newDistance)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java index 6f6653f7289..6452dfa67e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -95,6 +95,15 @@ DriverChannel next() { } } + int getAvailableIds() { + int availableIds = 0; + DriverChannel[] snapshot = this.channels; + for (DriverChannel channel : snapshot) { + availableIds += channel.getAvailableIds(); + } + return availableIds; + } + int size() { return this.channels.length; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java index dd911a717d5..dd72a447103 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RepreparePayload.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.session; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; import java.nio.ByteBuffer; import java.util.Map; @@ -31,12 +32,12 @@ public class RepreparePayload { public final String query; /** The keyspace that is set independently from the query string (see CASSANDRA-10145) */ - public final String keyspace; + public final CqlIdentifier keyspace; public final Map customPayload; public RepreparePayload( - ByteBuffer id, String query, String keyspace, Map customPayload) { + ByteBuffer id, String query, CqlIdentifier keyspace, Map customPayload) { this.id = id; this.query = query; this.keyspace = keyspace; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java new file mode 100644 index 00000000000..7cec3b93ebf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import java.util.concurrent.ThreadLocalRandom; + +public class ArrayUtils { + + public static void swap(T[] elements, int i, int j) { + if (i != j) { + T tmp = elements[i]; + elements[i] = elements[j]; + elements[j] = tmp; + } + } + + /** + * Moves an element towards the beginning of the array, shifting all the intermediary elements to + * the right (no-op if targetIndex >= sourceIndex). + */ + public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) { + for (int i = sourceIndex; i > targetIndex; i--) { + swap(elements, i, i - 1); + } + } + + /** + * Shuffles the first n elements of the array in-place. + * + * @see Modern + * Fisher-Yates shuffle + */ + public static void shuffleHead(T[] elements, int n) { + if (n > 1) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (int i = n - 1; i > 0; i--) { + int j = random.nextInt(i + 1); + swap(elements, i, j); + } + } + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 83cddae86eb..d39e0da6ff8 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -97,8 +97,32 @@ datastax-java-driver { # To disable periodic reloading, set this to 0. config-reload-interval = 5 minutes + # The policy that decides the "query plan" for each query; that is, which nodes to try as + # coordinators, and in which order. load-balancing-policy { - class = com.datastax.oss.driver.api.core.loadbalancing.RoundRobinLoadBalancingPolicy + class = com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy + + # The datacenter that is considered "local": the default policy will only include nodes from + # this datacenter in its query plans. + # + # This option can only be absent if you specified no contact points: in that case, the driver + # defaults to 127.0.0.1:9042, and that node's datacenter is used as the local datacenter. + # + # As soon as you provide contact points (either through the configuration or through the cluster + # builder), you must define the local datacenter explicitly, and initialization will fail if + # this property is absent. In addition, all contact points should be from this datacenter; + # warnings will be logged for nodes that are from a different one. + // local-datacenter = datacenter1 + + # A custom filter to include/exclude nodes. + # + # This option is not required; if present, it must be the fully-qualified name of a class that + # implements `java.util.function.Predicate`, and has a public constructor taking a single + # `DriverContext` argument. The predicate's `filter(Node)` method will be invoked each time the + # policy processes a topology or state change: if it returns false, the node will be set at + # distance IGNORED (meaning the driver won't ever connect to it), and never included in any + # query plan. + // filter.class= } connection { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java deleted file mode 100644 index 717ec57816b..00000000000 --- a/core/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/RoundRobinLoadBalancingPolicyTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.api.core.loadbalancing; - -import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.google.common.collect.ImmutableSet; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RoundRobinLoadBalancingPolicyTest { - @Mock private DriverContext context; - @Mock private DistanceReporter distanceReporter; - @Mock private Node node1, node2, node3; - - private RoundRobinLoadBalancingPolicy policy; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - Mockito.when(node1.getState()).thenReturn(NodeState.UP); - Mockito.when(node2.getState()).thenReturn(NodeState.UP); - Mockito.when(node3.getState()).thenReturn(NodeState.UP); - - policy = new RoundRobinLoadBalancingPolicy(context); - } - - @Test - public void should_set_all_nodes_to_local_distance() { - policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); - - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - } - - @Test - public void should_return_round_robin_query_plans() { - policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); - - assertThat(policy.newQueryPlan()).containsExactly(node1, node2, node3); - assertThat(policy.newQueryPlan()).containsExactly(node2, node3, node1); - assertThat(policy.newQueryPlan()).containsExactly(node3, node1, node2); - } - - @Test - public void should_only_include_unknown_or_up_nodes_at_init() { - Mockito.when(node1.getState()).thenReturn(NodeState.DOWN); - Mockito.when(node2.getState()).thenReturn(NodeState.UP); - Mockito.when(node3.getState()).thenReturn(NodeState.UNKNOWN); - - policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); - - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - - assertThat(policy.newQueryPlan()).containsExactly(node2, node3); - } - - @Test - public void should_remove_node_from_query_plan_if_down() { - policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); - - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - - assertThat(policy.newQueryPlan()).containsExactly(node1, node2, node3); - - policy.onDown(node1); - - assertThat(policy.newQueryPlan()).containsExactly(node3, node2); - Mockito.verifyNoMoreInteractions(distanceReporter); - } - - @Test - public void should_add_node_to_query_plan_if_up() { - Mockito.when(node1.getState()).thenReturn(NodeState.DOWN); - - policy.init(ImmutableSet.of(node1, node2, node3), distanceReporter); - - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - - assertThat(policy.newQueryPlan()).containsExactly(node2, node3); - - policy.onUp(node1); - - assertThat(policy.newQueryPlan()).containsExactly(node3, node1, node2); - Mockito.verifyNoMoreInteractions(distanceReporter); - } -} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index b4a93007797..6bb4754559e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.internal.core.ProtocolFeature; @@ -105,7 +106,8 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); - Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()).thenReturn(builder.buildQueryPlan()); + Mockito.when(loadBalancingPolicyWrapper.newQueryPlan(any(Request.class), any(Session.class))) + .thenReturn(builder.buildQueryPlan()); Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); Mockito.when(context.retryPolicy()).thenReturn(retryPolicy); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java new file mode 100644 index 00000000000..4e4960f8801 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -0,0 +1,155 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.loadbalancing; + +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPolicyTestBase { + + private DefaultLoadBalancingPolicy policy; + + @Before + public void setup() { + super.setup(); + + policy = new DefaultLoadBalancingPolicy("dc1", filter, context); + policy.init( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), + distanceReporter, + ImmutableSet.of(ADDRESS1)); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + + Mockito.reset(distanceReporter); + } + + @Test + public void should_remove_down_node_from_live_set() { + // When + policy.onDown(node2); + + // Then + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); + Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); + } + + @Test + public void should_remove_down_node_from_live_set_when_filtered() { + Mockito.when(filter.test(node2)).thenReturn(true); + should_remove_down_node_from_live_set(); + } + + @Test + public void should_remove_removed_node_from_live_set() { + // When + policy.onRemove(node2); + + // Then + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); + Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); + } + + @Test + public void should_remove_removed_node_from_live_set_when_filtered() { + Mockito.when(filter.test(node2)).thenReturn(true); + should_remove_removed_node_from_live_set(); + } + + @Test + public void should_set_added_node_to_local() { + // When + policy.onAdd(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + // Not added to the live set yet, we're waiting for the pool to open + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + } + + @Test + public void should_ignore_added_node_when_filtered() { + // Given + Mockito.when(filter.test(node3)).thenReturn(false); + + // When + policy.onAdd(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + } + + @Test + public void should_ignore_added_node_when_remote_dc() { + // Given + Mockito.when(node3.getDatacenter()).thenReturn("dc2"); + + // When + policy.onAdd(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + } + + @Test + public void should_add_up_node_to_live_set() { + // When + policy.onUp(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void should_ignore_up_node_when_filtered() { + // Given + Mockito.when(filter.test(node3)).thenReturn(false); + + // When + policy.onUp(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + } + + @Test + public void should_ignore_up_node_when_remote_dc() { + // Given + Mockito.when(node3.getDatacenter()).thenReturn("dc2"); + + // When + policy.onUp(node3); + + // Then + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java new file mode 100644 index 00000000000..785cb21b5b2 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -0,0 +1,143 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.loadbalancing; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.filter; +import static org.mockito.Mockito.atLeast; + +public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPolicyTestBase { + + @Test + public void should_infer_local_dc_if_no_explicit_contact_points() { + // Given + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(null, filter, context); + + // When + policy.init( + ImmutableMap.of(MetadataManager.DEFAULT_CONTACT_POINT, node1), + distanceReporter, + Collections.emptySet()); + + // Then + assertThat(policy.localDc).isEqualTo("dc1"); + } + + @Test + public void should_require_local_dc_if_explicit_contact_points() { + // Given + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(null, filter, context); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("You provided explicit contact points, the local DC must be specified"); + + // When + policy.init(ImmutableMap.of(ADDRESS2, node2), distanceReporter, ImmutableSet.of(ADDRESS2)); + } + + @Test + public void should_warn_if_contact_points_not_in_local_dc() { + // Given + Mockito.when(node2.getDatacenter()).thenReturn("dc2"); + Mockito.when(node3.getDatacenter()).thenReturn("dc3"); + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy("dc1", filter, context); + + // When + policy.init( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), + distanceReporter, + ImmutableSet.of(ADDRESS1, ADDRESS2, ADDRESS3)); + + // Then + Mockito.verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + Iterable warnLogs = + filter(loggingEventCaptor.getAllValues()).with("level", Level.WARN).get(); + assertThat(warnLogs).hasSize(1); + assertThat(warnLogs.iterator().next().getFormattedMessage()) + .contains( + "You specified dc1 as the local DC, but some contact points are from a different DC") + .contains("/127.0.0.2:9042=dc2") + .contains("/127.0.0.3:9042=dc3"); + } + + @Test + public void should_include_nodes_from_local_dc() { + // Given + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy("dc1", filter, context); + + // When + policy.init( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), + distanceReporter, + ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + + // Then + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void should_ignore_nodes_from_remote_dcs() { + // Given + Mockito.when(node2.getDatacenter()).thenReturn("dc2"); + Mockito.when(node3.getDatacenter()).thenReturn("dc3"); + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy("dc1", filter, context); + + // When + policy.init( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), + distanceReporter, + ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + + // Then + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); + } + + @Test + public void should_ignore_nodes_excluded_by_filter() { + // Given + Mockito.when(filter.test(node2)).thenReturn(false); + Mockito.when(filter.test(node3)).thenReturn(false); + + DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy("dc1", filter, context); + + // When + policy.init( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), + distanceReporter, + ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + + // Then + Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); + Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java new file mode 100644 index 00000000000..12cbcbb6952 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -0,0 +1,238 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.loadbalancing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.TokenMap; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancingPolicyTestBase { + + private static final CqlIdentifier KEYSPACE = CqlIdentifier.fromInternal("ks"); + private static final ByteBuffer ROUTING_KEY = Bytes.fromHexString("0xdeadbeef"); + + @Mock private Request request; + @Mock private DefaultSession session; + @Mock private MetadataManager metadataManager; + @Mock private Metadata metadata; + @Mock private TokenMap tokenMap; + + private Map pools; + private DefaultLoadBalancingPolicy policy; + + @Before + public void setup() { + super.setup(); + + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(metadata.getTokenMap()).thenReturn(Optional.of(tokenMap)); + + pools = new HashMap<>(); + Mockito.when(session.getPools()).thenReturn(pools); + + // Use a subclass to disable shuffling, we just spy to make sure that the shuffling method was + // called (makes tests easier) + policy = Mockito.spy(new NonShufflingPolicy("dc1", filter, context)); + policy.init( + ImmutableMap.of( + ADDRESS1, node1, + ADDRESS2, node2, + ADDRESS3, node3, + ADDRESS4, node4, + ADDRESS5, node5), + distanceReporter, + ImmutableSet.of(ADDRESS1)); + + // Note: this test relies on the fact that the policy uses a CopyOnWriteArraySet which preserves + // insertion order. + assertThat(policy.localDcLiveNodes).containsExactly(node1, node2, node3, node4, node5); + } + + @Test + public void should_use_round_robin_when_request_has_no_routing_keyspace() { + // By default from Mockito: + assertThat(request.getKeyspace()).isNull(); + assertThat(request.getRoutingKeyspace()).isNull(); + + assertRoundRobinQueryPlans(); + + Mockito.verify(request, never()).getRoutingKey(); + Mockito.verify(request, never()).getRoutingToken(); + Mockito.verify(metadataManager, never()).getMetadata(); + } + + @Test + public void should_use_round_robin_when_request_has_no_routing_key_or_token() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + assertThat(request.getRoutingKey()).isNull(); + assertThat(request.getRoutingToken()).isNull(); + + assertRoundRobinQueryPlans(); + + Mockito.verify(metadataManager, never()).getMetadata(); + } + + @Test + public void should_use_round_robin_when_token_map_absent() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + + Mockito.when(metadata.getTokenMap()).thenReturn(Optional.empty()); + + assertRoundRobinQueryPlans(); + + Mockito.verify(metadata, atLeast(1)).getTokenMap(); + } + + @Test + public void should_use_round_robin_when_token_map_returns_no_replicas() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(Collections.emptySet()); + + assertRoundRobinQueryPlans(); + + Mockito.verify(tokenMap, atLeast(1)).getReplicas(KEYSPACE, ROUTING_KEY); + } + + private void assertRoundRobinQueryPlans() { + for (int i = 0; i < 3; i++) { + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node1, node2, node3, node4, node5); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node2, node3, node4, node5, node1); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node4, node5, node1, node2); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node4, node5, node1, node2, node3); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node5, node1, node2, node3, node4); + } + } + + @Test + public void should_prioritize_single_replica() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(ImmutableSet.of(node3)); + + // node3 always first, round-robin on the rest + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node1, node2, node4, node5); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node2, node4, node5, node1); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node4, node5, node1, node2); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node5, node1, node2, node4); + + // Should not shuffle replicas since there is only one + Mockito.verify(policy, never()).shuffleHead(any(), anyInt()); + } + + @Test + public void should_prioritize_and_shuffle_two_replicas() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)) + .thenReturn(ImmutableSet.of(node3, node5)); + + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node5, node1, node2, node4); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node5, node2, node4, node1); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node5, node4, node1, node2); + + Mockito.verify(policy, times(3)).shuffleHead(any(), eq(2)); + // No power of two choices with only two replicas + Mockito.verify(session, never()).getPools(); + } + + @Test + public void should_use_power_of_two_choices_for_three_or_more_replicas() { + Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)) + .thenReturn(ImmutableSet.of(node3, node4, node5)); + + // node3 and node4 will always be the first two replicas (since shuffling is disabled), they + // should get ordered by increasing available ids + mockPoolWithAvailableIds(node3, 200); + mockPoolWithAvailableIds(node4, 100); + + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node4, node5, node1, node2); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node3, node4, node5, node2, node1); + Mockito.verify(policy, times(2)).shuffleHead(any(), eq(3)); + + mockPoolWithAvailableIds(node3, 100); + mockPoolWithAvailableIds(node4, 200); + + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node4, node3, node5, node1, node2); + assertThat(policy.newQueryPlan(request, session)) + .containsExactly(node4, node3, node5, node2, node1); + Mockito.verify(policy, times(4)).shuffleHead(any(), eq(3)); + } + + private void mockPoolWithAvailableIds(Node node, int availableIds) { + ChannelPool pool = Mockito.mock(ChannelPool.class); + Mockito.when(pool.getAvailableIds()).thenReturn(availableIds); + pools.put(node, pool); + } + + static class NonShufflingPolicy extends DefaultLoadBalancingPolicy { + NonShufflingPolicy( + String localDcFromConfig, Predicate filterFromConfig, DriverContext context) { + super(localDcFromConfig, filterFromConfig, context); + } + + @Override + protected void shuffleHead(Object[] currentNodes, int replicaCount) { + // nothing (keep in same order) + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java new file mode 100644 index 00000000000..52098815da3 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -0,0 +1,81 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.loadbalancing; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.collect.ImmutableList; +import java.net.InetSocketAddress; +import java.util.function.Predicate; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.LoggerFactory; + +import static org.mockito.ArgumentMatchers.any; + +@RunWith(MockitoJUnitRunner.class) +public abstract class DefaultLoadBalancingPolicyTestBase { + + protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + protected static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); + protected static final InetSocketAddress ADDRESS4 = new InetSocketAddress("127.0.0.4", 9042); + protected static final InetSocketAddress ADDRESS5 = new InetSocketAddress("127.0.0.5", 9042); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Mock protected Node node1; + @Mock protected Node node2; + @Mock protected Node node3; + @Mock protected Node node4; + @Mock protected Node node5; + @Mock protected InternalDriverContext context; + @Mock protected Predicate filter; + @Mock protected LoadBalancingPolicy.DistanceReporter distanceReporter; + @Mock protected Appender appender; + + @Captor protected ArgumentCaptor loggingEventCaptor; + + protected Logger logger; + + @Before + public void setup() { + Mockito.when(filter.test(any(Node.class))).thenReturn(true); + logger = (Logger) LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); + logger.addAppender(appender); + + for (Node node : ImmutableList.of(node1, node2, node3, node4, node5)) { + Mockito.when(node.getDatacenter()).thenReturn("dc1"); + } + } + + @After + public void teardown() { + logger.detachAppender(appender); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 9f9d4be9e45..a1afca7cbb7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -29,7 +29,6 @@ import java.net.InetSocketAddress; import java.util.Map; import java.util.Queue; -import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -42,7 +41,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; public class LoadBalancingPolicyWrapperTest { @@ -77,10 +77,11 @@ public void setup() { .build(); Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + Mockito.when(metadataManager.getContactPoints()).thenReturn(contactPointsMap.keySet()); Mockito.when(context.metadataManager()).thenReturn(metadataManager); policysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); - Mockito.when(loadBalancingPolicy.newQueryPlan()).thenReturn(policysQueryPlan); + Mockito.when(loadBalancingPolicy.newQueryPlan(null, null)).thenReturn(policysQueryPlan); eventBus = Mockito.spy(new EventBus("test")); Mockito.when(context.eventBus()).thenReturn(eventBus); @@ -94,7 +95,7 @@ public void should_build_query_plan_from_contact_points_before_init() { Queue queryPlan = wrapper.newQueryPlan(); // Then - Mockito.verify(loadBalancingPolicy, never()).newQueryPlan(); + Mockito.verify(loadBalancingPolicy, never()).newQueryPlan(null, null); assertThat(queryPlan).containsOnlyElementsOf(contactPointsMap.values()); } @@ -102,13 +103,14 @@ public void should_build_query_plan_from_contact_points_before_init() { public void should_fetch_query_plan_from_policy_after_init() { // Given wrapper.init(); - Mockito.verify(loadBalancingPolicy).init(anySet(), any(DistanceReporter.class)); + Mockito.verify(loadBalancingPolicy) + .init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); // When Queue queryPlan = wrapper.newQueryPlan(); // Then - Mockito.verify(loadBalancingPolicy).newQueryPlan(); + Mockito.verify(loadBalancingPolicy).newQueryPlan(null, null); assertThat(queryPlan).isEqualTo(policysQueryPlan); } @@ -131,10 +133,11 @@ public void should_init_policy_with_up_or_unknown_nodes() { // Then @SuppressWarnings("unchecked") - ArgumentCaptor> captor = ArgumentCaptor.forClass(Set.class); - Mockito.verify(loadBalancingPolicy).init(captor.capture(), any(DistanceReporter.class)); - Set initNodes = captor.getValue(); - assertThat(initNodes).containsOnly(node1, node2); + ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); + Mockito.verify(loadBalancingPolicy) + .init(captor.capture(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); + Map initNodes = captor.getValue(); + assertThat(initNodes.values()).containsOnly(node1, node2); } @Test @@ -142,7 +145,8 @@ public void should_propagate_distance_from_policy() { // Given wrapper.init(); ArgumentCaptor captor = ArgumentCaptor.forClass(DistanceReporter.class); - Mockito.verify(loadBalancingPolicy).init(anySet(), captor.capture()); + Mockito.verify(loadBalancingPolicy) + .init(anyMap(), captor.capture(), eq(contactPointsMap.keySet())); DistanceReporter distanceReporter = captor.getValue(); // When @@ -188,7 +192,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted }; Mockito.doAnswer(mockInit) .when(loadBalancingPolicy) - .init(anySet(), any(DistanceReporter.class)); + .init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); // When Runnable runnable = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java new file mode 100644 index 00000000000..bda9b1c50d5 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +import static com.datastax.oss.driver.Assertions.assertThat; + +public class ArrayUtilsTest { + + @Test + public void should_swap() { + String[] array = {"a", "b", "c"}; + ArrayUtils.swap(array, 0, 2); + assertThat(array).containsExactly("c", "b", "a"); + } + + @Test + public void should_swap_with_same_index() { + String[] array = {"a", "b", "c"}; + ArrayUtils.swap(array, 0, 0); + assertThat(array).containsExactly("a", "b", "c"); + } + + @Test + public void should_bubble_up() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleUp(array, 3, 1); + assertThat(array).containsExactly("a", "d", "b", "c", "e"); + } + + @Test + public void should_bubble_up_to_same_index() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleUp(array, 3, 3); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } + + @Test + public void should_not_bubble_up_when_target_index_higher() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleUp(array, 3, 5); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } + + @Test + public void should_shuffle_head() { + // Testing for randomness is hard, so we call the method a large number of times, and check that + // we get all permutations with a decent distribution. + Map, Integer> counts = new HashMap<>(); + for (int i = 0; i < 6000; i++) { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.shuffleHead(array, 3); + + // Tail elements should not move + assertThat(array[3]).isEqualTo("d"); + assertThat(array[4]).isEqualTo("e"); + + List permutation = ImmutableList.of(array[0], array[1], array[2]); + counts.merge(permutation, 1, (a, b) -> a + b); + } + + assertThat(counts).hasSize(6); + for (Integer count : counts.values()) { + assertThat(count).isBetween(900, 1100); + } + } + + @Test(expected = ArrayIndexOutOfBoundsException.class) + public void should_fail_to_shuffle_head_when_count_is_too_high() { + ArrayUtils.shuffleHead(new String[] {"a", "b", "c"}, 5); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java new file mode 100644 index 00000000000..da80c4f9a7c --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -0,0 +1,216 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.loadbalancing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.metadata.TokenMap; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + +public class DefaultLoadBalancingPolicyIT { + + private static final String LOCAL_DC = "dc1"; + + @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(5, 5).build(); + + @ClassRule + public static ClusterRule clusterRule = + ClusterRule.builder(ccmRule) + .withKeyspace(false) + .withDefaultSession(true) + .withOptions("request.timeout = 30 seconds") + .build(); + + @BeforeClass + public static void setup() { + CqlSession session = clusterRule.session(); + session.execute( + "CREATE KEYSPACE test " + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3, 'dc2': 3}"); + session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); + } + + @Test + public void should_ignore_remote_dcs() { + for (Node node : clusterRule.cluster().getMetadata().getNodes().values()) { + if (LOCAL_DC.equals(node.getDatacenter())) { + assertThat(node.getDistance()).isEqualTo(NodeDistance.LOCAL); + assertThat(node.getState()).isEqualTo(NodeState.UP); + // 1 regular connection, maybe 1 control connection + assertThat(node.getOpenConnections()).isBetween(1, 2); + assertThat(node.isReconnecting()).isFalse(); + } else { + assertThat(node.getDistance()).isEqualTo(NodeDistance.IGNORED); + assertThat(node.getOpenConnections()).isEqualTo(0); + assertThat(node.isReconnecting()).isFalse(); + } + } + } + + @Test + public void should_use_round_robin_on_local_dc_when_not_enough_routing_information() { + ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); + TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + // TODO add statements with setKeyspace when that is supported + List statements = + ImmutableList.of( + // No information at all + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1"), + // Keyspace present, missing routing key + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKeyspace(CqlIdentifier.fromCql("test")), + // Routing key present, missing keyspace + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKey(routingKey), + // Routing token present, missing keyspace + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingToken(tokenMap.newToken(routingKey))); + + for (Statement statement : statements) { + List coordinators = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + ResultSet rs = clusterRule.session().execute(statement); + Node coordinator = rs.getExecutionInfo().getCoordinator(); + assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); + coordinators.add(coordinator); + } + for (int i = 0; i < 5; i++) { + assertThat(coordinators.get(i)) + .isEqualTo(coordinators.get(5 + i)) + .isEqualTo(coordinators.get(10 + i)); + } + } + } + + @Test + public void should_prioritize_replicas_when_routing_information_present() { + CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); + ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); + TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + Set localReplicas = new HashSet<>(); + for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { + if (replica.getDatacenter().equals(LOCAL_DC)) { + localReplicas.add(replica); + } + } + assertThat(localReplicas).hasSize(3); + + // TODO add statements with setKeyspace when that is supported + List statements = + ImmutableList.of( + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKeyspace(keyspace) + .setRoutingKey(routingKey), + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKeyspace(keyspace) + .setRoutingToken(tokenMap.newToken(routingKey))); + + for (Statement statement : statements) { + // Since the exact order is randomized, just run a bunch of queries and check that we get a + // reasonable distribution: + Map hits = new HashMap<>(); + for (int i = 0; i < 3000; i++) { + ResultSet rs = clusterRule.session().execute(statement); + Node coordinator = rs.getExecutionInfo().getCoordinator(); + assertThat(localReplicas).contains(coordinator); + assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); + hits.merge(coordinator, 1, (a, b) -> a + b); + } + + for (Integer count : hits.values()) { + assertThat(count).isCloseTo(1000, withinPercentage(10)); + } + } + } + + @Test + public void should_hit_non_replicas_when_routing_information_present_but_all_replicas_down() { + CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); + ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); + TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + + InternalDriverContext context = (InternalDriverContext) clusterRule.cluster().getContext(); + + Set localReplicas = new HashSet<>(); + for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { + if (replica.getDatacenter().equals(LOCAL_DC)) { + localReplicas.add(replica); + context.eventBus().fire(TopologyEvent.forceDown(replica.getConnectAddress())); + ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isZero()) + .becomesTrue(); + } + } + assertThat(localReplicas).hasSize(3); + + // TODO add statements with setKeyspace when that is supported + List statements = + ImmutableList.of( + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKeyspace(keyspace) + .setRoutingKey(routingKey), + SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1") + .setRoutingKeyspace(keyspace) + .setRoutingToken(tokenMap.newToken(routingKey))); + + for (Statement statement : statements) { + List coordinators = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + ResultSet rs = clusterRule.session().execute(statement); + Node coordinator = rs.getExecutionInfo().getCoordinator(); + coordinators.add(coordinator); + assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); + assertThat(localReplicas).doesNotContain(coordinator); + } + // Should round-robin on the two non-replicas + for (int i = 0; i < 2; i++) { + assertThat(coordinators.get(i)) + .isEqualTo(coordinators.get(2 + i)) + .isEqualTo(coordinators.get(4 + i)); + } + } + + for (Node replica : localReplicas) { + context.eventBus().fire(TopologyEvent.forceUp(replica.getConnectAddress())); + ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isPositive()) + .becomesTrue(); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index c9782600115..47587ce0bd4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; @@ -33,15 +34,18 @@ import com.datastax.oss.simulacron.common.stubbing.CloseType; import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.RejectScope; +import com.google.common.collect.Sets; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -67,7 +71,10 @@ public class NodeStateIT { ClusterRule.builder(simulacron) .withOptions( "connection.pool.local.size = 2", - "connection.reconnection-policy.max-delay = 1 second") + "connection.reconnection-policy.max-delay = 1 second", + String.format( + "load-balancing-policy.filter.class = \"%s$CustomNodeFilter\"", + NodeStateIT.class.getName())) .withNodeStateListeners(nodeStateListener) .build(); @@ -222,9 +229,11 @@ public void should_bring_node_back_up_when_reconnection_succeeds() { @Test public void should_apply_up_and_down_topology_events_when_ignored() { + CustomNodeFilter.IGNORED_NODES.add(metadataRegularNode); driverContext .loadBalancingPolicyWrapper() .setDistance(metadataRegularNode, NodeDistance.IGNORED); + ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -260,9 +269,12 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .hasOpenConnections(0) .isNotReconnecting()) .as("SUGGEST_UP event applied") - .before(10, TimeUnit.SECONDS) + .before(10, TimeUnit.MINUTES) .becomesTrue(); inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); + + CustomNodeFilter.IGNORED_NODES.clear(); + driverContext.loadBalancingPolicyWrapper().setDistance(metadataRegularNode, NodeDistance.LOCAL); } @Test @@ -358,9 +370,11 @@ public void should_force_down_when_not_ignored() throws InterruptedException { @Test public void should_force_down_when_ignored() throws InterruptedException { + CustomNodeFilter.IGNORED_NODES.add(metadataRegularNode); driverContext .loadBalancingPolicyWrapper() .setDistance(metadataRegularNode, NodeDistance.IGNORED); + driverContext.eventBus().fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> @@ -399,6 +413,7 @@ public void should_force_down_when_ignored() throws InterruptedException { .becomesTrue(); inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); + CustomNodeFilter.IGNORED_NODES.clear(); driverContext.loadBalancingPolicyWrapper().setDistance(metadataRegularNode, NodeDistance.LOCAL); } @@ -595,4 +610,16 @@ private static synchronized int findAvailablePort() throws RuntimeException { } } } + + // Hack to allow tests to dynamically configure which nodes should be ignored + public static class CustomNodeFilter implements Predicate { + public static Set IGNORED_NODES = Sets.newConcurrentHashSet(); + + public CustomNodeFilter(@SuppressWarnings("unused") DriverContext context) {} + + @Override + public boolean test(Node node) { + return !IGNORED_NODES.contains(node); + } + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java index f4eb024d44e..faa7d637713 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import java.nio.ByteBuffer; import java.util.Map; @@ -43,7 +45,22 @@ public DriverConfigProfile getConfigProfile() { } @Override - public String getKeyspace() { + public CqlIdentifier getKeyspace() { + return null; + } + + @Override + public CqlIdentifier getRoutingKeyspace() { + return null; + } + + @Override + public ByteBuffer getRoutingKey() { + return null; + } + + @Override + public Token getRoutingToken() { return null; } diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf index 51275634afa..bf6c9e7854b 100644 --- a/integration-tests/src/test/resources/application.conf +++ b/integration-tests/src/test/resources/application.conf @@ -1,3 +1,4 @@ +# Configuration overrides for integration tests datastax-java-driver { connection { init-query-timeout = 5 seconds @@ -6,4 +7,11 @@ datastax-java-driver { control-connection.timeout = 5 seconds } request.trace.interval = 1 second + load-balancing-policy { + # Since our test infra always specifies explicit contact points, we need to set the local DC as + # well. + # Note that we rely on a hack to ensure that this name is always the same, even with one DC (see + # CcmBridge). + local-datacenter = dc1 + } } \ No newline at end of file diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 1e2cc7dd282..457b288532e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -107,7 +107,17 @@ private CcmBridge( Collection jvmArgs) { this.directory = directory; this.cassandraVersion = cassandraVersion; - this.nodes = nodes; + if (nodes.length == 1) { + // Hack to ensure that the default DC is always called 'dc1': pass a list ('-nX:0') even if + // there is only one DC (with '-nX', CCM configures `SimpleSnitch`, which hard-codes the name + // to 'datacenter1') + int[] tmp = new int[2]; + tmp[0] = nodes[0]; + tmp[1] = 0; + this.nodes = tmp; + } else { + this.nodes = nodes; + } this.ipPrefix = ipPrefix; this.initialConfiguration = initialConfiguration; this.createOptions = createOptions; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index d7bb54ec06c..3b66ed96ef1 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -20,8 +20,12 @@ import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.LinkedList; +import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.TreeSet; @@ -61,13 +65,16 @@ public SortingLoadBalancingPolicy(DriverContext context) { public SortingLoadBalancingPolicy() {} @Override - public void init(Set nodes, DistanceReporter distanceReporter) { - this.nodes.addAll(nodes); + public void init( + Map nodes, + DistanceReporter distanceReporter, + Set contactPoints) { + this.nodes.addAll(nodes.values()); this.nodes.forEach(n -> distanceReporter.setDistance(n, NodeDistance.LOCAL)); } @Override - public Queue newQueryPlan() { + public Queue newQueryPlan(Request request, Session session) { return new LinkedList<>(nodes); } From 22d382deed753a16f8b32ff3b626afc099e975e8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 21 Nov 2017 14:40:36 -0800 Subject: [PATCH 278/742] JAVA-1673: Remove schema agreement check when repreparing on up --- changelog/README.md | 1 + .../internal/core/session/ReprepareOnUp.java | 62 +++++++------------ .../core/session/ReprepareOnUpTest.java | 4 -- manual/core/metadata/schema/README.md | 2 - 4 files changed, 24 insertions(+), 45 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 366f498ece1..a643f4f440f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1673: Remove schema agreement check when repreparing on up - [new feature] JAVA-1526: Provide a single load balancing policy implementation - [improvement] JAVA-1680: Improve error message on batch log write timeout - [improvement] JAVA-1675: Remove dates from copyright headers diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index abace58fed3..7f8218f74ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -108,45 +108,29 @@ void start() { LOG.debug("[{}] No channel available to reprepare, done", logPrefix); whenPrepared.run(); } else { - topologyMonitor - .checkSchemaAgreement() - .whenComplete( - (agreed, error) -> { - if (error != null) { - LOG.debug( - "[{}] Error while checking schema agreement, proceeding anyway", - logPrefix, - error); - } else if (!agreed) { - LOG.debug("[{}] Did not reach schema agreement, proceeding anyway", logPrefix); - } - // Check log level because ConcurrentMap.size is not a constant operation - if (LOG.isDebugEnabled()) { - LOG.debug( - "[{}] {} statements to reprepare on newly added/up node", - logPrefix, - repreparePayloads.size()); - } - if (checkSystemTable) { - LOG.debug("[{}] Checking which statements the server knows about", logPrefix); - queryAsync( - QUERY_SERVER_IDS, - Collections.emptyMap(), - "QUERY system.prepared_statements") - .whenComplete(this::gatherServerIds); - } else { - LOG.debug( - "[{}] {} is disabled, repreparing directly", - logPrefix, - CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); - RunOrSchedule.on( - channel.eventLoop(), - () -> { - serverKnownIds = Collections.emptySet(); - gatherPayloadsToReprepare(); - }); - } - }); + // Check log level because ConcurrentMap.size is not a constant operation + if (LOG.isDebugEnabled()) { + LOG.debug( + "[{}] {} statements to reprepare on newly added/up node", + logPrefix, + repreparePayloads.size()); + } + if (checkSystemTable) { + LOG.debug("[{}] Checking which statements the server knows about", logPrefix); + queryAsync(QUERY_SERVER_IDS, Collections.emptyMap(), "QUERY system.prepared_statements") + .whenComplete(this::gatherServerIds); + } else { + LOG.debug( + "[{}] {} is disabled, repreparing directly", + logPrefix, + CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); + RunOrSchedule.on( + channel.eventLoop(), + () -> { + serverKnownIds = Collections.emptySet(); + gatherPayloadsToReprepare(); + }); + } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index dcda0e6df5d..ff7aa0c9e9d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -85,10 +85,6 @@ public void setup() { .thenReturn(100); Mockito.when(context.config()).thenReturn(config); - Mockito.when(topologyMonitor.checkSchemaAgreement()) - .thenReturn(CompletableFuture.completedFuture(true)); - Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); - done = new CompletableFuture<>(); whenPrepared = () -> ((CompletableFuture) done).complete(null); } diff --git a/manual/core/metadata/schema/README.md b/manual/core/metadata/schema/README.md index e4e95cf80f1..395bdb72602 100644 --- a/manual/core/metadata/schema/README.md +++ b/manual/core/metadata/schema/README.md @@ -151,8 +151,6 @@ To avoid this issue, the driver waits until all nodes agree on a common schema v Schema agreement is checked: * before a schema refresh; -* before [repreparing all queries](../../statements/prepared#how-the-driver-prepares) on a newly up - node; * before completing a successful schema-altering query (like in our example above). It is done by querying system tables to find out the schema version of all nodes that are currently From 61630b3cf52328099b27bea4bb6aee699ce47032 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 27 Nov 2017 10:50:45 -0800 Subject: [PATCH 279/742] JAVA-1678: Warn if auth is configured on the client but not the server --- changelog/README.md | 1 + .../api/core/config/CoreDriverOption.java | 1 + .../core/channel/ProtocolInitHandler.java | 24 +++++-- core/src/main/resources/reference.conf | 11 ++++ .../core/channel/ProtocolInitHandlerTest.java | 62 +++++++++++++++++++ 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index a643f4f440f..f1c6b4dfb62 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1678: Warn if auth is configured on the client but not the server - [improvement] JAVA-1673: Remove schema agreement check when repreparing on up - [new feature] JAVA-1526: Provide a single load balancing policy implementation - [improvement] JAVA-1680: Improve error message on batch log write timeout diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index 0bb0a7484b1..e612e6c7a32 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -85,6 +85,7 @@ public enum CoreDriverOption implements DriverOption { AUTH_PROVIDER_CLASS("protocol.auth-provider.class", false), AUTH_PROVIDER_USER_NAME("protocol.auth-provider.username", false), AUTH_PROVIDER_PASSWORD("protocol.auth-provider.password", false), + AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH("protocol.auth-provider.warn-if-no-server-auth", false), SSL_ENGINE_FACTORY_CLASS("ssl-engine-factory.class", false), SSL_CIPHER_SUITES("ssl-engine-factory.cipher-suites", false), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index d70790b9a3a..3d07fd22372 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -57,8 +57,9 @@ class ProtocolInitHandler extends ConnectInitHandler { private static final Query CLUSTER_NAME_QUERY = new Query("SELECT cluster_name FROM system.local"); - private final InternalDriverContext internalDriverContext; + private final InternalDriverContext context; private final long timeoutMillis; + private final boolean warnIfNoServerAuth; private final ProtocolVersion initialProtocolVersion; private final DriverChannelOptions options; // might be null if this is the first channel to this cluster @@ -68,18 +69,20 @@ class ProtocolInitHandler extends ConnectInitHandler { private ChannelHandlerContext ctx; ProtocolInitHandler( - InternalDriverContext internalDriverContext, + InternalDriverContext context, ProtocolVersion protocolVersion, String expectedClusterName, DriverChannelOptions options, HeartbeatHandler heartbeatHandler) { - this.internalDriverContext = internalDriverContext; + this.context = context; - DriverConfigProfile defaultConfig = internalDriverContext.config().getDefaultProfile(); + DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); this.timeoutMillis = defaultConfig.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); + this.warnIfNoServerAuth = + defaultConfig.getBoolean(CoreDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH); this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; this.options = options; @@ -140,7 +143,7 @@ String describe() { Message getRequest() { switch (step) { case STARTUP: - return new Startup(internalDriverContext.compressor().algorithm()); + return new Startup(context.compressor().algorithm()); case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: @@ -163,6 +166,15 @@ void onResponse(Message response) { ProtocolUtils.opcodeString(response.opcode)); try { if (step == Step.STARTUP && response instanceof Ready) { + if (warnIfNoServerAuth && context.authProvider().isPresent()) { + LOG.warn( + "[{}] {} did not send an authentication challenge; " + + "This is suspicious because the driver expects authentication " + + "(configured auth provider = {})", + logPrefix, + channel.remoteAddress(), + context.authProvider().get().getClass().getName()); + } step = Step.GET_CLUSTER_NAME; send(); } else if (step == Step.STARTUP && response instanceof Authenticate) { @@ -292,7 +304,7 @@ void fail(String message, Throwable cause) { } private Authenticator buildAuthenticator(SocketAddress address, String authenticator) { - return internalDriverContext + return context .authProvider() .map(p -> p.newAuthenticator(address, authenticator)) .orElseThrow( diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index d39e0da6ff8..d2dfeebc3f6 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -65,6 +65,17 @@ datastax-java-driver { # Sample configuration for the plain-text provider: // username = cassandra // password = cassandra + + # Whether to log a warning if an authentication provider is configured on the driver side, but + # the server does not issue an authentication challenge (i.e. lets the driver connect without + # any form of authentication). + # + # This is intended as a help to detect server configuration issues. The warning will be logged + # for every faulty node, and each time a new connection is created. + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. + warn-if-no-server-auth = true } # The compressor to use for protocol frames. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index b9690999ddf..a9ee0a8e621 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.channel; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; @@ -49,13 +53,19 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.filter.Filters; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.atLeast; public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @@ -65,9 +75,13 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private DriverConfig driverConfig; @Mock private DriverConfigProfile defaultConfigProfile; @Mock private Compressor compressor; + @Mock private Appender appender; + @Captor private ArgumentCaptor loggingEventCaptor; + private ProtocolVersionRegistry protocolVersionRegistry = new CassandraProtocolVersionRegistry("test"); private HeartbeatHandler heartbeatHandler; + private Logger logger; @Before @Override @@ -80,6 +94,9 @@ public void setup() { .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofMillis(30000)); + Mockito.when( + defaultConfigProfile.getBoolean(CoreDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH)) + .thenReturn(true); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); Mockito.when(internalDriverContext.compressor()).thenReturn(compressor); @@ -99,6 +116,14 @@ public void setup() { "test")); heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + + logger = (Logger) LoggerFactory.getLogger(ProtocolInitHandler.class); + logger.addAppender(appender); + } + + @After + public void teardown() { + logger.detachAppender(appender); } @Test @@ -290,6 +315,43 @@ public void should_initialize_with_authentication() { assertThat(connectFuture).isSuccess(); } + @Test + public void should_warn_if_auth_configured_but_server_does_not_send_challenge() { + channel + .pipeline() + .addLast( + "init", + new ProtocolInitHandler( + internalDriverContext, + CoreProtocolVersion.V4, + null, + DriverChannelOptions.DEFAULT, + heartbeatHandler)); + + AuthProvider authProvider = Mockito.mock(AuthProvider.class); + Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); + + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + + Frame requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Startup.class); + + // Simulate a READY response, a warning should be logged + writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); + Mockito.verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + Iterable warnLogs = + Filters.filter(loggingEventCaptor.getAllValues()).with("level", Level.WARN).get(); + assertThat(warnLogs).hasSize(1); + assertThat(warnLogs.iterator().next().getFormattedMessage()) + .contains("did not send an authentication challenge"); + + // Apart from the warning, init should proceed normally + requestFrame = readOutboundFrame(); + assertThat(requestFrame.message).isInstanceOf(Query.class); + writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("someClusterName")); + assertThat(connectFuture).isSuccess(); + } + @Test public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwable { channel From 3088de2e9e156d8934adc86d53c8e6d2364cef16 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 8 Dec 2017 10:28:25 -0800 Subject: [PATCH 280/742] Revisit LBP shuffling --- .../DefaultLoadBalancingPolicy.java | 36 ++++--------- .../driver/internal/core/util/ArrayUtils.java | 22 ++++++++ ...faultLoadBalancingPolicyQueryPlanTest.java | 40 +------------- .../internal/core/util/ArrayUtilsTest.java | 54 +++++++++++++++++++ 4 files changed, 87 insertions(+), 65 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 7fd24ebebbb..5a1d16803ad 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -28,8 +28,6 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.ArrayUtils; import com.datastax.oss.driver.internal.core.util.Reflection; import com.google.common.annotations.VisibleForTesting; @@ -58,7 +56,7 @@ public class DefaultLoadBalancingPolicy implements LoadBalancingPolicy { private final String logPrefix; private final MetadataManager metadataManager; private final Predicate filter; - private final AtomicInteger startIndex = new AtomicInteger(); + private final AtomicInteger roundRobinAmount = new AtomicInteger(); @VisibleForTesting final CopyOnWriteArraySet localDcLiveNodes = new CopyOnWriteArraySet<>(); private volatile DistanceReporter distanceReporter; @@ -167,28 +165,20 @@ public Queue newQueryPlan(Request request, Session session) { if (replicaCount > 1) { shuffleHead(currentNodes, replicaCount); } - - // Power of 2 choices: order the first two nodes by increasing load - if (replicaCount > 2 - && getAvailableIds((Node) currentNodes[1], session) - > getAvailableIds((Node) currentNodes[0], session)) { - ArrayUtils.swap(currentNodes, 0, 1); - } } LOG.trace("[{}] Prioritizing {} local replicas", logPrefix, replicaCount); - ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); - // Copy the replicas as-is (we've already shuffled/ordered them) - for (int i = 0; i < replicaCount; i++) { - queryPlan.offer((Node) currentNodes[i]); - } // Round-robin the remaining nodes - int remaining = currentNodes.length - replicaCount; - int myStartIndex = startIndex.getAndUpdate(INCREMENT); - for (int i = 0; i < remaining; i++) { - Node node = (Node) currentNodes[replicaCount + (myStartIndex + i) % remaining]; - queryPlan.offer(node); + ArrayUtils.rotate( + currentNodes, + replicaCount, + currentNodes.length - replicaCount, + roundRobinAmount.getAndUpdate(INCREMENT)); + + ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); + for (Object currentNode : currentNodes) { + queryPlan.offer((Node) currentNode); } return queryPlan; } @@ -234,12 +224,6 @@ protected void shuffleHead(Object[] currentNodes, int replicaCount) { ArrayUtils.shuffleHead(currentNodes, replicaCount); } - private int getAvailableIds(Node node, Session session) { - // The cast will always succeed because there's no way to replace the internal session impl - ChannelPool pool = ((DefaultSession) session).getPools().get(node); - return (pool == null) ? 0 : pool.getAvailableIds(); - } - @Override public void onAdd(Node node) { if (filter.test(node)) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 7cec3b93ebf..896b854c1a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -37,6 +37,16 @@ public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) } } + /** + * Moves an element towards the end of the array, shifting all the intermediary elements to the + * left (no-op if targetIndex <= sourceIndex). + */ + public static void bubbleDown(T[] elements, int sourceIndex, int targetIndex) { + for (int i = sourceIndex; i < targetIndex; i++) { + swap(elements, i, i + 1); + } + } + /** * Shuffles the first n elements of the array in-place. * @@ -53,4 +63,16 @@ public static void shuffleHead(T[] elements, int n) { } } } + + /** Rotates the elements in the specified range by the specified amount (round-robin). */ + public static void rotate(T[] elements, int startIndex, int length, int amount) { + if (length >= 2) { + amount = amount % length; + // Repeatedly shift by 1. This is not the most time-efficient but the array will typically be + // small so we don't care, and this avoids allocating a temporary buffer. + for (int i = 0; i < amount; i++) { + bubbleDown(elements, startIndex, startIndex + length - 1); + } + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 12cbcbb6952..01a5eaa31d7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -57,7 +57,6 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin @Mock private Metadata metadata; @Mock private TokenMap tokenMap; - private Map pools; private DefaultLoadBalancingPolicy policy; @Before @@ -68,9 +67,6 @@ public void setup() { Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); Mockito.when(metadata.getTokenMap()).thenReturn(Optional.of(tokenMap)); - pools = new HashMap<>(); - Mockito.when(session.getPools()).thenReturn(pools); - // Use a subclass to disable shuffling, we just spy to make sure that the shuffling method was // called (makes tests easier) policy = Mockito.spy(new NonShufflingPolicy("dc1", filter, context)); @@ -172,7 +168,7 @@ public void should_prioritize_single_replica() { } @Test - public void should_prioritize_and_shuffle_two_replicas() { + public void should_prioritize_and_shuffle_replicas() { Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)) @@ -190,40 +186,6 @@ public void should_prioritize_and_shuffle_two_replicas() { Mockito.verify(session, never()).getPools(); } - @Test - public void should_use_power_of_two_choices_for_three_or_more_replicas() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); - Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); - Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)) - .thenReturn(ImmutableSet.of(node3, node4, node5)); - - // node3 and node4 will always be the first two replicas (since shuffling is disabled), they - // should get ordered by increasing available ids - mockPoolWithAvailableIds(node3, 200); - mockPoolWithAvailableIds(node4, 100); - - assertThat(policy.newQueryPlan(request, session)) - .containsExactly(node3, node4, node5, node1, node2); - assertThat(policy.newQueryPlan(request, session)) - .containsExactly(node3, node4, node5, node2, node1); - Mockito.verify(policy, times(2)).shuffleHead(any(), eq(3)); - - mockPoolWithAvailableIds(node3, 100); - mockPoolWithAvailableIds(node4, 200); - - assertThat(policy.newQueryPlan(request, session)) - .containsExactly(node4, node3, node5, node1, node2); - assertThat(policy.newQueryPlan(request, session)) - .containsExactly(node4, node3, node5, node2, node1); - Mockito.verify(policy, times(4)).shuffleHead(any(), eq(3)); - } - - private void mockPoolWithAvailableIds(Node node, int availableIds) { - ChannelPool pool = Mockito.mock(ChannelPool.class); - Mockito.when(pool.getAvailableIds()).thenReturn(availableIds); - pools.put(node, pool); - } - static class NonShufflingPolicy extends DefaultLoadBalancingPolicy { NonShufflingPolicy( String localDcFromConfig, Predicate filterFromConfig, DriverContext context) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java index bda9b1c50d5..26bce21c2a7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -60,6 +60,27 @@ public void should_not_bubble_up_when_target_index_higher() { assertThat(array).containsExactly("a", "b", "c", "d", "e"); } + @Test + public void should_bubble_down() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleDown(array, 1, 3); + assertThat(array).containsExactly("a", "c", "d", "b", "e"); + } + + @Test + public void should_bubble_down_to_same_index() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleDown(array, 3, 3); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } + + @Test + public void should_not_bubble_down_when_target_index_lower() { + String[] array = {"a", "b", "c", "d", "e"}; + ArrayUtils.bubbleDown(array, 4, 2); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } + @Test public void should_shuffle_head() { // Testing for randomness is hard, so we call the method a large number of times, and check that @@ -87,4 +108,37 @@ public void should_shuffle_head() { public void should_fail_to_shuffle_head_when_count_is_too_high() { ArrayUtils.shuffleHead(new String[] {"a", "b", "c"}, 5); } + + @Test + public void should_rotate() { + String[] array = {"a", "b", "c", "d", "e"}; + + ArrayUtils.rotate(array, 1, 3, 1); + assertThat(array).containsExactly("a", "c", "d", "b", "e"); + + ArrayUtils.rotate(array, 0, 4, 2); + assertThat(array).containsExactly("d", "b", "a", "c", "e"); + + ArrayUtils.rotate(array, 2, 3, 10); + assertThat(array).containsExactly("d", "b", "c", "e", "a"); + } + + @Test + public void should_not_rotate_when_amount_multiple_of_range_size() { + String[] array = {"a", "b", "c", "d", "e"}; + + ArrayUtils.rotate(array, 1, 3, 9); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } + + @Test + public void should_not_rotate_when_range_is_singleton_or_empty() { + String[] array = {"a", "b", "c", "d", "e"}; + + ArrayUtils.rotate(array, 1, 1, 3); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + + ArrayUtils.rotate(array, 1, 0, 3); + assertThat(array).containsExactly("a", "b", "c", "d", "e"); + } } From 15894e1706403fd023ee5856169a6731957a3d25 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 30 Oct 2017 16:53:23 -0700 Subject: [PATCH 281/742] Allow custom Cassandra directory in CcmBridge --- .../driver/api/testinfra/ccm/CcmBridge.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 457b288532e..c881b7d5f49 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -50,8 +50,9 @@ public class CcmBridge implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(CcmBridge.class); private final CassandraVersion cassandraVersion; + private final String cassandraDirectory; - private final int nodes[]; + private final int[] nodes; private final Path directory; @@ -69,6 +70,9 @@ public class CcmBridge implements AutoCloseable { public static final CassandraVersion DEFAULT_CASSANDRA_VERSION = CassandraVersion.parse(System.getProperty("ccm.cassandraVersion", "3.11.0")); + public static final String DEFAULT_CASSANDRA_DIRECTORY = + System.getProperty("ccm.cassandraDirectory"); + public static final String DEFAULT_CLIENT_TRUSTSTORE_PASSWORD = "cassandra1sfun"; public static final String DEFAULT_CLIENT_TRUSTSTORE_PATH = "/client.truststore"; @@ -100,6 +104,7 @@ public class CcmBridge implements AutoCloseable { private CcmBridge( Path directory, CassandraVersion cassandraVersion, + String cassandraDirectory, int[] nodes, String ipPrefix, Map initialConfiguration, @@ -107,6 +112,7 @@ private CcmBridge( Collection jvmArgs) { this.directory = directory; this.cassandraVersion = cassandraVersion; + this.cassandraDirectory = cassandraDirectory; if (nodes.length == 1) { // Hack to ensure that the default DC is always called 'dc1': pass a list ('-nX:0') even if // there is only one DC (with '-nX', CCM configures `SimpleSnitch`, which hard-codes the name @@ -141,11 +147,14 @@ public CassandraVersion getCassandraVersion() { public void create() { if (created.compareAndSet(false, true)) { + if (cassandraDirectory != null) { + createOptions.add("--install-dir=" + new File(cassandraDirectory).getAbsolutePath()); + } else { + createOptions.add("-v " + cassandraVersion.toString()); + } execute( "create", "ccm_1", - "-v", - cassandraVersion.toString(), "-i", ipPrefix, "-n", @@ -272,6 +281,7 @@ public static class Builder { private final List jvmArgs = new ArrayList<>(); private String ipPrefix = "127.0.0."; private CassandraVersion cassandraVersion = CcmBridge.DEFAULT_CASSANDRA_VERSION; + private String cassandraDirectory = CcmBridge.DEFAULT_CASSANDRA_DIRECTORY; private final List createOptions = new ArrayList<>(); private final Path directory; @@ -302,6 +312,11 @@ public Builder withCassandraVersion(CassandraVersion cassandraVersion) { return this; } + public Builder withCassandraDirectory(String cassandraDirectory) { + this.cassandraDirectory = cassandraDirectory; + return this; + } + public Builder withNodes(int... nodes) { this.nodes = nodes; return this; @@ -346,6 +361,7 @@ public CcmBridge build() { return new CcmBridge( directory, cassandraVersion, + cassandraDirectory, nodes, ipPrefix, cassandraConfiguration, From 05aa94c4844ff7ff9aeefea28bbfec4e92048f59 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 30 Oct 2017 10:48:39 -0700 Subject: [PATCH 282/742] JAVA-1633: Handle per-request keyspace in protocol v5 --- changelog/README.md | 1 + .../driver/api/core/cql/BatchStatement.java | 18 ++ .../api/core/cql/BatchStatementBuilder.java | 10 +- .../driver/api/core/cql/BoundStatement.java | 10 ++ .../api/core/cql/BoundStatementBuilder.java | 4 +- .../driver/api/core/cql/SimpleStatement.java | 14 ++ .../api/core/cql/SimpleStatementBuilder.java | 9 + .../oss/driver/api/core/cql/Statement.java | 17 +- .../driver/api/core/cql/StatementBuilder.java | 1 - .../oss/driver/api/core/session/Request.java | 7 +- .../CassandraProtocolVersionRegistry.java | 2 + .../driver/internal/core/ProtocolFeature.java | 7 + .../driver/internal/core/cql/Conversions.java | 17 +- .../core/cql/CqlPrepareHandlerBase.java | 15 +- .../core/cql/DefaultBatchStatement.java | 30 +++- .../core/cql/DefaultBoundStatement.java | 19 -- .../core/cql/DefaultPreparedStatement.java | 6 +- .../core/cql/DefaultSimpleStatement.java | 19 ++ .../internal/core/session/DefaultSession.java | 4 - .../internal/core/session/ReprepareOnUp.java | 3 +- .../api/core/cql/PerRequestKeyspaceIT.java | 170 ++++++++++++++++++ 21 files changed, 331 insertions(+), 52 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java diff --git a/changelog/README.md b/changelog/README.md index f1c6b4dfb62..9487f637813 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [new feature] JAVA-1633: Handle per-request keyspace in protocol v5 - [improvement] JAVA-1678: Warn if auth is configured on the client but not the server - [improvement] JAVA-1673: Remove schema agreement check when repreparing on up - [new feature] JAVA-1526: Provide a single load balancing policy implementation diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 55519c25383..971d7eb992e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -93,6 +96,21 @@ static BatchStatementBuilder builder(BatchStatement template) { */ BatchStatement setBatchType(BatchType newBatchType); + /** + * Sets the CQL keyspace to associate with this batch. + * + *

          If the keyspace is not set explicitly with this method, it will be inferred from the first + * simple statement in the batch that has a keyspace set (or will be null if no such statement + * exists). + * + *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime + * error. + * + * @see Request#getKeyspace() + */ + BatchStatement setKeyspace(CqlIdentifier newKeyspace); + /** * Adds a new statement to the batch. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index c7e73274845..cc0ac0092af 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -24,6 +25,7 @@ public class BatchStatementBuilder extends StatementBuilder { private BatchType batchType; + private CqlIdentifier keyspace; private ImmutableList.Builder> statementsBuilder; private int statementsCount; @@ -39,6 +41,12 @@ public BatchStatementBuilder(BatchStatement template) { this.statementsCount = template.size(); } + /** @see BatchStatement#getKeyspace() */ + public BatchStatementBuilder withKeyspace(CqlIdentifier keyspace) { + this.keyspace = keyspace; + return this; + } + /** @see BatchStatement#add(BatchableStatement) */ public BatchStatementBuilder addStatement(BatchableStatement statement) { if (statementsCount >= 0xFFFF) { @@ -80,7 +88,7 @@ public BatchStatement build() { statementsBuilder.build(), configProfileName, configProfile, - null, + keyspace, routingKeyspace, routingKey, routingToken, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index cf2dc82ec1e..2159691f1f1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import java.nio.ByteBuffer; import java.util.List; @@ -32,4 +33,13 @@ public interface BoundStatement /** The values to bind, in their serialized form. */ List getValues(); + + /** + * Always returns null (bound statements can't have a per-request keyspace, they always inherit + * the one of the statement that was initially prepared). + */ + @Override + default CqlIdentifier getKeyspace() { + return null; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index c13ff6265cb..7e575a26f98 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -35,10 +35,12 @@ public class BoundStatementBuilder extends StatementBuilderThis feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime + * error. + * + * @see Request#getKeyspace() + */ + SimpleStatement setKeyspace(CqlIdentifier newKeyspace); + List getPositionalValues(); /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 1ca810883f4..dc0252249e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -27,6 +28,7 @@ public class SimpleStatementBuilder extends StatementBuilder { private String query; + private CqlIdentifier keyspace; private ImmutableList.Builder positionalValuesBuilder; private ImmutableMap.Builder namedValuesBuilder; @@ -51,11 +53,18 @@ public SimpleStatementBuilder(SimpleStatement template) { } } + /** @see SimpleStatement#getQuery() */ public SimpleStatementBuilder withQuery(String query) { this.query = query; return this; } + /** @see SimpleStatement#getKeyspace() */ + public SimpleStatementBuilder withKeyspace(CqlIdentifier keyspace) { + this.keyspace = keyspace; + return this; + } + /** @see SimpleStatement#setPositionalValues(List) */ public SimpleStatementBuilder addPositionalValue(Object value) { if (namedValuesBuilder != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 23e411bf802..44b6c18df9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -40,7 +41,7 @@ public interface Statement> extends Request { * *

          Most users won't use this explicitly. It is needed for the generic execute method ({@link * Session#execute(Request, GenericType)}), but CQL statements will generally be run with one of - * the driver's built-in helper methods (such as {@link Session#execute(Statement)}). + * the driver's built-in helper methods (such as {@link CqlSession#execute(Statement)}). */ GenericType SYNC = GenericType.of(ResultSet.class); @@ -49,7 +50,7 @@ public interface Statement> extends Request { * *

          Most users won't use this explicitly. It is needed for the generic execute method ({@link * Session#execute(Request, GenericType)}), but CQL statements will generally be run with one of - * the driver's built-in helper methods (such as {@link Session#executeAsync(Statement)}). + * the driver's built-in helper methods (such as {@link CqlSession#executeAsync(Statement)}). */ GenericType> ASYNC = new GenericType>() {}; @@ -73,18 +74,6 @@ public interface Statement> extends Request { */ T setConfigProfile(DriverConfigProfile newProfile); - /** - * NOT YET SUPPORTED -- sets the CQL keyspace to associate with the query. - * - *

          This will be available when CASSANDRA-10145 is merged in a - * stable server release. In the meantime, this method always throws {@link - * UnsupportedOperationException}. - */ - default T setKeyspace(CqlIdentifier newKeyspace) { - throw new UnsupportedOperationException("Per-query keyspaces are not yet supported"); - } - /** * Sets the keyspace to use for token-aware routing. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 8669b3fbb02..63d828434e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -37,7 +37,6 @@ public abstract class StatementBuilder, S exten protected String configProfileName; protected DriverConfigProfile configProfile; - protected CqlIdentifier keyspace; protected CqlIdentifier routingKeyspace; protected ByteBuffer routingKey; protected Token routingToken; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 921434b2d8b..fc8e07d194f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -54,12 +54,11 @@ public interface Request { DriverConfigProfile getConfigProfile(); /** - * NOT IMPLEMENTED YET (method added for binary compatibility, implementation coming soon) - * -- The CQL keyspace to execute this request in. + * The CQL keyspace to execute this request in. * *

          This overrides {@link Session#getKeyspace()} for this particular request, providing a way to - * specify the keyspace without forcing it globally on the session, or hard-coding it in the query - * string. + * specify the keyspace without forcing it globally on the session, nor hard-coding it in the + * query string. * *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 0754040bbc0..7dabb741566 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -170,6 +170,8 @@ public boolean supports(ProtocolVersion version, ProtocolFeature feature) { switch (feature) { case UNSET_BOUND_VALUES: return version.getCode() >= 4; + case PER_REQUEST_KEYSPACE: + return version.getCode() >= 5; default: throw new IllegalArgumentException("Unhandled protocol feature: " + feature); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java index 276a4a5e69f..d79069a40db 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java @@ -24,5 +24,12 @@ public enum ProtocolFeature { * @see CASSANDRA-7304 */ UNSET_BOUND_VALUES, + + /** + * The ability to override the keyspace on a per-request basis. + * + * @see CASSANDRA-10145 + */ + PER_REQUEST_KEYSPACE, ; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index c9dff8aad32..94354f1f492 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -107,14 +108,19 @@ static Message toMessage( CodecRegistry codecRegistry = context.codecRegistry(); ProtocolVersion protocolVersion = context.protocolVersion(); ProtocolVersionRegistry registry = context.protocolVersionRegistry(); + CqlIdentifier keyspace = statement.getKeyspace(); if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; - if (!simpleStatement.getPositionalValues().isEmpty() && !simpleStatement.getNamedValues().isEmpty()) { throw new IllegalArgumentException( "Can't have both positional and named values in a statement."); } + if (keyspace != null + && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + throw new IllegalArgumentException( + "Can't use per-request keyspace with protocol " + protocolVersion); + } QueryOptions queryOptions = new QueryOptions( consistency, @@ -125,7 +131,7 @@ static Message toMessage( statement.getPagingState(), serialConsistency, timestamp, - null); + (keyspace == null) ? null : keyspace.asInternal()); return new Query(simpleStatement.getQuery(), queryOptions); } else if (statement instanceof BoundStatement) { BoundStatement boundStatement = (BoundStatement) statement; @@ -150,6 +156,11 @@ static Message toMessage( if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(batchStatement); } + if (keyspace != null + && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + throw new IllegalArgumentException( + "Can't use per-request keyspace with protocol " + protocolVersion); + } List queriesOrIds = new ArrayList<>(batchStatement.size()); List> values = new ArrayList<>(batchStatement.size()); for (BatchableStatement child : batchStatement) { @@ -180,7 +191,7 @@ static Message toMessage( consistency, serialConsistency, timestamp, - null); + (keyspace == null) ? null : keyspace.asInternal()); } else { throw new IllegalArgumentException( "Unsupported statement type: " + statement.getClass().getName()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index e6bd5f5685f..c82c5998518 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -16,7 +16,9 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -30,6 +32,8 @@ import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException; import com.datastax.oss.driver.api.core.servererrors.ProtocolError; import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; +import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; @@ -127,7 +131,16 @@ protected CqlPrepareHandlerBase( } return null; }); - this.message = new Prepare(request.getQuery()); + ProtocolVersion protocolVersion = context.protocolVersion(); + ProtocolVersionRegistry registry = context.protocolVersionRegistry(); + CqlIdentifier keyspace = request.getKeyspace(); + if (keyspace != null + && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + throw new IllegalArgumentException( + "Can't use per-request keyspace with protocol " + protocolVersion); + } + this.message = + new Prepare(request.getQuery(), (keyspace == null) ? null : keyspace.asInternal()); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 07bad0ed8c4..8df6401da5a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchType; import com.datastax.oss.driver.api.core.cql.BatchableStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -96,6 +97,24 @@ public BatchStatement setBatchType(BatchType newBatchType) { pagingState); } + @Override + public BatchStatement setKeyspace(CqlIdentifier newKeyspace) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + newKeyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public BatchStatement add(BatchableStatement statement) { if (statements.size() >= 0xFFFF) { @@ -244,7 +263,16 @@ public DefaultBatchStatement setConfigProfile(DriverConfigProfile newProfile) { @Override public CqlIdentifier getKeyspace() { - return keyspace; + if (keyspace != null) { + return keyspace; + } else { + for (BatchableStatement statement : statements) { + if (statement instanceof SimpleStatement && statement.getKeyspace() != null) { + return statement.getKeyspace(); + } + } + } + return null; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 4209013114e..b678df5e163 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -37,7 +37,6 @@ public class DefaultBoundStatement implements BoundStatement { private final ByteBuffer[] values; private final String configProfileName; private final DriverConfigProfile configProfile; - private final CqlIdentifier keyspace; private final CqlIdentifier routingKeyspace; private final ByteBuffer routingKey; private final Token routingToken; @@ -55,7 +54,6 @@ public DefaultBoundStatement( ByteBuffer[] values, String configProfileName, DriverConfigProfile configProfile, - CqlIdentifier keyspace, CqlIdentifier routingKeyspace, ByteBuffer routingKey, Token routingToken, @@ -71,7 +69,6 @@ public DefaultBoundStatement( this.values = values; this.configProfileName = configProfileName; this.configProfile = configProfile; - this.keyspace = keyspace; this.routingKeyspace = routingKeyspace; this.routingKey = routingKey; this.routingToken = routingToken; @@ -129,7 +126,6 @@ public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { newValues, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -165,7 +161,6 @@ public BoundStatement setConfigProfileName(String newConfigProfileName) { values, newConfigProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -191,7 +186,6 @@ public BoundStatement setConfigProfile(DriverConfigProfile newConfigProfile) { values, configProfileName, newConfigProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -204,11 +198,6 @@ public BoundStatement setConfigProfile(DriverConfigProfile newConfigProfile) { protocolVersion); } - @Override - public CqlIdentifier getKeyspace() { - return keyspace; - } - @Override public CqlIdentifier getRoutingKeyspace() { // If it was set explicitly, use that value, else try to infer it from the prepared statement's @@ -229,7 +218,6 @@ public BoundStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { values, configProfileName, configProfile, - keyspace, newRoutingKeyspace, routingKey, routingToken, @@ -275,7 +263,6 @@ public BoundStatement setRoutingKey(ByteBuffer newRoutingKey) { values, configProfileName, configProfile, - keyspace, routingKeyspace, newRoutingKey, routingToken, @@ -301,7 +288,6 @@ public BoundStatement setRoutingToken(Token newRoutingToken) { values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, newRoutingToken, @@ -327,7 +313,6 @@ public BoundStatement setCustomPayload(Map newCustomPayload) values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -353,7 +338,6 @@ public BoundStatement setIdempotent(Boolean newIdempotence) { values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -379,7 +363,6 @@ public BoundStatement setTracing(boolean newTracing) { values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -405,7 +388,6 @@ public BoundStatement setTimestamp(long newTimestamp) { values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, @@ -431,7 +413,6 @@ public BoundStatement setPagingState(ByteBuffer newPagingState) { values, configProfileName, configProfile, - keyspace, routingKeyspace, routingKey, routingToken, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 04bb0356181..fabc9758c84 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -145,10 +145,11 @@ public BoundStatement bind(Object... values) { encodedValues, configProfileName, configProfile, + // If the prepared statement had a per-request keyspace, we want to use that as the routing + // keyspace. repreparePayload.keyspace, null, null, - null, customPayloadForBoundStatements, idempotent, false, @@ -160,7 +161,8 @@ public BoundStatement bind(Object... values) { @Override public BoundStatementBuilder boundStatementBuilder() { - return new BoundStatementBuilder(this, variableDefinitions, codecRegistry, protocolVersion); + return new BoundStatementBuilder( + this, variableDefinitions, repreparePayload.keyspace, codecRegistry, protocolVersion); } public RepreparePayload getRepreparePayload() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 71b102ad059..7b2a3aaf418 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -203,6 +203,25 @@ public CqlIdentifier getKeyspace() { return keyspace; } + @Override + public SimpleStatement setKeyspace(CqlIdentifier newKeyspace) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + newKeyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState); + } + @Override public CqlIdentifier getRoutingKeyspace() { return routingKeyspace; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 23c3d7d301c..ac184d72082 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -162,10 +162,6 @@ public Map getPools() { @Override public ResultT execute( RequestT request, GenericType resultType) { - if (request.getKeyspace() != null) { - // TODO CASSANDRA-10145 - throw new UnsupportedOperationException("Per-request keyspaces are not supported yet"); - } return processorRegistry .processorFor(request, resultType) .newHandler(request, this, context, logPrefix) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 7f8218f74ec..b27fb11d6fe 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -215,7 +215,8 @@ private void startWorker() { } else { RepreparePayload payload = toReprepare.poll(); queryAsync( - new Prepare(payload.query), + new Prepare( + payload.query, (payload.keyspace == null ? null : payload.keyspace.asInternal())), payload.customPayload, String.format("Reprepare '%s'", payload.query)) .handle( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java new file mode 100644 index 00000000000..05f0c87bd35 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -0,0 +1,170 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Note: at the time of writing, this test exercises features of an unreleased Cassandra version. To + * test against a local build, run with + * + *
          + *   -Dccm.cassandraVersion=4.0.0 -Dccm.cassandraDirectory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
          + * 
          + */ +@Category(ParallelizableTests.class) +public class PerRequestKeyspaceIT { + + @Rule public CcmRule ccmRule = CcmRule.getInstance(); + + @Rule public ClusterRule clusterRule = ClusterRule.builder(ccmRule).withKeyspace(true).build(); + + @Rule public ExpectedException thrown = ExpectedException.none(); + @Rule public TestName nameRule = new TestName(); + + @Before + public void setupSchema() { + CqlSession session = clusterRule.session(); + session.execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS foo (k text, cc int, v int, PRIMARY KEY(k, cc))") + .withConfigProfile(clusterRule.slowProfile()) + .build()); + } + + @Test + public void should_reject_simple_statement_with_keyspace_in_protocol_v4() { + should_reject_statement_with_keyspace_in_protocol_v4( + SimpleStatement.newInstance("SELECT * FROM foo").setKeyspace(clusterRule.keyspace())); + } + + @Test + public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4() { + SimpleStatement statementWithoutKeyspace = + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1); + should_reject_statement_with_keyspace_in_protocol_v4( + BatchStatement.builder(BatchType.LOGGED) + .withKeyspace(clusterRule.keyspace()) + .addStatement(statementWithoutKeyspace) + .build()); + } + + @Test + public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4() { + SimpleStatement statementWithKeyspace = + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) + .setKeyspace(clusterRule.keyspace()); + should_reject_statement_with_keyspace_in_protocol_v4( + BatchStatement.builder(BatchType.LOGGED).addStatement(statementWithKeyspace).build()); + } + + private void should_reject_statement_with_keyspace_in_protocol_v4(Statement statement) { + try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "protocol.version = V4")) { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Can't use per-request keyspace with protocol V4"); + cluster.connect().execute(statement); + } + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_execute_simple_statement_with_keyspace() { + CqlSession session = clusterRule.session(); + session.execute( + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) + .setKeyspace(clusterRule.keyspace())); + Row row = + session + .execute( + SimpleStatement.newInstance( + "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) + .setKeyspace(clusterRule.keyspace())) + .one(); + assertThat(row.getInt(0)).isEqualTo(1); + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_execute_batch_with_explicit_keyspace() { + CqlSession session = clusterRule.session(); + session.execute( + BatchStatement.builder(BatchType.LOGGED) + .withKeyspace(clusterRule.keyspace()) + .addStatements( + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1), + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 2, 2)) + .build()); + + Row row = + session + .execute( + SimpleStatement.newInstance( + "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) + .setKeyspace(clusterRule.keyspace())) + .one(); + assertThat(row.getInt(0)).isEqualTo(1); + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_execute_batch_with_inferred_keyspace() { + CqlSession session = clusterRule.session(); + session.execute( + BatchStatement.builder(BatchType.LOGGED) + .withKeyspace(clusterRule.keyspace()) + .addStatements( + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", + nameRule.getMethodName(), + 1, + 1) + .setKeyspace(clusterRule.keyspace()), + SimpleStatement.newInstance( + "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", + nameRule.getMethodName(), + 2, + 2) + .setKeyspace(clusterRule.keyspace())) + .build()); + + Row row = + session + .execute( + SimpleStatement.newInstance( + "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) + .setKeyspace(clusterRule.keyspace())) + .one(); + assertThat(row.getInt(0)).isEqualTo(1); + } +} From c1fd587dc43c9c067a1f482c93b5d0195677089c Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 14 Dec 2017 14:50:50 -0600 Subject: [PATCH 283/742] Require C* 2.2 for Protocol V4 tests --- .../datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 05f0c87bd35..0fda1e04a74 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -59,12 +59,14 @@ public void setupSchema() { } @Test + @CassandraRequirement(min = "2.2") public void should_reject_simple_statement_with_keyspace_in_protocol_v4() { should_reject_statement_with_keyspace_in_protocol_v4( SimpleStatement.newInstance("SELECT * FROM foo").setKeyspace(clusterRule.keyspace())); } @Test + @CassandraRequirement(min = "2.2") public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4() { SimpleStatement statementWithoutKeyspace = SimpleStatement.newInstance( @@ -77,6 +79,7 @@ public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4( } @Test + @CassandraRequirement(min = "2.2") public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4() { SimpleStatement statementWithKeyspace = SimpleStatement.newInstance( From de0e0a8d5af32690c7eb333d0dd8917aaa679084 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 14 Dec 2017 17:06:02 -0800 Subject: [PATCH 284/742] Add note in manual about usage in a framework --- manual/core/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/manual/core/README.md b/manual/core/README.md index 0701dc9e80d..a9e7a4d8a51 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -41,8 +41,14 @@ from it. In this simple example, we can use a try-with-resources block because ` `java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods (`close`, `closeAsync`, `forceCloseAsync`) explicitly. -Note: this example uses the synchronous API. Most methods have asynchronous equivalents (look for -`*Async` variants that return a `CompletionStage`). +This example uses the synchronous API. Most methods have asynchronous equivalents (look for `*Async` +variants that return a `CompletionStage`). + +Note to framework implementors: if you design an API that lets users provide their own cluster +instance, use a bounded type parameter, like `Cluster` (or even +`Cluster` if you don't use any CQL-specific method). This allows custom cluster +implementations to be used as a drop-in replacement (see `RequestProcessorIT` in the integration +tests for an example of what such a custom implementation looks like). ### Setting up the driver From 4d79ac8e09d4e1b08b0828aac931474554941b6e Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 1 Nov 2017 13:27:02 -0700 Subject: [PATCH 285/742] Also wrap unchecked exceptions in DriverExecutionException --- .../driver/internal/core/util/concurrent/CompletableFutures.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index a64d2f88c89..739703cd092 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -111,7 +111,6 @@ public static T getUninterruptibly(CompletionStage stage) { if (cause instanceof DriverException) { throw ((DriverException) cause).copy(); } - Throwables.throwIfUnchecked(cause); throw new DriverExecutionException(cause); } } From 316ab058a7855c96ad029d98fe398902ee27957b Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 31 Oct 2017 11:38:36 -0700 Subject: [PATCH 286/742] JAVA-1647: Handle metadata_changed flag in protocol v5 --- changelog/README.md | 1 + .../api/core/cql/PreparedStatement.java | 29 ++ .../adminrequest/AdminRequestHandler.java | 6 +- .../UnexpectedResponseException.java | 28 ++ .../driver/internal/core/cql/Conversions.java | 45 ++- .../core/cql/CqlRequestHandlerBase.java | 23 +- .../core/cql/DefaultPreparedStatement.java | 28 +- .../internal/core/cql/MultiPageResultSet.java | 9 +- .../core/cql/CqlRequestHandlerTest.java | 5 + .../api/core/cql/PerRequestKeyspaceIT.java | 20 ++ .../cql/PreparedStatementInvalidationIT.java | 284 ++++++++++++++++++ pom.xml | 2 +- 12 files changed, 458 insertions(+), 22 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/UnexpectedResponseException.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java diff --git a/changelog/README.md b/changelog/README.md index 9487f637813..481e631d759 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [new feature] JAVA-1647: Handle metadata_changed flag in protocol v5 - [new feature] JAVA-1633: Handle per-request keyspace in protocol v5 - [improvement] JAVA-1678: Warn if auth is configured on the client but not the server - [improvement] JAVA-1673: Remove schema agreement check when repreparing on up diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index a40d0afe45d..271a0d95efa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CoreProtocolVersion; import java.nio.ByteBuffer; import java.util.List; @@ -68,12 +69,40 @@ public interface PreparedStatement { */ List getPrimaryKeyIndices(); + /** + * A unique identifier for result metadata (essentially a hash of {@link + * #getResultSetDefinitions()}). + * + *

          This information is mostly for internal use: with protocol {@link CoreProtocolVersion#V5} or + * higher, the driver sends it with every execution of the prepared statement, to validate that + * its result metadata is still up-to-date. + * + *

          Note: this method returns null for protocol {@link CoreProtocolVersion#V4} or lower; + * otherwise, the returned buffer is read-only. + * + * @see CASSANDRA-10786 + */ + ByteBuffer getResultMetadataId(); + /** * A description of the result set that will be returned when this prepared statement is bound and * executed. + * + *

          This information is only present for {@code SELECT} queries, otherwise it is always empty. + * Note that this is slightly incorrect for conditional updates (e.g. {@code INSERT ... IF NOT + * EXISTS}), which do return columns; for those cases, use {@link + * ResultSet#getColumnDefinitions()} on the result, not this method. */ ColumnDefinitions getResultSetDefinitions(); + /** + * Updates {@link #getResultMetadataId()} and {@link #getResultSetDefinitions()} atomically. + * + *

          This is for internal use by the driver. Calling this manually with incorrect information can + * cause existing queries to fail. + */ + void setResultMetadata(ByteBuffer newResultMetadataId, ColumnDefinitions newResultSetDefinitions); + /** * Builds an executable statement that associates a set of values with the bind variables. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 86c7fde4eab..3e32a8310f0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -147,11 +147,7 @@ public void onResponse(Frame responseFrame) { // success, not the actual result, so this is good enough: result.complete(null); } else { - result.completeExceptionally( - // The actual exception type does not really matters, this is only logged, never - // returned to the client - new IllegalArgumentException( - String.format("%s got unexpected response %s", debugString, message))); + result.completeExceptionally(new UnexpectedResponseException(debugString, message)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/UnexpectedResponseException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/UnexpectedResponseException.java new file mode 100644 index 00000000000..d475a4400db --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/UnexpectedResponseException.java @@ -0,0 +1,28 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest; + +import com.datastax.oss.protocol.internal.Message; + +public class UnexpectedResponseException extends Exception { + + public final Message message; + + UnexpectedResponseException(String requestName, Message message) { + super(String.format("%s got unexpected response %s", requestName, message)); + this.message = message; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 94354f1f492..5307a7cd8f5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -138,19 +139,26 @@ static Message toMessage( if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(boundStatement); } + boolean skipMetadata = + boundStatement.getPreparedStatement().getResultSetDefinitions().size() > 0; QueryOptions queryOptions = new QueryOptions( consistency, boundStatement.getValues(), Collections.emptyMap(), - true, + skipMetadata, pageSize, statement.getPagingState(), serialConsistency, timestamp, null); - ByteBuffer id = boundStatement.getPreparedStatement().getId(); - return new Execute(Bytes.getArray(id), queryOptions); + PreparedStatement preparedStatement = boundStatement.getPreparedStatement(); + ByteBuffer id = preparedStatement.getId(); + ByteBuffer resultMetadataId = preparedStatement.getResultMetadataId(); + return new Execute( + Bytes.getArray(id), + (resultMetadataId == null) ? null : Bytes.getArray(resultMetadataId), + queryOptions); } else if (statement instanceof BatchStatement) { BatchStatement batchStatement = (BatchStatement) statement; if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { @@ -269,10 +277,7 @@ static AsyncResultSet toResultSet( if (result instanceof Rows) { Rows rows = (Rows) result; Statement statement = executionInfo.getStatement(); - ColumnDefinitions columnDefinitions = - (statement instanceof BoundStatement) - ? ((BoundStatement) statement).getPreparedStatement().getResultSetDefinitions() - : toColumnDefinitions(rows.getMetadata(), context); + ColumnDefinitions columnDefinitions = getResultDefinitions(rows, statement, context); return new DefaultAsyncResultSet( columnDefinitions, executionInfo, rows.getData(), session, context); } else if (result instanceof Prepared) { @@ -284,6 +289,29 @@ static AsyncResultSet toResultSet( } } + private static ColumnDefinitions getResultDefinitions( + Rows rows, Statement statement, InternalDriverContext context) { + RowsMetadata rowsMetadata = rows.getMetadata(); + if (rowsMetadata.columnSpecs.isEmpty()) { + // If the response has no metadata, it means the request had SKIP_METADATA set, the driver + // only ever does that for bound statements. + BoundStatement boundStatement = (BoundStatement) statement; + return boundStatement.getPreparedStatement().getResultSetDefinitions(); + } else { + // The response has metadata, always use it above anything else we might have locally. + ColumnDefinitions definitions = toColumnDefinitions(rowsMetadata, context); + // In addition, if the server signaled a schema change (see CASSANDRA-10786), update the + // prepared statement's copy of the metadata + if (rowsMetadata.newResultMetadataId != null) { + BoundStatement boundStatement = (BoundStatement) statement; + PreparedStatement preparedStatement = boundStatement.getPreparedStatement(); + preparedStatement.setResultMetadata( + ByteBuffer.wrap(rowsMetadata.newResultMetadataId).asReadOnlyBuffer(), definitions); + } + return definitions; + } + } + static DefaultPreparedStatement toPreparedStatement( Prepared response, PrepareRequest request, InternalDriverContext context) { return new DefaultPreparedStatement( @@ -291,6 +319,9 @@ static DefaultPreparedStatement toPreparedStatement( request.getQuery(), toColumnDefinitions(response.variablesMetadata, context), asList(response.variablesMetadata.pkIndices), + (response.resultMetadataId == null) + ? null + : ByteBuffer.wrap(response.resultMetadataId).asReadOnlyBuffer(), toColumnDefinitions(response.resultMetadata, context), request.getConfigProfileNameForBoundStatements(), request.getConfigProfileForBoundStatements(), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 34d47f2ebdb..10c49ee119e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -38,6 +38,7 @@ import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -452,9 +453,25 @@ private void processErrorResponse(Error errorMessage) { reprepareHandler .start(repreparePayload.customPayload) .handle( - (result, error) -> { - if (error != null) { - recordError(node, error); + (result, exception) -> { + if (exception != null) { + // If the error is not recoverable, surface it to the client instead of retrying + if (exception instanceof UnexpectedResponseException) { + Message prepareErrorMessage = + ((UnexpectedResponseException) exception).message; + if (prepareErrorMessage instanceof Error) { + CoordinatorException prepareError = + Conversions.toThrowable(node, (Error) prepareErrorMessage); + if (prepareError instanceof QueryValidationException + || prepareError instanceof FunctionFailureException + || prepareError instanceof ProtocolError) { + LOG.debug("[{}] Unrecoverable error on reprepare, rethrowing", logPrefix); + setFinalError(prepareError); + return null; + } + } + } + recordError(node, exception); LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); sendRequest(null, execution, retryCount); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index fabc9758c84..ec3a2d9b29c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -41,7 +41,7 @@ public class DefaultPreparedStatement implements PreparedStatement { private final RepreparePayload repreparePayload; private final ColumnDefinitions variableDefinitions; private final List primaryKeyIndices; - private final ColumnDefinitions resultSetDefinitions; + private volatile ResultMetadata resultMetadata; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; // The options to propagate to the bound statements: @@ -55,6 +55,7 @@ public DefaultPreparedStatement( String query, ColumnDefinitions variableDefinitions, List primaryKeyIndices, + ByteBuffer resultMetadataId, ColumnDefinitions resultSetDefinitions, String configProfileName, DriverConfigProfile configProfile, @@ -70,7 +71,7 @@ public DefaultPreparedStatement( // the map in DefaultSession if no client reference the PreparedStatement anymore. this.repreparePayload = new RepreparePayload(id, query, keyspace, customPayloadForPrepare); this.variableDefinitions = variableDefinitions; - this.resultSetDefinitions = resultSetDefinitions; + this.resultMetadata = new ResultMetadata(resultMetadataId, resultSetDefinitions); this.configProfileName = configProfileName; this.configProfile = configProfile; this.customPayloadForBoundStatements = customPayloadForBoundStatements; @@ -99,9 +100,20 @@ public List getPrimaryKeyIndices() { return primaryKeyIndices; } + @Override + public ByteBuffer getResultMetadataId() { + return resultMetadata.resultMetadataId; + } + @Override public ColumnDefinitions getResultSetDefinitions() { - return resultSetDefinitions; + return resultMetadata.resultSetDefinitions; + } + + @Override + public void setResultMetadata( + ByteBuffer newResultMetadataId, ColumnDefinitions newResultSetDefinitions) { + this.resultMetadata = new ResultMetadata(newResultMetadataId, newResultSetDefinitions); } @Override @@ -168,4 +180,14 @@ public BoundStatementBuilder boundStatementBuilder() { public RepreparePayload getRepreparePayload() { return this.repreparePayload; } + + private static class ResultMetadata { + private ByteBuffer resultMetadataId; + private ColumnDefinitions resultSetDefinitions; + + private ResultMetadata(ByteBuffer resultMetadataId, ColumnDefinitions resultSetDefinitions) { + this.resultMetadataId = resultMetadataId; + this.resultSetDefinitions = resultSetDefinitions; + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index a928d7405df..8a205733d74 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -34,14 +34,13 @@ public class MultiPageResultSet implements ResultSet { // Reminder: by contract this is not thread-safe, so we don't need any synchronization. private final RowIterator iterator; - private final ColumnDefinitions columnDefinitions; private final List executionInfos = new ArrayList<>(); + private ColumnDefinitions columnDefinitions; public MultiPageResultSet(AsyncResultSet firstPage) { assert firstPage.hasMorePages(); this.iterator = new RowIterator(firstPage); this.executionInfos.add(firstPage.getExecutionInfo()); - // This is the same for all pages this.columnDefinitions = firstPage.getColumnDefinitions(); } @@ -103,7 +102,11 @@ private void maybeMoveToNextPage() { // We've just finished iterating the current page, remove it pages.removeFirst(); if (!pages.isEmpty()) { - currentRows = pages.getFirst().currentPage().iterator(); + AsyncResultSet nextPage = pages.getFirst(); + // The definitions can change from page to page if this result set was built from a bound + // 'SELECT *', and the schema was altered. + columnDefinitions = nextPage.getColumnDefinitions(); + currentRows = nextPage.currentPage().iterator(); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 6cb442cb0f7..376f42f83ca 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Row; @@ -162,6 +164,9 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class); Mockito.when(preparedStatement.getId()).thenReturn(mockId); + ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); + Mockito.when(columnDefinitions.size()).thenReturn(0); + Mockito.when(preparedStatement.getResultSetDefinitions()).thenReturn(columnDefinitions); BoundStatement boundStatement = Mockito.mock(BoundStatement.class); Mockito.when(boundStatement.getPreparedStatement()).thenReturn(preparedStatement); Mockito.when(boundStatement.getValues()).thenReturn(Collections.emptyList()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 0fda1e04a74..06d108440f7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -170,4 +170,24 @@ public void should_execute_batch_with_inferred_keyspace() { .one(); assertThat(row.getInt(0)).isEqualTo(1); } + + @Test + @CassandraRequirement(min = "4.0") + public void should_prepare_statement_with_keyspace() { + CqlSession session = clusterRule.session(); + PreparedStatement prepared = + session.prepare( + SimpleStatement.newInstance("INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)") + .setKeyspace(clusterRule.keyspace())); + session.execute(prepared.bind(nameRule.getMethodName(), 1, 1)); + + Row row = + session + .execute( + SimpleStatement.newInstance( + "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) + .setKeyspace(clusterRule.keyspace())) + .one(); + assertThat(row.getInt(0)).isEqualTo(1); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java new file mode 100644 index 00000000000..d289ea575ba --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -0,0 +1,284 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; + +import static junit.framework.TestCase.fail; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Note: at the time of writing, some of these tests exercises features of an unreleased Cassandra + * version. To test against a local build, run with + * + *

          + *   -Dccm.cassandraVersion=4.0.0 -Dccm.cassandraDirectory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
          + * 
          + */ +@Category(ParallelizableTests.class) +public class PreparedStatementInvalidationIT { + + @Rule public CcmRule ccmRule = CcmRule.getInstance(); + + @Rule + public ClusterRule clusterRule = + new ClusterRule(ccmRule, "request.page-size = 2", "request.timeout = 30 seconds"); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setupSchema() { + for (String query : + ImmutableList.of( + "CREATE TABLE prepared_statement_invalidation_test (a int PRIMARY KEY, b int, c int)", + "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (1, 1, 1)", + "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (2, 2, 2)", + "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (3, 3, 3)", + "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (4, 4, 4)")) { + clusterRule + .session() + .execute( + SimpleStatement.builder(query).withConfigProfile(clusterRule.slowProfile()).build()); + } + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_update_metadata_when_schema_changed_across_executions() { + // Given + CqlSession session = clusterRule.session(); + PreparedStatement ps = + session.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); + ByteBuffer idBefore = ps.getResultMetadataId(); + + // When + session.execute( + SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") + .withConfigProfile(clusterRule.slowProfile()) + .build()); + BoundStatement bs = ps.bind(1); + ResultSet rows = session.execute(bs); + + // Then + ByteBuffer idAfter = ps.getResultMetadataId(); + assertThat(Bytes.toHexString(idAfter)).isNotEqualTo(Bytes.toHexString(idBefore)); + for (ColumnDefinitions columnDefinitions : + ImmutableList.of( + ps.getResultSetDefinitions(), + bs.getPreparedStatement().getResultSetDefinitions(), + rows.getColumnDefinitions())) { + assertThat(columnDefinitions).hasSize(4); + assertThat(columnDefinitions.get("d").getType()).isEqualTo(DataTypes.INT); + } + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_update_metadata_when_schema_changed_across_pages() { + // Given + CqlSession session = clusterRule.session(); + PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_invalidation_test"); + ByteBuffer idBefore = ps.getResultMetadataId(); + assertThat(ps.getResultSetDefinitions()).hasSize(3); + + ResultSet rows = session.execute(ps.bind()); + assertThat(rows.isFullyFetched()).isFalse(); + assertThat(rows.getColumnDefinitions()).hasSize(3); + assertThat(rows.getColumnDefinitions().contains("d")).isFalse(); + // Consume the first page + int remaining = rows.getAvailableWithoutFetching(); + while (remaining-- > 0) { + try { + rows.one().getInt("d"); + fail("expected an error"); + } catch (ArrayIndexOutOfBoundsException e) { + /*expected*/ + } + } + + // When + session.execute( + SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") + .withConfigProfile(clusterRule.slowProfile()) + .build()); + + // Then + // this should trigger a background fetch of the second page, and therefore update the definitions + for (Row row : rows) { + assertThat(row.isNull("d")).isTrue(); + } + assertThat(rows.getColumnDefinitions()).hasSize(4); + assertThat(rows.getColumnDefinitions().get("d").getType()).isEqualTo(DataTypes.INT); + // Should have updated the prepared statement too + ByteBuffer idAfter = ps.getResultMetadataId(); + assertThat(Bytes.toHexString(idAfter)).isNotEqualTo(Bytes.toHexString(idBefore)); + assertThat(ps.getResultSetDefinitions()).hasSize(4); + assertThat(ps.getResultSetDefinitions().get("d").getType()).isEqualTo(DataTypes.INT); + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_update_metadata_when_schema_changed_across_sessions() { + // Given + CqlSession session1 = clusterRule.session(); + CqlSession session2 = clusterRule.cluster().connect(clusterRule.keyspace()); + + PreparedStatement ps1 = + session1.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); + PreparedStatement ps2 = + session2.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); + + ByteBuffer id1a = ps1.getResultMetadataId(); + ByteBuffer id2a = ps2.getResultMetadataId(); + + ResultSet rows1 = session1.execute(ps1.bind(1)); + ResultSet rows2 = session2.execute(ps2.bind(1)); + + assertThat(rows1.getColumnDefinitions()).hasSize(3); + assertThat(rows1.getColumnDefinitions().contains("d")).isFalse(); + assertThat(rows2.getColumnDefinitions()).hasSize(3); + assertThat(rows2.getColumnDefinitions().contains("d")).isFalse(); + + // When + session1.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + + rows1 = session1.execute(ps1.bind(1)); + rows2 = session2.execute(ps2.bind(1)); + + ByteBuffer id1b = ps1.getResultMetadataId(); + ByteBuffer id2b = ps2.getResultMetadataId(); + + // Then + assertThat(Bytes.toHexString(id1b)).isNotEqualTo(Bytes.toHexString(id1a)); + assertThat(Bytes.toHexString(id2b)).isNotEqualTo(Bytes.toHexString(id2a)); + + assertThat(ps1.getResultSetDefinitions()).hasSize(4); + assertThat(ps1.getResultSetDefinitions().contains("d")).isTrue(); + assertThat(ps2.getResultSetDefinitions()).hasSize(4); + assertThat(ps2.getResultSetDefinitions().contains("d")).isTrue(); + + assertThat(rows1.getColumnDefinitions()).hasSize(4); + assertThat(rows1.getColumnDefinitions().contains("d")).isTrue(); + assertThat(rows2.getColumnDefinitions()).hasSize(4); + assertThat(rows2.getColumnDefinitions().contains("d")).isTrue(); + + session2.close(); + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_fail_to_reprepare_if_query_becomes_invalid() { + // Given + CqlSession session = clusterRule.session(); + session.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + PreparedStatement ps = + session.prepare("SELECT a, b, c, d FROM prepared_statement_invalidation_test WHERE a = ?"); + session.execute("ALTER TABLE prepared_statement_invalidation_test DROP d"); + + thrown.expect(InvalidQueryException.class); + thrown.expectMessage("Undefined column name d"); + + // When + session.execute(ps.bind()); + } + + @Test + @CassandraRequirement(min = "4.0") + public void should_not_store_metadata_for_conditional_updates() { + should_not_store_metadata_for_conditional_updates(clusterRule.session()); + } + + @Test + @CassandraRequirement(min = "2.2") + public void should_not_store_metadata_for_conditional_updates_in_legacy_protocol() { + try (Cluster cluster = + ClusterUtils.newCluster(ccmRule, "protocol.version = V4", "request.timeout = 30 seconds")) { + should_not_store_metadata_for_conditional_updates(cluster.connect(clusterRule.keyspace())); + } + } + + private void should_not_store_metadata_for_conditional_updates(CqlSession session) { + // Given + PreparedStatement ps = + session.prepare( + "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (?, ?, ?) IF NOT EXISTS"); + + // Never store metadata in the prepared statement for conditional updates, since the result set can change + // depending on the outcome. + assertThat(ps.getResultSetDefinitions()).hasSize(0); + ByteBuffer idBefore = ps.getResultMetadataId(); + + // When + ResultSet rs = session.execute(ps.bind(5, 5, 5)); + + // Then + // Successful conditional update => only contains the [applied] column + assertThat(rs.wasApplied()).isTrue(); + assertThat(rs.getColumnDefinitions()).hasSize(1); + assertThat(rs.getColumnDefinitions().get("[applied]").getType()).isEqualTo(DataTypes.BOOLEAN); + // However the prepared statement shouldn't have changed + assertThat(ps.getResultSetDefinitions()).hasSize(0); + assertThat(Bytes.toHexString(ps.getResultMetadataId())).isEqualTo(Bytes.toHexString(idBefore)); + + // When + rs = session.execute(ps.bind(5, 5, 5)); + + // Then + // Failed conditional update => regular metadata + assertThat(rs.wasApplied()).isFalse(); + assertThat(rs.getColumnDefinitions()).hasSize(4); + Row row = rs.one(); + assertThat(row.getBoolean("[applied]")).isFalse(); + assertThat(row.getInt("a")).isEqualTo(5); + assertThat(row.getInt("b")).isEqualTo(5); + assertThat(row.getInt("c")).isEqualTo(5); + // The prepared statement still shouldn't have changed + assertThat(ps.getResultSetDefinitions()).hasSize(0); + assertThat(Bytes.toHexString(ps.getResultMetadataId())).isEqualTo(Bytes.toHexString(idBefore)); + + // When + session.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + rs = session.execute(ps.bind(5, 5, 5)); + + // Then + // Failed conditional update => regular metadata that should also contain the new column + assertThat(rs.wasApplied()).isFalse(); + assertThat(rs.getColumnDefinitions()).hasSize(5); + row = rs.one(); + assertThat(row.getBoolean("[applied]")).isFalse(); + assertThat(row.getInt("a")).isEqualTo(5); + assertThat(row.getInt("b")).isEqualTo(5); + assertThat(row.getInt("c")).isEqualTo(5); + assertThat(row.isNull("d")).isTrue(); + assertThat(ps.getResultSetDefinitions()).hasSize(0); + assertThat(Bytes.toHexString(ps.getResultMetadataId())).isEqualTo(Bytes.toHexString(idBefore)); + } +} diff --git a/pom.xml b/pom.xml index dc64e867fdb..195dbea8bee 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ com.datastax.oss native-protocol - 1.4.1 + 1.4.2-SNAPSHOT io.netty From 1dae69b10c05336c49046bcab619c77f6e25d647 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 3 Nov 2017 15:16:09 -0700 Subject: [PATCH 287/742] Add integration test for prepared update queries --- .../oss/driver/api/core/cql/BoundStatementIT.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 2761d22bea8..54c75e6fd2b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -117,6 +117,17 @@ public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { verifyUnset(boundStatement); } + @Test + public void should_have_empty_result_definitions_for_update_query() { + PreparedStatement prepared = + cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + + assertThat(prepared.getResultSetDefinitions()).hasSize(0); + + ResultSet rs = cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + assertThat(rs.getColumnDefinitions()).hasSize(0); + } + private void verifyUnset(BoundStatement boundStatement) { cluster.session().execute(boundStatement.unset(1)); From 91221cc223a06600b7fefba445534afe9eb96bbb Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 14 Dec 2017 16:16:59 -0600 Subject: [PATCH 288/742] Update QueryTraceIT test to check for DriverExecutionException --- .../oss/driver/api/core/cql/QueryTraceIT.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index 5ef00b13895..89cc306790b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -15,9 +15,12 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.DriverExecutionException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -43,8 +46,24 @@ public void should_not_have_tracing_id_when_tracing_disabled() { assertThat(executionInfo.getTracingId()).isNull(); - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Tracing was disabled for this request"); + // Should get a DriverExecutionException with an underlying IllegalStateException indicating + // Tracing was disabled. + thrown.expect(DriverExecutionException.class); + String expectedMessage = "Tracing was disabled for this request"; + thrown.expectCause( + new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText( + "Expected IllegalStateException with message of '" + expectedMessage + "'"); + } + + @Override + protected boolean matchesSafely(Throwable item) { + return item instanceof IllegalStateException + && item.getMessage().equals(expectedMessage); + } + }); executionInfo.getQueryTrace(); } From 3ace5cec015adb911b7b794a15f9c690676522bc Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 14 Dec 2017 17:09:27 -0600 Subject: [PATCH 289/742] Upgrade to simulacron 0.8.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 195dbea8bee..8271cb12924 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ com.datastax.oss.simulacron simulacron-native-server - 0.6.0 + 0.8.1 org.apache.commons From ee0628b4ef12e04e9fe873160ab89f8b41ed791a Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 15 Dec 2017 14:40:35 -0800 Subject: [PATCH 290/742] Revisit custom request processor example in integration tests - separate in api/internal packages - declare the additional methods as default implementations on an interface - don't expose a concrete Cluster implementation --- .../api/core/session/RequestProcessorIT.java | 30 ++++++++++++------- .../guava/api}/GuavaClusterBuilder.java | 11 ++++--- .../example/guava/api/GuavaClusterUtils.java | 22 ++++++++++++++ .../guava/api}/GuavaSession.java | 22 ++++++-------- .../guava/internal/DefaultGuavaCluster.java} | 14 ++++----- .../guava/internal/DefaultGuavaSession.java | 27 +++++++++++++++++ .../guava/internal}/GuavaDriverContext.java | 5 ++-- .../internal}/GuavaRequestAsyncProcessor.java | 3 +- .../guava/internal}/KeyRequest.java | 3 +- .../guava/internal}/KeyRequestProcessor.java | 6 ++-- 10 files changed, 101 insertions(+), 42 deletions(-) rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/api}/GuavaClusterBuilder.java (72%) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/api}/GuavaSession.java (68%) rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session/GuavaCluster.java => example/guava/internal/DefaultGuavaCluster.java} (79%) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/internal}/GuavaDriverContext.java (93%) rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/internal}/GuavaRequestAsyncProcessor.java (96%) rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/internal}/KeyRequest.java (94%) rename integration-tests/src/test/java/com/datastax/oss/driver/{api/core/session => example/guava/internal}/KeyRequestProcessor.java (92%) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index f25b541a650..73caf3bc7bb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -22,6 +23,12 @@ import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.example.guava.api.GuavaClusterUtils; +import com.datastax.oss.driver.example.guava.api.GuavaSession; +import com.datastax.oss.driver.example.guava.internal.DefaultGuavaCluster; +import com.datastax.oss.driver.example.guava.internal.GuavaDriverContext; +import com.datastax.oss.driver.example.guava.internal.KeyRequest; +import com.datastax.oss.driver.example.guava.internal.KeyRequestProcessor; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; import com.google.common.collect.Iterables; @@ -41,11 +48,12 @@ * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations to add-in * additional request handling and response types. * - *

          Uses {@link GuavaCluster} which is a specialized cluster implementation that uses {@link - * GuavaDriverContext} which overrides {@link DefaultDriverContext#requestProcessorRegistry()} to - * provide its own {@link com.datastax.oss.driver.internal.core.session.RequestProcessor} - * implementations for returning {@link ListenableFuture}s rather than {@link - * java.util.concurrent.CompletionStage}s in async method responses. + *

          Uses {@link DefaultGuavaCluster} which is a specialized cluster implementation that uses + * {@link GuavaDriverContext} which overrides {@link + * DefaultDriverContext#requestProcessorRegistry()} to provide its own {@link + * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations for returning + * {@link ListenableFuture}s rather than {@link java.util.concurrent.CompletionStage}s in async + * method responses. * *

          {@link GuavaSession} provides execute method implementation shortcuts that mimics {@link * CqlSession}'s async methods. @@ -62,7 +70,7 @@ public class RequestProcessorIT { @Rule public ExpectedException thrown = ExpectedException.none(); - static final String KEY = "test"; + public static final String KEY = "test"; @BeforeClass public static void setupSchema() { @@ -84,8 +92,8 @@ public static void setupSchema() { } } - private GuavaCluster newCluster(String... options) { - return GuavaCluster.builder() + private Cluster newCluster(String... options) { + return GuavaClusterUtils.builder() .addContactPoints(ccm.getContactPoints()) .withConfigLoader(new TestConfigLoader(options)) .build(); @@ -93,7 +101,7 @@ private GuavaCluster newCluster(String... options) { @Test public void should_use_custom_request_processor_for_prepareAsync() throws Exception { - try (GuavaCluster gCluster = newCluster()) { + try (Cluster gCluster = newCluster()) { GuavaSession session = gCluster.connect(cluster.keyspace()); ListenableFuture preparedFuture = session.prepareAsync("select * from test"); @@ -113,7 +121,7 @@ public void should_use_custom_request_processor_for_prepareAsync() throws Except @Test public void should_use_custom_request_processor_for_handling_special_request_type() throws Exception { - try (GuavaCluster gCluster = newCluster()) { + try (Cluster gCluster = newCluster()) { GuavaSession session = gCluster.connect(cluster.keyspace()); // RequestProcessor executes "select v from test where k = " and returns v as Integer. @@ -128,7 +136,7 @@ public void should_use_custom_request_processor_for_handling_special_request_typ @Test public void should_use_custom_request_processor_for_executeAsync() throws Exception { - try (GuavaCluster gCluster = newCluster()) { + try (Cluster gCluster = newCluster()) { GuavaSession session = gCluster.connect(cluster.keyspace()); ListenableFuture future = session.executeAsync("select * from test"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java similarity index 72% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java index 391189ca4dc..e02f845a6c7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaClusterBuilder.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java @@ -13,17 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.api; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.example.guava.internal.DefaultGuavaCluster; +import com.datastax.oss.driver.example.guava.internal.GuavaDriverContext; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import java.util.List; -public class GuavaClusterBuilder extends ClusterBuilder { +public class GuavaClusterBuilder + extends ClusterBuilder> { @Override protected DriverContext buildContext( @@ -32,7 +35,7 @@ protected DriverContext buildContext( } @Override - protected GuavaCluster wrap(Cluster defaultCluster) { - return new GuavaCluster(defaultCluster); + protected Cluster wrap(Cluster defaultCluster) { + return new DefaultGuavaCluster(defaultCluster); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java new file mode 100644 index 00000000000..e44c8de328b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java @@ -0,0 +1,22 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.example.guava.api; + +public class GuavaClusterUtils { + public static GuavaClusterBuilder builder() { + return new GuavaClusterBuilder(); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSession.java similarity index 68% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSession.java index 1c999ccb06b..48f74fcb8df 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaSession.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSession.java @@ -13,42 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.api; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; -import com.datastax.oss.driver.internal.core.session.SessionWrapper; import com.google.common.util.concurrent.ListenableFuture; -public class GuavaSession extends SessionWrapper { +public interface GuavaSession extends Session { - static final GenericType> ASYNC = + GenericType> ASYNC = new GenericType>() {}; - static final GenericType> ASYNC_PREPARED = + GenericType> ASYNC_PREPARED = new GenericType>() {}; - GuavaSession(Session delegate) { - super(delegate); - } - - ListenableFuture executeAsync(Statement statement) { + default ListenableFuture executeAsync(Statement statement) { return this.execute(statement, ASYNC); } - ListenableFuture executeAsync(String statement) { + default ListenableFuture executeAsync(String statement) { return this.executeAsync(SimpleStatement.newInstance(statement)); } - ListenableFuture prepareAsync(SimpleStatement statement) { + default ListenableFuture prepareAsync(SimpleStatement statement) { return this.execute(new DefaultPrepareRequest(statement), ASYNC_PREPARED); } - ListenableFuture prepareAsync(String statement) { + default ListenableFuture prepareAsync(String statement) { return this.prepareAsync(SimpleStatement.newInstance(statement)); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java similarity index 79% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java index d508a5d65e1..a443e6d282d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaCluster.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java @@ -28,24 +28,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.internal; import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.example.guava.api.GuavaClusterBuilder; +import com.datastax.oss.driver.example.guava.api.GuavaSession; import com.datastax.oss.driver.internal.core.ClusterWrapper; -public class GuavaCluster extends ClusterWrapper { +public class DefaultGuavaCluster extends ClusterWrapper { - GuavaCluster(Cluster delegate) { + public DefaultGuavaCluster(Cluster delegate) { super(delegate); } @Override protected GuavaSession wrap(CqlSession session) { - return new GuavaSession(session); - } - - public static GuavaClusterBuilder builder() { - return new GuavaClusterBuilder(); + return new DefaultGuavaSession(session); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java new file mode 100644 index 00000000000..17f891baa70 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java @@ -0,0 +1,27 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.example.guava.internal; + +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.example.guava.api.GuavaSession; +import com.datastax.oss.driver.internal.core.session.SessionWrapper; + +public class DefaultGuavaSession extends SessionWrapper implements GuavaSession { + + DefaultGuavaSession(Session delegate) { + super(delegate); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java similarity index 93% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index 67e5eae4198..7db05413643 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.internal; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.example.guava.api.GuavaSession; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; @@ -37,7 +38,7 @@ */ public class GuavaDriverContext extends DefaultDriverContext { - GuavaDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { + public GuavaDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { super(configLoader, typeCodecs); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java similarity index 96% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java index 3182d49e6cd..1fb993b6fb7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/GuavaRequestAsyncProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.internal; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java similarity index 94% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java index faa7d637713..3dcfca2f96c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequest.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.internal; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.api.core.session.Request; import java.nio.ByteBuffer; import java.util.Map; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java similarity index 92% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java index b921ac513a1..e1037dfe282 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/KeyRequestProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.session; +package com.datastax.oss.driver.example.guava.internal; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.RequestProcessorIT; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; @@ -32,7 +34,7 @@ */ public class KeyRequestProcessor implements RequestProcessor { - static final GenericType INT_TYPE = GenericType.of(Integer.class); + public static final GenericType INT_TYPE = GenericType.of(Integer.class); private final CqlRequestSyncProcessor subProcessor; From 1e9d9396a2ccc7a5d2feead4c126cef33ea53563 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 4 Jan 2018 16:38:48 -0800 Subject: [PATCH 291/742] JAVA-1714: Make replication strategies pluggable --- changelog/README.md | 1 + .../core/context/DefaultDriverContext.java | 14 ++++ .../core/context/InternalDriverContext.java | 3 + .../core/metadata/AddNodeRefresh.java | 11 +-- .../core/metadata/DefaultMetadata.java | 44 +++++++----- .../core/metadata/FullNodeListRefresh.java | 17 +++-- .../metadata/InitContactPointsRefresh.java | 12 ++-- .../core/metadata/MetadataManager.java | 12 ++-- .../core/metadata/MetadataRefresh.java | 14 ++-- .../internal/core/metadata/NodesRefresh.java | 6 +- .../core/metadata/RemoveNodeRefresh.java | 12 ++-- .../core/metadata/TokensChangedRefresh.java | 12 ++-- .../metadata/schema/parsing/SchemaParser.java | 2 +- .../schema/refresh/SchemaRefresh.java | 12 ++-- .../DefaultReplicationStrategyFactory.java | 46 +++++++++++++ .../core/metadata/token/DefaultTokenMap.java | 24 +++++-- .../core/metadata/token/KeyspaceTokenMap.java | 3 +- .../metadata/token/ReplicationStrategy.java | 16 ----- .../token/ReplicationStrategyFactory.java | 22 ++++++ .../core/metadata/AddNodeRefreshTest.java | 19 ++++-- .../metadata/DefaultMetadataTokenMapTest.java | 51 ++++++++------ .../metadata/FullNodeListRefreshTest.java | 12 ++-- .../InitContactPointsRefreshTest.java | 11 ++- .../core/metadata/RemoveNodeRefreshTest.java | 19 ++++-- .../schema/refresh/SchemaRefreshTest.java | 67 +++++++++++-------- .../metadata/token/DefaultTokenMapTest.java | 62 +++++++++++++---- 26 files changed, 352 insertions(+), 172 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategyFactory.java diff --git a/changelog/README.md b/changelog/README.md index 481e631d759..36e63444bb6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1714: Make replication strategies pluggable - [new feature] JAVA-1647: Handle metadata_changed flag in protocol v5 - [new feature] JAVA-1633: Handle per-request keyspace in protocol v5 - [improvement] JAVA-1678: Warn if auth is configured on the client but not the server diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 45b57da9a5e..f15be6836a1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -44,7 +44,9 @@ import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.DefaultSchemaQueriesFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenFactoryRegistry; +import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; @@ -136,6 +138,9 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("schemaParserFactory", this::buildSchemaParserFactory, cycleDetector); private final LazyReference tokenFactoryRegistryRef = new LazyReference<>("tokenFactoryRegistry", this::buildTokenFactoryRegistry, cycleDetector); + private final LazyReference replicationStrategyFactoryRef = + new LazyReference<>( + "replicationStrategyFactory", this::buildReplicationStrategyFactory, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -312,6 +317,10 @@ protected TokenFactoryRegistry buildTokenFactoryRegistry() { return new DefaultTokenFactoryRegistry(this); } + protected ReplicationStrategyFactory buildReplicationStrategyFactory() { + return new DefaultReplicationStrategyFactory(this); + } + @Override public String clusterName() { return clusterName; @@ -452,6 +461,11 @@ public TokenFactoryRegistry tokenFactoryRegistry() { return tokenFactoryRegistryRef.get(); } + @Override + public ReplicationStrategyFactory replicationStrategyFactory() { + return replicationStrategyFactoryRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index bf5e74b202c..003bfb339ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; @@ -73,4 +74,6 @@ public interface InternalDriverContext extends DriverContext { SchemaParserFactory schemaParserFactory(); TokenFactoryRegistry tokenFactoryRegistry(); + + ReplicationStrategyFactory replicationStrategyFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 22805530854..483e2820246 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -26,26 +27,26 @@ public class AddNodeRefresh extends NodesRefresh { @VisibleForTesting final NodeInfo newNodeInfo; - AddNodeRefresh(NodeInfo newNodeInfo, String logPrefix) { - super(logPrefix); + AddNodeRefresh(NodeInfo newNodeInfo) { this.newNodeInfo = newNodeInfo; } @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { Map oldNodes = oldMetadata.getNodes(); if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { return new Result(oldMetadata); } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); - copyInfos(newNodeInfo, newNode, null, logPrefix); + copyInfos(newNodeInfo, newNode, null, context.clusterName()); Map newNodes = ImmutableMap.builder() .putAll(oldNodes) .put(newNode.getConnectAddress(), newNode) .build(); return new Result( - oldMetadata.withNodes(newNodes, tokenMapEnabled, false, null), + oldMetadata.withNodes(newNodes, tokenMapEnabled, false, null, context), ImmutableList.of(NodeStateEvent.added(newNode))); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 36cccb254c6..2391ef4e0a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -20,7 +20,9 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenMap; +import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.NanoTime; @@ -39,26 +41,23 @@ */ public class DefaultMetadata implements Metadata { private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadata.class); - public static DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap(), null); + public static DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); private final Map nodes; private final Map keyspaces; private final Optional tokenMap; - private final String logPrefix; - public DefaultMetadata(Map nodes, String logPrefix) { - this(ImmutableMap.copyOf(nodes), Collections.emptyMap(), Optional.empty(), logPrefix); + public DefaultMetadata(Map nodes) { + this(ImmutableMap.copyOf(nodes), Collections.emptyMap(), Optional.empty()); } private DefaultMetadata( Map nodes, Map keyspaces, - Optional tokenMap, - String logPrefix) { + Optional tokenMap) { this.nodes = nodes; this.keyspaces = keyspaces; this.tokenMap = tokenMap; - this.logPrefix = logPrefix; } @Override @@ -87,7 +86,8 @@ public DefaultMetadata withNodes( Map newNodes, boolean tokenMapEnabled, boolean tokensChanged, - TokenFactory tokenFactory) { + TokenFactory tokenFactory, + InternalDriverContext context) { // Force a rebuild if at least one node has different tokens, or there are new or removed nodes. boolean forceFullRebuild = tokensChanged || !newNodes.equals(nodes); @@ -95,17 +95,18 @@ public DefaultMetadata withNodes( return new DefaultMetadata( ImmutableMap.copyOf(newNodes), this.keyspaces, - rebuildTokenMap(newNodes, keyspaces, tokenMapEnabled, forceFullRebuild, tokenFactory), - logPrefix); + rebuildTokenMap( + newNodes, keyspaces, tokenMapEnabled, forceFullRebuild, tokenFactory, context)); } public DefaultMetadata withSchema( - Map newKeyspaces, boolean tokenMapEnabled) { + Map newKeyspaces, + boolean tokenMapEnabled, + InternalDriverContext context) { return new DefaultMetadata( this.nodes, ImmutableMap.copyOf(newKeyspaces), - rebuildTokenMap(nodes, newKeyspaces, tokenMapEnabled, false, null), - logPrefix); + rebuildTokenMap(nodes, newKeyspaces, tokenMapEnabled, false, null, context)); } private Optional rebuildTokenMap( @@ -113,7 +114,11 @@ private Optional rebuildTokenMap( Map newKeyspaces, boolean tokenMapEnabled, boolean forceFullRebuild, - TokenFactory tokenFactory) { + TokenFactory tokenFactory, + InternalDriverContext context) { + + String logPrefix = context.clusterName(); + ReplicationStrategyFactory replicationStrategyFactory = context.replicationStrategyFactory(); if (!tokenMapEnabled) { LOG.debug("[{}] Token map is disabled, skipping", logPrefix); @@ -133,7 +138,11 @@ private Optional rebuildTokenMap( LOG.debug("[{}] Building initial token map", logPrefix); return Optional.of( DefaultTokenMap.build( - newNodes.values(), newKeyspaces.values(), tokenFactory, logPrefix)); + newNodes.values(), + newKeyspaces.values(), + tokenFactory, + replicationStrategyFactory, + logPrefix)); } } else if (forceFullRebuild) { LOG.debug( @@ -143,10 +152,13 @@ private Optional rebuildTokenMap( newNodes.values(), newKeyspaces.values(), oldTokenMap.getTokenFactory(), + replicationStrategyFactory, logPrefix)); } else { LOG.debug("[{}] Refreshing token map (only schema has changed)", logPrefix); - return Optional.of(oldTokenMap.refresh(newNodes.values(), newKeyspaces.values())); + return Optional.of( + oldTokenMap.refresh( + newNodes.values(), newKeyspaces.values(), replicationStrategyFactory)); } } catch (Throwable t) { Loggers.warnWithException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 11d4e86764a..bcdf5a0da6b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -37,16 +37,18 @@ class FullNodeListRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(FullNodeListRefresh.class); @VisibleForTesting final Iterable nodeInfos; - private final TokenFactoryRegistry tokenFactoryRegistry; - FullNodeListRefresh(Iterable nodeInfos, InternalDriverContext context) { - super(context.clusterName()); + FullNodeListRefresh(Iterable nodeInfos) { this.nodeInfos = nodeInfos; - this.tokenFactoryRegistry = context.tokenFactoryRegistry(); } @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { + + String logPrefix = context.clusterName(); + TokenFactoryRegistry tokenFactoryRegistry = context.tokenFactoryRegistry(); + Map oldNodes = oldMetadata.getNodes(); Map added = new HashMap<>(); @@ -83,7 +85,8 @@ public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { // rebuild: if (!oldMetadata.getTokenMap().isPresent() && tokenFactory != null) { return new Result( - oldMetadata.withNodes(oldMetadata.getNodes(), tokenMapEnabled, true, tokenFactory)); + oldMetadata.withNodes( + oldMetadata.getNodes(), tokenMapEnabled, true, tokenFactory, context)); } else { return new Result(oldMetadata); } @@ -108,7 +111,7 @@ public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { return new Result( oldMetadata.withNodes( - newNodesBuilder.build(), tokenMapEnabled, tokensChanged, tokenFactory), + newNodesBuilder.build(), tokenMapEnabled, tokensChanged, tokenFactory, context), eventsBuilder.build()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index c1eeb53931b..a3f73ea6289 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; @@ -24,26 +25,27 @@ import org.slf4j.LoggerFactory; /** Creates minimal node info about the contact points, before the first connection. */ -class InitContactPointsRefresh extends MetadataRefresh { +class InitContactPointsRefresh implements MetadataRefresh { private static final Logger LOG = LoggerFactory.getLogger(InitContactPointsRefresh.class); @VisibleForTesting final Set contactPoints; - InitContactPointsRefresh(Set contactPoints, String logPrefix) { - super(logPrefix); + InitContactPointsRefresh(Set contactPoints) { this.contactPoints = contactPoints; } @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { assert oldMetadata == DefaultMetadata.EMPTY; + String logPrefix = context.clusterName(); LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); ImmutableMap.Builder newNodes = ImmutableMap.builder(); for (InetSocketAddress address : contactPoints) { newNodes.put(address, new DefaultNode(address)); } - return new Result(new DefaultMetadata(newNodes.build(), logPrefix)); + return new Result(new DefaultMetadata(newNodes.build())); // No token map refresh, because we don't have enough information yet } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 76c1da1ee31..a36c10836e2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -145,7 +145,7 @@ public CompletionStage refreshNode(Node node) { boolean tokensChanged = NodesRefresh.copyInfos(maybeInfo.get(), (DefaultNode) node, null, logPrefix); if (tokensChanged) { - apply(new TokensChangedRefresh(logPrefix)); + apply(new TokensChangedRefresh()); } } else { LOG.debug( @@ -269,13 +269,13 @@ private SingleThreaded(InternalDriverContext context, DriverConfigProfile config private void initNodes( Set addresses, CompletableFuture initNodesFuture) { - apply(new InitContactPointsRefresh(addresses, logPrefix)); + apply(new InitContactPointsRefresh(addresses)); initNodesFuture.complete(null); } private Void refreshNodes(Iterable nodeInfos) { didFirstNodeListRefresh = true; - return apply(new FullNodeListRefresh(nodeInfos, context)); + return apply(new FullNodeListRefresh(nodeInfos)); } private void addNode(InetSocketAddress address, Optional maybeInfo) { @@ -291,7 +291,7 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { address, info.getConnectAddress()); } else { - apply(new AddNodeRefresh(info, logPrefix)); + apply(new AddNodeRefresh(info)); } } else { LOG.debug( @@ -306,7 +306,7 @@ private void addNode(InetSocketAddress address, Optional maybeInfo) { } private void removeNode(InetSocketAddress address) { - apply(new RemoveNodeRefresh(address, logPrefix)); + apply(new RemoveNodeRefresh(address)); } private void refreshSchema( @@ -446,7 +446,7 @@ private void close() { @VisibleForTesting Void apply(MetadataRefresh refresh) { assert adminExecutor.inEventLoop(); - MetadataRefresh.Result result = refresh.compute(metadata, tokenMapEnabled); + MetadataRefresh.Result result = refresh.compute(metadata, tokenMapEnabled, context); metadata = result.newMetadata; boolean isFirstSchemaRefresh = refresh instanceof SchemaRefresh && !singleThreaded.firstSchemaRefreshFuture.isDone(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java index ae733e54217..c8311dfb470 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import java.util.Collections; import java.util.List; @@ -32,17 +33,12 @@ * * @see Cluster#getMetadata() */ -public abstract class MetadataRefresh { +public interface MetadataRefresh { - protected final String logPrefix; + Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context); - protected MetadataRefresh(String logPrefix) { - this.logPrefix = logPrefix; - } - - public abstract Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled); - - public static class Result { + class Result { public final DefaultMetadata newMetadata; public final List events; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 248d0bc3c32..1836c77d892 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -22,14 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class NodesRefresh extends MetadataRefresh { +abstract class NodesRefresh implements MetadataRefresh { private static final Logger LOG = LoggerFactory.getLogger(NodesRefresh.class); - protected NodesRefresh(String logPrefix) { - super(logPrefix); - } - /** * @return whether the node's token have changed as a result of this operation (unfortunately we * mutate the tokens in-place, so there is no way to check this after the fact). diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 30fecfe7dc4..735b40e96c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -30,13 +31,16 @@ public class RemoveNodeRefresh extends NodesRefresh { @VisibleForTesting final InetSocketAddress toRemove; - RemoveNodeRefresh(InetSocketAddress toRemove, String logPrefix) { - super(logPrefix); + RemoveNodeRefresh(InetSocketAddress toRemove) { this.toRemove = toRemove; } @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { + + String logPrefix = context.clusterName(); + Map oldNodes = oldMetadata.getNodes(); Node node = oldNodes.get(toRemove); if (node == null) { @@ -52,7 +56,7 @@ public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { } } return new Result( - oldMetadata.withNodes(newNodesBuilder.build(), tokenMapEnabled, false, null), + oldMetadata.withNodes(newNodesBuilder.build(), tokenMapEnabled, false, null, context), ImmutableList.of(NodeStateEvent.removed((DefaultNode) node))); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java index acb07447bdd..05e987c00ab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TokensChangedRefresh.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.metadata; -class TokensChangedRefresh extends MetadataRefresh { +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; - TokensChangedRefresh(String logPrefix) { - super(logPrefix); - } +class TokensChangedRefresh implements MetadataRefresh { @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { - return new Result(oldMetadata.withNodes(oldMetadata.getNodes(), tokenMapEnabled, true, null)); + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { + return new Result( + oldMetadata.withNodes(oldMetadata.getNodes(), tokenMapEnabled, true, null, context)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java index abc21cfd499..2988b9d9455 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java @@ -74,7 +74,7 @@ public SchemaRefresh parse() { KeyspaceMetadata keyspace = parseKeyspace(row); keyspacesBuilder.put(keyspace.getName(), keyspace); } - SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build(), logPrefix); + SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build()); LOG.debug("[{}] Schema parsing took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); return refresh; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java index 66b02c4720c..7980668b0d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; import com.datastax.oss.driver.internal.core.metadata.schema.events.AggregateChangeEvent; @@ -33,17 +34,17 @@ import java.util.function.BiFunction; import java.util.function.Function; -public class SchemaRefresh extends MetadataRefresh { +public class SchemaRefresh implements MetadataRefresh { @VisibleForTesting public final Map newKeyspaces; - public SchemaRefresh(Map newKeyspaces, String logPrefix) { - super(logPrefix); + public SchemaRefresh(Map newKeyspaces) { this.newKeyspaces = newKeyspaces; } @Override - public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { ImmutableList.Builder events = ImmutableList.builder(); Map oldKeyspaces = oldMetadata.getKeyspaces(); @@ -55,7 +56,8 @@ public Result compute(DefaultMetadata oldMetadata, boolean tokenMapEnabled) { computeEvents(oldKeyspaces.get(key), entry.getValue(), events); } - return new Result(oldMetadata.withSchema(this.newKeyspaces, tokenMapEnabled), events.build()); + return new Result( + oldMetadata.withSchema(this.newKeyspaces, tokenMapEnabled, context), events.build()); } private static boolean shallowEquals(KeyspaceMetadata keyspace1, KeyspaceMetadata keyspace2) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java new file mode 100644 index 00000000000..5a9977dafd9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.base.Preconditions; +import java.util.Map; + +public class DefaultReplicationStrategyFactory implements ReplicationStrategyFactory { + + private final String logPrefix; + + public DefaultReplicationStrategyFactory(InternalDriverContext context) { + this.logPrefix = context.clusterName(); + } + + @Override + public ReplicationStrategy newInstance(Map replicationConfig) { + String strategyClass = replicationConfig.get("class"); + Preconditions.checkNotNull( + strategyClass, "Missing replication strategy class in " + replicationConfig); + switch (strategyClass) { + case "org.apache.cassandra.locator.LocalStrategy": + return new LocalReplicationStrategy(); + case "org.apache.cassandra.locator.SimpleStrategy": + return new SimpleReplicationStrategy(replicationConfig); + case "org.apache.cassandra.locator.NetworkTopologyStrategy": + return new NetworkTopologyReplicationStrategy(replicationConfig, logPrefix); + default: + throw new IllegalArgumentException("Unsupported replication strategy: " + strategyClass); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java index b725dd7e004..15fa267e064 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -48,6 +48,7 @@ public static DefaultTokenMap build( Collection nodes, Collection keyspaces, TokenFactory tokenFactory, + ReplicationStrategyFactory replicationStrategyFactory, String logPrefix) { TokenToPrimaryAndRing tmp = buildTokenToPrimaryAndRing(nodes, tokenFactory); @@ -79,7 +80,13 @@ public static DefaultTokenMap build( keyspaceMapsBuilder.put( config, KeyspaceTokenMap.build( - config, tokenToPrimary, ring, tokenRanges, tokenFactory, logPrefix)); + config, + tokenToPrimary, + ring, + tokenRanges, + tokenFactory, + replicationStrategyFactory, + logPrefix)); } return new DefaultTokenMap( tokenFactory, @@ -97,7 +104,7 @@ public static DefaultTokenMap build( @VisibleForTesting final Map, KeyspaceTokenMap> keyspaceMaps; private final String logPrefix; - public DefaultTokenMap( + private DefaultTokenMap( TokenFactory tokenFactory, Set tokenRanges, SetMultimap tokenRangesByPrimary, @@ -170,7 +177,10 @@ private KeyspaceTokenMap getKeyspaceMap(CqlIdentifier keyspace) { } /** Called when only the schema has changed. */ - public DefaultTokenMap refresh(Collection nodes, Collection keyspaces) { + public DefaultTokenMap refresh( + Collection nodes, + Collection keyspaces, + ReplicationStrategyFactory replicationStrategyFactory) { Map> newReplicationConfigs = buildReplicationConfigs(keyspaces, logPrefix); @@ -200,7 +210,13 @@ public DefaultTokenMap refresh(Collection nodes, Collection ring, Set tokenRanges, TokenFactory tokenFactory, + ReplicationStrategyFactory replicationStrategyFactory, String logPrefix) { long start = System.nanoTime(); try { - ReplicationStrategy strategy = ReplicationStrategy.newInstance(replicationConfig, logPrefix); + ReplicationStrategy strategy = replicationStrategyFactory.newInstance(replicationConfig); SetMultimap replicasByToken = strategy.computeReplicasByToken(tokenToPrimary, ring); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java index 43f8a5a6770..e3606489734 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java @@ -23,22 +23,6 @@ import java.util.Map; interface ReplicationStrategy { - static ReplicationStrategy newInstance(Map replicationConfig, String logPrefix) { - String strategyClass = replicationConfig.get("class"); - Preconditions.checkNotNull( - strategyClass, "Missing replication strategy class in " + replicationConfig); - switch (strategyClass) { - case "org.apache.cassandra.locator.LocalStrategy": - return new LocalReplicationStrategy(); - case "org.apache.cassandra.locator.SimpleStrategy": - return new SimpleReplicationStrategy(replicationConfig); - case "org.apache.cassandra.locator.NetworkTopologyStrategy": - return new NetworkTopologyReplicationStrategy(replicationConfig, logPrefix); - default: - throw new IllegalArgumentException("Unsupported replication strategy: " + strategyClass); - } - } - SetMultimap computeReplicasByToken( Map tokenToPrimary, List ring); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategyFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategyFactory.java new file mode 100644 index 00000000000..2b7bff0316c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategyFactory.java @@ -0,0 +1,22 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.token; + +import java.util.Map; + +public interface ReplicationStrategyFactory { + ReplicationStrategy newInstance(Map replicationConfig); +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 607d3be98cf..9f52f10a583 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -16,33 +16,40 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class AddNodeRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); private static final DefaultNode node1 = new DefaultNode(ADDRESS1); + @Mock private InternalDriverContext context; + @Test public void should_add_new_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS2) .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); + AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then Map newNodes = result.newMetadata.getNodes(); @@ -56,17 +63,17 @@ public void should_add_new_node() { @Test public void should_not_add_existing_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS1) .withDatacenter("dc1") .withRack("rack2") .build(); - AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo, "test"); + AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index ee41cdbf885..6cb09579abc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -18,17 +18,23 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3TokenFactory; -import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.net.InetSocketAddress; import java.util.Map; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class DefaultMetadataTokenMapTest { // Simulate the simplest setup possible for a functional token map. We're not testing the token @@ -37,7 +43,6 @@ public class DefaultMetadataTokenMapTest { private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); private static final String TOKEN1 = "-9000000000000000000"; private static final String TOKEN2 = "9000000000000000000"; - private static final TokenFactory TOKEN_FACTORY = new Murmur3TokenFactory(); private static final Node NODE1 = mockNode(TOKEN1); private static final Node NODE2 = mockNode(TOKEN2); private static final CqlIdentifier KEYSPACE_NAME = CqlIdentifier.fromInternal("ks"); @@ -47,64 +52,68 @@ public class DefaultMetadataTokenMapTest { ImmutableMap.of( "class", "org.apache.cassandra.locator.SimpleStrategy", "replication_factor", "1")); + @Mock private InternalDriverContext context; + + @Before + public void setup() { + DefaultReplicationStrategyFactory replicationStrategyFactory = + new DefaultReplicationStrategyFactory(context); + Mockito.when(context.replicationStrategyFactory()).thenReturn(replicationStrategyFactory); + } + @Test public void should_not_build_token_map_when_initializing_with_contact_points() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); assertThat(contactPointsMetadata.getTokenMap()).isNotPresent(); } @Test public void should_build_minimal_token_map_on_first_refresh() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); assertThat(firstRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(1); } @Test public void should_not_build_token_map_when_disabled() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), false, true, new Murmur3TokenFactory()); + ImmutableMap.of(ADDRESS1, NODE1), false, true, new Murmur3TokenFactory(), context); assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); } @Test public void should_stay_empty_on_first_refresh_if_partitioner_missing() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); DefaultMetadata firstRefreshMetadata = - contactPointsMetadata.withNodes(ImmutableMap.of(ADDRESS1, NODE1), true, true, null); + contactPointsMetadata.withNodes( + ImmutableMap.of(ADDRESS1, NODE1), true, true, null, context); assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); } @Test public void should_update_minimal_token_map_if_new_node_and_still_no_schema() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); DefaultMetadata secondRefreshMetadata = firstRefreshMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1, ADDRESS2, NODE2), true, false, null); + ImmutableMap.of(ADDRESS1, NODE1, ADDRESS2, NODE2), true, false, null, context); assertThat(secondRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(2); } @Test public void should_update_token_map_when_schema_changes() { - DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), "test"); + DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory()); + ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); DefaultMetadata schemaRefreshMetadata = - firstRefreshMetadata.withSchema(ImmutableMap.of(KEYSPACE_NAME, KEYSPACE), true); + firstRefreshMetadata.withSchema(ImmutableMap.of(KEYSPACE_NAME, KEYSPACE), true, context); assertThat(schemaRefreshMetadata.getTokenMap().get().getTokenRanges(KEYSPACE_NAME, NODE1)) .isNotEmpty(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 0b49b2639d8..ef05e3ae61c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -43,15 +43,15 @@ public class FullNodeListRefreshTest { public void should_add_and_remove_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, context); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); @@ -63,7 +63,7 @@ public void should_add_and_remove_nodes() { public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder() @@ -76,10 +76,10 @@ public void should_update_existing_nodes() { .withDatacenter("dc1") .withRack("rack2") .build()); - FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos, context); + FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 70ad98ab1c8..b6fcea129de 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -15,25 +15,32 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableSet; import java.net.InetSocketAddress; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class InitContactPointsRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + @Mock private InternalDriverContext context; + @Test public void should_create_nodes() { // Given InitContactPointsRefresh refresh = - new InitContactPointsRefresh(ImmutableSet.of(ADDRESS1, ADDRESS2), "test"); + new InitContactPointsRefresh(ImmutableSet.of(ADDRESS1, ADDRESS2)); // When - MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY, false); + MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index e91fc5142db..549c64835f5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -15,12 +15,17 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class RemoveNodeRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); @@ -29,15 +34,17 @@ public class RemoveNodeRefreshTest { private static final DefaultNode node1 = new DefaultNode(ADDRESS1); private static final DefaultNode node2 = new DefaultNode(ADDRESS2); + @Mock private InternalDriverContext context; + @Test public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), "test"); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); @@ -47,11 +54,11 @@ public void should_remove_existing_node() { @Test public void should_not_remove_nonexistent_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), "test"); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2, "test"); + DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); // When - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java index bfa13d61f15..8c5af1cc01f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; import com.datastax.oss.driver.internal.core.metadata.schema.DefaultKeyspaceMetadata; @@ -26,10 +27,15 @@ import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.google.common.collect.ImmutableMap; import java.util.Collections; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class SchemaRefreshTest { private static final UserDefinedType OLD_T1 = @@ -44,30 +50,20 @@ public class SchemaRefreshTest { .build(); private static final DefaultKeyspaceMetadata OLD_KS1 = newKeyspace("ks1", true, OLD_T1, OLD_T2); - private DefaultMetadata oldMetadata = - DefaultMetadata.EMPTY.withSchema(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1), false); + @Mock private InternalDriverContext context; + private DefaultMetadata oldMetadata; - private static DefaultKeyspaceMetadata newKeyspace( - String name, boolean durableWrites, UserDefinedType... userTypes) { - ImmutableMap.Builder typesMapBuilder = ImmutableMap.builder(); - for (UserDefinedType type : userTypes) { - typesMapBuilder.put(type.getName(), type); - } - return new DefaultKeyspaceMetadata( - CqlIdentifier.fromInternal(name), - durableWrites, - Collections.emptyMap(), - typesMapBuilder.build(), - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyMap()); + @Before + public void setup() { + oldMetadata = + DefaultMetadata.EMPTY.withSchema( + ImmutableMap.of(OLD_KS1.getName(), OLD_KS1), false, context); } @Test public void should_detect_dropped_keyspace() { - SchemaRefresh refresh = new SchemaRefresh(Collections.emptyMap(), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + SchemaRefresh refresh = new SchemaRefresh(Collections.emptyMap()); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); assertThat(result.newMetadata.getKeyspaces()).isEmpty(); assertThat(result.events).containsExactly(KeyspaceChangeEvent.dropped(OLD_KS1)); } @@ -76,8 +72,8 @@ public void should_detect_dropped_keyspace() { public void should_detect_created_keyspace() { DefaultKeyspaceMetadata ks2 = newKeyspace("ks2", true); SchemaRefresh refresh = - new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1, ks2.getName(), ks2), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), OLD_KS1, ks2.getName(), ks2)); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); assertThat(result.newMetadata.getKeyspaces()).hasSize(2); assertThat(result.events).containsExactly(KeyspaceChangeEvent.created(ks2)); } @@ -86,8 +82,8 @@ public void should_detect_created_keyspace() { public void should_detect_top_level_update_in_keyspace() { // Change only one top-level option (durable writes) DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, OLD_T1, OLD_T2); - SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1)); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); assertThat(result.newMetadata.getKeyspaces()).hasSize(1); assertThat(result.events).containsExactly(KeyspaceChangeEvent.updated(OLD_KS1, newKs1)); } @@ -107,8 +103,8 @@ public void should_detect_updated_children_in_keyspace() { .build(); DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", true, newT2, t3); - SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1)); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); assertThat(result.events) .containsExactly( @@ -133,8 +129,8 @@ public void should_detect_top_level_change_and_children_changes() { // Also disable durable writes DefaultKeyspaceMetadata newKs1 = newKeyspace("ks1", false, newT2, t3); - SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1), "test"); - MetadataRefresh.Result result = refresh.compute(oldMetadata, false); + SchemaRefresh refresh = new SchemaRefresh(ImmutableMap.of(OLD_KS1.getName(), newKs1)); + MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); assertThat(result.newMetadata.getKeyspaces().get(OLD_KS1.getName())).isEqualTo(newKs1); assertThat(result.events) .containsExactly( @@ -143,4 +139,21 @@ public void should_detect_top_level_change_and_children_changes() { TypeChangeEvent.updated(OLD_T2, newT2), TypeChangeEvent.created(t3)); } + + private static DefaultKeyspaceMetadata newKeyspace( + String name, boolean durableWrites, UserDefinedType... userTypes) { + ImmutableMap.Builder typesMapBuilder = ImmutableMap.builder(); + for (UserDefinedType type : userTypes) { + typesMapBuilder.put(type.getName(), type); + } + return new DefaultKeyspaceMetadata( + CqlIdentifier.fromInternal(name), + durableWrites, + Collections.emptyMap(), + typesMapBuilder.build(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap()); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java index 1934fad2c59..886b76fb73d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -30,11 +31,16 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; import static com.datastax.oss.driver.Assertions.assertThat; +@RunWith(MockitoJUnitRunner.class) public class DefaultTokenMapTest { private static final String DC1 = "DC1"; @@ -70,6 +76,14 @@ public class DefaultTokenMapTest { private static final ImmutableMap REPLICATE_ON_DC1 = ImmutableMap.of("class", "org.apache.cassandra.locator.NetworkTopologyStrategy", DC1, "1"); + @Mock private InternalDriverContext context; + private ReplicationStrategyFactory replicationStrategyFactory; + + @Before + public void setup() { + replicationStrategyFactory = new DefaultReplicationStrategyFactory(context); + } + @Test public void should_build_token_map() { // Given @@ -83,7 +97,8 @@ public void should_build_token_map() { mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); // When - DefaultTokenMap tokenMap = DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap tokenMap = + DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); // Then assertThat(tokenMap.getTokenRanges()).containsExactly(RANGE12, RANGE23, RANGE34, RANGE41); @@ -131,7 +146,8 @@ public void should_build_token_map_with_single_node() { mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); // When - DefaultTokenMap tokenMap = DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap tokenMap = + DefaultTokenMap.build(nodes, keyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); // Then assertThat(tokenMap.getTokenRanges()).containsExactly(FULL_RING); @@ -162,7 +178,9 @@ public void should_refresh_when_keyspace_replication_has_not_changed() { List oldKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); // When // The schema gets refreshed, but no keyspaces are created or dropped, and the replication @@ -171,7 +189,8 @@ public void should_refresh_when_keyspace_replication_has_not_changed() { List newKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then // Nothing was recomputed @@ -191,14 +210,17 @@ public void should_refresh_when_new_keyspace_with_existing_replication() { List nodes = ImmutableList.of(node1, node2, node3, node4); List oldKeyspaces = ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); // When List newKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); @@ -220,14 +242,17 @@ public void should_refresh_when_new_keyspace_with_new_replication() { List nodes = ImmutableList.of(node1, node2, node3, node4); List oldKeyspaces = ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); // When List newKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); @@ -250,13 +275,16 @@ public void should_refresh_when_dropped_keyspace_with_replication_still_used() { List oldKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS); // When List newKeyspaces = ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); @@ -276,13 +304,16 @@ public void should_refresh_when_dropped_keyspace_with_replication_not_used_anymo List oldKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS, REPLICATE_ON_DC1); // When List newKeyspaces = ImmutableList.of(mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); @@ -302,14 +333,17 @@ public void should_refresh_when_updated_keyspace_with_different_replication() { List oldKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_DC1)); - DefaultTokenMap oldTokenMap = DefaultTokenMap.build(nodes, oldKeyspaces, TOKEN_FACTORY, "test"); + DefaultTokenMap oldTokenMap = + DefaultTokenMap.build( + nodes, oldKeyspaces, TOKEN_FACTORY, replicationStrategyFactory, "test"); assertThat(oldTokenMap.keyspaceMaps).containsOnlyKeys(REPLICATE_ON_BOTH_DCS, REPLICATE_ON_DC1); // When List newKeyspaces = ImmutableList.of( mockKeyspace(KS1, REPLICATE_ON_BOTH_DCS), mockKeyspace(KS2, REPLICATE_ON_BOTH_DCS)); - DefaultTokenMap newTokenMap = oldTokenMap.refresh(nodes, newKeyspaces); + DefaultTokenMap newTokenMap = + oldTokenMap.refresh(nodes, newKeyspaces, replicationStrategyFactory); // Then assertThat(newTokenMap.tokenRanges).isSameAs(oldTokenMap.tokenRanges); From 8a6b736a9f295aa5888e07b59965ccdaff86e76b Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 8 Jan 2018 09:25:35 -0800 Subject: [PATCH 292/742] Fix visibility of ReplicationStrategy Now that replication strategies are pluggable, this must be public. --- .../internal/core/metadata/token/ReplicationStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java index e3606489734..30b4d5c0867 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -interface ReplicationStrategy { +public interface ReplicationStrategy { SetMultimap computeReplicasByToken( Map tokenToPrimary, List ring); } From 9030a7a1f1409923a6215849ce74288bff394660 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 8 Jan 2018 11:19:52 -0800 Subject: [PATCH 293/742] Remove obsolete comment --- .../com/datastax/oss/driver/internal/core/DefaultCluster.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java index ec675f25e4f..221fca3fb31 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java @@ -207,8 +207,6 @@ private void init() { initWasCalled = true; LOG.debug("[{}] Starting initialization", logPrefix); - // TODO fail if LBP=default, initialContactPoints not empty and local DC missing - nodeStateListeners.forEach(l -> l.onRegister(DefaultCluster.this)); MetadataManager metadataManager = context.metadataManager(); From 5134b67de511994a0ff674651a43eb230e7e9fdd Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 8 Jan 2018 14:08:45 -0600 Subject: [PATCH 294/742] JAVA-1715: Propagate unchecked exceptions to future in SyncAuthenticator (#929) --- changelog/README.md | 1 + .../api/core/auth/SyncAuthenticator.java | 14 ++++++++----- .../util/concurrent/CompletableFutures.java | 21 ++++++++++++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 36e63444bb6..1eff2517803 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [bug] JAVA-1715: Propagate unchecked exceptions to CompletableFuture in SyncAuthenticator methods - [improvement] JAVA-1714: Make replication strategies pluggable - [new feature] JAVA-1647: Handle metadata_changed flag in protocol v5 - [new feature] JAVA-1633: Handle per-request keyspace in protocol v5 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java index e24e33a28b4..e310e5a5ff1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java @@ -15,8 +15,9 @@ */ package com.datastax.oss.driver.api.core.auth; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; + import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; /** @@ -54,17 +55,20 @@ public interface SyncAuthenticator extends Authenticator { @Override default CompletionStage initialResponse() { - return CompletableFuture.completedFuture(initialResponseSync()); + return CompletableFutures.wrap(this::initialResponseSync); } @Override default CompletionStage evaluateChallenge(ByteBuffer challenge) { - return CompletableFuture.completedFuture(evaluateChallengeSync(challenge)); + return CompletableFutures.wrap(() -> evaluateChallengeSync(challenge)); } @Override default CompletionStage onAuthenticationSuccess(ByteBuffer token) { - onAuthenticationSuccessSync(token); - return CompletableFuture.completedFuture(null); + return CompletableFutures.wrap( + () -> { + onAuthenticationSuccess(token); + return null; + }); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 739703cd092..f6c40e4d6db 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -18,13 +18,14 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.google.common.base.Preconditions; -import com.google.common.base.Throwables; + import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; public class CompletableFutures { @@ -120,4 +121,22 @@ public static T getUninterruptibly(CompletionStage stage) { } } } + + /** + * Executes a function on the calling thread and returns result in a {@link CompletableFuture}. + * + *

          Similar to {@link CompletableFuture#completedFuture} except takes a {@link Supplier} and if + * the supplier throws an unchecked exception, the returning future fails with that exception. + * + * @param supplier Function to execute + * @param Type of result + * @return result of function wrapped in future + */ + public static CompletableFuture wrap(Supplier supplier) { + try { + return CompletableFuture.completedFuture(supplier.get()); + } catch (Throwable t) { + return failedFuture(t); + } + } } From 4e10f84aea73491283a7f4dd17b84e4cbef6507b Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 9 Jan 2018 14:03:56 -0600 Subject: [PATCH 295/742] Fix SyncAuthenticator.onAuthenticationSuccess --- .../datastax/oss/driver/api/core/auth/SyncAuthenticator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java index e310e5a5ff1..179a256c714 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/SyncAuthenticator.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core.auth; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; - import java.nio.ByteBuffer; import java.util.concurrent.CompletionStage; @@ -67,7 +66,7 @@ default CompletionStage evaluateChallenge(ByteBuffer challenge) { default CompletionStage onAuthenticationSuccess(ByteBuffer token) { return CompletableFutures.wrap( () -> { - onAuthenticationSuccess(token); + onAuthenticationSuccessSync(token); return null; }); } From 22d14ac6b099b711d1fcd55c553d3ced43517366 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 21 Dec 2017 15:25:40 -0600 Subject: [PATCH 296/742] JAVA-1707: Add test infrastructure for running DSE clusters with CCM --- build.yaml | 2 +- changelog/README.md | 2 + integration-tests/pom.xml | 29 ++-- .../api/core/cql/PerRequestKeyspaceIT.java | 2 +- .../cql/PreparedStatementInvalidationIT.java | 6 +- .../api/core/heartbeat/HeartbeatIT.java | 3 +- .../driver/api/core/metadata/DescribeIT.java | 31 ++-- pom.xml | 4 + .../driver/api/testinfra/DseRequirement.java | 39 +++++ .../driver/api/testinfra/ccm/BaseCcmRule.java | 100 ++++++++----- .../driver/api/testinfra/ccm/CcmBridge.java | 135 ++++++++++++------ .../api/testinfra/ccm/CustomCcmRule.java | 14 +- .../api/testinfra/cluster/ClusterRule.java | 17 +-- .../testinfra/cluster/ClusterRuleBuilder.java | 26 ++-- .../api/testinfra/cluster/ClusterUtils.java | 65 +++++++-- .../DefaultClusterBuilderInstantiator.java | 29 ++++ .../testinfra/cluster/TestConfigLoader.java | 9 +- 17 files changed, 354 insertions(+), 159 deletions(-) create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java diff --git a/build.yaml b/build.yaml index 6afa93be9cf..04fd7913449 100644 --- a/build.yaml +++ b/build.yaml @@ -12,7 +12,7 @@ build: version: 3.2.5 goals: verify properties: | - ccm.cassandraVersion=$CCM_CASSANDRA_VERSION + ccm.version=$CCM_CASSANDRA_VERSION - xunit: - "**/target/surefire-reports/TEST-*.xml" - "**/target/failsafe-reports/TEST-*.xml" diff --git a/changelog/README.md b/changelog/README.md index 1eff2517803..0650864160b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1707: Add test infrastructure for running DSE clusters with CCM - [bug] JAVA-1715: Propagate unchecked exceptions to CompletableFuture in SyncAuthenticator methods - [improvement] JAVA-1714: Make replication strategies pluggable - [new feature] JAVA-1647: Handle metadata_changed flag in protocol v5 @@ -20,6 +21,7 @@ - [improvement] JAVA-1566: Enforce API rules automatically - [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 + ### 4.0.0-alpha2 - [new feature] JAVA-1525: Handle token metadata diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 969a0e0fb38..d5b8a299255 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -76,27 +76,16 @@ - - org.apache.maven.plugins - maven-install-plugin - - true - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - org.sonatype.plugins - nexus-staging-maven-plugin - - true - + maven-jar-plugin + + + test-jar + + test-jar + + + org.apache.maven.plugins diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 06d108440f7..c8783814ee1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -35,7 +35,7 @@ * test against a local build, run with * *

          - *   -Dccm.cassandraVersion=4.0.0 -Dccm.cassandraDirectory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
          + *   -Dccm.version=4.0.0 -Dccm.directory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
            * 
          */ @Category(ParallelizableTests.class) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index d289ea575ba..2ff9e431fc9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -40,7 +40,7 @@ * version. To test against a local build, run with * *
          - *   -Dccm.cassandraVersion=4.0.0 -Dccm.cassandraDirectory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
          + *   -Dccm.version=4.0.0 -Dccm.directory=/path/to/cassandra -Ddatastax-java-driver.protocol.version=V5
            * 
          */ @Category(ParallelizableTests.class) @@ -49,8 +49,8 @@ public class PreparedStatementInvalidationIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); @Rule - public ClusterRule clusterRule = - new ClusterRule(ccmRule, "request.page-size = 2", "request.timeout = 30 seconds"); + public ClusterRule clusterRule = + new ClusterRule<>(ccmRule, "request.page-size = 2", "request.timeout = 30 seconds"); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index b3ba3d8fe14..29e73efa7a5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -55,8 +55,9 @@ public class HeartbeatIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + @SuppressWarnings("unchecked") @Rule - public ClusterRule cluster = + public ClusterRule cluster = ClusterRule.builder(simulacron) .withDefaultSession(false) .withOptions( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 180d9b160ef..498b28216ee 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -47,8 +47,8 @@ public class DescribeIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( + public static ClusterRule clusterRule = + new ClusterRule<>( ccmRule, false, true, @@ -89,25 +89,28 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { KeyspaceMetadata originalKsMeta = clusterRule.cluster().getMetadata().getKeyspace(keyspace); - // Usertype 'ztype' with two columns. Given name to ensure that even though it has an alphabetically - // later name, it shows up before other user types ('ctype') that depend on it. + // Usertype 'ztype' with two columns. Given name to ensure that even though it has an + // alphabetically later name, it shows up before other user types ('ctype') that depend on it. session.execute("CREATE TYPE ztype(c text, a int)"); - // Usertype 'xtype' with two columns. At same level as 'ztype' since both are depended on by ctype, should - // show up before 'ztype' because it's alphabetically before, even though it was created after. + // Usertype 'xtype' with two columns. At same level as 'ztype' since both are depended on by + // ctype, should show up before 'ztype' because it's alphabetically before, even though it was + // created after. session.execute("CREATE TYPE xtype(d text)"); - // Usertype 'ctype' which depends on both ztype and xtype, therefore ztype and xtype should show up earlier. + // Usertype 'ctype' which depends on both ztype and xtype, therefore ztype and xtype should show + // up earlier. session.execute( String.format( "CREATE TYPE ctype(z frozen<%s.ztype>, x frozen<%s.xtype>)", keyspaceAsCql, keyspaceAsCql)); - // Usertype 'btype' which has no dependencies, should show up before 'xtype' and 'ztype' since it's - // alphabetically before. + // Usertype 'btype' which has no dependencies, should show up before 'xtype' and 'ztype' since + // it's alphabetically before. session.execute("CREATE TYPE btype(a text)"); - // Usertype 'atype' which depends on 'ctype', so should show up after 'ctype', 'xtype' and 'ztype'. + // Usertype 'atype' which depends on 'ctype', so should show up after 'ctype', 'xtype' and + // 'ztype'. session.execute(String.format("CREATE TYPE atype(c frozen<%s.ctype>)", keyspaceAsCql)); // A simple table with a udt column and LCS compaction strategy. @@ -129,8 +132,8 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { // materialized views require 3.0+ if (ccmRule.getCassandraVersion().compareTo(CassandraVersion.V3_0_0) >= 0) { - // A materialized view for cyclist_mv, reverse clustering. created first to ensure creation order does not - // matter, alphabetical does. + // A materialized view for cyclist_mv, reverse clustering. created first to ensure creation + // order does not matter, alphabetical does. session.execute( "CREATE MATERIALIZED VIEW cyclist_by_r_age " + "AS SELECT age, birthday, name, country " @@ -196,8 +199,8 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { KeyspaceMetadata ks = clusterRule.cluster().getMetadata().getKeyspace(keyspace); assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); - // Also validate that when you create a Cluster with schema already created that the exported string - // is the same. + // Also validate that when you create a Cluster with schema already created that the exported + // string is the same. try (Cluster newCluster = ClusterUtils.newCluster(ccmRule)) { ks = newCluster.getMetadata().getKeyspace(keyspace); assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); diff --git a/pom.xml b/pom.xml index 8271cb12924..e6f574e72be 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,10 @@ maven-javadoc-plugin 3.0.0-M1 + + maven-jar-plugin + 3.0.2 + org.sonatype.plugins nexus-staging-maven-plugin diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java new file mode 100644 index 00000000000..7fbffcc8784 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java @@ -0,0 +1,39 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation for a Class or Method that defines a DSE Version requirement. If the DSE version in + * use does not meet the version requirement or DSE isn't used at all, the test is skipped. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface DseRequirement { + + /** @return The minimum version required to execute this test, i.e. "5.0.13" */ + String min() default ""; + + /** + * @return the maximum exclusive version allowed to execute this test, i.e. "2.2" means only tests + * < "2.2" may execute this test. + */ + String max() default ""; + + /** @return The description returned if this version requirement is not met. */ + String description() default "Does not meet version requirement."; +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java index a8cf45a6f8b..04315367e12 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java @@ -20,20 +20,19 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; -import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.DseRequirement; import org.junit.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.Optional; + public abstract class BaseCcmRule extends CassandraResourceRule { protected final CcmBridge ccmBridge; - private final CassandraVersion cassandraVersion; - - public BaseCcmRule(CcmBridge ccmBridge) { + BaseCcmRule(CcmBridge ccmBridge) { this.ccmBridge = ccmBridge; - this.cassandraVersion = ccmBridge.getCassandraVersion(); Runtime.getRuntime() .addShutdownHook( new Thread( @@ -57,8 +56,28 @@ protected void after() { ccmBridge.remove(); } + private Statement buildErrorStatement( + CassandraVersion requirement, String description, boolean lessThan, boolean dse) { + return new Statement() { + + @Override + public void evaluate() { + throw new AssumptionViolatedException( + String.format( + "Test requires %s %s %s but %s is configured. Description: %s", + lessThan ? "less than" : "at least", + dse ? "DSE" : "C*", + requirement, + dse ? ccmBridge.getDseVersion().orElse(null) : ccmBridge.getCassandraVersion(), + description)); + } + }; + } + @Override public Statement apply(Statement base, Description description) { + // If test is annotated with CassandraRequirement or DseRequirement, ensure configured CCM + // cluster meets those requirements. CassandraRequirement cassandraRequirement = description.getAnnotation(CassandraRequirement.class); @@ -66,21 +85,8 @@ public Statement apply(Statement base, Description description) { // if the configured cassandra cassandraRequirement exceeds the one being used skip this test. if (!cassandraRequirement.min().isEmpty()) { CassandraVersion minVersion = CassandraVersion.parse(cassandraRequirement.min()); - if (minVersion.compareTo(cassandraVersion) > 0) { - // Create a statement which simply indicates that the configured cassandra cassandraRequirement is too old for this test. - return new Statement() { - - @Override - public void evaluate() throws Throwable { - throw new AssumptionViolatedException( - "Test requires C* " - + minVersion - + " but " - + cassandraVersion - + " is configured. Description: " - + cassandraRequirement.description()); - } - }; + if (minVersion.compareTo(ccmBridge.getCassandraVersion()) > 0) { + return buildErrorStatement(minVersion, cassandraRequirement.description(), false, false); } } @@ -88,20 +94,38 @@ public void evaluate() throws Throwable { // if the test version exceeds the maximum configured one, fail out. CassandraVersion maxVersion = CassandraVersion.parse(cassandraRequirement.max()); - if (maxVersion.compareTo(cassandraVersion) <= 0) { - return new Statement() { - - @Override - public void evaluate() throws Throwable { - throw new AssumptionViolatedException( - "Test requires C* less than " - + maxVersion - + " but " - + cassandraVersion - + " is configured. Description: " - + cassandraRequirement.description()); - } - }; + if (maxVersion.compareTo(ccmBridge.getCassandraVersion()) <= 0) { + return buildErrorStatement(maxVersion, cassandraRequirement.description(), true, false); + } + } + } + + DseRequirement dseRequirement = description.getAnnotation(DseRequirement.class); + if (dseRequirement != null) { + Optional dseVersionOption = ccmBridge.getDseVersion(); + if (!dseVersionOption.isPresent()) { + return new Statement() { + + @Override + public void evaluate() { + throw new AssumptionViolatedException("Test Requires DSE but C* is configured."); + } + }; + } else { + CassandraVersion dseVersion = dseVersionOption.get(); + if (!dseRequirement.min().isEmpty()) { + CassandraVersion minVersion = CassandraVersion.parse(dseRequirement.min()); + if (minVersion.compareTo(dseVersion) > 0) { + return buildErrorStatement(dseVersion, dseRequirement.description(), false, true); + } + } + + if (!dseRequirement.max().isEmpty()) { + CassandraVersion maxVersion = CassandraVersion.parse(dseRequirement.max()); + + if (maxVersion.compareTo(ccmBridge.getCassandraVersion()) <= 0) { + return buildErrorStatement(dseVersion, dseRequirement.description(), true, true); + } } } } @@ -109,12 +133,16 @@ public void evaluate() throws Throwable { } public CassandraVersion getCassandraVersion() { - return cassandraVersion; + return ccmBridge.getCassandraVersion(); + } + + public Optional getDseVersion() { + return ccmBridge.getDseVersion(); } @Override public ProtocolVersion getHighestProtocolVersion() { - if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) >= 0) { + if (ccmBridge.getCassandraVersion().compareTo(CassandraVersion.V2_2_0) >= 0) { return CoreProtocolVersion.V4; } else { return CoreProtocolVersion.V3; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index c881b7d5f49..94611bb4ae7 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.testinfra.ccm; import com.datastax.oss.driver.api.core.CassandraVersion; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -30,9 +31,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; + import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteStreamHandler; @@ -49,12 +52,9 @@ public class CcmBridge implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(CcmBridge.class); - private final CassandraVersion cassandraVersion; - private final String cassandraDirectory; - private final int[] nodes; - private final Path directory; + private final Path configDirectory; private final AtomicBoolean started = new AtomicBoolean(); @@ -62,16 +62,19 @@ public class CcmBridge implements AutoCloseable { private final String ipPrefix; - private final Map initialConfiguration; + private final Map cassandraConfiguration; + private final Map dseConfiguration; private final List createOptions; + private final List dseWorkloads; private final String jvmArgs; - public static final CassandraVersion DEFAULT_CASSANDRA_VERSION = - CassandraVersion.parse(System.getProperty("ccm.cassandraVersion", "3.11.0")); + public static final CassandraVersion VERSION = + CassandraVersion.parse(System.getProperty("ccm.version", "3.11.0")); + + public static final String INSTALL_DIRECTORY = System.getProperty("ccm.directory"); - public static final String DEFAULT_CASSANDRA_DIRECTORY = - System.getProperty("ccm.cassandraDirectory"); + public static final Boolean DSE_ENABLEMENT = Boolean.getBoolean("ccm.dse"); public static final String DEFAULT_CLIENT_TRUSTSTORE_PASSWORD = "cassandra1sfun"; public static final String DEFAULT_CLIENT_TRUSTSTORE_PATH = "/client.truststore"; @@ -101,18 +104,27 @@ public class CcmBridge implements AutoCloseable { private static final File DEFAULT_SERVER_KEYSTORE_FILE = createTempStore(DEFAULT_SERVER_KEYSTORE_PATH); + // major DSE versions + private static final CassandraVersion V6_0_0 = CassandraVersion.parse("6.0.0"); + private static final CassandraVersion V5_1_0 = CassandraVersion.parse("5.1.0"); + private static final CassandraVersion V5_0_0 = CassandraVersion.parse("5.0.0"); + + // mapped C* versions from DSE versions + private static final CassandraVersion V4_0_0 = CassandraVersion.parse("4.0.0"); + private static final CassandraVersion V3_10 = CassandraVersion.parse("3.10"); + private static final CassandraVersion V3_0_15 = CassandraVersion.parse("3.0.15"); + private static final CassandraVersion V2_1_19 = CassandraVersion.parse("2.1.19"); + private CcmBridge( - Path directory, - CassandraVersion cassandraVersion, - String cassandraDirectory, + Path configDirectory, int[] nodes, String ipPrefix, - Map initialConfiguration, + Map cassandraConfiguration, + Map dseConfiguration, List createOptions, - Collection jvmArgs) { - this.directory = directory; - this.cassandraVersion = cassandraVersion; - this.cassandraDirectory = cassandraDirectory; + Collection jvmArgs, + List dseWorkloads) { + this.configDirectory = configDirectory; if (nodes.length == 1) { // Hack to ensure that the default DC is always called 'dc1': pass a list ('-nX:0') even if // there is only one DC (with '-nX', CCM configures `SimpleSnitch`, which hard-codes the name @@ -125,7 +137,8 @@ private CcmBridge( this.nodes = nodes; } this.ipPrefix = ipPrefix; - this.initialConfiguration = initialConfiguration; + this.cassandraConfiguration = cassandraConfiguration; + this.dseConfiguration = dseConfiguration; this.createOptions = createOptions; StringBuilder allJvmArgs = new StringBuilder(""); @@ -139,18 +152,39 @@ private CcmBridge( allJvmArgs.append(quote); } this.jvmArgs = allJvmArgs.toString(); + this.dseWorkloads = dseWorkloads; + } + + public Optional getDseVersion() { + return DSE_ENABLEMENT ? Optional.of(VERSION) : Optional.empty(); } public CassandraVersion getCassandraVersion() { - return cassandraVersion; + if (!DSE_ENABLEMENT) { + return VERSION; + } else { + CassandraVersion stableVersion = VERSION.nextStable(); + if (stableVersion.compareTo(V6_0_0) >= 0) { + return V4_0_0; + } else if (stableVersion.compareTo(V5_1_0) >= 0) { + return V3_10; + } else if (stableVersion.compareTo(V5_0_0) >= 0) { + return V3_0_15; + } else { + return V2_1_19; + } + } } public void create() { if (created.compareAndSet(false, true)) { - if (cassandraDirectory != null) { - createOptions.add("--install-dir=" + new File(cassandraDirectory).getAbsolutePath()); + if (INSTALL_DIRECTORY != null) { + createOptions.add("--install-dir=" + new File(INSTALL_DIRECTORY).getAbsolutePath()); } else { - createOptions.add("-v " + cassandraVersion.toString()); + createOptions.add("-v " + VERSION.toString()); + } + if (DSE_ENABLEMENT) { + createOptions.add("--dse"); } execute( "create", @@ -161,9 +195,20 @@ public void create() { Arrays.stream(nodes).mapToObj(n -> "" + n).collect(Collectors.joining(":")), createOptions.stream().collect(Collectors.joining(" "))); - for (Map.Entry conf : initialConfiguration.entrySet()) { + for (Map.Entry conf : cassandraConfiguration.entrySet()) { execute("updateconf", String.format("%s:%s", conf.getKey(), conf.getValue())); } + if (getCassandraVersion().compareTo(CassandraVersion.V2_2_0) >= 0) { + execute("updateconf", "enable_user_defined_functions:true"); + } + if (DSE_ENABLEMENT) { + for (Map.Entry conf : dseConfiguration.entrySet()) { + execute("updatedseconf", String.format("%s:%s", conf.getKey(), conf.getValue())); + } + if (!dseWorkloads.isEmpty()) { + execute("setworkload", String.join(",", dseWorkloads)); + } + } } } @@ -201,7 +246,10 @@ public void stop(int n) { synchronized void execute(String... args) { String command = - "ccm " + String.join(" ", args) + " --config-dir=" + directory.toFile().getAbsolutePath(); + "ccm " + + String.join(" ", args) + + " --config-dir=" + + configDirectory.toFile().getAbsolutePath(); logger.debug("Executing: " + command); CommandLine cli = CommandLine.parse(command); @@ -247,6 +295,8 @@ public void close() { /** * Extracts a keystore from the classpath into a temporary file. * + *

          + * *

          This is needed as the keystore could be part of a built test jar used by other projects, and * they need to be extracted to a file system so cassandra may use them. * @@ -278,17 +328,17 @@ public static Builder builder() { public static class Builder { private int[] nodes = {1}; private final Map cassandraConfiguration = new LinkedHashMap<>(); + private final Map dseConfiguration = new LinkedHashMap<>(); private final List jvmArgs = new ArrayList<>(); private String ipPrefix = "127.0.0."; - private CassandraVersion cassandraVersion = CcmBridge.DEFAULT_CASSANDRA_VERSION; - private String cassandraDirectory = CcmBridge.DEFAULT_CASSANDRA_DIRECTORY; private final List createOptions = new ArrayList<>(); + private final List dseWorkloads = new ArrayList<>(); - private final Path directory; + private final Path configDirectory; private Builder() { try { - this.directory = Files.createTempDirectory("ccm"); + this.configDirectory = Files.createTempDirectory("ccm"); } catch (IOException e) { // change to unchecked for now. throw new RuntimeException(e); @@ -302,18 +352,13 @@ public Builder withCassandraConfiguration(String key, Object value) { return this; } - public Builder withJvmArgs(String... jvmArgs) { - Collections.addAll(this.jvmArgs, jvmArgs); - return this; - } - - public Builder withCassandraVersion(CassandraVersion cassandraVersion) { - this.cassandraVersion = cassandraVersion; + public Builder withDseConfiguration(String key, Object value) { + dseConfiguration.put(key, value); return this; } - public Builder withCassandraDirectory(String cassandraDirectory) { - this.cassandraDirectory = cassandraDirectory; + public Builder withJvmArgs(String... jvmArgs) { + Collections.addAll(this.jvmArgs, jvmArgs); return this; } @@ -354,19 +399,21 @@ public Builder withSslAuth() { return this; } + public Builder withDseWorkloads(String... workloads) { + this.dseWorkloads.addAll(Arrays.asList(workloads)); + return this; + } + public CcmBridge build() { - if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) >= 0) { - cassandraConfiguration.put("enable_user_defined_functions", "true"); - } return new CcmBridge( - directory, - cassandraVersion, - cassandraDirectory, + configDirectory, nodes, ipPrefix, cassandraConfiguration, + dseConfiguration, createOptions, - jvmArgs); + jvmArgs, + dseWorkloads); } } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index 49bbadf83e5..d6acbb841e4 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.testinfra.ccm; -import com.datastax.oss.driver.api.core.CassandraVersion; import java.util.concurrent.atomic.AtomicReference; /** @@ -73,13 +72,18 @@ public Builder withCassandraConfiguration(String key, Object value) { return this; } - public Builder withJvmArgs(String... jvmArgs) { - bridgeBuilder.withJvmArgs(jvmArgs); + public Builder withDseConfiguration(String key, Object value) { + bridgeBuilder.withDseConfiguration(key, value); return this; } - public Builder withCassandraVersion(CassandraVersion cassandraVersion) { - bridgeBuilder.withCassandraVersion(cassandraVersion); + public Builder withDseWorkloads(String... workloads) { + bridgeBuilder.withDseWorkloads(workloads); + return this; + } + + public Builder withJvmArgs(String... jvmArgs) { + bridgeBuilder.withJvmArgs(jvmArgs); return this; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java index 9794502aa6f..82d2e9fceed 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import org.junit.rules.ExternalResource; @@ -49,7 +49,7 @@ *

          If you would rather create a new keyspace manually in each test, see the utility methods in * {@link ClusterUtils}. */ -public class ClusterRule extends ExternalResource { +public class ClusterRule extends ExternalResource { // the CCM or Simulacron rule to depend on private final CassandraResourceRule cassandraResource; @@ -59,10 +59,10 @@ public class ClusterRule extends ExternalResource { private final String[] defaultClusterOptions; // the default cluster that is auto created for this rule. - private Cluster cluster; + private Cluster cluster; // the default session that is auto created for this rule and is tied to the given keyspace. - private CqlSession defaultSession; + private T defaultSession; private DriverConfigProfile slowProfile; @@ -71,8 +71,9 @@ public class ClusterRule extends ExternalResource { * * @param cassandraResource resource to create clusters for. */ - public static ClusterRuleBuilder builder(CassandraResourceRule cassandraResource) { - return new ClusterRuleBuilder(cassandraResource); + public static ClusterRuleBuilder builder( + CassandraResourceRule cassandraResource) { + return new ClusterRuleBuilder<>(cassandraResource); } /** @see #builder(CassandraResourceRule) */ @@ -122,7 +123,7 @@ protected void after() { } /** @return the cluster created with this rule. */ - public Cluster cluster() { + public Cluster cluster() { return cluster; } @@ -130,7 +131,7 @@ public Cluster cluster() { * @return the default session created with this rule, or {@code null} if no default session was * created. */ - public CqlSession session() { + public T session() { return defaultSession; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java index 71daf98a534..0f2480dc51d 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java @@ -15,12 +15,13 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -public class ClusterRuleBuilder { +public class ClusterRuleBuilder { private final CassandraResourceRule cassandraResource; private boolean createDefaultSession = true; @@ -28,6 +29,9 @@ public class ClusterRuleBuilder { private String[] options = new String[] {}; private NodeStateListener[] nodeStateListeners = new NodeStateListener[] {}; + @SuppressWarnings("unchecked") + protected final SelfT self = (SelfT) this; + public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { this.cassandraResource = cassandraResource; } @@ -46,9 +50,9 @@ public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { * {@link SimulacronRule}, this option is ignored, no keyspace gets created, and {@link * ClusterRule#keyspace()} returns {@code null}. */ - public ClusterRuleBuilder withKeyspace(boolean createKeyspace) { + public SelfT withKeyspace(boolean createKeyspace) { this.createKeyspace = createKeyspace; - return this; + return self; } /** @@ -60,24 +64,24 @@ public ClusterRuleBuilder withKeyspace(boolean createKeyspace) { * *

          If this method is not called, the default value is {@code true}. */ - public ClusterRuleBuilder withDefaultSession(boolean createDefaultSession) { + public SelfT withDefaultSession(boolean createDefaultSession) { this.createDefaultSession = createDefaultSession; - return this; + return self; } /** A set of options to override in the cluster configuration. */ - public ClusterRuleBuilder withOptions(String... options) { + public SelfT withOptions(String... options) { this.options = options; - return this; + return self; } - public ClusterRuleBuilder withNodeStateListeners(NodeStateListener... listeners) { + public SelfT withNodeStateListeners(NodeStateListener... listeners) { this.nodeStateListeners = listeners; - return this; + return self; } - public ClusterRule build() { - return new ClusterRule( + public ClusterRule build() { + return new ClusterRule<>( cassandraResource, createKeyspace, createDefaultSession, nodeStateListeners, options); } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java index 20d925c6b87..50000e7748e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java @@ -16,14 +16,19 @@ package com.datastax.oss.driver.api.testinfra.cluster; import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; /** @@ -53,27 +58,61 @@ * ClusterRule} provides a simpler alternative. */ public class ClusterUtils { + private static final Logger LOG = LoggerFactory.getLogger(ClusterUtils.class); private static final AtomicInteger keyspaceId = new AtomicInteger(); + private static final String CLUSTER_BUILDER_CLASS = + System.getProperty( + "cluster.builder", + "com.datastax.oss.driver.api.testinfra.cluster.DefaultClusterBuilderInstantiator"); + + @SuppressWarnings("unchecked") + public static ClusterBuilder> baseBuilder() { + try { + Class clazz = Class.forName(CLUSTER_BUILDER_CLASS); + Method m = clazz.getMethod("builder"); + return (ClusterBuilder>) m.invoke(null); + } catch (Exception e) { + LOG.warn( + "Could not construct ClusterBuilder from {}, using default implementation.", + CLUSTER_BUILDER_CLASS, + e); + return (ClusterBuilder>) Cluster.builder(); + } + } + + public static String getConfigPath() { + try { + Class clazz = Class.forName(CLUSTER_BUILDER_CLASS); + Method m = clazz.getMethod("configPath"); + return (String) m.invoke(null); + } catch (Exception e) { + LOG.warn("Could not get config path from {}, using default.", CLUSTER_BUILDER_CLASS, e); + return "datastax-java-driver"; + } + } + /** * Creates a new instance of the driver's default {@code Cluster} implementation, using the nodes * in the 0th DC of the provided Cassandra resource as contact points, and the default * configuration augmented with the provided options. */ - public static Cluster newCluster( + public static Cluster newCluster( CassandraResourceRule cassandraResource, String... options) { return newCluster(cassandraResource, new NodeStateListener[0], options); } - public static Cluster newCluster( + @SuppressWarnings("unchecked") + public static Cluster newCluster( CassandraResourceRule cassandraResource, NodeStateListener[] nodeStateListeners, String... options) { - return Cluster.builder() - .addContactPoints(cassandraResource.getContactPoints()) - .addNodeStateListeners(nodeStateListeners) - .withConfigLoader(new TestConfigLoader(options)) - .build(); + return (Cluster) + baseBuilder() + .addContactPoints(cassandraResource.getContactPoints()) + .addNodeStateListeners(nodeStateListeners) + .withConfigLoader(new TestConfigLoader(options)) + .build(); } /** @@ -87,7 +126,7 @@ public static CqlIdentifier uniqueKeyspaceId() { /** Creates a keyspace through the given cluster instance, with the given profile. */ public static void createKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { try (CqlSession session = cluster.connect()) { SimpleStatement createKeyspace = SimpleStatement.builder( @@ -108,13 +147,13 @@ public static void createKeyspace( * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, * and storing it in a local variable so it can be reused. */ - public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { + public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { createKeyspace(cluster, keyspace, slowProfile(cluster)); } /** Drops a keyspace through the given cluster instance, with the given profile. */ public static void dropKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { + Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { try (CqlSession session = cluster.connect()) { session.execute( SimpleStatement.builder( @@ -132,7 +171,7 @@ public static void dropKeyspace( * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, * and storing it in a local variable so it can be reused. */ - public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { + public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { dropKeyspace(cluster, keyspace, slowProfile(cluster)); } @@ -140,7 +179,7 @@ public static void dropKeyspace(Cluster cluster, CqlIdentifier keysp * Builds a profile derived from the given cluster's default profile, with a higher request * timeout (30 seconds) that is appropriate for DML operations. */ - public static DriverConfigProfile slowProfile(Cluster cluster) { + public static DriverConfigProfile slowProfile(Cluster cluster) { return cluster .getContext() .config() diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java new file mode 100644 index 00000000000..ed0c82bd47e --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java @@ -0,0 +1,29 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.cluster; + +import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.ClusterBuilder; + +public class DefaultClusterBuilderInstantiator { + public static ClusterBuilder builder() { + return Cluster.builder(); + } + + public static String configPath() { + return "datastax-java-driver"; + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java index 0dd9c314c71..ef33367d975 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.testinfra.cluster; import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -35,7 +36,11 @@ private static Config buildConfig(String... customOptions) { customConfig, "netty.io-group.shutdown.quiet-period = 0", "netty.admin-group.shutdown.quiet-period = 0"); - return ConfigFactory.parseString(additionalCustomConfig) - .withFallback(DEFAULT_CONFIG_SUPPLIER.get()); + return ConfigFactory.parseString(additionalCustomConfig).withFallback(getConfig()); + } + + public static Config getConfig() { + ConfigFactory.invalidateCaches(); + return ConfigFactory.load().getConfig(ClusterUtils.getConfigPath()); } } From 590630d85759215e4ae8d1c9de49fbbd94467cc8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Jan 2018 15:11:18 -0800 Subject: [PATCH 297/742] JAVA-1713: Use less nodes in DefaultLoadBalancingPolicyIT --- changelog/README.md | 1 + .../DefaultLoadBalancingPolicyIT.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 0650864160b..d2f89995ef4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1713: Use less nodes in DefaultLoadBalancingPolicyIT - [improvement] JAVA-1707: Add test infrastructure for running DSE clusters with CCM - [bug] JAVA-1715: Propagate unchecked exceptions to CompletableFuture in SyncAuthenticator methods - [improvement] JAVA-1714: Make replication strategies pluggable diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index da80c4f9a7c..549fc0ad68c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -49,7 +49,7 @@ public class DefaultLoadBalancingPolicyIT { private static final String LOCAL_DC = "dc1"; - @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(5, 5).build(); + @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(4, 1).build(); @ClassRule public static ClusterRule clusterRule = @@ -64,7 +64,7 @@ public static void setup() { CqlSession session = clusterRule.session(); session.execute( "CREATE KEYSPACE test " - + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3, 'dc2': 3}"); + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 2, 'dc2': 1}"); session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); } @@ -106,16 +106,16 @@ public void should_use_round_robin_on_local_dc_when_not_enough_routing_informati for (Statement statement : statements) { List coordinators = new ArrayList<>(); - for (int i = 0; i < 15; i++) { + for (int i = 0; i < 12; i++) { ResultSet rs = clusterRule.session().execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); coordinators.add(coordinator); } - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 4; i++) { assertThat(coordinators.get(i)) - .isEqualTo(coordinators.get(5 + i)) - .isEqualTo(coordinators.get(10 + i)); + .isEqualTo(coordinators.get(4 + i)) + .isEqualTo(coordinators.get(8 + i)); } } } @@ -131,7 +131,7 @@ public void should_prioritize_replicas_when_routing_information_present() { localReplicas.add(replica); } } - assertThat(localReplicas).hasSize(3); + assertThat(localReplicas).hasSize(2); // TODO add statements with setKeyspace when that is supported List statements = @@ -147,7 +147,7 @@ public void should_prioritize_replicas_when_routing_information_present() { // Since the exact order is randomized, just run a bunch of queries and check that we get a // reasonable distribution: Map hits = new HashMap<>(); - for (int i = 0; i < 3000; i++) { + for (int i = 0; i < 2000; i++) { ResultSet rs = clusterRule.session().execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); assertThat(localReplicas).contains(coordinator); @@ -178,7 +178,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep .becomesTrue(); } } - assertThat(localReplicas).hasSize(3); + assertThat(localReplicas).hasSize(2); // TODO add statements with setKeyspace when that is supported List statements = From 0dc32102088a0dd1ca94d81327907801d963a158 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 10 Jan 2018 18:25:38 -0800 Subject: [PATCH 298/742] JAVA-1720: Merge Cluster and Session into a single interface --- changelog/README.md | 1 + core/console.scala | 10 +- .../datastax/oss/driver/api/core/Cluster.java | 218 ------ .../driver/api/core/{cql => }/CqlSession.java | 13 +- ...terBuilder.java => CqlSessionBuilder.java} | 11 +- .../api/core/config/CoreDriverOption.java | 3 +- .../api/core/context/DriverContext.java | 6 +- .../driver/api/core/cql/AsyncResultSet.java | 1 + .../driver/api/core/cql/ExecutionInfo.java | 4 +- .../driver/api/core/cql/PrepareRequest.java | 1 + .../api/core/cql/PreparedStatement.java | 1 + .../oss/driver/api/core/cql/ResultSet.java | 1 + .../driver/api/core/cql/SimpleStatement.java | 1 + .../oss/driver/api/core/cql/Statement.java | 2 +- .../loadbalancing/LoadBalancingPolicy.java | 4 +- .../driver/api/core/metadata/Metadata.java | 6 +- .../api/core/metadata/NodeStateListener.java | 16 +- .../driver/api/core/metadata/TokenMap.java | 4 +- .../metadata/schema/SchemaChangeListener.java | 12 +- .../schema/SchemaChangeListenerBase.java | 6 +- .../oss/driver/api/core/session/Session.java | 176 ++++- .../SessionBuilder.java} | 46 +- .../specex/SpeculativeExecutionPolicy.java | 7 +- .../driver/internal/core/ClusterWrapper.java | 117 ---- .../driver/internal/core/DefaultCluster.java | 472 ------------- .../typesafe/DefaultDriverConfigLoader.java | 6 +- .../core/context/DefaultDriverContext.java | 34 +- .../core/context/DefaultNettyOptions.java | 4 +- .../core/context/InternalDriverContext.java | 3 + .../core/control/ControlConnection.java | 2 +- .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/DefaultAsyncResultSet.java | 2 +- .../internal/core/cql/QueryTraceFetcher.java | 2 +- .../DefaultLoadBalancingPolicy.java | 2 +- .../core/metadata/AddNodeRefresh.java | 2 +- .../core/metadata/DefaultMetadata.java | 2 +- .../core/metadata/DefaultTopologyMonitor.java | 2 +- .../core/metadata/FullNodeListRefresh.java | 2 +- .../metadata/InitContactPointsRefresh.java | 2 +- .../metadata/LoadBalancingPolicyWrapper.java | 2 +- .../core/metadata/MetadataManager.java | 2 +- .../core/metadata/MetadataRefresh.java | 4 +- .../core/metadata/NodeStateManager.java | 2 +- .../core/metadata/RemoveNodeRefresh.java | 2 +- .../core/metadata/TopologyMonitor.java | 4 +- .../parsing/DataTypeClassNameParser.java | 2 +- .../schema/parsing/FunctionParser.java | 2 +- .../schema/parsing/RelationParser.java | 2 +- .../metadata/schema/parsing/SchemaParser.java | 2 +- .../queries/DefaultSchemaQueriesFactory.java | 2 +- .../DefaultReplicationStrategyFactory.java | 2 +- .../token/DefaultTokenFactoryRegistry.java | 2 +- .../internal/core/pool/ChannelPool.java | 3 +- .../internal/core/protocol/Lz4Compressor.java | 2 +- .../internal/core/session/DefaultSession.java | 628 +++++++++--------- .../internal/core/session/PoolManager.java | 505 ++++++++++++++ .../{ => session}/SchemaListenerNotifier.java | 2 +- .../internal/core/session/SessionWrapper.java | 59 ++ core/src/main/resources/reference.conf | 18 +- .../DefaultDriverConfigLoaderTest.java | 2 +- .../core/cql/DefaultAsyncResultSetTest.java | 2 +- .../core/cql/QueryTraceFetcherTest.java | 2 +- ...Test.java => DefaultSessionPoolsTest.java} | 139 +++- .../oss/driver/api/core/ConnectIT.java | 14 +- .../ProtocolVersionInitialNegotiationIT.java | 27 +- .../core/ProtocolVersionMixedClusterIT.java | 23 +- .../core/auth/PlainTextAuthProviderIT.java | 18 +- .../core/compression/DirectCompressionIT.java | 18 +- .../core/compression/HeapCompressionIT.java | 18 +- .../core/config/DriverConfigProfileIT.java | 40 +- .../config/DriverConfigProfileReloadIT.java | 41 +- .../api/core/connection/FrameLengthIT.java | 15 +- .../driver/api/core/cql/AsyncResultSetIT.java | 22 +- .../driver/api/core/cql/BatchStatementIT.java | 62 +- .../driver/api/core/cql/BoundStatementIT.java | 43 +- .../api/core/cql/PerRequestKeyspaceIT.java | 59 +- .../cql/PreparedStatementInvalidationIT.java | 40 +- .../oss/driver/api/core/cql/QueryTraceIT.java | 9 +- .../api/core/cql/SimpleStatementIT.java | 6 +- .../oss/driver/api/core/data/DataTypeIT.java | 53 +- .../core/heartbeat/HeartbeatDisabledIT.java | 53 +- .../api/core/heartbeat/HeartbeatIT.java | 326 ++++----- .../DefaultLoadBalancingPolicyIT.java | 27 +- .../api/core/metadata/ByteOrderedTokenIT.java | 19 +- .../metadata/ByteOrderedTokenVnodesIT.java | 19 +- .../driver/api/core/metadata/DescribeIT.java | 42 +- .../api/core/metadata/Murmur3TokenIT.java | 19 +- .../core/metadata/Murmur3TokenVnodesIT.java | 19 +- .../driver/api/core/metadata/NodeStateIT.java | 80 ++- .../api/core/metadata/RandomTokenIT.java | 19 +- .../core/metadata/RandomTokenVnodesIT.java | 19 +- .../api/core/metadata/SchemaAgreementIT.java | 29 +- .../api/core/metadata/SchemaChangesIT.java | 179 ++--- .../driver/api/core/metadata/SchemaIT.java | 86 ++- .../driver/api/core/metadata/TokenITBase.java | 40 +- .../api/core/retry/DefaultRetryPolicyIT.java | 39 +- .../api/core/session/RequestProcessorIT.java | 38 +- .../core/specex/SpeculativeExecutionIT.java | 36 +- .../core/ssl/DefaultSslEngineFactoryIT.java | 10 +- ...faultSslEngineFactoryWithClientAuthIT.java | 10 +- ...ineFactoryWithClientAuthNotProvidedIT.java | 10 +- ...ineFactoryWithTruststoreNotProvidedIT.java | 8 +- .../type/codec/registry/CodecRegistryIT.java | 39 +- ...rBuilder.java => GuavaSessionBuilder.java} | 16 +- ...usterUtils.java => GuavaSessionUtils.java} | 6 +- .../guava/internal/DefaultGuavaCluster.java | 49 -- .../guava/internal/DefaultGuavaSession.java | 2 +- .../guava/internal/GuavaDriverContext.java | 2 +- .../src/test/resources/application.conf | 1 + manual/core/README.md | 110 +-- manual/core/address_resolution/README.md | 4 +- manual/core/configuration/README.md | 40 +- manual/core/metadata/README.md | 8 +- manual/core/metadata/node/README.md | 4 +- manual/core/metadata/schema/README.md | 20 +- manual/core/metadata/token/README.md | 4 +- manual/core/statements/simple/README.md | 4 +- .../cluster/CqlSessionRuleBuilder.java | 31 + ...=> DefaultSessionBuilderInstantiator.java} | 10 +- .../{ClusterRule.java => SessionRule.java} | 77 +-- ...leBuilder.java => SessionRuleBuilder.java} | 43 +- .../{ClusterUtils.java => SessionUtils.java} | 130 ++-- .../testinfra/cluster/TestConfigLoader.java | 4 +- upgrade_guide/README.md | 46 +- 124 files changed, 2345 insertions(+), 2538 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java rename core/src/main/java/com/datastax/oss/driver/api/core/{cql => }/CqlSession.java (85%) rename core/src/main/java/com/datastax/oss/driver/api/core/{DefaultClusterBuilder.java => CqlSessionBuilder.java} (64%) rename core/src/main/java/com/datastax/oss/driver/api/core/{ClusterBuilder.java => session/SessionBuilder.java} (83%) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java rename core/src/main/java/com/datastax/oss/driver/internal/core/{ => session}/SchemaListenerNotifier.java (99%) rename core/src/test/java/com/datastax/oss/driver/internal/core/session/{DefaultSessionTest.java => DefaultSessionPoolsTest.java} (86%) rename integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/{GuavaClusterBuilder.java => GuavaSessionBuilder.java} (74%) rename integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/{GuavaClusterUtils.java => GuavaSessionUtils.java} (84%) delete mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/{DefaultClusterBuilderInstantiator.java => DefaultSessionBuilderInstantiator.java} (74%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/{ClusterRule.java => SessionRule.java} (60%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/{ClusterRuleBuilder.java => SessionRuleBuilder.java} (57%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/{ClusterUtils.java => SessionUtils.java} (55%) diff --git a/changelog/README.md b/changelog/README.md index d2f89995ef4..9125af39fa3 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1720: Merge Cluster and Session into a single interface - [improvement] JAVA-1713: Use less nodes in DefaultLoadBalancingPolicyIT - [improvement] JAVA-1707: Add test infrastructure for running DSE clusters with CCM - [bug] JAVA-1715: Propagate unchecked exceptions to CompletableFuture in SyncAuthenticator methods diff --git a/core/console.scala b/core/console.scala index cac890d08e6..f01b3141842 100644 --- a/core/console.scala +++ b/core/console.scala @@ -15,7 +15,7 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyEvent import com.datastax.oss.driver.internal.core.context.InternalDriverContext import java.net.InetSocketAddress -import com.datastax.oss.driver.api.core.cql.CqlSession +import CqlSession // Heartbeat logs every 30 seconds are annoying in the console, raise the interval System.setProperty("datastax-java-driver.connection.heartbeat.interval", "1 hour") @@ -27,13 +27,13 @@ val address4 = new InetSocketAddress("127.0.0.4", 9042) val address5 = new InetSocketAddress("127.0.0.5", 9042) val address6 = new InetSocketAddress("127.0.0.6", 9042) -val builder = Cluster.builder().addContactPoint(address1) +val builder = CqlSession.builder().addContactPoint(address1) println("********************************************") println("* To start a driver instance, run: *") -println("* implicit val cluster = builder.build *") +println("* implicit val session = builder.build *") println("********************************************") -def fire(event: AnyRef)(implicit cluster: Cluster[CqlSession]): Unit = { - cluster.getContext.asInstanceOf[InternalDriverContext].eventBus().fire(event) +def fire(event: AnyRef)(implicit session: CqlSession): Unit = { + session.getContext.asInstanceOf[InternalDriverContext].eventBus().fire(event) } \ No newline at end of file diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java b/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java deleted file mode 100644 index f9a56f2b4d0..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Cluster.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.api.core; - -import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.metadata.Metadata; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.cql.CqlSession; -import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import java.util.ResourceBundle; -import java.util.concurrent.CompletionStage; - -/** - * An instance of the driver, that connects to a Cassandra cluster. - * - * @param the type of session returned by this cluster. By default, this is {@link - * CqlSession}. - */ -public interface Cluster extends AsyncAutoCloseable { - - /** Returns a builder to create a new instance of the default implementation. */ - static DefaultClusterBuilder builder() { - return new DefaultClusterBuilder(); - } - - /** - * The current version of the driver. - * - *

          This is intended for products that wrap or extend the driver, as a way to check - * compatibility if end-users override the driver version in their application. - */ - static String getDriverVersion() { - // Note: getBundle caches its result - return ResourceBundle.getBundle("com.datastax.oss.driver.Driver").getString("driver.version"); - } - - /** - * The unique name identifying this cluster. - * - * @see CoreDriverOption#CLUSTER_NAME - */ - String getName(); - - /** - * Returns a snapshot of the Cassandra cluster's topology and schema metadata. - * - *

          In order to provide atomic updates, this method returns an immutable object: the node list, - * token map, and schema contained in a given instance will always be consistent with each other - * (but note that {@link Node} itself is not immutable: some of its properties will be updated - * dynamically, in particular {@link Node#getState()}). - * - *

          As a consequence of the above, you should call this method each time you need a fresh view - * of the metadata. Do not call it once and store the result, because it is a frozen - * snapshot that will become stale over time. - * - *

          If a metadata refresh triggers events (such as node added/removed, or schema events), then - * the new version of the metadata is guaranteed to be visible by the time you receive these - * events. - */ - Metadata getMetadata(); - - /** Whether schema metadata is currently enabled. */ - boolean isSchemaMetadataEnabled(); - - /** - * Enable or disable schema metadata programmatically. - * - *

          Use this method to override the value defined in the driver's configuration; one typical use - * case is to temporarily disable schema metadata while the client issues a sequence of DDL - * statements. - * - *

          If calling this method re-enables the metadata (that is, {@link #isSchemaMetadataEnabled()} - * was false before, and becomes true as a result of the call), a refresh is also triggered. - * - * @param newValue a boolean value to enable or disable schema metadata programmatically, or - * {@code null} to use the driver's configuration. - * @see CoreDriverOption#METADATA_SCHEMA_ENABLED - * @return if this call triggered a refresh, a future that will complete when that refresh is - * complete. Otherwise, a completed future with the current metadata. - */ - CompletionStage setSchemaMetadataEnabled(Boolean newValue); - - /** - * Force an immediate refresh of the schema metadata, even if it is currently disabled (either in - * the configuration or via {@link #setSchemaMetadataEnabled(Boolean)}). - * - *

          The new metadata is returned in the resulting future (and will also be reflected by {@link - * #getMetadata()} when that future completes). - */ - CompletionStage refreshSchemaAsync(); - - /** - * Convenience method to call {@link #refreshSchemaAsync()} and block for the result. - * - *

          This must not be called on a driver thread. - */ - default Metadata refreshSchema() { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(refreshSchemaAsync()); - } - - /** - * Checks if all nodes in the cluster agree on a common schema version. - * - *

          Due to the distributed nature of Cassandra, schema changes made on one node might not be - * immediately visible to others. Under certain circumstances, the driver waits until all nodes - * agree on a common schema version (namely: before a schema refresh, before repreparing all - * queries on a newly up node, and before completing a successful schema-altering query). To do - * so, it queries system tables to find out the schema version of all nodes that are currently - * {@link NodeState#UP UP}. If all the versions match, the check succeeds, otherwise it is retried - * periodically, until a given timeout (specified in the configuration). - * - *

          A schema agreement failure is not fatal, but it might produce unexpected results (for - * example, getting an "unconfigured table" error for a table that you created right before, just - * because the two queries went to different coordinators). - * - *

          Note that schema agreement never succeeds in a mixed-version cluster (it would be - * challenging because the way the schema version is computed varies across server versions); the - * assumption is that schema updates are unlikely to happen during a rolling upgrade anyway. - * - * @return a future that completes with {@code true} if the nodes agree, or {@code false} if the - * timeout fired. - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT - */ - CompletionStage checkSchemaAgreementAsync(); - - /** - * Convenience method to call {@link #checkSchemaAgreementAsync()} and block for the result. - * - *

          This must not be called on a driver thread. - */ - default boolean checkSchemaAgreement() { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(checkSchemaAgreementAsync()); - } - - /** Returns a context that provides access to all the policies used by this driver instance. */ - DriverContext getContext(); - - /** Creates a new session to execute requests against a given keyspace. */ - CompletionStage connectAsync(CqlIdentifier keyspace); - - /** - * Creates a new session not tied to any keyspace. - * - *

          This is equivalent to {@code this.connectAsync(null)}. - */ - default CompletionStage connectAsync() { - return connectAsync(null); - } - - /** - * Convenience method to call {@link #connectAsync(CqlIdentifier)} and block for the result. - * - *

          This must not be called on a driver thread. - */ - default SessionT connect(CqlIdentifier keyspace) { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(connectAsync(keyspace)); - } - - /** - * Convenience method to call {@link #connectAsync()} and block for the result. - * - *

          This must not be called on a driver thread. - */ - default SessionT connect() { - return connect(null); - } - - /** - * Registers the provided schema change listener. - * - *

          This is a no-op if the listener was registered already. - */ - Cluster register(SchemaChangeListener listener); - - /** - * Unregisters the provided schema change listener. - * - *

          This is a no-op if the listener was not registered. - */ - Cluster unregister(SchemaChangeListener listener); - - /** - * Registers the provided node state listener. - * - *

          This is a no-op if the listener was registered already. - */ - Cluster register(NodeStateListener listener); - - /** - * Unregisters the provided node state listener. - * - *

          This is a no-op if the listener was not registered. - */ - Cluster unregister(NodeStateListener listener); -} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java similarity index 85% rename from core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java rename to core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index 2e5a1826443..135b09fe7e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.cql; +package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; import java.util.concurrent.CompletionStage; @@ -22,6 +28,11 @@ /** A specialized session with convenience methods to execute CQL statements. */ public interface CqlSession extends Session { + /** Returns a builder to create a new instance. */ + static CqlSessionBuilder builder() { + return new CqlSessionBuilder(); + } + /** * Executes a CQL statement synchronously (the calling thread blocks until the result becomes * available). diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSessionBuilder.java similarity index 64% rename from core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java rename to core/src/main/java/com/datastax/oss/driver/api/core/CqlSessionBuilder.java index aa19e4afeea..3b0091a88ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DefaultClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSessionBuilder.java @@ -15,14 +15,13 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.session.SessionBuilder; -/** Helper class to build an instance of the default {@link Cluster} implementation. */ -public class DefaultClusterBuilder - extends ClusterBuilder> { +/** Helper class to build a {@link CqlSession} instance. */ +public class CqlSessionBuilder extends SessionBuilder { @Override - protected Cluster wrap(Cluster defaultCluster) { - return defaultCluster; + protected CqlSession wrap(CqlSession defaultSession) { + return defaultSession; } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index e612e6c7a32..aef6a80ea5f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -27,7 +27,8 @@ public enum CoreDriverOption implements DriverOption { PROTOCOL_MAX_FRAME_LENGTH("protocol.max-frame-length", true), PROTOCOL_COMPRESSOR_CLASS("protocol.compressor.class", false), - CLUSTER_NAME("cluster-name", false), + SESSION_NAME("session-name", false), + SESSION_KEYSPACE("session-keyspace", false), CONFIG_RELOAD_INTERVAL("config-reload-interval", false), CONNECTION_INIT_QUERY_TIMEOUT("connection.init-query-timeout", true), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 5f82663ff38..571c6c30ffb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.context; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -23,6 +22,7 @@ import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.retry.RetryPolicy; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.api.core.time.TimestampGenerator; @@ -32,10 +32,10 @@ public interface DriverContext extends AttachmentPoint { /** - * This is the same as {@link Cluster#getName()}, it's exposed here for components that only have + * This is the same as {@link Session#getName()}, it's exposed here for components that only have * a reference to the context. */ - String clusterName(); + String sessionName(); DriverConfig config(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index eaadda24667..24181ae608f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import java.util.Iterator; import java.util.concurrent.CompletionStage; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 8cd00fbe0ba..7e94320fb37 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -108,7 +108,7 @@ public interface ExecutionInfo { * (the retry delay and timeout are set through the configuration). * *

          If this method returns {@code false}, clients can call {@link - * Cluster#checkSchemaAgreement()} later to perform the check manually. + * Session#checkSchemaAgreement()} later to perform the check manually. * *

          Schema agreement is only checked for schema-altering queries. For other query types, this * method will always return {@code true}. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index e994eebb8b4..795de6392d7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 271a0d95efa..c5037404210 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlSession; import java.nio.ByteBuffer; import java.util.List; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index c325a96ae48..4a698135ae9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.util.Collections; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 92416e03d2e..5fe4ee8b44c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import java.util.Arrays; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 44b6c18df9a..802cc44f26d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index 53cf0fa7aad..f1aef7f9b36 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; -import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Map; @@ -41,7 +41,7 @@ public interface LoadBalancingPolicy extends AutoCloseable { * if so you will receive a notification * @param distanceReporter an object that will be used by the policy to signal distance changes. * @param contactPoints the set of contact points that the driver was initialized with (see {@link - * ClusterBuilder#addContactPoints(Collection)}). This is provided for reference, in case the + * SessionBuilder#addContactPoints(Collection)}). This is provided for reference, in case the * policy needs to handle those nodes in a particular way. Each address in this set should * normally have a corresponding entry in {@code nodes}, except for contact points that were * down or invalid. If no contact points were provided, the driver defaults to 127.0.0.1:9042, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 8683387f6c2..3280988eef4 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.session.Session; import java.net.InetSocketAddress; import java.util.Map; import java.util.Optional; @@ -31,7 +31,7 @@ * are the only mutable objects in the hierarchy, and some of their fields will be modified * dynamically (in particular the node state). * - * @see Cluster#getMetadata() + * @see Session#getMetadata() */ public interface Metadata { /** @@ -47,7 +47,7 @@ public interface Metadata { * this map might be empty or incomplete. * * @see CoreDriverOption#METADATA_SCHEMA_ENABLED - * @see Cluster#setSchemaMetadataEnabled(Boolean) + * @see Session#setSchemaMetadataEnabled(Boolean) * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ Map getKeyspaces(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java index 3a09a3c7cc5..1316c081b64 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/NodeStateListener.java @@ -15,16 +15,16 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.SessionBuilder; /** * A listener that gets notified when nodes states change. * *

          An implementation of this interface can be registered with {@link - * ClusterBuilder#addNodeStateListeners(NodeStateListener...)} or at runtime with {@link - * Cluster#register(NodeStateListener)}. + * SessionBuilder#addNodeStateListeners(NodeStateListener...)} or at runtime with {@link + * Session#register(NodeStateListener)}. * *

          Note that the methods defined by this interface will be executed by internal driver threads, * and are therefore expected to have short execution times. If you need to perform long @@ -62,12 +62,12 @@ public interface NodeStateListener { */ void onRemove(Node node); - /** Invoked when the listener is registered with a cluster. */ - void onRegister(Cluster cluster); + /** Invoked when the listener is registered with a session. */ + void onRegister(Session session); /** - * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever + * Invoked when the listener is unregistered from a session, or at session shutdown, whichever * comes first. */ - void onUnregister(Cluster cluster); + void onUnregister(Session session); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java index 2bc607f22e8..9646b77bd35 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.google.common.collect.ImmutableSet; import java.nio.ByteBuffer; @@ -34,7 +34,7 @@ * results for some or all of the keyspaces. * * @see CoreDriverOption#METADATA_SCHEMA_ENABLED - * @see Cluster#setSchemaMetadataEnabled(Boolean) + * @see Session#setSchemaMetadataEnabled(Boolean) * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ public interface TokenMap { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java index 49051547640..48d3717f80f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.api.core.metadata.schema; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.UserDefinedType; /** * Tracks schema changes. * *

          An implementation of this interface can be registered with {@link - * Cluster#register(SchemaChangeListener)}. + * Session#register(SchemaChangeListener)}. * *

          Note that the methods defined by this interface will be executed by internal driver threads, * and are therefore expected to have short execution times. If you need to perform long @@ -67,12 +67,12 @@ public interface SchemaChangeListener { void onViewUpdated(ViewMetadata current, ViewMetadata previous); - /** Invoked when the listener is registered with a cluster. */ - void onRegister(Cluster cluster); + /** Invoked when the listener is registered with a session. */ + void onRegister(Session session); /** - * Invoked when the listener is unregistered from a cluster, or at cluster shutdown, whichever + * Invoked when the listener is unregistered from a session, or at session shutdown, whichever * comes first. */ - void onUnregister(Cluster cluster); + void onUnregister(Session session); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java index 311f68b4565..624af127f8e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.metadata.schema; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.UserDefinedType; /** @@ -81,8 +81,8 @@ public void onViewDropped(ViewMetadata view) {} public void onViewUpdated(ViewMetadata current, ViewMetadata previous) {} @Override - public void onRegister(Cluster cluster) {} + public void onRegister(Session session) {} @Override - public void onUnregister(Cluster cluster) {} + public void onUnregister(Session session) {} } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index cc4029cd279..82c392e7b50 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -16,34 +16,164 @@ package com.datastax.oss.driver.api.core.session; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.ResourceBundle; +import java.util.concurrent.CompletionStage; /** * A nexus to send requests to a Cassandra cluster. * *

          This is a high-level abstraction capable of handling arbitrary request and result types. For - * CQL statements, {@link CqlSession} provides convenience methods with more familiar signatures. + * CQL statements, {@link CqlSession} provides convenience methods with more familiar signatures (by + * default, all the instances returned by the driver also implement {@link CqlSession}). * *

          The driver's request execution logic is pluggable (see {@code RequestProcessor} in the * internal API). This is intended for future extensions, for example a reactive API for CQL * statements, or graph requests in the Datastax Enterprise driver. Hence the generic {@link * #execute(Request, GenericType)} method in this interface, that makes no assumptions about the * request or result type. + * + * @see CqlSession#builder() */ public interface Session extends AsyncAutoCloseable { + /** + * The current version of the driver. + * + *

          This is intended for products that wrap or extend the driver, as a way to check + * compatibility if end-users override the driver version in their application. + */ + static String getDriverVersion() { + // Note: getBundle caches its result + return ResourceBundle.getBundle("com.datastax.oss.driver.Driver").getString("driver.version"); + } + + /** + * The unique name identifying this client. + * + * @see CoreDriverOption#SESSION_NAME + */ + String getName(); + + /** + * Returns a snapshot of the Cassandra cluster's topology and schema metadata. + * + *

          In order to provide atomic updates, this method returns an immutable object: the node list, + * token map, and schema contained in a given instance will always be consistent with each other + * (but note that {@link Node} itself is not immutable: some of its properties will be updated + * dynamically, in particular {@link Node#getState()}). + * + *

          As a consequence of the above, you should call this method each time you need a fresh view + * of the metadata. Do not call it once and store the result, because it is a frozen + * snapshot that will become stale over time. + * + *

          If a metadata refresh triggers events (such as node added/removed, or schema events), then + * the new version of the metadata is guaranteed to be visible by the time you receive these + * events. + */ + Metadata getMetadata(); + + /** Whether schema metadata is currently enabled. */ + boolean isSchemaMetadataEnabled(); + + /** + * Enable or disable schema metadata programmatically. + * + *

          Use this method to override the value defined in the driver's configuration; one typical use + * case is to temporarily disable schema metadata while the client issues a sequence of DDL + * statements. + * + *

          If calling this method re-enables the metadata (that is, {@link #isSchemaMetadataEnabled()} + * was false before, and becomes true as a result of the call), a refresh is also triggered. + * + * @param newValue a boolean value to enable or disable schema metadata programmatically, or + * {@code null} to use the driver's configuration. + * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @return if this call triggered a refresh, a future that will complete when that refresh is + * complete. Otherwise, a completed future with the current metadata. + */ + CompletionStage setSchemaMetadataEnabled(Boolean newValue); + + /** + * Force an immediate refresh of the schema metadata, even if it is currently disabled (either in + * the configuration or via {@link #setSchemaMetadataEnabled(Boolean)}). + * + *

          The new metadata is returned in the resulting future (and will also be reflected by {@link + * #getMetadata()} when that future completes). + */ + CompletionStage refreshSchemaAsync(); + + /** + * Convenience method to call {@link #refreshSchemaAsync()} and block for the result. + * + *

          This must not be called on a driver thread. + */ + default Metadata refreshSchema() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(refreshSchemaAsync()); + } + + /** + * Checks if all nodes in the cluster agree on a common schema version. + * + *

          Due to the distributed nature of Cassandra, schema changes made on one node might not be + * immediately visible to others. Under certain circumstances, the driver waits until all nodes + * agree on a common schema version (namely: before a schema refresh, and before completing a + * successful schema-altering query). To do so, it queries system tables to find out the schema + * version of all nodes that are currently {@link NodeState#UP UP}. If all the versions match, the + * check succeeds, otherwise it is retried periodically, until a given timeout (specified in the + * configuration). + * + *

          A schema agreement failure is not fatal, but it might produce unexpected results (for + * example, getting an "unconfigured table" error for a table that you created right before, just + * because the two queries went to different coordinators). + * + *

          Note that schema agreement never succeeds in a mixed-version cluster (it would be + * challenging because the way the schema version is computed varies across server versions); the + * assumption is that schema updates are unlikely to happen during a rolling upgrade anyway. + * + * @return a future that completes with {@code true} if the nodes agree, or {@code false} if the + * timeout fired. + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL + * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT + */ + CompletionStage checkSchemaAgreementAsync(); + + /** + * Convenience method to call {@link #checkSchemaAgreementAsync()} and block for the result. + * + *

          This must not be called on a driver thread. + */ + default boolean checkSchemaAgreement() { + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly(checkSchemaAgreementAsync()); + } + + /** Returns a context that provides access to all the policies used by this driver instance. */ + DriverContext getContext(); + /** * The keyspace that this session is currently connected to. * - *

          There are two ways that this can be set: during initialization, if the session was created - * with {@link Cluster#connect(CqlIdentifier)} or {@link Cluster#connectAsync(CqlIdentifier)}; at - * runtime, if the client issues a request that changes the keyspace (such as a CQL {@code USE} - * query). Note that this second method is inherently unsafe, since other requests expecting the - * old keyspace might be executing concurrently. Therefore it is highly discouraged, aside from - * trivial cases (such as a cqlsh-style program where requests are never concurrent). + *

          There are two ways that this can be set: before initializing the session (either with the + * {@code session-keyspace} option in the configuration, or with {@link + * CqlSessionBuilder#withKeyspace(CqlIdentifier)}); or at runtime, if the client issues a request + * that changes the keyspace (such as a CQL {@code USE} query). Note that this second method is + * inherently unsafe, since other requests expecting the old keyspace might be executing + * concurrently. Therefore it is highly discouraged, aside from trivial cases (such as a + * cqlsh-style program where requests are never concurrent). */ CqlIdentifier getKeyspace(); @@ -56,4 +186,32 @@ public interface Session extends AsyncAutoCloseable { */ ResultT execute( RequestT request, GenericType resultType); + + /** + * Registers the provided schema change listener. + * + *

          This is a no-op if the listener was registered already. + */ + void register(SchemaChangeListener listener); + + /** + * Unregisters the provided schema change listener. + * + *

          This is a no-op if the listener was not registered. + */ + void unregister(SchemaChangeListener listener); + + /** + * Registers the provided node state listener. + * + *

          This is a no-op if the listener was registered already. + */ + void register(NodeStateListener listener); + + /** + * Unregisters the provided node state listener. + * + *

          This is a no-op if the listener was not registered. + */ + void unregister(NodeStateListener listener); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java similarity index 83% rename from core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java rename to core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index f639313b182..38600744cde 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ClusterBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core; +package com.datastax.oss.driver.api.core.session; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; -import com.datastax.oss.driver.internal.core.DefaultCluster; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.net.InetSocketAddress; @@ -40,12 +41,12 @@ import java.util.function.Supplier; /** - * Base implementation to build cluster instances. + * Base implementation to build session instances. * *

          You only need to deal with this directly if you use custom driver extensions. For the default - * cluster implementation, see {@link Cluster#builder()}. + * session implementation, see {@link CqlSession#builder()}. */ -public abstract class ClusterBuilder { +public abstract class SessionBuilder { @SuppressWarnings("unchecked") protected final SelfT self = (SelfT) this; @@ -54,6 +55,7 @@ public abstract class ClusterBuilder { protected Set programmaticContactPoints = new HashSet<>(); protected List> typeCodecs = new ArrayList<>(); protected final Set nodeStateListeners = new HashSet<>(); + protected CqlIdentifier keyspace; /** * Sets the configuration loader to use. @@ -135,12 +137,23 @@ public SelfT addNodeStateListeners(NodeStateListener... newListeners) { } /** - * Creates the cluster with the options set by this builder. + * Sets the keyspace to connect the session to. * - * @return a completion stage that completes with the cluster when it is fully initialized. + *

          Note that this can also be provided by the configuration; if both are defined, this method + * takes precedence. */ - public CompletionStage buildAsync() { - return buildDefaultClusterAsync().thenApply(this::wrap); + public SelfT withKeyspace(CqlIdentifier keyspace) { + this.keyspace = keyspace; + return self; + } + + /** + * Creates the session with the options set by this builder. + * + * @return a completion stage that completes with the session when it is fully initialized. + */ + public CompletionStage buildAsync() { + return buildDefaultSessionAsync().thenApply(this::wrap); } /** @@ -148,14 +161,14 @@ public CompletionStage buildAsync() { * *

          This must not be called on a driver thread. */ - public ClusterT build() { + public SessionT build() { BlockingOperation.checkNotDriverThread(); return CompletableFutures.getUninterruptibly(buildAsync()); } - protected abstract ClusterT wrap(Cluster defaultCluster); + protected abstract SessionT wrap(CqlSession defaultSession); - protected final CompletionStage> buildDefaultClusterAsync() { + protected final CompletionStage buildDefaultSessionAsync() { DriverConfigLoader configLoader = buildIfNull(this.configLoader, this::defaultConfigLoader); DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); @@ -167,9 +180,14 @@ protected final CompletionStage> buildDefaultClusterAsync() Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); - return DefaultCluster.init( + if (keyspace == null && defaultConfig.isDefined(CoreDriverOption.SESSION_KEYSPACE)) { + keyspace = CqlIdentifier.fromCql(defaultConfig.getString(CoreDriverOption.SESSION_KEYSPACE)); + } + + return DefaultSession.init( (InternalDriverContext) buildContext(configLoader, typeCodecs), contactPoints, + keyspace, nodeStateListeners); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java index 018a0787f37..47182ce89a1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.api.core.specex; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.SessionBuilder; /** * The policy that decides if the driver will send speculative queries to the next nodes when the @@ -27,8 +27,9 @@ public interface SpeculativeExecutionPolicy extends AutoCloseable { /** * @param keyspace the CQL keyspace currently associated to the session. This is set either - * through {@link Cluster#connect(CqlIdentifier)} or by manually executing a {@code USE} CQL - * statement. It can be {@code null} if the session has no keyspace. + * through the configuration, by calling {@link SessionBuilder#withKeyspace(CqlIdentifier)}, + * or by manually executing a {@code USE} CQL statement. It can be {@code null} if the session + * has no keyspace. * @param request the request to execute. * @param runningExecutions the number of executions that are already running (including the * initial, non-speculative request). For example, if this is 2 it means the initial attempt diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java deleted file mode 100644 index 96d78cb07c0..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ClusterWrapper.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core; - -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.metadata.Metadata; -import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.session.Session; -import java.util.concurrent.CompletionStage; - -/** Utility class to wrap a cluster and make it return a different session type. */ -public abstract class ClusterWrapper - implements Cluster { - - private final Cluster delegate; - - public ClusterWrapper(Cluster delegate) { - this.delegate = delegate; - } - - protected abstract TargetSessionT wrap(SourceSessionT session); - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public Metadata getMetadata() { - return delegate.getMetadata(); - } - - @Override - public boolean isSchemaMetadataEnabled() { - return delegate.isSchemaMetadataEnabled(); - } - - @Override - public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { - return delegate.setSchemaMetadataEnabled(newValue); - } - - @Override - public CompletionStage refreshSchemaAsync() { - return delegate.refreshSchemaAsync(); - } - - @Override - public CompletionStage checkSchemaAgreementAsync() { - return delegate.checkSchemaAgreementAsync(); - } - - @Override - public DriverContext getContext() { - return delegate.getContext(); - } - - @Override - public CompletionStage connectAsync(CqlIdentifier keyspace) { - return delegate.connectAsync(keyspace).thenApply(this::wrap); - } - - @Override - public Cluster register(SchemaChangeListener listener) { - delegate.register(listener); - return this; - } - - @Override - public Cluster unregister(SchemaChangeListener listener) { - delegate.unregister(listener); - return this; - } - - @Override - public Cluster register(NodeStateListener listener) { - delegate.register(listener); - return this; - } - - @Override - public Cluster unregister(NodeStateListener listener) { - delegate.unregister(listener); - return this; - } - - @Override - public CompletionStage closeFuture() { - return delegate.closeFuture(); - } - - @Override - public CompletionStage closeAsync() { - return delegate.closeAsync(); - } - - @Override - public CompletionStage forceCloseAsync() { - return delegate.forceCloseAsync(); - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java deleted file mode 100644 index 221fca3fb31..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultCluster.java +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core; - -import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.cql.CqlSession; -import com.datastax.oss.driver.api.core.metadata.Metadata; -import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.control.ControlConnection; -import com.datastax.oss.driver.internal.core.metadata.MetadataManager; -import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; -import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.util.Loggers; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; -import com.google.common.collect.ImmutableList; -import io.netty.util.concurrent.EventExecutor; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DefaultCluster implements Cluster { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultCluster.class); - - public static CompletableFuture> init( - InternalDriverContext context, - Set contactPoints, - Set nodeStateListeners) { - DefaultCluster cluster = new DefaultCluster(context, contactPoints, nodeStateListeners); - return cluster.init(); - } - - private final InternalDriverContext context; - private final EventExecutor adminExecutor; - private final SingleThreaded singleThreaded; - private final MetadataManager metadataManager; - private final String logPrefix; - - private DefaultCluster( - InternalDriverContext context, - Set contactPoints, - Set nodeStateListeners) { - LOG.debug("Creating new cluster {}", context.clusterName()); - this.context = context; - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.singleThreaded = new SingleThreaded(context, contactPoints, nodeStateListeners); - this.metadataManager = context.metadataManager(); - this.logPrefix = context.clusterName(); - } - - private CompletableFuture> init() { - RunOrSchedule.on(adminExecutor, singleThreaded::init); - return singleThreaded.initFuture; - } - - @Override - public String getName() { - return context.clusterName(); - } - - @Override - public Metadata getMetadata() { - return metadataManager.getMetadata(); - } - - @Override - public boolean isSchemaMetadataEnabled() { - return metadataManager.isSchemaEnabled(); - } - - @Override - public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { - return metadataManager.setSchemaEnabled(newValue); - } - - @Override - public CompletionStage refreshSchemaAsync() { - return metadataManager.refreshSchema(null, true, true); - } - - @Override - public CompletionStage checkSchemaAgreementAsync() { - return context.topologyMonitor().checkSchemaAgreement(); - } - - @Override - public DriverContext getContext() { - return context; - } - - @Override - public CompletionStage connectAsync(CqlIdentifier keyspace) { - CompletableFuture connectFuture = new CompletableFuture<>(); - RunOrSchedule.on(adminExecutor, () -> singleThreaded.connect(keyspace, connectFuture)); - return connectFuture; - } - - @Override - public Cluster register(SchemaChangeListener listener) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); - return this; - } - - @Override - public Cluster unregister(SchemaChangeListener listener) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); - return this; - } - - @Override - public Cluster register(NodeStateListener listener) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); - return this; - } - - @Override - public Cluster unregister(NodeStateListener listener) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); - return this; - } - - @Override - public CompletionStage closeFuture() { - return singleThreaded.closeFuture; - } - - @Override - public CompletionStage closeAsync() { - RunOrSchedule.on(adminExecutor, singleThreaded::close); - return singleThreaded.closeFuture; - } - - @Override - public CompletionStage forceCloseAsync() { - RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); - return singleThreaded.closeFuture; - } - - private class SingleThreaded { - - private final InternalDriverContext context; - private final Set initialContactPoints; - private final NodeStateManager nodeStateManager; - private final CompletableFuture> initFuture = new CompletableFuture<>(); - private boolean initWasCalled; - private final CompletableFuture closeFuture = new CompletableFuture<>(); - private boolean closeWasCalled; - private boolean forceCloseWasCalled; - // Note: closed sessions are not removed from the list. If this creates a memory issue, there - // is something really wrong in the client program - private List sessions; - private int sessionCounter; - private Set schemaChangeListeners = new HashSet<>(); - private Set nodeStateListeners; - - private SingleThreaded( - InternalDriverContext context, - Set contactPoints, - Set nodeStateListeners) { - this.context = context; - this.nodeStateManager = new NodeStateManager(context); - this.initialContactPoints = contactPoints; - this.nodeStateListeners = nodeStateListeners; - this.sessions = new ArrayList<>(); - new SchemaListenerNotifier(schemaChangeListeners, context.eventBus(), adminExecutor); - context - .eventBus() - .register( - NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onNodeStateChanged)); - } - - private void init() { - assert adminExecutor.inEventLoop(); - if (initWasCalled) { - return; - } - initWasCalled = true; - LOG.debug("[{}] Starting initialization", logPrefix); - - nodeStateListeners.forEach(l -> l.onRegister(DefaultCluster.this)); - - MetadataManager metadataManager = context.metadataManager(); - metadataManager - // Store contact points in the metadata right away, the control connection will need them - // if it has to initialize (if the set is empty, 127.0.0.1 is used as a default). - .addContactPoints(initialContactPoints) - .thenCompose(v -> context.topologyMonitor().init()) - .thenCompose(v -> metadataManager.refreshNodes()) - .thenAccept(this::afterInitialNodeListRefresh) - .exceptionally( - error -> { - initFuture.completeExceptionally(error); - RunOrSchedule.on(adminExecutor, this::close); - return null; - }); - } - - private void afterInitialNodeListRefresh(@SuppressWarnings("unused") Void ignored) { - try { - boolean protocolWasForced = - context.config().getDefaultProfile().isDefined(CoreDriverOption.PROTOCOL_VERSION); - boolean needSchemaRefresh = true; - if (!protocolWasForced) { - ProtocolVersion currentVersion = context.protocolVersion(); - ProtocolVersion bestVersion = - context - .protocolVersionRegistry() - .highestCommon(metadataManager.getMetadata().getNodes().values()); - if (!currentVersion.equals(bestVersion)) { - LOG.info( - "[{}] Negotiated protocol version {} for the initial contact point, " - + "but other nodes only support {}, downgrading", - logPrefix, - currentVersion, - bestVersion); - context.channelFactory().setProtocolVersion(bestVersion); - ControlConnection controlConnection = context.controlConnection(); - // Might not have initialized yet if there is a custom TopologyMonitor - if (controlConnection.isInit()) { - controlConnection.reconnectNow(); - // Reconnection already triggers a full schema refresh - needSchemaRefresh = false; - } - } - } - if (needSchemaRefresh) { - metadataManager.refreshSchema(null, false, true); - } - metadataManager.firstSchemaRefreshFuture().thenAccept(this::afterInitialSchemaRefresh); - - } catch (Throwable throwable) { - initFuture.completeExceptionally(throwable); - } - } - - private void afterInitialSchemaRefresh(@SuppressWarnings("unused") Void ignored) { - try { - nodeStateManager.markInitialized(); - context.loadBalancingPolicyWrapper().init(); - context.configLoader().onDriverInit(context); - LOG.debug("[{}] Initialization complete, ready", logPrefix); - initFuture.complete(DefaultCluster.this); - } catch (Throwable throwable) { - initFuture.completeExceptionally(throwable); - } - } - - private void connect(CqlIdentifier keyspace, CompletableFuture connectFuture) { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - connectFuture.completeExceptionally(new IllegalStateException("Cluster was closed")); - } else { - String sessionLogPrefix = logPrefix + "|s" + sessionCounter++; - LOG.debug( - "[{}] Opening new session {} to keyspace {}", logPrefix, sessionLogPrefix, keyspace); - DefaultSession.init(context, keyspace, sessionLogPrefix) - .whenCompleteAsync( - (session, error) -> { - if (error != null) { - connectFuture.completeExceptionally(error); - } else if (closeWasCalled) { - connectFuture.completeExceptionally( - new IllegalStateException( - "Cluster was closed while session was initializing")); - session.forceCloseAsync(); - } else { - sessions.add(session); - connectFuture.complete(session); - } - }, - adminExecutor); - } - } - - private void register(SchemaChangeListener listener) { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - return; - } - // We want onRegister to be called before any event. We can add the listener before, because - // schema events are processed on this same thread. - if (schemaChangeListeners.add(listener)) { - listener.onRegister(DefaultCluster.this); - } - } - - private void unregister(SchemaChangeListener listener) { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - return; - } - if (schemaChangeListeners.remove(listener)) { - listener.onUnregister(DefaultCluster.this); - } - } - - private void register(NodeStateListener listener) { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - return; - } - if (nodeStateListeners.add(listener)) { - listener.onRegister(DefaultCluster.this); - } - } - - private void unregister(NodeStateListener listener) { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - return; - } - if (nodeStateListeners.remove(listener)) { - listener.onUnregister(DefaultCluster.this); - } - } - - private void onNodeStateChanged(NodeStateEvent event) { - assert adminExecutor.inEventLoop(); - if (event.newState == null) { - nodeStateListeners.forEach(listener -> listener.onRemove(event.node)); - } else if (event.oldState == null && event.newState == NodeState.UNKNOWN) { - nodeStateListeners.forEach(listener -> listener.onAdd(event.node)); - } else if (event.newState == NodeState.UP) { - nodeStateListeners.forEach(listener -> listener.onUp(event.node)); - } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { - nodeStateListeners.forEach(listener -> listener.onDown(event.node)); - } - } - - private void close() { - assert adminExecutor.inEventLoop(); - if (closeWasCalled) { - return; - } - closeWasCalled = true; - - LOG.debug("[{}] Starting shutdown", logPrefix); - for (SchemaChangeListener listener : schemaChangeListeners) { - listener.onUnregister(DefaultCluster.this); - } - schemaChangeListeners.clear(); - for (NodeStateListener listener : nodeStateListeners) { - listener.onUnregister(DefaultCluster.this); - } - nodeStateListeners.clear(); - List> childrenCloseStages = new ArrayList<>(); - closePolicies(); - for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - childrenCloseStages.add(closeable.closeAsync()); - } - CompletableFutures.whenAllDone( - childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); - } - - private void forceClose() { - assert adminExecutor.inEventLoop(); - if (forceCloseWasCalled) { - return; - } - forceCloseWasCalled = true; - LOG.debug( - "[{}] Starting forced shutdown (was {}closed before)", - logPrefix, - (closeWasCalled ? "" : "not ")); - - if (closeWasCalled) { - // onChildrenClosed has already been called - for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - closeable.forceCloseAsync(); - } - } else { - closeWasCalled = true; - closePolicies(); - List> childrenCloseStages = new ArrayList<>(); - for (AsyncAutoCloseable closeable : internalComponentsToClose()) { - childrenCloseStages.add(closeable.forceCloseAsync()); - } - CompletableFutures.whenAllDone( - childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); - } - } - - private void onChildrenClosed(List> childrenCloseStages) { - assert adminExecutor.inEventLoop(); - for (CompletionStage stage : childrenCloseStages) { - warnIfFailed(stage); - } - context - .nettyOptions() - .onClose() - .addListener( - f -> { - if (!f.isSuccess()) { - closeFuture.completeExceptionally(f.cause()); - } else { - LOG.debug("[{}] Shutdown complete", logPrefix); - closeFuture.complete(null); - } - }); - } - - private void warnIfFailed(CompletionStage stage) { - CompletableFuture future = stage.toCompletableFuture(); - assert future.isDone(); - if (future.isCompletedExceptionally()) { - Loggers.warnWithException( - LOG, - "[{}] Unexpected error while closing", - logPrefix, - CompletableFutures.getFailed(future)); - } - } - - private void closePolicies() { - for (AutoCloseable closeable : - ImmutableList.of( - context.reconnectionPolicy(), - context.retryPolicy(), - context.loadBalancingPolicyWrapper(), - context.speculativeExecutionPolicy(), - context.addressTranslator(), - context.configLoader())) { - try { - closeable.close(); - } catch (Throwable t) { - Loggers.warnWithException(LOG, "[{}] Error while closing {}", logPrefix, closeable, t); - } - } - } - - private List internalComponentsToClose() { - return ImmutableList.builder() - .addAll(sessions) - .add( - nodeStateManager, - metadataManager, - context.topologyMonitor(), - context.controlConnection()) - .build(); - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index b7477224c23..23122b1c9ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; -import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -55,7 +55,7 @@ public class DefaultDriverConfigLoader implements DriverConfigLoader { /** * Builds a new instance with the default TypeSafe config loading rules (documented in {@link - * ClusterBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. + * SessionBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. */ public DefaultDriverConfigLoader() { this(DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values()); @@ -100,7 +100,7 @@ private class SingleThreaded { private boolean closeWasCalled; private SingleThreaded(InternalDriverContext context) { - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.eventBus = context.eventBus(); this.config = context.config().getDefaultProfile(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index f15be6836a1..1c0054428a2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -50,6 +50,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -83,7 +84,7 @@ */ public class DefaultDriverContext implements InternalDriverContext { - private static final AtomicInteger CLUSTER_NAME_COUNTER = new AtomicInteger(); + private static final AtomicInteger SESSION_NAME_COUNTER = new AtomicInteger(); private final CycleDetector cycleDetector = new CycleDetector("Detected cycle in context initialization"); @@ -141,23 +142,25 @@ public class DefaultDriverContext implements InternalDriverContext { private final LazyReference replicationStrategyFactoryRef = new LazyReference<>( "replicationStrategyFactory", this::buildReplicationStrategyFactory, cycleDetector); + private final LazyReference poolManagerRef = + new LazyReference<>("poolManager", this::buildPoolManager, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; private final ChannelPoolFactory channelPoolFactory = new ChannelPoolFactory(); private final CodecRegistry codecRegistry; - private final String clusterName; + private final String sessionName; public DefaultDriverContext(DriverConfigLoader configLoader, List> typeCodecs) { this.config = configLoader.getInitialConfig(); this.configLoader = configLoader; DriverConfigProfile defaultProfile = config.getDefaultProfile(); - if (defaultProfile.isDefined(CoreDriverOption.CLUSTER_NAME)) { - this.clusterName = defaultProfile.getString(CoreDriverOption.CLUSTER_NAME); + if (defaultProfile.isDefined(CoreDriverOption.SESSION_NAME)) { + this.sessionName = defaultProfile.getString(CoreDriverOption.SESSION_NAME); } else { - this.clusterName = "c" + CLUSTER_NAME_COUNTER.getAndIncrement(); + this.sessionName = "s" + SESSION_NAME_COUNTER.getAndIncrement(); } - this.codecRegistry = buildCodecRegistry(this.clusterName, typeCodecs); + this.codecRegistry = buildCodecRegistry(this.sessionName, typeCodecs); } protected LoadBalancingPolicy buildLoadBalancingPolicy() { @@ -227,7 +230,7 @@ protected Optional buildSslEngineFactory() { } protected EventBus buildEventBus() { - return new EventBus(clusterName()); + return new EventBus(sessionName()); } @SuppressWarnings("unchecked") @@ -244,7 +247,7 @@ protected FrameCodec buildFrameCodec() { } protected ProtocolVersionRegistry buildProtocolVersionRegistry() { - return new CassandraProtocolVersionRegistry(clusterName()); + return new CassandraProtocolVersionRegistry(sessionName()); } protected NettyOptions buildNettyOptions() { @@ -321,9 +324,13 @@ protected ReplicationStrategyFactory buildReplicationStrategyFactory() { return new DefaultReplicationStrategyFactory(this); } + protected PoolManager buildPoolManager() { + return new PoolManager(this); + } + @Override - public String clusterName() { - return clusterName; + public String sessionName() { + return sessionName; } @Override @@ -438,7 +445,7 @@ public ControlConnection controlConnection() { @Override public RequestProcessorRegistry requestProcessorRegistry() { - return RequestProcessorRegistry.defaultCqlProcessors(clusterName()); + return RequestProcessorRegistry.defaultCqlProcessors(sessionName()); } @Override @@ -466,6 +473,11 @@ public ReplicationStrategyFactory replicationStrategyFactory() { return replicationStrategyFactoryRef.get(); } + @Override + public PoolManager poolManager() { + return poolManagerRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 7b8b2c9d9db..324b9585828 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -62,14 +62,14 @@ public DefaultNettyOptions(InternalDriverContext context) { ThreadFactory ioThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(context.clusterName() + "-io-%d") + .setNameFormat(context.sessionName() + "-io-%d") .build(); this.ioEventLoopGroup = new NioEventLoopGroup(ioGroupSize, ioThreadFactory); ThreadFactory adminThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(context.clusterName() + "-admin-%d") + .setNameFormat(context.sessionName() + "-admin-%d") .build(); this.adminEventLoopGroup = new DefaultEventLoopGroup(adminGroupSize, adminThreadFactory); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 003bfb339ce..8672621e487 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; @@ -76,4 +77,6 @@ public interface InternalDriverContext extends DriverContext { TokenFactoryRegistry tokenFactoryRegistry(); ReplicationStrategyFactory replicationStrategyFactory(); + + PoolManager poolManager(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index dc82b2fcd2e..d6c84fde6e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -77,7 +77,7 @@ public class ControlConnection implements EventCallback, AsyncAutoCloseable { public ControlConnection(InternalDriverContext context) { this.context = context; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 5307a7cd8f5..877a71efdfa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 447a860a45b..293ef51ba4d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index a3f095bf155..69a870d9ddd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 5a1d16803ad..1ffcb128365 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -69,7 +69,7 @@ public DefaultLoadBalancingPolicy(DriverContext context) { @VisibleForTesting DefaultLoadBalancingPolicy( String localDcFromConfig, Predicate filterFromConfig, DriverContext context) { - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); this.metadataManager = ((InternalDriverContext) context).metadataManager(); if (localDcFromConfig != null) { LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcFromConfig); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 483e2820246..e12f457492e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -39,7 +39,7 @@ public Result compute( return new Result(oldMetadata); } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); - copyInfos(newNodeInfo, newNode, null, context.clusterName()); + copyInfos(newNodeInfo, newNode, null, context.sessionName()); Map newNodes = ImmutableMap.builder() .putAll(oldNodes) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 2391ef4e0a7..f424dea990e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -117,7 +117,7 @@ private Optional rebuildTokenMap( TokenFactory tokenFactory, InternalDriverContext context) { - String logPrefix = context.clusterName(); + String logPrefix = context.sessionName(); ReplicationStrategyFactory replicationStrategyFactory = context.replicationStrategyFactory(); if (!tokenMapEnabled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 55a19528391..f8d71b7b3d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -63,7 +63,7 @@ public class DefaultTopologyMonitor implements TopologyMonitor { @VisibleForTesting volatile int port = -1; public DefaultTopologyMonitor(InternalDriverContext context) { - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); this.context = context; this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index bcdf5a0da6b..44c2844c05c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -46,7 +46,7 @@ class FullNodeListRefresh extends NodesRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - String logPrefix = context.clusterName(); + String logPrefix = context.sessionName(); TokenFactoryRegistry tokenFactoryRegistry = context.tokenFactoryRegistry(); Map oldNodes = oldMetadata.getNodes(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index a3f73ea6289..aafde53793e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -38,7 +38,7 @@ class InitContactPointsRefresh implements MetadataRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { assert oldMetadata == DefaultMetadata.EMPTY; - String logPrefix = context.clusterName(); + String logPrefix = context.sessionName(); LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); ImmutableMap.Builder newNodes = ImmutableMap.builder(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index e1547ce81e6..ec85478c397 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -69,7 +69,7 @@ private enum State { public LoadBalancingPolicyWrapper(InternalDriverContext context, LoadBalancingPolicy policy) { this.context = context; this.policy = policy; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); context.eventBus().register(NodeStateEvent.class, this::onNodeStateEvent); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index a36c10836e2..444b0d23911 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -68,7 +68,7 @@ public class MetadataManager implements AsyncAutoCloseable { public MetadataManager(InternalDriverContext context) { this.context = context; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.config = context.config().getDefaultProfile(); this.singleThreaded = new SingleThreaded(context, config); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java index c8311dfb470..aab77f2a756 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataRefresh.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import java.util.Collections; import java.util.List; @@ -31,7 +31,7 @@ *

          This is only instantiated and called from {@link MetadataManager}'s admin thread, therefore * implementations don't need to be thread-safe. * - * @see Cluster#getMetadata() + * @see Session#getMetadata() */ public interface MetadataRefresh { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 934bf71e1a3..1d935fbe631 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -53,7 +53,7 @@ public class NodeStateManager implements AsyncAutoCloseable { public NodeStateManager(InternalDriverContext context) { this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 735b40e96c9..83bbfbf13c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -39,7 +39,7 @@ public class RemoveNodeRefresh extends NodesRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - String logPrefix = context.clusterName(); + String logPrefix = context.sessionName(); Map oldNodes = oldMetadata.getNodes(); Node node = oldNodes.get(toRemove); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index e549b8aa585..28adb6f627a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -16,10 +16,10 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import java.net.InetSocketAddress; @@ -78,7 +78,7 @@ public interface TopologyMonitor extends AsyncAutoCloseable { * blocking I/O or heavy computations, it should be scheduled on a separate thread. * *

          The driver calls this at initialization, and uses the result to initialize the {@link - * LoadBalancingPolicy}; successful initialization of the {@link Cluster} object depends on that + * LoadBalancingPolicy}; successful initialization of the {@link Session} object depends on that * initial call succeeding. * * @return a future that completes with the information. We assume that the full node list will diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java index 75daee5f7c8..adcb36daa7c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java @@ -93,7 +93,7 @@ public DataType parse( LOG.warn( "[{}] Got o.a.c.db.marshal.FrozenType for something else than a collection, " + "this driver version might be too old for your version of Cassandra", - context.clusterName()); + context.sessionName()); if (next.startsWith("org.apache.cassandra.db.marshal.UserType")) { ++parser.idx; // skipping '(' diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java index a07d991db23..976219058b9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java @@ -41,7 +41,7 @@ class FunctionParser { FunctionParser(DataTypeParser dataTypeParser, InternalDriverContext context) { this.dataTypeParser = dataTypeParser; this.context = context; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); } FunctionMetadata parseFunction( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java index 5d8ff74d93f..7902ee6a7c8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -40,7 +40,7 @@ protected RelationParser( this.rows = rows; this.dataTypeParser = dataTypeParser; this.context = context; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); } protected Map parseOptions(AdminRow row) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java index 2988b9d9455..43b6cd0e58f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java @@ -56,7 +56,7 @@ public class SchemaParser { public SchemaParser(SchemaRows rows, InternalDriverContext context) { this.rows = rows; - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); DataTypeParser dataTypeParser = rows.isCassandraV3 ? new DataTypeCqlNameParser() : new DataTypeClassNameParser(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index a7785fdef01..8f8909cf1f0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -36,7 +36,7 @@ public DefaultSchemaQueriesFactory(InternalDriverContext context) { } public SchemaQueries newInstance(CompletableFuture refreshFuture) { - String logPrefix = context.clusterName(); + String logPrefix = context.sessionName(); DriverChannel channel = context.controlConnection().channel(); if (channel == null || channel.closeFuture().isDone()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java index 5a9977dafd9..6c7740a9415 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java @@ -24,7 +24,7 @@ public class DefaultReplicationStrategyFactory implements ReplicationStrategyFac private final String logPrefix; public DefaultReplicationStrategyFactory(InternalDriverContext context) { - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java index b1366f8c528..1a0dbde6ed4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java @@ -26,7 +26,7 @@ public class DefaultTokenFactoryRegistry implements TokenFactoryRegistry { private final String logPrefix; public DefaultTokenFactoryRegistry(InternalDriverContext context) { - this.logPrefix = context.clusterName(); + this.logPrefix = context.sessionName(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 73366abfd15..68efca3ad99 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -314,7 +314,8 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } } // If all channels failed, assume the keyspace is wrong - invalidKeyspace = (invalidKeyspaceErrors == pendingChannels.size()); + invalidKeyspace = + invalidKeyspaceErrors > 0 && invalidKeyspaceErrors == pendingChannels.size(); pendingChannels.clear(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java index 8cc9b99f4a2..113117d40b8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java @@ -34,7 +34,7 @@ public class Lz4Compressor extends ByteBufCompressor { public Lz4Compressor(DriverContext context) { try { LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); - LOG.info("[{}] Using {}", context.clusterName(), lz4Factory.toString()); + LOG.info("[{}] Using {}", context.sessionName(), lz4Factory.toString()); this.compressor = lz4Factory.fastCompressor(); this.decompressor = lz4Factory.fastDecompressor(); } catch (NoClassDefFoundError e) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index ac184d72082..f70357dfc86 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -15,44 +15,41 @@ */ package com.datastax.oss.driver.internal.core.session; +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; -import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.metadata.DefaultNode; -import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; -import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; -import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; -import com.google.common.collect.Lists; -import com.google.common.collect.MapMaker; +import com.google.common.collect.ImmutableList; import io.netty.util.concurrent.EventExecutor; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.WeakHashMap; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,54 +74,78 @@ public class DefaultSession implements CqlSession { private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); public static CompletionStage init( - InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { - return new DefaultSession(context, keyspace, logPrefix).init(); + InternalDriverContext context, + Set contactPoints, + CqlIdentifier keyspace, + Set nodeStateListeners) { + return new DefaultSession(context, contactPoints, nodeStateListeners).init(keyspace); } private final InternalDriverContext context; - private final DriverConfig config; private final EventExecutor adminExecutor; private final String logPrefix; private final SingleThreaded singleThreaded; + private final MetadataManager metadataManager; private final RequestProcessorRegistry processorRegistry; + private final PoolManager poolManager; - // This is read concurrently, but only updated from adminExecutor - private volatile CqlIdentifier keyspace; - - private final ConcurrentMap pools = - new ConcurrentHashMap<>( - 16, - 0.75f, - // the map will only be updated from adminExecutor - 1); - - // The raw data to reprepare requests on the fly, if we hit a node that doesn't have them in - // its cache. - // This is raw protocol-level data, as opposed to the actual instances returned to the client - // (e.g. DefaultPreparedStatement) which are handled at the protocol level (e.g. - // CqlPrepareAsyncProcessor). We keep the two separate to avoid introducing a dependency from the - // session to a particular processor implementation. - private ConcurrentMap repreparePayloads = - new MapMaker().weakValues().makeMap(); - - private DefaultSession(InternalDriverContext context, CqlIdentifier keyspace, String logPrefix) { + private DefaultSession( + InternalDriverContext context, + Set contactPoints, + Set nodeStateListeners) { + LOG.debug("Creating new session {}", context.sessionName()); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.context = context; - this.config = context.config(); - this.singleThreaded = new SingleThreaded(context); + this.singleThreaded = new SingleThreaded(context, contactPoints, nodeStateListeners); + this.metadataManager = context.metadataManager(); this.processorRegistry = context.requestProcessorRegistry(); - this.keyspace = keyspace; - this.logPrefix = logPrefix; + this.poolManager = context.poolManager(); + this.logPrefix = context.sessionName(); } - private CompletionStage init() { - RunOrSchedule.on(adminExecutor, singleThreaded::init); + private CompletionStage init(CqlIdentifier keyspace) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.init(keyspace)); return singleThreaded.initFuture; } + @Override + public String getName() { + return context.sessionName(); + } + + @Override + public Metadata getMetadata() { + return metadataManager.getMetadata(); + } + + @Override + public boolean isSchemaMetadataEnabled() { + return metadataManager.isSchemaEnabled(); + } + + @Override + public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { + return metadataManager.setSchemaEnabled(newValue); + } + + @Override + public CompletionStage refreshSchemaAsync() { + return metadataManager.refreshSchema(null, true, true); + } + + @Override + public CompletionStage checkSchemaAgreementAsync() { + return context.topologyMonitor().checkSchemaAgreement(); + } + + @Override + public DriverContext getContext() { + return context; + } + @Override public CqlIdentifier getKeyspace() { - return keyspace; + return poolManager.getKeyspace(); } /** @@ -135,28 +156,11 @@ public CqlIdentifier getKeyspace() { * wreak havoc (close all connections and make the session unusable). */ public CompletionStage setKeyspace(CqlIdentifier newKeyspace) { - CqlIdentifier oldKeyspace = this.keyspace; - if (Objects.equals(oldKeyspace, newKeyspace)) { - return CompletableFuture.completedFuture(null); - } - if (config.getDefaultProfile().getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { - LOG.warn( - "[{}] Detected a keyspace change at runtime ({} => {}). " - + "This is an anti-pattern that should be avoided in production " - + "(see '{}' in the configuration).", - logPrefix, - (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), - newKeyspace.asInternal(), - CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); - } - this.keyspace = newKeyspace; - CompletableFuture result = new CompletableFuture<>(); - RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace, result)); - return result; + return poolManager.setKeyspace(newKeyspace); } public Map getPools() { - return pools; + return poolManager.getPools(); } @Override @@ -169,7 +173,7 @@ public ResultT execute( } public DriverChannel getChannel(Node node, String logPrefix) { - ChannelPool pool = pools.get(node); + ChannelPool pool = poolManager.getPools().get(node); if (pool == null) { LOG.debug("[{}] No pool to {}, skipping", logPrefix, node); return null; @@ -188,7 +192,27 @@ public DriverChannel getChannel(Node node, String logPrefix) { } public ConcurrentMap getRepreparePayloads() { - return repreparePayloads; + return poolManager.getRepreparePayloads(); + } + + @Override + public void register(SchemaChangeListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); + } + + @Override + public void unregister(SchemaChangeListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); + } + + @Override + public void register(NodeStateListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); + } + + @Override + public void unregister(NodeStateListener listener) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.unregister(listener)); } @Override @@ -211,283 +235,171 @@ public CompletionStage forceCloseAsync() { private class SingleThreaded { private final InternalDriverContext context; - private final ChannelPoolFactory channelPoolFactory; + private final Set initialContactPoints; + private final NodeStateManager nodeStateManager; private final CompletableFuture initFuture = new CompletableFuture<>(); private boolean initWasCalled; private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; private boolean forceCloseWasCalled; - private final Object distanceListenerKey; - private final ReplayingEventFilter distanceEventFilter = - new ReplayingEventFilter<>(this::processDistanceEvent); - private final Object stateListenerKey; - private final ReplayingEventFilter stateEventFilter = - new ReplayingEventFilter<>(this::processStateEvent); - private final Object topologyListenerKey; - // The pools that we have opened but have not finished initializing yet - private final Map> pending = new HashMap<>(); - // If we receive events while a pool is initializing, the last one is stored here - private final Map pendingDistanceEvents = new WeakHashMap<>(); - private final Map pendingStateEvents = new WeakHashMap<>(); - - private SingleThreaded(InternalDriverContext context) { + private Set schemaChangeListeners = new HashSet<>(); + private Set nodeStateListeners; + + private SingleThreaded( + InternalDriverContext context, + Set contactPoints, + Set nodeStateListeners) { this.context = context; - this.channelPoolFactory = context.channelPoolFactory(); - this.distanceListenerKey = - context - .eventBus() - .register( - DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); - this.stateListenerKey = - context - .eventBus() - .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); - this.topologyListenerKey = - context - .eventBus() - .register( - TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); + this.nodeStateManager = new NodeStateManager(context); + this.initialContactPoints = contactPoints; + this.nodeStateListeners = nodeStateListeners; + new SchemaListenerNotifier(schemaChangeListeners, context.eventBus(), adminExecutor); + context + .eventBus() + .register( + NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onNodeStateChanged)); } - private void init() { + private void init(CqlIdentifier keyspace) { assert adminExecutor.inEventLoop(); if (initWasCalled) { return; } initWasCalled = true; - LOG.debug("[{}] Starting initialization", logPrefix); - // Make sure we don't miss any event while the pools are initializing - distanceEventFilter.start(); - stateEventFilter.start(); - - Collection nodes = context.metadataManager().getMetadata().getNodes().values(); - List> poolStages = new ArrayList<>(nodes.size()); - for (Node node : nodes) { - NodeDistance distance = node.getDistance(); - if (distance == NodeDistance.IGNORED) { - LOG.debug("[{}] Skipping {} because it is IGNORED", logPrefix, node); - } else if (node.getState() == NodeState.FORCED_DOWN) { - LOG.debug("[{}] Skipping {} because it is FORCED_DOWN", logPrefix, node); - } else { - LOG.debug("[{}] Creating a pool for {}", logPrefix, node); - poolStages.add(channelPoolFactory.init(node, keyspace, distance, context, logPrefix)); - } - } - CompletableFutures.whenAllDone(poolStages, () -> this.onPoolsInit(poolStages), adminExecutor); - } - - private void onPoolsInit(List> poolStages) { - assert adminExecutor.inEventLoop(); - LOG.debug("[{}] All pools have finished initializing", logPrefix); - // We will only propagate an invalid keyspace error if all pools get it - boolean allInvalidKeyspaces = poolStages.size() > 0; - for (CompletionStage poolStage : poolStages) { - // Note: pool init always succeeds - ChannelPool pool = CompletableFutures.getCompleted(poolStage.toCompletableFuture()); - boolean invalidKeyspace = pool.isInvalidKeyspace(); - if (invalidKeyspace) { - LOG.debug("[{}] Pool to {} reports an invalid keyspace", logPrefix, pool.getNode()); - } - allInvalidKeyspaces &= invalidKeyspace; - pools.put(pool.getNode(), pool); - } - if (allInvalidKeyspaces) { - initFuture.completeExceptionally( - new InvalidKeyspaceException("Invalid keyspace " + keyspace.asCql(true))); - forceClose(); - } else { - LOG.debug("[{}] Initialization complete, ready", logPrefix); - initFuture.complete(DefaultSession.this); - distanceEventFilter.markReady(); - stateEventFilter.markReady(); - } - } - - private void onDistanceEvent(DistanceEvent event) { - assert adminExecutor.inEventLoop(); - distanceEventFilter.accept(event); + nodeStateListeners.forEach(l -> l.onRegister(DefaultSession.this)); + + MetadataManager metadataManager = context.metadataManager(); + metadataManager + // Store contact points in the metadata right away, the control connection will need them + // if it has to initialize (if the set is empty, 127.0.0.1 is used as a default). + .addContactPoints(initialContactPoints) + .thenCompose(v -> context.topologyMonitor().init()) + .thenCompose(v -> metadataManager.refreshNodes()) + .thenAccept(v -> afterInitialNodeListRefresh(keyspace)) + .exceptionally( + error -> { + initFuture.completeExceptionally(error); + RunOrSchedule.on(adminExecutor, this::close); + return null; + }); } - private void onStateEvent(NodeStateEvent event) { - assert adminExecutor.inEventLoop(); - stateEventFilter.accept(event); - } - - private void processDistanceEvent(DistanceEvent event) { - assert adminExecutor.inEventLoop(); - // no need to check closeWasCalled, because we stop listening for events one closed - DefaultNode node = event.node; - NodeDistance newDistance = event.distance; - if (pending.containsKey(node)) { - pendingDistanceEvents.put(node, event); - } else if (newDistance == NodeDistance.IGNORED) { - ChannelPool pool = pools.remove(node); - if (pool != null) { - LOG.debug("[{}] {} became IGNORED, destroying pool", logPrefix, node); - pool.closeAsync() - .exceptionally( - error -> { - Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); - return null; - }); - } - } else { - NodeState state = node.getState(); - if (state == NodeState.FORCED_DOWN) { - LOG.warn( - "[{}] {} became {} but it is FORCED_DOWN, ignoring", logPrefix, node, newDistance); - return; + private void afterInitialNodeListRefresh(CqlIdentifier keyspace) { + try { + boolean protocolWasForced = + context.config().getDefaultProfile().isDefined(CoreDriverOption.PROTOCOL_VERSION); + boolean needSchemaRefresh = true; + if (!protocolWasForced) { + ProtocolVersion currentVersion = context.protocolVersion(); + ProtocolVersion bestVersion = + context + .protocolVersionRegistry() + .highestCommon(metadataManager.getMetadata().getNodes().values()); + if (!currentVersion.equals(bestVersion)) { + LOG.info( + "[{}] Negotiated protocol version {} for the initial contact point, " + + "but other nodes only support {}, downgrading", + logPrefix, + currentVersion, + bestVersion); + context.channelFactory().setProtocolVersion(bestVersion); + ControlConnection controlConnection = context.controlConnection(); + // Might not have initialized yet if there is a custom TopologyMonitor + if (controlConnection.isInit()) { + controlConnection.reconnectNow(); + // Reconnection already triggers a full schema refresh + needSchemaRefresh = false; + } + } } - ChannelPool pool = pools.get(node); - if (pool == null) { - LOG.debug( - "[{}] {} became {} and no pool found, initializing it", logPrefix, node, newDistance); - CompletionStage poolFuture = - channelPoolFactory.init(node, keyspace, newDistance, context, logPrefix); - pending.put(node, poolFuture); - poolFuture - .thenAcceptAsync(this::onPoolInitialized, adminExecutor) - .exceptionally(UncaughtExceptions::log); - } else { - LOG.debug("[{}] {} became {}, resizing it", logPrefix, node, newDistance); - pool.resize(newDistance); + if (needSchemaRefresh) { + metadataManager.refreshSchema(null, false, true); } + metadataManager + .firstSchemaRefreshFuture() + .thenAccept(v -> afterInitialSchemaRefresh(keyspace)); + + } catch (Throwable throwable) { + initFuture.completeExceptionally(throwable); } } - private void processStateEvent(NodeStateEvent event) { - assert adminExecutor.inEventLoop(); - // no need to check closeWasCalled, because we stop listening for events once closed - DefaultNode node = event.node; - NodeState oldState = event.oldState; - NodeState newState = event.newState; - if (pending.containsKey(node)) { - pendingStateEvents.put(node, event); - } else if (newState == NodeState.FORCED_DOWN) { - ChannelPool pool = pools.remove(node); - if (pool != null) { - LOG.debug("[{}] {} was FORCED_DOWN, destroying pool", logPrefix, node); - pool.closeAsync() - .exceptionally( - error -> { - Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); - return null; - }); - } - } else if (oldState == NodeState.FORCED_DOWN - && newState == NodeState.UP - && node.getDistance() != NodeDistance.IGNORED) { - LOG.debug("[{}] {} was forced back UP, initializing pool", logPrefix, node); - createOrReconnectPool(node); + private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { + try { + nodeStateManager.markInitialized(); + context.loadBalancingPolicyWrapper().init(); + context.configLoader().onDriverInit(context); + LOG.debug("[{}] Initialization complete, ready", logPrefix); + poolManager + .init(keyspace) + .whenComplete( + (v, error) -> { + if (error != null) { + initFuture.completeExceptionally(error); + } else { + initFuture.complete(DefaultSession.this); + } + }); + } catch (Throwable throwable) { + initFuture.completeExceptionally(throwable); } } - private void onTopologyEvent(TopologyEvent event) { + private void register(SchemaChangeListener listener) { assert adminExecutor.inEventLoop(); - if (event.type == TopologyEvent.Type.SUGGEST_UP) { - Node node = context.metadataManager().getMetadata().getNodes().get(event.address); - if (node.getDistance() != NodeDistance.IGNORED) { - LOG.debug( - "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); - createOrReconnectPool(node); - } + if (closeWasCalled) { + return; } - } - - private void createOrReconnectPool(Node node) { - ChannelPool pool = pools.get(node); - if (pool == null) { - CompletionStage poolFuture = - channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); - pending.put(node, poolFuture); - poolFuture - .thenAcceptAsync(this::onPoolInitialized, adminExecutor) - .exceptionally(UncaughtExceptions::log); - } else { - pool.reconnectNow(); + // We want onRegister to be called before any event. We can add the listener before, because + // schema events are processed on this same thread. + if (schemaChangeListeners.add(listener)) { + listener.onRegister(DefaultSession.this); } } - private void onPoolInitialized(ChannelPool pool) { + private void unregister(SchemaChangeListener listener) { assert adminExecutor.inEventLoop(); - Node node = pool.getNode(); if (closeWasCalled) { - LOG.debug( - "[{}] Session closed while a pool to {} was initializing, closing it", logPrefix, node); - pool.forceCloseAsync(); - } else { - LOG.debug("[{}] New pool to {} initialized", logPrefix, node); - if (Objects.equals(keyspace, pool.getInitialKeyspaceName())) { - reprepareStatements(pool); - } else { - // The keyspace changed while the pool was being initialized, switch it now. - pool.setKeyspace(keyspace) - .handleAsync( - (result, error) -> { - if (error != null) { - Loggers.warnWithException( - LOG, "Error while switching keyspace to " + keyspace, error); - } - reprepareStatements(pool); - return null; - }, - adminExecutor); - } + return; } - } - - private void reprepareStatements(ChannelPool pool) { - assert adminExecutor.inEventLoop(); - if (config.getDefaultProfile().getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { - new ReprepareOnUp( - logPrefix + "|" + pool.getNode().getConnectAddress(), - pool, - repreparePayloads, - context, - () -> RunOrSchedule.on(adminExecutor, () -> onPoolReady(pool))) - .start(); - } else { - LOG.debug("[{}] Reprepare on up is disabled, skipping", logPrefix); - onPoolReady(pool); + if (schemaChangeListeners.remove(listener)) { + listener.onUnregister(DefaultSession.this); } } - private void onPoolReady(ChannelPool pool) { + private void register(NodeStateListener listener) { assert adminExecutor.inEventLoop(); - Node node = pool.getNode(); - pending.remove(node); - pools.put(node, pool); - DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); - NodeStateEvent stateEvent = pendingStateEvents.remove(node); - if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { - LOG.debug( - "[{}] Received {} while the pool was initializing, processing it now", - logPrefix, - stateEvent); - processStateEvent(stateEvent); - } else if (distanceEvent != null) { - LOG.debug( - "[{}] Received {} while the pool was initializing, processing it now", - logPrefix, - distanceEvent); - processDistanceEvent(distanceEvent); + if (closeWasCalled) { + return; + } + if (nodeStateListeners.add(listener)) { + listener.onRegister(DefaultSession.this); } } - private void setKeyspace(CqlIdentifier newKeyspace, CompletableFuture doneFuture) { + private void unregister(NodeStateListener listener) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { - doneFuture.complete(null); return; } - LOG.debug("[{}] Switching to keyspace {}", logPrefix, newKeyspace); - List> poolReadyFutures = Lists.newArrayListWithCapacity(pools.size()); - for (ChannelPool pool : pools.values()) { - poolReadyFutures.add(pool.setKeyspace(newKeyspace)); + if (nodeStateListeners.remove(listener)) { + listener.onUnregister(DefaultSession.this); + } + } + + private void onNodeStateChanged(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + if (event.newState == null) { + nodeStateListeners.forEach(listener -> listener.onRemove(event.node)); + } else if (event.oldState == null && event.newState == NodeState.UNKNOWN) { + nodeStateListeners.forEach(listener -> listener.onAdd(event.node)); + } else if (event.newState == NodeState.UP) { + nodeStateListeners.forEach(listener -> listener.onUp(event.node)); + } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { + nodeStateListeners.forEach(listener -> listener.onDown(event.node)); } - CompletableFutures.completeFrom(CompletableFutures.allDone(poolReadyFutures), doneFuture); } private void close() { @@ -498,17 +410,23 @@ private void close() { closeWasCalled = true; LOG.debug("[{}] Starting shutdown", logPrefix); - // Stop listening for events - context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); - context.eventBus().unregister(stateListenerKey, NodeStateEvent.class); - context.eventBus().unregister(topologyListenerKey, TopologyEvent.class); + for (SchemaChangeListener listener : schemaChangeListeners) { + listener.onUnregister(DefaultSession.this); + } + schemaChangeListeners.clear(); + for (NodeStateListener listener : nodeStateListeners) { + listener.onUnregister(DefaultSession.this); + } + nodeStateListeners.clear(); + + closePolicies(); - List> closePoolStages = new ArrayList<>(pools.size()); - for (ChannelPool pool : pools.values()) { - closePoolStages.add(pool.closeAsync()); + List> childrenCloseStages = new ArrayList<>(); + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + childrenCloseStages.add(closeable.closeAsync()); } CompletableFutures.whenAllDone( - closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); } private void forceClose() { @@ -523,40 +441,86 @@ private void forceClose() { (closeWasCalled ? "" : "not ")); if (closeWasCalled) { - for (ChannelPool pool : pools.values()) { - pool.forceCloseAsync(); + // onChildrenClosed has already been scheduled + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + closeable.forceCloseAsync(); } } else { - List> closePoolStages = new ArrayList<>(pools.size()); - for (ChannelPool pool : pools.values()) { - closePoolStages.add(pool.forceCloseAsync()); + for (SchemaChangeListener listener : schemaChangeListeners) { + listener.onUnregister(DefaultSession.this); + } + schemaChangeListeners.clear(); + for (NodeStateListener listener : nodeStateListeners) { + listener.onUnregister(DefaultSession.this); + } + nodeStateListeners.clear(); + closePolicies(); + List> childrenCloseStages = new ArrayList<>(); + for (AsyncAutoCloseable closeable : internalComponentsToClose()) { + childrenCloseStages.add(closeable.forceCloseAsync()); } CompletableFutures.whenAllDone( - closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + childrenCloseStages, () -> onChildrenClosed(childrenCloseStages), adminExecutor); } } - private void onAllPoolsClosed(List> closePoolStages) { + private void onChildrenClosed(List> childrenCloseStages) { assert adminExecutor.inEventLoop(); - Throwable firstError = null; - for (CompletionStage closePoolStage : closePoolStages) { - CompletableFuture closePoolFuture = closePoolStage.toCompletableFuture(); - assert closePoolFuture.isDone(); - if (closePoolFuture.isCompletedExceptionally()) { - Throwable error = CompletableFutures.getFailed(closePoolFuture); - if (firstError == null) { - firstError = error; - } else { - firstError.addSuppressed(error); - } - } + for (CompletionStage stage : childrenCloseStages) { + warnIfFailed(stage); } - if (firstError != null) { - closeFuture.completeExceptionally(firstError); - } else { - LOG.debug("[{}] Shutdown complete", logPrefix); - closeFuture.complete(null); + context + .nettyOptions() + .onClose() + .addListener( + f -> { + if (!f.isSuccess()) { + closeFuture.completeExceptionally(f.cause()); + } else { + LOG.debug("[{}] Shutdown complete", logPrefix); + closeFuture.complete(null); + } + }); + } + + private void warnIfFailed(CompletionStage stage) { + CompletableFuture future = stage.toCompletableFuture(); + assert future.isDone(); + if (future.isCompletedExceptionally()) { + Loggers.warnWithException( + LOG, + "[{}] Unexpected error while closing", + logPrefix, + CompletableFutures.getFailed(future)); } } + + private void closePolicies() { + for (AutoCloseable closeable : + ImmutableList.of( + context.reconnectionPolicy(), + context.retryPolicy(), + context.loadBalancingPolicyWrapper(), + context.speculativeExecutionPolicy(), + context.addressTranslator(), + context.configLoader())) { + try { + closeable.close(); + } catch (Throwable t) { + Loggers.warnWithException(LOG, "[{}] Error while closing {}", logPrefix, closeable, t); + } + } + } + + private List internalComponentsToClose() { + return ImmutableList.builder() + .add( + poolManager, + nodeStateManager, + metadataManager, + context.topologyMonitor(), + context.controlConnection()) + .build(); + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java new file mode 100644 index 00000000000..84197598029 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -0,0 +1,505 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session; + +import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.util.Loggers; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.internal.core.util.concurrent.ReplayingEventFilter; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; +import io.netty.util.concurrent.EventExecutor; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Maintains the connection pools of a session. + * + *

          Logically this belongs to {@link DefaultSession}, but it's extracted here in order to be + * accessible from the context (notably for metrics). + */ +public class PoolManager implements AsyncAutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(PoolManager.class); + + // This is read concurrently, but only updated from adminExecutor + private volatile CqlIdentifier keyspace; + + private final ConcurrentMap pools = + new ConcurrentHashMap<>( + 16, + 0.75f, + // the map will only be updated from adminExecutor + 1); + + // The raw data to reprepare requests on the fly, if we hit a node that doesn't have them in + // its cache. + // This is raw protocol-level data, as opposed to the actual instances returned to the client + // (e.g. DefaultPreparedStatement) which are handled at the protocol level (e.g. + // CqlPrepareAsyncProcessor). We keep the two separate to avoid introducing a dependency from the + // session to a particular processor implementation. + private ConcurrentMap repreparePayloads = + new MapMaker().weakValues().makeMap(); + + private final String logPrefix; + private final EventExecutor adminExecutor; + private final DriverConfigProfile config; + private final SingleThreaded singleThreaded; + + public PoolManager(InternalDriverContext context) { + this.logPrefix = context.sessionName(); + this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.config = context.config().getDefaultProfile(); + this.singleThreaded = new SingleThreaded(context); + } + + public CompletionStage init(CqlIdentifier keyspace) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.init(keyspace)); + return singleThreaded.initFuture; + } + + public CqlIdentifier getKeyspace() { + return keyspace; + } + + public CompletionStage setKeyspace(CqlIdentifier newKeyspace) { + CqlIdentifier oldKeyspace = this.keyspace; + if (Objects.equals(oldKeyspace, newKeyspace)) { + return CompletableFuture.completedFuture(null); + } + if (config.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { + LOG.warn( + "[{}] Detected a keyspace change at runtime ({} => {}). " + + "This is an anti-pattern that should be avoided in production " + + "(see '{}' in the configuration).", + logPrefix, + (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), + newKeyspace.asInternal(), + CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); + } + this.keyspace = newKeyspace; + CompletableFuture result = new CompletableFuture<>(); + RunOrSchedule.on(adminExecutor, () -> singleThreaded.setKeyspace(newKeyspace, result)); + return result; + } + + public Map getPools() { + return pools; + } + + public ConcurrentMap getRepreparePayloads() { + return repreparePayloads; + } + + @Override + public CompletionStage closeFuture() { + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage closeAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::close); + return singleThreaded.closeFuture; + } + + @Override + public CompletionStage forceCloseAsync() { + RunOrSchedule.on(adminExecutor, singleThreaded::forceClose); + return singleThreaded.closeFuture; + } + + private class SingleThreaded { + + private final InternalDriverContext context; + private final ChannelPoolFactory channelPoolFactory; + private final CompletableFuture initFuture = new CompletableFuture<>(); + private boolean initWasCalled; + private final CompletableFuture closeFuture = new CompletableFuture<>(); + private boolean closeWasCalled; + private boolean forceCloseWasCalled; + private final Object distanceListenerKey; + private final ReplayingEventFilter distanceEventFilter = + new ReplayingEventFilter<>(this::processDistanceEvent); + private final Object stateListenerKey; + private final ReplayingEventFilter stateEventFilter = + new ReplayingEventFilter<>(this::processStateEvent); + private final Object topologyListenerKey; + // The pools that we have opened but have not finished initializing yet + private final Map> pending = new HashMap<>(); + // If we receive events while a pool is initializing, the last one is stored here + private final Map pendingDistanceEvents = new WeakHashMap<>(); + private final Map pendingStateEvents = new WeakHashMap<>(); + + private SingleThreaded(InternalDriverContext context) { + this.context = context; + this.channelPoolFactory = context.channelPoolFactory(); + this.distanceListenerKey = + context + .eventBus() + .register( + DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); + this.stateListenerKey = + context + .eventBus() + .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); + this.topologyListenerKey = + context + .eventBus() + .register( + TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); + } + + private void init(CqlIdentifier keyspace) { + assert adminExecutor.inEventLoop(); + if (initWasCalled) { + return; + } + initWasCalled = true; + + LOG.debug("[{}] Starting initialization", logPrefix); + + PoolManager.this.keyspace = keyspace; + + // Make sure we don't miss any event while the pools are initializing + distanceEventFilter.start(); + stateEventFilter.start(); + + Collection nodes = context.metadataManager().getMetadata().getNodes().values(); + List> poolStages = new ArrayList<>(nodes.size()); + for (Node node : nodes) { + NodeDistance distance = node.getDistance(); + if (distance == NodeDistance.IGNORED) { + LOG.debug("[{}] Skipping {} because it is IGNORED", logPrefix, node); + } else if (node.getState() == NodeState.FORCED_DOWN) { + LOG.debug("[{}] Skipping {} because it is FORCED_DOWN", logPrefix, node); + } else { + LOG.debug("[{}] Creating a pool for {}", logPrefix, node); + poolStages.add(channelPoolFactory.init(node, keyspace, distance, context, logPrefix)); + } + } + CompletableFutures.whenAllDone(poolStages, () -> this.onPoolsInit(poolStages), adminExecutor); + } + + private void onPoolsInit(List> poolStages) { + assert adminExecutor.inEventLoop(); + LOG.debug("[{}] All pools have finished initializing", logPrefix); + // We will only propagate an invalid keyspace error if all pools get it + boolean allInvalidKeyspaces = poolStages.size() > 0; + for (CompletionStage poolStage : poolStages) { + // Note: pool init always succeeds + ChannelPool pool = CompletableFutures.getCompleted(poolStage.toCompletableFuture()); + boolean invalidKeyspace = pool.isInvalidKeyspace(); + if (invalidKeyspace) { + LOG.debug("[{}] Pool to {} reports an invalid keyspace", logPrefix, pool.getNode()); + } + allInvalidKeyspaces &= invalidKeyspace; + pools.put(pool.getNode(), pool); + } + if (allInvalidKeyspaces) { + initFuture.completeExceptionally( + new InvalidKeyspaceException("Invalid keyspace " + keyspace.asCql(true))); + forceClose(); + } else { + LOG.debug("[{}] Initialization complete, ready", logPrefix); + initFuture.complete(null); + distanceEventFilter.markReady(); + stateEventFilter.markReady(); + } + } + + private void onDistanceEvent(DistanceEvent event) { + assert adminExecutor.inEventLoop(); + distanceEventFilter.accept(event); + } + + private void onStateEvent(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + stateEventFilter.accept(event); + } + + private void processDistanceEvent(DistanceEvent event) { + assert adminExecutor.inEventLoop(); + // no need to check closeWasCalled, because we stop listening for events one closed + DefaultNode node = event.node; + NodeDistance newDistance = event.distance; + if (pending.containsKey(node)) { + pendingDistanceEvents.put(node, event); + } else if (newDistance == NodeDistance.IGNORED) { + ChannelPool pool = pools.remove(node); + if (pool != null) { + LOG.debug("[{}] {} became IGNORED, destroying pool", logPrefix, node); + pool.closeAsync() + .exceptionally( + error -> { + Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); + return null; + }); + } + } else { + NodeState state = node.getState(); + if (state == NodeState.FORCED_DOWN) { + LOG.warn( + "[{}] {} became {} but it is FORCED_DOWN, ignoring", logPrefix, node, newDistance); + return; + } + ChannelPool pool = pools.get(node); + if (pool == null) { + LOG.debug( + "[{}] {} became {} and no pool found, initializing it", logPrefix, node, newDistance); + CompletionStage poolFuture = + channelPoolFactory.init(node, keyspace, newDistance, context, logPrefix); + pending.put(node, poolFuture); + poolFuture + .thenAcceptAsync(this::onPoolInitialized, adminExecutor) + .exceptionally(UncaughtExceptions::log); + } else { + LOG.debug("[{}] {} became {}, resizing it", logPrefix, node, newDistance); + pool.resize(newDistance); + } + } + } + + private void processStateEvent(NodeStateEvent event) { + assert adminExecutor.inEventLoop(); + // no need to check closeWasCalled, because we stop listening for events once closed + DefaultNode node = event.node; + NodeState oldState = event.oldState; + NodeState newState = event.newState; + if (pending.containsKey(node)) { + pendingStateEvents.put(node, event); + } else if (newState == NodeState.FORCED_DOWN) { + ChannelPool pool = pools.remove(node); + if (pool != null) { + LOG.debug("[{}] {} was FORCED_DOWN, destroying pool", logPrefix, node); + pool.closeAsync() + .exceptionally( + error -> { + Loggers.warnWithException(LOG, "[{}] Error closing pool", logPrefix, error); + return null; + }); + } + } else if (oldState == NodeState.FORCED_DOWN + && newState == NodeState.UP + && node.getDistance() != NodeDistance.IGNORED) { + LOG.debug("[{}] {} was forced back UP, initializing pool", logPrefix, node); + createOrReconnectPool(node); + } + } + + private void onTopologyEvent(TopologyEvent event) { + assert adminExecutor.inEventLoop(); + if (event.type == TopologyEvent.Type.SUGGEST_UP) { + Node node = context.metadataManager().getMetadata().getNodes().get(event.address); + if (node.getDistance() != NodeDistance.IGNORED) { + LOG.debug( + "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); + createOrReconnectPool(node); + } + } + } + + private void createOrReconnectPool(Node node) { + ChannelPool pool = pools.get(node); + if (pool == null) { + CompletionStage poolFuture = + channelPoolFactory.init(node, keyspace, node.getDistance(), context, logPrefix); + pending.put(node, poolFuture); + poolFuture + .thenAcceptAsync(this::onPoolInitialized, adminExecutor) + .exceptionally(UncaughtExceptions::log); + } else { + pool.reconnectNow(); + } + } + + private void onPoolInitialized(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + Node node = pool.getNode(); + if (closeWasCalled) { + LOG.debug( + "[{}] Session closed while a pool to {} was initializing, closing it", logPrefix, node); + pool.forceCloseAsync(); + } else { + LOG.debug("[{}] New pool to {} initialized", logPrefix, node); + if (Objects.equals(keyspace, pool.getInitialKeyspaceName())) { + reprepareStatements(pool); + } else { + // The keyspace changed while the pool was being initialized, switch it now. + pool.setKeyspace(keyspace) + .handleAsync( + (result, error) -> { + if (error != null) { + Loggers.warnWithException( + LOG, "Error while switching keyspace to " + keyspace, error); + } + reprepareStatements(pool); + return null; + }, + adminExecutor); + } + } + } + + private void reprepareStatements(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + if (config.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { + new ReprepareOnUp( + logPrefix + "|" + pool.getNode().getConnectAddress(), + pool, + repreparePayloads, + context, + () -> RunOrSchedule.on(adminExecutor, () -> onPoolReady(pool))) + .start(); + } else { + LOG.debug("[{}] Reprepare on up is disabled, skipping", logPrefix); + onPoolReady(pool); + } + } + + private void onPoolReady(ChannelPool pool) { + assert adminExecutor.inEventLoop(); + Node node = pool.getNode(); + pending.remove(node); + pools.put(node, pool); + DistanceEvent distanceEvent = pendingDistanceEvents.remove(node); + NodeStateEvent stateEvent = pendingStateEvents.remove(node); + if (stateEvent != null && stateEvent.newState == NodeState.FORCED_DOWN) { + LOG.debug( + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + stateEvent); + processStateEvent(stateEvent); + } else if (distanceEvent != null) { + LOG.debug( + "[{}] Received {} while the pool was initializing, processing it now", + logPrefix, + distanceEvent); + processDistanceEvent(distanceEvent); + } + } + + private void setKeyspace(CqlIdentifier newKeyspace, CompletableFuture doneFuture) { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + doneFuture.complete(null); + return; + } + LOG.debug("[{}] Switching to keyspace {}", logPrefix, newKeyspace); + List> poolReadyFutures = Lists.newArrayListWithCapacity(pools.size()); + for (ChannelPool pool : pools.values()) { + poolReadyFutures.add(pool.setKeyspace(newKeyspace)); + } + CompletableFutures.completeFrom(CompletableFutures.allDone(poolReadyFutures), doneFuture); + } + + private void close() { + assert adminExecutor.inEventLoop(); + if (closeWasCalled) { + return; + } + closeWasCalled = true; + LOG.debug("[{}] Starting shutdown", logPrefix); + + // Stop listening for events + context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); + context.eventBus().unregister(stateListenerKey, NodeStateEvent.class); + context.eventBus().unregister(topologyListenerKey, TopologyEvent.class); + + List> closePoolStages = new ArrayList<>(pools.size()); + for (ChannelPool pool : pools.values()) { + closePoolStages.add(pool.closeAsync()); + } + CompletableFutures.whenAllDone( + closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + } + + private void forceClose() { + assert adminExecutor.inEventLoop(); + if (forceCloseWasCalled) { + return; + } + forceCloseWasCalled = true; + LOG.debug( + "[{}] Starting forced shutdown (was {}closed before)", + logPrefix, + (closeWasCalled ? "" : "not ")); + + if (closeWasCalled) { + for (ChannelPool pool : pools.values()) { + pool.forceCloseAsync(); + } + } else { + List> closePoolStages = new ArrayList<>(pools.size()); + for (ChannelPool pool : pools.values()) { + closePoolStages.add(pool.forceCloseAsync()); + } + CompletableFutures.whenAllDone( + closePoolStages, () -> onAllPoolsClosed(closePoolStages), adminExecutor); + } + } + + private void onAllPoolsClosed(List> closePoolStages) { + assert adminExecutor.inEventLoop(); + Throwable firstError = null; + for (CompletionStage closePoolStage : closePoolStages) { + CompletableFuture closePoolFuture = closePoolStage.toCompletableFuture(); + assert closePoolFuture.isDone(); + if (closePoolFuture.isCompletedExceptionally()) { + Throwable error = CompletableFutures.getFailed(closePoolFuture); + if (firstError == null) { + firstError = error; + } else { + firstError.addSuppressed(error); + } + } + } + if (firstError != null) { + closeFuture.completeExceptionally(firstError); + } else { + LOG.debug("[{}] Shutdown complete", logPrefix); + closeFuture.complete(null); + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SchemaListenerNotifier.java similarity index 99% rename from core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/session/SchemaListenerNotifier.java index 2b36d566e88..9ef67589036 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/SchemaListenerNotifier.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SchemaListenerNotifier.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.core; +package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.internal.core.context.EventBus; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java index 64bd8175103..8250739ff38 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -16,6 +16,10 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.reflect.GenericType; @@ -42,6 +46,41 @@ public SessionWrapper(Session delegate) { this.delegate = delegate; } + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Metadata getMetadata() { + return delegate.getMetadata(); + } + + @Override + public boolean isSchemaMetadataEnabled() { + return delegate.isSchemaMetadataEnabled(); + } + + @Override + public CompletionStage setSchemaMetadataEnabled(Boolean newValue) { + return delegate.setSchemaMetadataEnabled(newValue); + } + + @Override + public CompletionStage refreshSchemaAsync() { + return delegate.refreshSchemaAsync(); + } + + @Override + public CompletionStage checkSchemaAgreementAsync() { + return delegate.checkSchemaAgreementAsync(); + } + + @Override + public DriverContext getContext() { + return delegate.getContext(); + } + @Override public CqlIdentifier getKeyspace() { return delegate.getKeyspace(); @@ -53,6 +92,26 @@ public ResultT execute( return delegate.execute(request, resultType); } + @Override + public void register(SchemaChangeListener listener) { + delegate.register(listener); + } + + @Override + public void unregister(SchemaChangeListener listener) { + delegate.unregister(listener); + } + + @Override + public void register(NodeStateListener listener) { + delegate.register(listener); + } + + @Override + public void unregister(NodeStateListener listener) { + delegate.unregister(listener); + } + @Override public CompletionStage closeFuture() { return delegate.closeFuture(); diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index d2dfeebc3f6..28cb7e378fa 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -96,11 +96,25 @@ datastax-java-driver { # A name that uniquely identifies the driver instance created from this configuration. This is # used as a prefix for log messages and metrics. # This option is not required; if it is not specified, the driver will generate an identifier - # composed of the letter 'c' followed by an incrementing counter. + # composed of the letter 's' followed by an incrementing counter. # If you provide a different value, try to keep it short to keep the logs readable. Also, make # sure it is unique: reusing the same value will not break the driver, but it will mix up the logs # and metrics. - // cluster-name = my_cluster + // session-name = my_session + + # The name of the keyspace that the session should initially be connected to. + # + # This expects the same format as in a CQL query: case-sensitive names must be quoted (note that + # the quotes must be escaped in HOCON format). For example: + # session-keyspace = case_insensitive_name + # session-keyspace = \"CaseSensitiveName\" + # + # This option is not required; if it is left unspecified, the session won't be connected to any + # keyspace, and you'll have to either qualify table names in your queries, or use the per-query + # keyspace feature available in Cassandra 4 and above (see Request.getKeyspace()). + # + # This can also be provided programatically in CqlSessionBuilder. + // session-keyspace = my_keyspace # How often the driver tries to reload the configuration. # This option is required, except if you use a different config loader than the driver's built-in diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index 9ace0d5e2c6..6a915bbeb2e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -54,7 +54,7 @@ public class DefaultDriverConfigLoaderTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(context.clusterName()).thenReturn("test"); + Mockito.when(context.sessionName()).thenReturn("test"); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index aa96e9a6430..3fa049f9b8a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 8acb6527325..4a403dca32a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java similarity index 86% rename from core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index d6240d99bd8..fb201c9e592 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -16,30 +16,42 @@ package com.datastax.oss.driver.internal.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultEventLoopGroup; +import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; @@ -54,9 +66,10 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.timeout; -public class DefaultSessionTest { +public class DefaultSessionPoolsTest { private static final CqlIdentifier KEYSPACE = CqlIdentifier.fromInternal("ks"); @@ -64,9 +77,17 @@ public class DefaultSessionTest { @Mock private NettyOptions nettyOptions; @Mock private ChannelPoolFactory channelPoolFactory; @Mock private MetadataManager metadataManager; + @Mock private TopologyMonitor topologyMonitor; + @Mock private LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; + @Mock private DriverConfigLoader configLoader; @Mock private Metadata metadata; @Mock private DriverConfig config; @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private ReconnectionPolicy reconnectionPolicy; + @Mock private RetryPolicy retryPolicy; + @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; + @Mock private AddressTranslator addressTranslator; + @Mock private ControlConnection controlConnection; private DefaultNode node1; private DefaultNode node2; @@ -79,8 +100,41 @@ public void setup() { MockitoAnnotations.initMocks(this); adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + + // Config: + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) + .thenReturn(false); + Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + .thenReturn(true); + Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + .thenReturn(Duration.ZERO); + Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + .thenReturn(1); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(context.config()).thenReturn(config); + + // Init sequence: + Mockito.when(metadataManager.addContactPoints(anySet())) + .thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(metadataManager.refreshNodes()) + .thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(metadataManager.firstSchemaRefreshFuture()) + .thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(context.metadataManager()).thenReturn(metadataManager); + + Mockito.when(topologyMonitor.init()).thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); + + Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + + Mockito.when(context.configLoader()).thenReturn(configLoader); + + // Runtime behavior: + Mockito.when(context.sessionName()).thenReturn("test"); Mockito.when(context.channelPoolFactory()).thenReturn(channelPoolFactory); @@ -97,14 +151,33 @@ public void setup() { node3.getConnectAddress(), node3); Mockito.when(metadata.getNodes()).thenReturn(nodes); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) - .thenReturn(false); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(context.config()).thenReturn(config); + PoolManager poolManager = new PoolManager(context); + Mockito.when(context.poolManager()).thenReturn(poolManager); + + // Shutdown sequence: + Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(context.retryPolicy()).thenReturn(retryPolicy); + Mockito.when(context.speculativeExecutionPolicy()).thenReturn(speculativeExecutionPolicy); + Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + + Mockito.when(metadataManager.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(metadataManager.forceCloseAsync()) + .thenReturn(CompletableFuture.completedFuture(null)); + + Mockito.when(topologyMonitor.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(topologyMonitor.forceCloseAsync()) + .thenReturn(CompletableFuture.completedFuture(null)); + + Mockito.when(context.controlConnection()).thenReturn(controlConnection); + Mockito.when(controlConnection.closeAsync()) + .thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(controlConnection.forceCloseAsync()) + .thenReturn(CompletableFuture.completedFuture(null)); + + DefaultPromise nettyCloseFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + nettyCloseFuture.setSuccess(null); + Mockito.when(nettyOptions.onClose()).thenAnswer(invocation -> nettyCloseFuture); } @Test @@ -124,7 +197,7 @@ public void should_initialize_pools_with_distances() { .pending(node3, KEYSPACE, NodeDistance.REMOTE, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -158,7 +231,7 @@ public void should_not_connect_to_ignored_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -182,7 +255,7 @@ public void should_not_connect_to_forced_down_nodes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -208,7 +281,7 @@ public void should_adjust_distance_if_changed_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -249,7 +322,7 @@ public void should_remove_pool_if_ignored_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -289,7 +362,7 @@ public void should_remove_pool_if_forced_down_while_init() { .pending(node3, KEYSPACE, NodeDistance.LOCAL, pool3Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -326,7 +399,7 @@ public void should_resize_pool_if_distance_changes() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -350,7 +423,7 @@ public void should_remove_pool_if_node_becomes_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -377,7 +450,7 @@ public void should_do_nothing_if_node_becomes_ignored_but_was_already_ignored() .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -413,7 +486,7 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -441,7 +514,7 @@ public void should_remove_pool_if_node_is_forced_down() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -472,7 +545,7 @@ public void should_recreate_pool_if_node_is_forced_back_up() { .success(node2, KEYSPACE, NodeDistance.LOCAL, pool2) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -502,7 +575,7 @@ public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -534,7 +607,7 @@ public void should_adjust_distance_if_changed_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -577,7 +650,7 @@ public void should_remove_pool_if_ignored_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -620,7 +693,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -658,7 +731,7 @@ public void should_close_all_pools_when_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -688,7 +761,7 @@ public void should_force_close_all_pools_when_force_closing() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -723,7 +796,7 @@ public void should_close_pool_if_recreated_while_closing() { .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -761,7 +834,7 @@ public void should_set_keyspace_on_all_pools() { .success(node3, KEYSPACE, NodeDistance.LOCAL, pool3) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); @@ -796,7 +869,7 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() .pending(node2, KEYSPACE, NodeDistance.LOCAL, pool2Future) .build(); - CompletionStage initFuture = DefaultSession.init(context, KEYSPACE, "test"); + CompletionStage initFuture = newSession(); factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); @@ -848,6 +921,10 @@ private ChannelPool mockPool(Node node) { return pool; } + private CompletionStage newSession() { + return DefaultSession.init(context, Collections.emptySet(), KEYSPACE, Collections.emptySet()); + } + private static DefaultNode mockLocalNode(int i) { DefaultNode node = Mockito.mock(DefaultNode.class); Mockito.when(node.getConnectAddress()).thenReturn(new InetSocketAddress("127.0.0." + i, 9042)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 653fc7cf5bf..9ad9aadf110 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -17,7 +17,8 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.ClassRule; import org.junit.Test; @@ -29,20 +30,19 @@ public class ConnectIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule - public static ClusterRule cluster = ClusterRule.builder(ccm).withDefaultSession(false).build(); + @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); @Test public void should_connect_to_existing_keyspace() { - CqlIdentifier keyspace = cluster.keyspace(); - try (Session session = cluster.cluster().connect(keyspace)) { + CqlIdentifier keyspace = sessionRule.keyspace(); + try (Session session = SessionUtils.newSession(ccm, keyspace)) { assertThat(session.getKeyspace()).isEqualTo(keyspace); } } @Test public void should_connect_with_no_keyspace() { - try (Session session = cluster.cluster().connect()) { + try (Session session = SessionUtils.newSession(ccm)) { assertThat(session.getKeyspace()).isNull(); } } @@ -50,6 +50,6 @@ public void should_connect_with_no_keyspace() { @Test(expected = InvalidKeyspaceException.class) public void should_fail_to_connect_to_non_existent_keyspace() { CqlIdentifier keyspace = CqlIdentifier.fromInternal("does not exist"); - cluster.cluster().connect(keyspace); + SessionUtils.newSession(ccm, keyspace); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 05df1bfc1e4..633fee11622 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Rule; import org.junit.Test; @@ -39,10 +38,8 @@ public class ProtocolVersionInitialNegotiationIT { ) @Test public void should_downgrade_to_v3() { - try (Cluster v3cluster = ClusterUtils.newCluster(ccm)) { - assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - - CqlSession session = v3cluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm)) { + assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } } @@ -54,10 +51,8 @@ public void should_downgrade_to_v3() { ) @Test public void should_fail_if_provided_version_isnt_supported() { - try (Cluster v4cluster = ClusterUtils.newCluster(ccm, "protocol.version = V4")) { - assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - - CqlSession session = v4cluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm, "protocol.version = V4")) { + assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } catch (AllNodesFailedException anfe) { Throwable cause = anfe.getErrors().values().iterator().next(); @@ -71,10 +66,8 @@ public void should_fail_if_provided_version_isnt_supported() { @CassandraRequirement(min = "2.2", description = "required to meet default protocol version") @Test public void should_not_downgrade_if_server_supports_latest_version() { - try (Cluster v4cluster = ClusterUtils.newCluster(ccm)) { - assertThat(v4cluster.getContext().protocolVersion().getCode()).isEqualTo(4); - - CqlSession session = v4cluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm)) { + assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(4); session.execute("select * from system.local"); } } @@ -82,10 +75,8 @@ public void should_not_downgrade_if_server_supports_latest_version() { @CassandraRequirement(min = "2.2", description = "required to use an older protocol version") @Test public void should_use_explicitly_provided_protocol_version() { - try (Cluster v3cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { - assertThat(v3cluster.getContext().protocolVersion().getCode()).isEqualTo(3); - - CqlSession session = v3cluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm, "protocol.version = V3")) { + assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 9d609dc1131..1e5286c7f60 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -50,13 +49,13 @@ public class ProtocolVersionMixedClusterIT { public void should_downgrade_if_peer_does_not_support_negotiated_version() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.1.0"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = - Cluster.builder() + CqlSession session = + CqlSession.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader("metadata.schema.enabled = false")) .build()) { - InternalDriverContext context = (InternalDriverContext) cluster.getContext(); + InternalDriverContext context = (InternalDriverContext) session.getContext(); assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V3); // Find out which node became the control node after the reconnection (not necessarily node 0) @@ -90,13 +89,13 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { public void should_keep_current_if_supported_by_all_peers() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "3.11"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = - Cluster.builder() + CqlSession session = + CqlSession.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader("metadata.schema.enabled = false")) .build()) { - InternalDriverContext context = (InternalDriverContext) cluster.getContext(); + InternalDriverContext context = (InternalDriverContext) session.getContext(); assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(3); assertThat(protocolQueries(contactPoint, 4)) @@ -116,8 +115,8 @@ public void should_fail_if_peer_does_not_support_v3() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.0.9", "3.11"); BoundNode contactPoint = simulacron.node(0); - Cluster ignored = - Cluster.builder() + CqlSession ignored = + CqlSession.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(new TestConfigLoader()) .build()) { @@ -129,14 +128,14 @@ public void should_fail_if_peer_does_not_support_v3() { public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.0.0"); BoundNode contactPoint = simulacron.node(0); - Cluster cluster = - Cluster.builder() + CqlSession session = + CqlSession.builder() .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader( new TestConfigLoader( "protocol.version = V4", "metadata.schema.enabled = false")) .build()) { - assertThat(cluster.getContext().protocolVersion()).isEqualTo(CoreProtocolVersion.V4); + assertThat(session.getContext().protocolVersion()).isEqualTo(CoreProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(3); assertThat(protocolQueries(contactPoint, 4)) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index b25f4362366..4032b2b0fe6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -17,10 +17,9 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.google.common.util.concurrent.Uninterruptibles; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; @@ -46,34 +45,31 @@ public static void sleepForAuth() { @Test public void should_connect_with_credentials() { - try (Cluster authCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = cassandra", "protocol.auth-provider.password = cassandra")) { - CqlSession session = authCluster.connect(); session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_invalid_credentials() { - try (Cluster authCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "protocol.auth-provider.class = com.datastax.oss.driver.api.core.auth.PlainTextAuthProvider", "protocol.auth-provider.username = baduser", "protocol.auth-provider.password = badpass")) { - CqlSession session = authCluster.connect(); session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_without_credentials() { - try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { - CqlSession session = plainCluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 3ca8478ecb3..17674351a4e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -15,14 +15,13 @@ */ package com.datastax.oss.driver.api.core.compression; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -38,12 +37,12 @@ public class DirectCompressionIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); @ClassRule - public static ClusterRule schemaClusterRule = - new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + public static SessionRule schemaSessionRule = + new SessionRule<>(ccmRule, "request.timeout = 30 seconds"); @BeforeClass public static void setup() { - schemaClusterRule + schemaSessionRule .session() .execute("CREATE TABLE test (k text PRIMARY KEY, t text, i int, f float)"); } @@ -78,9 +77,8 @@ public void should_execute_queries_with_lz4_compression() throws Exception { private void createAndCheckCluster(String compressorOption) { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { - CqlSession session = cluster.connect(schemaClusterRule.keyspace()); - + try (CqlSession session = + SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), compressorOption)) { // Run a couple of simple test queries ResultSet rs = session.execute( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index e46ec209282..b6575d5402f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -15,14 +15,13 @@ */ package com.datastax.oss.driver.api.core.compression; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -43,12 +42,12 @@ public class HeapCompressionIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); @ClassRule - public static ClusterRule schemaClusterRule = - new ClusterRule(ccmRule, "request.timeout = 30 seconds"); + public static SessionRule schemaSessionRule = + new SessionRule<>(ccmRule, "request.timeout = 30 seconds"); @BeforeClass public static void setup() { - schemaClusterRule + schemaSessionRule .session() .execute("CREATE TABLE test (k text PRIMARY KEY, t text, i int, f float)"); } @@ -81,9 +80,8 @@ public void should_execute_queries_with_lz4_compression() throws Exception { private void createAndCheckCluster(String compressorOption) { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, compressorOption)) { - CqlSession session = cluster.connect(schemaClusterRule.keyspace()); - + try (CqlSession session = + SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), compressorOption)) { // Run a couple of simple test queries ResultSet rs = session.execute( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 4e3059257ee..44d0ceaf35a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -16,19 +16,18 @@ package com.datastax.oss.driver.api.core.config; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -59,9 +58,7 @@ public class DriverConfigProfileIT { @Test public void should_fail_if_config_profile_specified_doesnt_exist() { - try (Cluster profileCluster = ClusterUtils.newCluster(simulacron)) { - CqlSession session = profileCluster.connect(); - + try (CqlSession session = SessionUtils.newSession(simulacron)) { SimpleStatement statement = SimpleStatement.builder("select * from system.local") .withConfigProfileName("IDONTEXIST") @@ -75,12 +72,11 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { @Test public void should_use_profile_request_timeout() { - try (Cluster profileCluster = - ClusterUtils.newCluster(simulacron, "profiles.olap.request.timeout = 10s")) { + try (CqlSession session = + SessionUtils.newSession(simulacron, "profiles.olap.request.timeout = 10s")) { String query = "mockquery"; // configure query with delay of 4 seconds. simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - CqlSession session = profileCluster.connect(); // Execute query without profile, should timeout with default (2s). try { @@ -97,14 +93,12 @@ public void should_use_profile_request_timeout() { @Test public void should_use_profile_default_idempotence() { - try (Cluster profileCluster = - ClusterUtils.newCluster(simulacron, "profiles.idem.request.default-idempotence = true")) { + try (CqlSession session = + SessionUtils.newSession(simulacron, "profiles.idem.request.default-idempotence = true")) { String query = "mockquery"; // configure query with server error which should invoke onRequestError in retry policy. simulacron.cluster().prime(when(query).then(serverError("fail"))); - CqlSession session = profileCluster.connect(); - // Execute query without profile, should fail because couldn't be retried. try { session.execute(query); @@ -121,15 +115,13 @@ public void should_use_profile_default_idempotence() { @Test public void should_use_profile_consistency() { - try (Cluster profileCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( simulacron, "profiles.cl.request.consistency = LOCAL_QUORUM", "profiles.cl.request.serial-consistency = LOCAL_SERIAL")) { String query = "mockquery"; - CqlSession session = profileCluster.connect(); - // Execute query without profile, should use default CLs (LOCAL_ONE, SERIAL). session.execute(query); @@ -176,15 +168,15 @@ public void should_use_profile_consistency() { @Test public void should_use_profile_page_size() { - try (Cluster profileCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "request.page-size = 100", "profiles.smallpages.request.page-size = 10")) { - CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); - DriverConfigProfile slowProfile = ClusterUtils.slowProfile(profileCluster); - ClusterUtils.createKeyspace(profileCluster, keyspace, slowProfile); + CqlIdentifier keyspace = SessionUtils.uniqueKeyspaceId(); + DriverConfigProfile slowProfile = SessionUtils.slowProfile(session); + SessionUtils.createKeyspace(session, keyspace, slowProfile); - CqlSession session = profileCluster.connect(keyspace); + session.execute(String.format("USE %s", keyspace.asCql(false))); // load 500 rows (value beyond page size). session.execute( @@ -217,7 +209,7 @@ public void should_use_profile_page_size() { result.fetchNextPage(); assertThat(result.getAvailableWithoutFetching()).isEqualTo(20); - ClusterUtils.dropKeyspace(profileCluster, keyspace, slowProfile); + SessionUtils.dropKeyspace(session, keyspace, slowProfile); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index ee53184383f..b361106bcb6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -15,9 +15,8 @@ */ package com.datastax.oss.driver.api.core.config; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.DriverTimeoutException; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -56,15 +55,13 @@ public void should_periodically_reload_configuration() throws Exception { ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - CqlSession session = configCluster.connect(); - // Expect timeout since default timeout is 2s try { session.execute(query); @@ -75,7 +72,7 @@ public void should_periodically_reload_configuration() throws Exception { // Bump up request timeout to 10 seconds and wait for config to reload. configSource.set("request.timeout = 10s"); - waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should not timeout. session.execute(query); @@ -93,15 +90,13 @@ public void should_reload_configuration_when_event_fired() throws Exception { ConfigFactory.parseString("config-reload-interval = 0\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - CqlSession session = configCluster.connect(); - // Expect timeout since default timeout is 2s try { session.execute(query); @@ -112,10 +107,10 @@ public void should_reload_configuration_when_event_fired() throws Exception { // Bump up request timeout to 10 seconds and trigger a manual reload. configSource.set("request.timeout = 10s"); - ((InternalDriverContext) configCluster.getContext()) + ((InternalDriverContext) session.getContext()) .eventBus() .fire(ForceReloadConfigEvent.INSTANCE); - waitForConfigChange(configCluster, 500, TimeUnit.MILLISECONDS); + waitForConfigChange(session, 500, TimeUnit.MILLISECONDS); // Execute again, should not timeout. session.execute(query); @@ -133,15 +128,13 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - CqlSession session = configCluster.connect(); - // Expect failure because profile doesn't exist. try { session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); @@ -152,7 +145,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { // Bump up request timeout to 10 seconds on profile and wait for config to reload. configSource.set("profiles.slow.request.timeout = 2s"); - waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should expect to fail again because doesn't allow to dynamically define profile. thrown.expect(IllegalArgumentException.class); @@ -174,15 +167,13 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), CoreDriverOption.values()); - try (Cluster configCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .withConfigLoader(loader) .addContactPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - CqlSession session = configCluster.connect(); - // Expect failure because profile doesn't exist. try { session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); @@ -193,16 +184,16 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio // Bump up request timeout to 10 seconds on profile and wait for config to reload. configSource.set("profiles.slow.request.timeout = 10s"); - waitForConfigChange(configCluster, 3, TimeUnit.SECONDS); + waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should succeed because profile timeout was increased. session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); } } - private void waitForConfigChange(Cluster cluster, long timeout, TimeUnit unit) { + private void waitForConfigChange(CqlSession session, long timeout, TimeUnit unit) { CountDownLatch latch = new CountDownLatch(1); - ((InternalDriverContext) cluster.getContext()) + ((InternalDriverContext) session.getContext()) .eventBus() .register(ConfigChangeEvent.class, (e) -> latch.countDown()); try { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 770f452e759..e1c275c9af1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -16,14 +16,14 @@ package com.datastax.oss.driver.api.core.connection; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -48,8 +48,8 @@ public class FrameLengthIT { new SimulacronRule(ClusterSpec.builder().withNodes(1)); @ClassRule - public static ClusterRule cluster = - new ClusterRule( + public static SessionRule sessionRule = + new SessionRule<>( simulacron, "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", "request.retry-policy.class = \"com.datastax.oss.driver.api.core.connection.FrameLengthIT$AlwaysRetryAbortedPolicy\"", @@ -76,16 +76,17 @@ public void primeQueries() { @Test(expected = FrameTooLongException.class) public void should_fail_if_request_exceeds_max_frame_length() { - cluster + sessionRule .session() .execute(SimpleStatement.newInstance("insert into foo (k) values (?)", ONE_HUNDRED_KB)); } @Test public void should_fail_if_response_exceeds_max_frame_length() { - CompletionStage slowResultFuture = cluster.session().executeAsync(SLOW_QUERY); + CompletionStage slowResultFuture = + sessionRule.session().executeAsync(SLOW_QUERY); try { - cluster.session().execute(LARGE_QUERY); + sessionRule.session().execute(LARGE_QUERY); fail("Expected a " + FrameTooLongException.class.getSimpleName()); } catch (FrameTooLongException e) { // expected diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index fdc7b9d19d5..8aa36953639 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -15,8 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import java.util.concurrent.CompletableFuture; @@ -40,21 +41,22 @@ public class AsyncResultSetIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); @ClassRule - public static ClusterRule cluster = new ClusterRule(ccm, "request.page-size = " + PAGE_SIZE); + public static SessionRule sessionRule = + new SessionRule<>(ccm, "request.page-size = " + PAGE_SIZE); @BeforeClass public static void setupSchema() { // create table and load data across two partitions so we can test paging across tokens. - cluster + sessionRule .session() .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k0 text, k1 int, v int, PRIMARY KEY(k0, k1))") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); PreparedStatement prepared = - cluster.session().prepare("INSERT INTO test (k0, k1, v) VALUES (?, ?, ?)"); + sessionRule.session().prepare("INSERT INTO test (k0, k1, v) VALUES (?, ?, ?)"); BatchStatementBuilder batchPart1 = BatchStatement.builder(BatchType.UNLOGGED); BatchStatementBuilder batchPart2 = BatchStatement.builder(BatchType.UNLOGGED); @@ -64,15 +66,15 @@ public static void setupSchema() { prepared.bind(PARTITION_KEY2, i + ROWS_PER_PARTITION, i + ROWS_PER_PARTITION)); } - cluster.session().execute(batchPart1.withConfigProfile(cluster.slowProfile()).build()); - cluster.session().execute(batchPart2.withConfigProfile(cluster.slowProfile()).build()); + sessionRule.session().execute(batchPart1.withConfigProfile(sessionRule.slowProfile()).build()); + sessionRule.session().execute(batchPart2.withConfigProfile(sessionRule.slowProfile()).build()); } @Test public void should_only_iterate_over_rows_in_current_page() throws Exception { // very basic test that just ensures that iterating over an AsyncResultSet only visits the first page. CompletionStage result = - cluster + sessionRule .session() .executeAsync( SimpleStatement.builder("SELECT * FROM test where k0 = ?") @@ -98,7 +100,7 @@ public void should_only_iterate_over_rows_in_current_page() throws Exception { public void should_iterate_over_all_pages_asynchronously_single_partition() throws Exception { // Validates async paging behavior over single partition. CompletionStage result = - cluster + sessionRule .session() .executeAsync( SimpleStatement.builder("SELECT * FROM test where k0 = ?") @@ -116,7 +118,7 @@ public void should_iterate_over_all_pages_asynchronously_single_partition() thro public void should_iterate_over_all_pages_asynchronously_cross_partition() throws Exception { // Validates async paging behavior over a range query. CompletionStage result = - cluster + sessionRule .session() .executeAsync("SELECT * FROM test") .thenCompose(new AsyncResultSetConsumingFunction()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 32c075b7f48..3f52511dc53 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -15,13 +15,12 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import org.junit.Before; @@ -37,7 +36,7 @@ public class BatchStatementIT { @Rule public CcmRule ccm = CcmRule.getInstance(); - @Rule public ClusterRule cluster = new ClusterRule(ccm); + @Rule public SessionRule sessionRule = new SessionRule<>(ccm); @Rule public TestName name = new TestName(); @@ -54,10 +53,11 @@ public void createTable() { }; for (String schemaStatement : schemaStatements) { - cluster + sessionRule .session() .execute( - SimpleStatement.newInstance(schemaStatement).setConfigProfile(cluster.slowProfile())); + SimpleStatement.newInstance(schemaStatement) + .setConfigProfile(sessionRule.slowProfile())); } } @@ -76,7 +76,7 @@ public void should_execute_batch_of_simple_statements_with_variables() { } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); verifyBatchInsert(); } @@ -90,14 +90,14 @@ public void should_execute_batch_of_bound_statements_with_variables() { String.format( "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) .build(); - PreparedStatement preparedStatement = cluster.session().prepare(insert); + PreparedStatement preparedStatement = sessionRule.session().prepare(insert); for (int i = 0; i < batchCount; i++) { builder.addStatement(preparedStatement.bind(i, i + 1)); } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); verifyBatchInsert(); } @@ -112,14 +112,14 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { String.format( "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) .build(); - PreparedStatement preparedStatement = cluster.session().prepare(insert); + PreparedStatement preparedStatement = sessionRule.session().prepare(insert); for (int i = 0; i < batchCount; i++) { builder.addStatement(preparedStatement.bind(i, i + 1)); } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); verifyBatchInsert(); @@ -133,14 +133,14 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { builder.addStatement(boundStatement); } - cluster.session().execute(builder2.build()); + sessionRule.session().execute(builder2.build()); Statement select = SimpleStatement.builder("SELECT * from test where k0 = ?") .addPositionalValue(name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); @@ -162,7 +162,7 @@ public void should_execute_batch_of_bound_statements_with_named_variables() { // Build a batch of batchCount statements with bound statements, each with their own named variable values. BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); PreparedStatement preparedStatement = - cluster.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); + sessionRule.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); for (int i = 0; i < batchCount; i++) { builder.addStatement( @@ -175,7 +175,7 @@ public void should_execute_batch_of_bound_statements_with_named_variables() { } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); verifyBatchInsert(); } @@ -189,7 +189,7 @@ public void should_execute_batch_of_bound_and_simple_statements_with_variables() String.format( "INSERT INTO test (k0, k1, v) values ('%s', ? , ?)", name.getMethodName())) .build(); - PreparedStatement preparedStatement = cluster.session().prepare(insert); + PreparedStatement preparedStatement = sessionRule.session().prepare(insert); for (int i = 0; i < batchCount; i++) { if (i % 2 == 1) { @@ -206,7 +206,7 @@ public void should_execute_batch_of_bound_and_simple_statements_with_variables() } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); verifyBatchInsert(); } @@ -221,20 +221,20 @@ public void should_execute_cas_batch() { "INSERT INTO test (k0, k1, v) values ('%s', ? , ?) IF NOT EXISTS", name.getMethodName())) .build(); - PreparedStatement preparedStatement = cluster.session().prepare(insert); + PreparedStatement preparedStatement = sessionRule.session().prepare(insert); for (int i = 0; i < batchCount; i++) { builder.addStatement(preparedStatement.bind(i, i + 1)); } BatchStatement batchStatement = builder.build(); - ResultSet result = cluster.session().execute(batchStatement); + ResultSet result = sessionRule.session().execute(batchStatement); assertThat(result.wasApplied()).isTrue(); verifyBatchInsert(); // re execute same batch and ensure wasn't applied. - result = cluster.session().execute(batchStatement); + result = sessionRule.session().execute(batchStatement); assertThat(result.wasApplied()).isFalse(); } @@ -254,11 +254,11 @@ public void should_execute_counter_batch() { } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); for (int i = 1; i <= 3; i++) { ResultSet result = - cluster + sessionRule .session() .execute( String.format( @@ -287,7 +287,7 @@ public void should_fail_logged_batch_with_counter_increment() { } BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); } @Test(expected = InvalidQueryException.class) @@ -314,16 +314,16 @@ public void should_fail_counter_batch_with_non_counter_increment() { builder.addStatement(simpleInsert); BatchStatement batchStatement = builder.build(); - cluster.session().execute(batchStatement); + sessionRule.session().execute(batchStatement); } @Test(expected = IllegalStateException.class) public void should_not_allow_unset_value_when_protocol_less_than_v4() { // CREATE TABLE test (k0 text, k1 int, v int, PRIMARY KEY (k0, k1)) - try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { - CqlIdentifier keyspace = cluster.keyspace(); - CqlSession session = v3Cluster.connect(keyspace); - PreparedStatement prepared = session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); + try (CqlSession v3Session = + SessionUtils.newSession(ccm, sessionRule.keyspace(), "protocol.version = V3")) { + PreparedStatement prepared = + v3Session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); BatchStatementBuilder builder = BatchStatement.builder(BatchType.LOGGED); builder.addStatements( @@ -337,7 +337,7 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { .unset(2) .build()); - session.execute(builder.build()); + v3Session.execute(builder.build()); } } @@ -348,7 +348,7 @@ private void verifyBatchInsert() { .addPositionalValue(name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 54c75e6fd2b..427e793e8c6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -15,12 +15,11 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; import org.junit.Rule; @@ -35,7 +34,8 @@ public class BoundStatementIT { @Rule public CcmRule ccm = CcmRule.getInstance(); - @Rule public ClusterRule cluster = new ClusterRule(ccm, "request.page-size = 20"); + @Rule + public SessionRule sessionRule = new SessionRule<>(ccm, "request.page-size = 20"); @Rule public TestName name = new TestName(); @@ -44,25 +44,24 @@ public class BoundStatementIT { @Before public void setupSchema() { // table with simple primary key, single cell. - cluster + sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v0 int)") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); } @Test(expected = IllegalStateException.class) public void should_not_allow_unset_value_when_protocol_less_than_v4() { - try (Cluster v3Cluster = ClusterUtils.newCluster(ccm, "protocol.version = V3")) { - CqlIdentifier keyspace = cluster.keyspace(); - CqlSession session = v3Cluster.connect(keyspace); - PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + try (CqlSession v3Session = + SessionUtils.newSession(ccm, sessionRule.keyspace(), "protocol.version = V3")) { + PreparedStatement prepared = v3Session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); BoundStatement boundStatement = prepared.boundStatementBuilder().setString(0, name.getMethodName()).unset(1).build(); - session.execute(boundStatement); + v3Session.execute(boundStatement); } } @@ -70,9 +69,9 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { @CassandraRequirement(min = "2.2") public void should_not_write_tombstone_if_value_is_implicitly_unset() { PreparedStatement prepared = - cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); BoundStatement boundStatement = prepared.boundStatementBuilder().setString(0, name.getMethodName()).build(); @@ -84,9 +83,9 @@ public void should_not_write_tombstone_if_value_is_implicitly_unset() { @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset() { PreparedStatement prepared = - cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); BoundStatement boundStatement = prepared @@ -102,9 +101,9 @@ public void should_write_tombstone_if_value_is_explicitly_unset() { @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { PreparedStatement prepared = - cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); BoundStatement boundStatement = prepared @@ -120,20 +119,20 @@ public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { @Test public void should_have_empty_result_definitions_for_update_query() { PreparedStatement prepared = - cluster.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); assertThat(prepared.getResultSetDefinitions()).hasSize(0); - ResultSet rs = cluster.session().execute(prepared.bind(name.getMethodName(), VALUE)); + ResultSet rs = sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); assertThat(rs.getColumnDefinitions()).hasSize(0); } private void verifyUnset(BoundStatement boundStatement) { - cluster.session().execute(boundStatement.unset(1)); + sessionRule.session().execute(boundStatement.unset(1)); // Verify that no tombstone was written by reading data back and ensuring initial value is retained. ResultSet result = - cluster + sessionRule .session() .execute( SimpleStatement.builder("SELECT v0 from test2 where k = ?") diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index c8783814ee1..0bd8e739f0a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; import org.junit.Rule; @@ -43,26 +43,27 @@ public class PerRequestKeyspaceIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); - @Rule public ClusterRule clusterRule = ClusterRule.builder(ccmRule).withKeyspace(true).build(); + @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public TestName nameRule = new TestName(); @Before public void setupSchema() { - CqlSession session = clusterRule.session(); - session.execute( - SimpleStatement.builder( - "CREATE TABLE IF NOT EXISTS foo (k text, cc int, v int, PRIMARY KEY(k, cc))") - .withConfigProfile(clusterRule.slowProfile()) - .build()); + sessionRule + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS foo (k text, cc int, v int, PRIMARY KEY(k, cc))") + .withConfigProfile(sessionRule.slowProfile()) + .build()); } @Test @CassandraRequirement(min = "2.2") public void should_reject_simple_statement_with_keyspace_in_protocol_v4() { should_reject_statement_with_keyspace_in_protocol_v4( - SimpleStatement.newInstance("SELECT * FROM foo").setKeyspace(clusterRule.keyspace())); + SimpleStatement.newInstance("SELECT * FROM foo").setKeyspace(sessionRule.keyspace())); } @Test @@ -73,7 +74,7 @@ public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1); should_reject_statement_with_keyspace_in_protocol_v4( BatchStatement.builder(BatchType.LOGGED) - .withKeyspace(clusterRule.keyspace()) + .withKeyspace(sessionRule.keyspace()) .addStatement(statementWithoutKeyspace) .build()); } @@ -84,33 +85,33 @@ public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4( SimpleStatement statementWithKeyspace = SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) - .setKeyspace(clusterRule.keyspace()); + .setKeyspace(sessionRule.keyspace()); should_reject_statement_with_keyspace_in_protocol_v4( BatchStatement.builder(BatchType.LOGGED).addStatement(statementWithKeyspace).build()); } private void should_reject_statement_with_keyspace_in_protocol_v4(Statement statement) { - try (Cluster cluster = ClusterUtils.newCluster(ccmRule, "protocol.version = V4")) { + try (CqlSession session = SessionUtils.newSession(ccmRule, "protocol.version = V4")) { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Can't use per-request keyspace with protocol V4"); - cluster.connect().execute(statement); + session.execute(statement); } } @Test @CassandraRequirement(min = "4.0") public void should_execute_simple_statement_with_keyspace() { - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); session.execute( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) - .setKeyspace(clusterRule.keyspace())); + .setKeyspace(sessionRule.keyspace())); Row row = session .execute( SimpleStatement.newInstance( "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) - .setKeyspace(clusterRule.keyspace())) + .setKeyspace(sessionRule.keyspace())) .one(); assertThat(row.getInt(0)).isEqualTo(1); } @@ -118,10 +119,10 @@ public void should_execute_simple_statement_with_keyspace() { @Test @CassandraRequirement(min = "4.0") public void should_execute_batch_with_explicit_keyspace() { - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); session.execute( BatchStatement.builder(BatchType.LOGGED) - .withKeyspace(clusterRule.keyspace()) + .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1), @@ -134,7 +135,7 @@ public void should_execute_batch_with_explicit_keyspace() { .execute( SimpleStatement.newInstance( "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) - .setKeyspace(clusterRule.keyspace())) + .setKeyspace(sessionRule.keyspace())) .one(); assertThat(row.getInt(0)).isEqualTo(1); } @@ -142,23 +143,23 @@ public void should_execute_batch_with_explicit_keyspace() { @Test @CassandraRequirement(min = "4.0") public void should_execute_batch_with_inferred_keyspace() { - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); session.execute( BatchStatement.builder(BatchType.LOGGED) - .withKeyspace(clusterRule.keyspace()) + .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) - .setKeyspace(clusterRule.keyspace()), + .setKeyspace(sessionRule.keyspace()), SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 2, 2) - .setKeyspace(clusterRule.keyspace())) + .setKeyspace(sessionRule.keyspace())) .build()); Row row = @@ -166,7 +167,7 @@ public void should_execute_batch_with_inferred_keyspace() { .execute( SimpleStatement.newInstance( "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) - .setKeyspace(clusterRule.keyspace())) + .setKeyspace(sessionRule.keyspace())) .one(); assertThat(row.getInt(0)).isEqualTo(1); } @@ -174,11 +175,11 @@ public void should_execute_batch_with_inferred_keyspace() { @Test @CassandraRequirement(min = "4.0") public void should_prepare_statement_with_keyspace() { - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); PreparedStatement prepared = session.prepare( SimpleStatement.newInstance("INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)") - .setKeyspace(clusterRule.keyspace())); + .setKeyspace(sessionRule.keyspace())); session.execute(prepared.bind(nameRule.getMethodName(), 1, 1)); Row row = @@ -186,7 +187,7 @@ public void should_prepare_statement_with_keyspace() { .execute( SimpleStatement.newInstance( "SELECT v FROM foo WHERE k = ? AND cc = 1", nameRule.getMethodName()) - .setKeyspace(clusterRule.keyspace())) + .setKeyspace(sessionRule.keyspace())) .one(); assertThat(row.getInt(0)).isEqualTo(1); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index 2ff9e431fc9..abe0e3d8700 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; @@ -49,8 +49,8 @@ public class PreparedStatementInvalidationIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); @Rule - public ClusterRule clusterRule = - new ClusterRule<>(ccmRule, "request.page-size = 2", "request.timeout = 30 seconds"); + public SessionRule sessionRule = + new SessionRule<>(ccmRule, "request.page-size = 2", "request.timeout = 30 seconds"); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -63,10 +63,10 @@ public void setupSchema() { "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (2, 2, 2)", "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (3, 3, 3)", "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (4, 4, 4)")) { - clusterRule + sessionRule .session() .execute( - SimpleStatement.builder(query).withConfigProfile(clusterRule.slowProfile()).build()); + SimpleStatement.builder(query).withConfigProfile(sessionRule.slowProfile()).build()); } } @@ -74,7 +74,7 @@ public void setupSchema() { @CassandraRequirement(min = "4.0") public void should_update_metadata_when_schema_changed_across_executions() { // Given - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); ByteBuffer idBefore = ps.getResultMetadataId(); @@ -82,7 +82,7 @@ public void should_update_metadata_when_schema_changed_across_executions() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") - .withConfigProfile(clusterRule.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); BoundStatement bs = ps.bind(1); ResultSet rows = session.execute(bs); @@ -104,7 +104,7 @@ public void should_update_metadata_when_schema_changed_across_executions() { @CassandraRequirement(min = "4.0") public void should_update_metadata_when_schema_changed_across_pages() { // Given - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_invalidation_test"); ByteBuffer idBefore = ps.getResultMetadataId(); assertThat(ps.getResultSetDefinitions()).hasSize(3); @@ -127,7 +127,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") - .withConfigProfile(clusterRule.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); // Then @@ -148,8 +148,8 @@ public void should_update_metadata_when_schema_changed_across_pages() { @CassandraRequirement(min = "4.0") public void should_update_metadata_when_schema_changed_across_sessions() { // Given - CqlSession session1 = clusterRule.session(); - CqlSession session2 = clusterRule.cluster().connect(clusterRule.keyspace()); + CqlSession session1 = sessionRule.session(); + CqlSession session2 = SessionUtils.newSession(ccmRule, sessionRule.keyspace()); PreparedStatement ps1 = session1.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); @@ -197,7 +197,7 @@ public void should_update_metadata_when_schema_changed_across_sessions() { @CassandraRequirement(min = "4.0") public void should_fail_to_reprepare_if_query_becomes_invalid() { // Given - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); session.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); PreparedStatement ps = session.prepare("SELECT a, b, c, d FROM prepared_statement_invalidation_test WHERE a = ?"); @@ -213,15 +213,19 @@ public void should_fail_to_reprepare_if_query_becomes_invalid() { @Test @CassandraRequirement(min = "4.0") public void should_not_store_metadata_for_conditional_updates() { - should_not_store_metadata_for_conditional_updates(clusterRule.session()); + should_not_store_metadata_for_conditional_updates(sessionRule.session()); } @Test @CassandraRequirement(min = "2.2") public void should_not_store_metadata_for_conditional_updates_in_legacy_protocol() { - try (Cluster cluster = - ClusterUtils.newCluster(ccmRule, "protocol.version = V4", "request.timeout = 30 seconds")) { - should_not_store_metadata_for_conditional_updates(cluster.connect(clusterRule.keyspace())); + try (CqlSession session = + SessionUtils.newSession( + ccmRule, + sessionRule.keyspace(), + "protocol.version = V4", + "request.timeout = 30 seconds")) { + should_not_store_metadata_for_conditional_updates(session); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index 89cc306790b..c416dacb3a1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -15,9 +15,10 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; @@ -33,13 +34,13 @@ public class QueryTraceIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); - @ClassRule public static ClusterRule clusterRule = new ClusterRule(ccmRule); + @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccmRule); @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void should_not_have_tracing_id_when_tracing_disabled() { ExecutionInfo executionInfo = - clusterRule + sessionRule .session() .execute("SELECT release_version FROM system.local") .getExecutionInfo(); @@ -70,7 +71,7 @@ protected boolean matchesSafely(Throwable item) { @Test public void should_fetch_trace_when_tracing_enabled() { ExecutionInfo executionInfo = - clusterRule + sessionRule .session() .execute( SimpleStatement.builder("SELECT release_version FROM system.local") diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 60604cd870a..6259e7f58b5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -15,9 +15,10 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; @@ -35,7 +36,8 @@ public class SimpleStatementIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm, "request.page-size = 20"); + @ClassRule + public static SessionRule cluster = new SessionRule<>(ccm, "request.page-size = 20"); @Rule public TestName name = new TestName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 5c03f910f95..04914c48e04 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -35,7 +36,7 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.DefaultListType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; @@ -83,7 +84,7 @@ public class DataTypeIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); @Rule public TestName name = new TestName(); @@ -234,7 +235,7 @@ public static Object[][] typeSamples() { UserDefinedType udt = new DefaultUserDefinedType( - cluster.keyspace(), + sessionRule.keyspace(), CqlIdentifier.fromCql(userTypeFor(types)), false, typeNames, @@ -270,14 +271,14 @@ public static void createTable() { } } - cluster + sessionRule .session() .execute( SimpleStatement.builder( String.format( "CREATE TABLE IF NOT EXISTS %s (k int primary key, %s)", tableName, String.join(",", columnData))) - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); } @@ -293,7 +294,7 @@ private static int nextKey() { @Test public void should_insert_non_primary_key_column_simple_statement_using_format( DataType dataType, K value, K expectedPrimitiveValue) { - TypeCodec codec = cluster.cluster().getContext().codecRegistry().codecFor(dataType); + TypeCodec codec = sessionRule.session().getContext().codecRegistry().codecFor(dataType); int key = nextKey(); String columnName = columnNameFor(dataType); @@ -306,7 +307,7 @@ public void should_insert_non_primary_key_column_simple_statement_using_form .addPositionalValue(key) .build(); - cluster.session().execute(insert); + sessionRule.session().execute(insert); SimpleStatement select = SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) @@ -332,7 +333,7 @@ public void should_insert_non_primary_key_column_simple_statement_positional .addPositionalValues(key, value) .build(); - cluster.session().execute(insert); + sessionRule.session().execute(insert); SimpleStatement select = SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) @@ -359,7 +360,7 @@ public void should_insert_non_primary_key_column_simple_statement_named_valu .addNamedValue("v", value) .build(); - cluster.session().execute(insert); + sessionRule.session().execute(insert); SimpleStatement select = SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) @@ -381,18 +382,18 @@ public void should_insert_non_primary_key_column_bound_statement_positional_ String.format("INSERT INTO %s (k, %s) values (?, ?)", tableName, columnName)) .build(); - PreparedStatement preparedInsert = cluster.session().prepare(insert); + PreparedStatement preparedInsert = sessionRule.session().prepare(insert); BoundStatementBuilder boundBuilder = preparedInsert.boundStatementBuilder(); boundBuilder = setValue(0, boundBuilder, DataTypes.INT, key); boundBuilder = setValue(1, boundBuilder, dataType, value); BoundStatement boundInsert = boundBuilder.build(); - cluster.session().execute(boundInsert); + sessionRule.session().execute(boundInsert); SimpleStatement select = SimpleStatement.builder(String.format("SELECT %s FROM %s where k=?", columnName, tableName)) .build(); - PreparedStatement preparedSelect = cluster.session().prepare(select); + PreparedStatement preparedSelect = sessionRule.session().prepare(select); BoundStatement boundSelect = setValue(0, preparedSelect.bind(), DataTypes.INT, key); readValue(boundSelect, dataType, value, expectedPrimitiveValue); @@ -410,19 +411,19 @@ public void should_insert_non_primary_key_column_bound_statement_named_value String.format("INSERT INTO %s (k, %s) values (:k, :v)", tableName, columnName)) .build(); - PreparedStatement preparedInsert = cluster.session().prepare(insert); + PreparedStatement preparedInsert = sessionRule.session().prepare(insert); BoundStatementBuilder boundBuilder = preparedInsert.boundStatementBuilder(); boundBuilder = setValue("k", boundBuilder, DataTypes.INT, key); boundBuilder = setValue("v", boundBuilder, dataType, value); BoundStatement boundInsert = boundBuilder.build(); - cluster.session().execute(boundInsert); + sessionRule.session().execute(boundInsert); SimpleStatement select = SimpleStatement.builder( String.format("SELECT %s FROM %s where k=:k", columnName, tableName)) .build(); - PreparedStatement preparedSelect = cluster.session().prepare(select); + PreparedStatement preparedSelect = sessionRule.session().prepare(select); BoundStatement boundSelect = setValue("k", preparedSelect.bind(), DataTypes.INT, key); boundSelect.setInt("k", key); @@ -432,8 +433,8 @@ public void should_insert_non_primary_key_column_bound_statement_named_value private static > S setValue( int index, S bs, DataType dataType, Object value) { TypeCodec codec = - cluster.cluster() != null - ? cluster.cluster().getContext().codecRegistry().codecFor(dataType) + sessionRule.session() != null + ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) : null; // set to null if value is null instead of getting possible NPE when casting from null to primitive. @@ -522,8 +523,8 @@ private static > S setValue( private static > S setValue( String name, S bs, DataType dataType, Object value) { TypeCodec codec = - cluster.cluster() != null - ? cluster.cluster().getContext().codecRegistry().codecFor(dataType) + sessionRule.session() != null + ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) : null; // set to null if value is null instead of getting possible NPE when casting from null to primitive. @@ -611,8 +612,8 @@ private static > S setValue( private void readValue( Statement select, DataType dataType, K value, K expectedPrimitiveValue) { - TypeCodec codec = cluster.cluster().getContext().codecRegistry().codecFor(dataType); - ResultSet result = cluster.session().execute(select); + TypeCodec codec = sessionRule.session().getContext().codecRegistry().codecFor(dataType); + ResultSet result = sessionRule.session().execute(select); String columnName = columnNameFor(dataType); @@ -717,7 +718,7 @@ private void readValue( for (int i = 0; i < exValue.getType().getComponentTypes().size(); i++) { DataType compType = exValue.getType().getComponentTypes().get(i); TypeCodec typeCodec = - cluster.cluster().getContext().codecRegistry().codecFor(compType); + sessionRule.session().getContext().codecRegistry().codecFor(compType); assertThat(returnedValue.get(i, typeCodec)).isEqualTo(exValue.get(i, typeCodec)); } @@ -736,7 +737,7 @@ private void readValue( String compName = exUdtValue.getType().getFieldNames().get(i).asCql(false); TypeCodec typeCodec = - cluster.cluster().getContext().codecRegistry().codecFor(compType); + sessionRule.session().getContext().codecRegistry().codecFor(compType); assertThat(returnedUdtValue.get(compName, typeCodec)) .isEqualTo(exUdtValue.get(compName, typeCodec)); @@ -753,7 +754,7 @@ private void readValue( } // Decode directly using the codec - ProtocolVersion protocolVersion = cluster.cluster().getContext().protocolVersion(); + ProtocolVersion protocolVersion = sessionRule.session().getContext().protocolVersion(); assertThat(codec.decode(row.getBytesUnsafe(columnName), protocolVersion)).isEqualTo(value); assertThat(codec.decode(row.getBytesUnsafe(0), protocolVersion)).isEqualTo(value); } @@ -792,14 +793,14 @@ private static String typeFor(DataType dataType) { fieldParts.add(fieldName + " " + fieldType); } - cluster + sessionRule .session() .execute( SimpleStatement.builder( String.format( "CREATE TYPE IF NOT EXISTS %s (%s)", udt.getName().asCql(false), String.join(",", fieldParts))) - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); typeName = "frozen<" + udt.getName().asCql(false) + ">"; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java index 657312d222a..f8568cffda5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java @@ -15,24 +15,16 @@ */ package com.datastax.oss.driver.api.core.heartbeat; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; -import com.datastax.oss.simulacron.server.BoundNode; -import java.net.SocketAddress; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Before; import org.junit.ClassRule; -import org.junit.Rule; import org.junit.Test; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; /** This test is separate from {@link HeartbeatIT} because it can't be parallelized. */ public class HeartbeatDisabledIT { @@ -40,46 +32,11 @@ public class HeartbeatDisabledIT { @ClassRule public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); - @Rule - public ClusterRule cluster = - ClusterRule.builder(simulacron) - .withDefaultSession(false) - .withOptions( - "connection.heartbeat.interval = 1 second", - "connection.heartbeat.timeout = 500 milliseconds", - "connection.init-query-timeout = 2 seconds", - "connection.reconnection-policy.max-delay = 1 second") - .build(); - - private SocketAddress controlConnection; - - @Before - public void setUp() { - simulacron.cluster().clearLogs(); - simulacron.cluster().clearPrimes(true); - - Optional controlNodeOption = - simulacron - .cluster() - .getNodes() - .stream() - .filter(n -> n.getActiveConnections() == 1) - .findFirst(); - - if (controlNodeOption.isPresent()) { - BoundNode controlNode = controlNodeOption.get(); - controlConnection = controlNode.getConnections().getConnections().get(0); - } else { - fail("Control node not found"); - } - } - @Test public void should_not_send_heartbeat_when_disabled() throws InterruptedException { // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any - try (Cluster cluster = - ClusterUtils.newCluster(simulacron, "connection.heartbeat.interval = 0 second")) { - cluster.connect(); + try (CqlSession session = + SessionUtils.newSession(simulacron, "connection.heartbeat.interval = 0 second")) { AtomicInteger heartbeats = registerHeartbeatListener(); SECONDS.sleep(35); @@ -94,7 +51,7 @@ private AtomicInteger registerHeartbeatListener() { .registerQueryListener( (n, l) -> nonControlHeartbeats.incrementAndGet(), false, - (l) -> l.getQuery().equals("OPTIONS") && !l.getConnection().equals(controlConnection)); + (l) -> l.getQuery().equals("OPTIONS")); return nonControlHeartbeats; } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index 29e73efa7a5..c2b252245e9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -15,12 +15,13 @@ */ package com.datastax.oss.driver.api.core.heartbeat; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.protocol.internal.request.Register; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import com.datastax.oss.simulacron.common.request.Options; @@ -30,8 +31,8 @@ import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.RejectScope; import java.net.SocketAddress; +import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -48,174 +49,98 @@ import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.fail; @Category(ParallelizableTests.class) public class HeartbeatIT { - @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); - @SuppressWarnings("unchecked") - @Rule - public ClusterRule cluster = - ClusterRule.builder(simulacron) - .withDefaultSession(false) - .withOptions( - "connection.heartbeat.interval = 1 second", - "connection.heartbeat.timeout = 500 milliseconds", - "connection.init-query-timeout = 2 seconds", - "connection.reconnection-policy.max-delay = 1 second") - .build(); + private static final String QUERY = "select * from foo"; + private static final Predicate IS_OPTION_REQUEST = + (l) -> l.getQuery().equals("OPTIONS"); - private static final String queryStr = "select * from foo"; - - private BoundNode nonControlNode; - private BoundNode controlNode; - private SocketAddress controlConnection; + private BoundNode simulacronNode; @Before public void setUp() { simulacron.cluster().clearLogs(); simulacron.cluster().clearPrimes(true); - Optional nonControlNodeOption = - simulacron - .cluster() - .getNodes() - .stream() - .filter(n -> n.getActiveConnections() == 0) - .findFirst(); - - if (nonControlNodeOption.isPresent()) { - nonControlNode = nonControlNodeOption.get(); - } else { - fail("Non-control node not found"); - } - - Optional controlNodeOption = - simulacron - .cluster() - .getNodes() - .stream() - .filter(n -> n.getActiveConnections() == 1) - .findFirst(); - - if (controlNodeOption.isPresent()) { - controlNode = controlNodeOption.get(); - controlConnection = controlNode.getConnections().getConnections().get(0); - } else { - fail("Control node not found"); - } + simulacronNode = simulacron.cluster().getNodes().iterator().next(); } @Test - public void node_should_go_down_gracefully_when_connection_closed_during_heartbeat() - throws InterruptedException { - // Create session to initialize pools. - cluster.cluster().connect(); - - // Node should be up. - Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); - assertThat(node.getState()).isEqualTo(NodeState.UP); + public void node_should_go_down_gracefully_when_connection_closed_during_heartbeat() { + try (CqlSession session = newSession()) { - // Stop listening for new connections (so it can't reconnect) - nonControlNode.rejectConnections(0, RejectScope.UNBIND); + Node node = session.getMetadata().getNodes().values().iterator().next(); + assertThat(node.getState()).isEqualTo(NodeState.UP); - int heartbeatCount = getHeartbeatsForNode(nonControlNode).size(); - // When node receives a heartbeat, close the connection. - nonControlNode.prime( - when(Options.INSTANCE) - .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); + // Stop listening for new connections (so it can't reconnect) + simulacronNode.rejectConnections(0, RejectScope.UNBIND); - // Wait for heartbeat and for node to subsequently close it's connection. - waitForDown(node); + int heartbeatCount = getHeartbeatsForNode().size(); + // When node receives a heartbeat, close the connection. + simulacronNode.prime( + when(Options.INSTANCE) + .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); - // Should have been a heartbeat received since that's what caused the disconnect. - assertThat(getHeartbeatsForNode(nonControlNode).size()).isGreaterThan(heartbeatCount); + // Wait for heartbeat and for node to subsequently close its connection. + waitForDown(node); - nonControlNode.acceptConnections(); + // Should have been a heartbeat received since that's what caused the disconnect. + assertThat(getHeartbeatsForNode().size()).isGreaterThan(heartbeatCount); + } } - private static final Predicate optionsRequest = (l) -> l.getQuery().equals("OPTIONS"); - @Test - public void should_not_send_heartbeat_during_protocol_initialization() - throws InterruptedException { + public void should_not_send_heartbeat_during_protocol_initialization() { // Configure node to reject startup. - nonControlNode.rejectConnections(0, RejectScope.REJECT_STARTUP); - Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); - - // Create session to initialize pools. - cluster.cluster().connect(); - - // wait for node to go down as result of startup failing. - waitForDown(node); - - // no heartbeats should have been sent while protocol was initializing. - assertThat(getHeartbeatsForNode(nonControlNode)).isEmpty(); - - // node should be down since there were no successful connections. - assertThat(node.getState()).isEqualTo(NodeState.DOWN); - - // start accepting connections again. - nonControlNode.acceptConnections(); - - // listen for heartbeats on node. - AtomicInteger heartbeats = new AtomicInteger(); - nonControlNode.registerQueryListener( - (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); + simulacronNode.rejectConnections(0, RejectScope.REJECT_STARTUP); + + // Try to create a session. Note that the init query timeout is twice the heartbeat interval, so + // we're sure that at least one heartbeat would be sent if it was not properly disabled during + // init. + try (CqlSession session = newSession()) { + fail("Expected session creation to fail"); + } catch (Exception expected) { + // no heartbeats should have been sent while protocol was initializing. + assertThat(getHeartbeatsForNode()).isEmpty(); + } + } - // wait a heartbeat to be sent (indicating node is up and sending heartbeats) - checkThat(() -> heartbeats.get() > 0).every(100).becomesTrue(); - assertThat(getHeartbeatsForNode(nonControlNode)).isNotEmpty(); - assertThat(node.getState()).isEqualTo(NodeState.UP); + @Test + public void should_send_heartbeat_on_control_connection() { + + try (CqlSession session = + newSession( + // Ensure we only have the control connection + "connection.pool.local.size = 0")) { + AtomicInteger heartbeats = countHeartbeatsOnControlConnection(); + checkThat(() -> heartbeats.get() > 0).becomesTrue(); + } } @Test - public void should_send_heartbeat_on_interval() throws InterruptedException { + public void should_send_heartbeat_on_regular_connection() throws InterruptedException { // Prime a simple query so we get at least some results - simulacron - .cluster() - .prime(when(queryStr).then(PrimeDsl.rows().row("column1", "1", "column2", "2"))); - - // listen for heartbeats on node. - AtomicInteger controlNodeHeartbeats = new AtomicInteger(); - controlNode.registerQueryListener( - (n, l) -> controlNodeHeartbeats.incrementAndGet(), true, optionsRequest); - - // Ensure heartbeats are received on control node, even when no sessions are present. - checkThat(() -> controlNodeHeartbeats.get() > 0).becomesTrue(); - - // Create session to initialize pools. - CqlSession session = cluster.cluster().connect(); - - // Ensure we get a heartbeat after a second. - AtomicInteger heartbeats = new AtomicInteger(); - nonControlNode.registerQueryListener( - (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); - - checkThat(() -> heartbeats.get() > 0).becomesTrue(); - - // count all options received not from control connection. - AtomicInteger nonControlHeartbeats = null; - - // Make a bunch of queries over two seconds. This should preempt any heartbeats. - for (int i = 0; i < 20; i++) { - assertThat(session.execute(queryStr)).hasSize(1); - assertThat(session.execute(queryStr)).hasSize(1); - - // after first write, start counting number of heartbeats. - if (i == 0) { - nonControlHeartbeats = registerHeartbeatListener(); + simulacronNode.prime(when(QUERY).then(PrimeDsl.rows().row("column1", "1", "column2", "2"))); + + try (CqlSession session = newSession()) { + // Make a bunch of queries over two seconds. This should preempt any heartbeats. + assertThat(session.execute(QUERY)).hasSize(1); + final AtomicInteger nonControlHeartbeats = countHeartbeatsOnRegularConnection(); + for (int i = 0; i < 20; i++) { + assertThat(session.execute(QUERY)).hasSize(1); + MILLISECONDS.sleep(100); } - MILLISECONDS.sleep(100); - } - // No heartbeats should be sent, except those on the control connection. - assertThat(nonControlHeartbeats.get()).isZero(); + // No heartbeats should be sent, except those on the control connection. + assertThat(nonControlHeartbeats.get()).isZero(); - // Wait for 2 more heartbeats to be sent (one on each node). - AtomicInteger fNonControlHeartbeats = nonControlHeartbeats; - checkThat(() -> fNonControlHeartbeats.get() >= 2).becomesTrue(); + // Stop querying, heartbeats should be sent again + checkThat(() -> nonControlHeartbeats.get() >= 1).becomesTrue(); + } } @Test @@ -225,76 +150,105 @@ public void should_send_heartbeat_when_requests_being_written_but_nothing_receiv String noResponseQueryStr = "delay"; simulacron.cluster().prime(when(noResponseQueryStr).then(noResult())); - AtomicInteger heartbeats = registerHeartbeatListener(); + try (CqlSession session = newSession()) { + AtomicInteger heartbeats = countHeartbeatsOnRegularConnection(); - // Send requests over 2.5 seconds. - CqlSession session = cluster.cluster().connect(); - for (int i = 0; i < 25; i++) { - session.executeAsync(noResponseQueryStr); - session.executeAsync(noResponseQueryStr); - MILLISECONDS.sleep(100); - } + for (int i = 0; i < 25; i++) { + session.executeAsync(noResponseQueryStr); + session.executeAsync(noResponseQueryStr); + MILLISECONDS.sleep(100); + } - // We should expect at least 4 heartbeats (2 from each node's connection) - assertThat(heartbeats.get()).isGreaterThanOrEqualTo(4); + // We should expect at least 2 heartbeats + assertThat(heartbeats.get()).isGreaterThanOrEqualTo(2); + } } @Test public void should_close_connection_when_heartbeat_times_out() { - cluster.cluster().connect(); + try (CqlSession session = newSession()) { + Node node = session.getMetadata().getNodes().values().iterator().next(); + assertThat(node.getState()).isEqualTo(NodeState.UP); - Node node = cluster.cluster().getMetadata().getNodes().get(nonControlNode.inetSocketAddress()); + // Ensure we get some heartbeats and the node remains up. + AtomicInteger heartbeats = new AtomicInteger(); + simulacronNode.registerQueryListener( + (n, l) -> heartbeats.incrementAndGet(), true, IS_OPTION_REQUEST); - // Node should be up. - assertThat(node.getState()).isEqualTo(NodeState.UP); + checkThat(() -> heartbeats.get() >= 2).becomesTrue(); + assertThat(node.getState()).isEqualTo(NodeState.UP); - // Ensure we get some heartbeats and the node remains up. - AtomicInteger heartbeats = new AtomicInteger(); - nonControlNode.registerQueryListener( - (n, l) -> heartbeats.incrementAndGet(), true, optionsRequest); + // configure node to not respond to options request, which should cause a timeout. + simulacronNode.prime(when(Options.INSTANCE).then(noResult())); + heartbeats.set(0); - checkThat(() -> heartbeats.get() >= 2).becomesTrue(); + // wait for heartbeat to be sent. + checkThat(() -> heartbeats.get() >= 1).becomesTrue(); + heartbeats.set(0); - // configure node to not respond to options request, which should cause a timeout. - nonControlNode.prime(when(Options.INSTANCE).then(noResult())); - heartbeats.set(0); + // node should go down because heartbeat was unanswered. + waitForDown(node); - // wait for heartbeat to be sent. - checkThat(() -> heartbeats.get() == 1).becomesTrue(); - heartbeats.set(0); + // clear prime so now responds to options request again. + simulacronNode.clearPrimes(); - // node should go down because heartbeat was not set. - waitForDown(node); + // wait for node to come up again and ensure heartbeats are successful and node remains up. + waitForUp(node); - // clear prime so now responds to options request again. - nonControlNode.clearPrimes(); + checkThat(() -> heartbeats.get() >= 2).becomesTrue(); + assertThat(node.getState()).isEqualTo(NodeState.UP); + } + } + + private CqlSession newSession(String... extraOptions) { + String[] defaultOptions = + new String[] { + "connection.heartbeat.interval = 1 second", + "connection.heartbeat.timeout = 500 milliseconds", + "connection.init-query-timeout = 2 seconds", + "connection.reconnection-policy.max-delay = 1 second" + }; + String[] allOptions = + Arrays.copyOf(defaultOptions, defaultOptions.length + extraOptions.length); + System.arraycopy(extraOptions, 0, allOptions, defaultOptions.length, extraOptions.length); + return SessionUtils.newSession(simulacron, allOptions); + } - // wait for node to come up again and ensure heartbeats are successful and node remains up. - waitForUp(node); + private AtomicInteger countHeartbeatsOnRegularConnection() { + return countHeartbeats(true); + } - checkThat(() -> heartbeats.get() >= 2).becomesTrue(); - assertThat(node.getState()).isEqualTo(NodeState.UP); + private AtomicInteger countHeartbeatsOnControlConnection() { + return countHeartbeats(false); } - /** - * Registers a query listener that increments the returned {@link AtomicInteger} whenever a - * heartbeat is received on the non-control connection. - * - * @return integer that represents current count of heartbeats. - */ - private AtomicInteger registerHeartbeatListener() { - AtomicInteger nonControlHeartbeats = new AtomicInteger(); + private AtomicInteger countHeartbeats(boolean regularConnection) { + SocketAddress controlConnectionAddress = findControlConnectionAddress(); + AtomicInteger count = new AtomicInteger(); simulacron .cluster() .registerQueryListener( - (n, l) -> nonControlHeartbeats.incrementAndGet(), + (n, l) -> count.incrementAndGet(), false, - (l) -> optionsRequest.test(l) && !l.getConnection().equals(controlConnection)); - return nonControlHeartbeats; + (l) -> + IS_OPTION_REQUEST.test(l) + && regularConnection ^ l.getConnection().equals(controlConnectionAddress)); + return count; + } + + private SocketAddress findControlConnectionAddress() { + List logs = simulacronNode.getLogs().getQueryLogs(); + for (QueryLog log : logs) { + if (log.getFrame().message instanceof Register) { + return log.getConnection(); + } + } + throw new AssertionError("Could not find address of control connection"); } - private List getHeartbeatsForNode(BoundNode node) { - return node.getLogs() + private List getHeartbeatsForNode() { + return simulacronNode + .getLogs() .getQueryLogs() .stream() .filter(l -> l.getQuery().equals("OPTIONS")) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 549fc0ad68c..934819feac0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; @@ -52,16 +52,15 @@ public class DefaultLoadBalancingPolicyIT { @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(4, 1).build(); @ClassRule - public static ClusterRule clusterRule = - ClusterRule.builder(ccmRule) + public static SessionRule sessionRule = + SessionRule.builder(ccmRule) .withKeyspace(false) - .withDefaultSession(true) .withOptions("request.timeout = 30 seconds") .build(); @BeforeClass public static void setup() { - CqlSession session = clusterRule.session(); + CqlSession session = sessionRule.session(); session.execute( "CREATE KEYSPACE test " + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 2, 'dc2': 1}"); @@ -70,7 +69,7 @@ public static void setup() { @Test public void should_ignore_remote_dcs() { - for (Node node : clusterRule.cluster().getMetadata().getNodes().values()) { + for (Node node : sessionRule.session().getMetadata().getNodes().values()) { if (LOCAL_DC.equals(node.getDatacenter())) { assertThat(node.getDistance()).isEqualTo(NodeDistance.LOCAL); assertThat(node.getState()).isEqualTo(NodeState.UP); @@ -88,7 +87,7 @@ public void should_ignore_remote_dcs() { @Test public void should_use_round_robin_on_local_dc_when_not_enough_routing_information() { ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); - TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = sessionRule.session().getMetadata().getTokenMap().get(); // TODO add statements with setKeyspace when that is supported List statements = ImmutableList.of( @@ -107,7 +106,7 @@ public void should_use_round_robin_on_local_dc_when_not_enough_routing_informati for (Statement statement : statements) { List coordinators = new ArrayList<>(); for (int i = 0; i < 12; i++) { - ResultSet rs = clusterRule.session().execute(statement); + ResultSet rs = sessionRule.session().execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); coordinators.add(coordinator); @@ -124,7 +123,7 @@ public void should_use_round_robin_on_local_dc_when_not_enough_routing_informati public void should_prioritize_replicas_when_routing_information_present() { CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); - TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = sessionRule.session().getMetadata().getTokenMap().get(); Set localReplicas = new HashSet<>(); for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { if (replica.getDatacenter().equals(LOCAL_DC)) { @@ -148,7 +147,7 @@ public void should_prioritize_replicas_when_routing_information_present() { // reasonable distribution: Map hits = new HashMap<>(); for (int i = 0; i < 2000; i++) { - ResultSet rs = clusterRule.session().execute(statement); + ResultSet rs = sessionRule.session().execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); assertThat(localReplicas).contains(coordinator); assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); @@ -165,9 +164,9 @@ public void should_prioritize_replicas_when_routing_information_present() { public void should_hit_non_replicas_when_routing_information_present_but_all_replicas_down() { CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); ByteBuffer routingKey = TypeCodecs.INT.encodePrimitive(1, ProtocolVersion.DEFAULT); - TokenMap tokenMap = clusterRule.cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = sessionRule.session().getMetadata().getTokenMap().get(); - InternalDriverContext context = (InternalDriverContext) clusterRule.cluster().getContext(); + InternalDriverContext context = (InternalDriverContext) sessionRule.session().getContext(); Set localReplicas = new HashSet<>(); for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { @@ -193,7 +192,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep for (Statement statement : statements) { List coordinators = new ArrayList<>(); for (int i = 0; i < 6; i++) { - ResultSet rs = clusterRule.session().execute(statement); + ResultSet rs = sessionRule.session().execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); coordinators.add(coordinator); assertThat(coordinator.getDatacenter()).isEqualTo(LOCAL_DC); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index 8f502c93654..2836de0373f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,26 +29,20 @@ public class ByteOrderedTokenIT extends TokenITBase { CustomCcmRule.builder().withNodes(3).withCreateOption("-p ByteOrderedPartitioner").build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public ByteOrderedTokenIT() { super(ByteOrderedToken.class, false); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index b5c65b5ef95..ec2cf268ae5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -34,26 +33,20 @@ public class ByteOrderedTokenVnodesIT extends TokenITBase { .build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public ByteOrderedTokenVnodesIT() { super(ByteOrderedToken.class, true); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 498b28216ee..5b60ba56509 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -16,13 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.io.ByteStreams; import com.google.common.io.Closer; @@ -47,11 +46,10 @@ public class DescribeIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule<>( + public static SessionRule sessionRule = + new SessionRule<>( ccmRule, false, - true, new NodeStateListener[0], "request.timeout = 30 seconds", "metadata.schema.debouncer.window = 0 seconds"); // disable debouncer to speed up test. @@ -71,23 +69,23 @@ public class DescribeIT { */ @Test public void create_schema_and_ensure_exported_cql_is_as_expected() { - CqlIdentifier keyspace = ClusterUtils.uniqueKeyspaceId(); + CqlIdentifier keyspace = SessionUtils.uniqueKeyspaceId(); String keyspaceAsCql = keyspace.asCql(true); String expectedCql = getExpectedCqlString(keyspaceAsCql); + CqlSession session = sessionRule.session(); + // create keyspace - clusterRule - .session() - .execute( - String.format( - "CREATE KEYSPACE %s " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", - keyspace)); + session.execute( + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + keyspace)); - // create session from this keyspace. - CqlSession session = clusterRule.cluster().connect(keyspace); + // connect session to this keyspace. + session.execute(String.format("USE %s", keyspace.asCql(false))); - KeyspaceMetadata originalKsMeta = clusterRule.cluster().getMetadata().getKeyspace(keyspace); + KeyspaceMetadata originalKsMeta = session.getMetadata().getKeyspace(keyspace); // Usertype 'ztype' with two columns. Given name to ensure that even though it has an // alphabetically later name, it shows up before other user types ('ctype') that depend on it. @@ -196,13 +194,13 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { assertThat(originalKsMeta.getUserDefinedTypes()).isEmpty(); // validate that the exported schema matches what was expected exactly. - KeyspaceMetadata ks = clusterRule.cluster().getMetadata().getKeyspace(keyspace); + KeyspaceMetadata ks = sessionRule.session().getMetadata().getKeyspace(keyspace); assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); - // Also validate that when you create a Cluster with schema already created that the exported + // Also validate that when you create a Session with schema already created that the exported // string is the same. - try (Cluster newCluster = ClusterUtils.newCluster(ccmRule)) { - ks = newCluster.getMetadata().getKeyspace(keyspace); + try (CqlSession newSession = SessionUtils.newSession(ccmRule)) { + ks = newSession.getMetadata().getKeyspace(keyspace); assertThat(ks.describeWithChildren(true).trim()).isEqualTo(expectedCql); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index bdd38aa29c8..7418211a040 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -28,26 +27,20 @@ public class Murmur3TokenIT extends TokenITBase { @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public Murmur3TokenIT() { super(Murmur3Token.class, false); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index c5f8ed332b7..55d72f9aaf9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,26 +29,20 @@ public class Murmur3TokenVnodesIT extends TokenITBase { CustomCcmRule.builder().withNodes(3).withCreateOption("--vnodes").build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public Murmur3TokenVnodesIT() { super(Murmur3Token.class, true); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 47587ce0bd4..86b009e210e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -15,12 +15,11 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; @@ -67,8 +66,8 @@ public class NodeStateIT { private NodeStateListener nodeStateListener = Mockito.mock(NodeStateListener.class); private InOrder inOrder; - public @Rule ClusterRule cluster = - ClusterRule.builder(simulacron) + public @Rule SessionRule sessionRule = + SessionRule.builder(simulacron) .withOptions( "connection.pool.local.size = 2", "connection.reconnection-policy.max-delay = 1 second", @@ -90,7 +89,7 @@ public class NodeStateIT { public void setup() { inOrder = Mockito.inOrder(nodeStateListener); - driverContext = (InternalDriverContext) cluster.cluster().getContext(); + driverContext = (InternalDriverContext) sessionRule.session().getContext(); driverContext.eventBus().register(NodeStateEvent.class, stateEvents::add); // Sanity check: the driver should have connected to simulacron @@ -115,13 +114,13 @@ public void setup() { assertThat(simulacronControlNode).isNotNull(); assertThat(simulacronRegularNode).isNotNull(); - Map nodesMetadata = cluster.cluster().getMetadata().getNodes(); + Map nodesMetadata = sessionRule.session().getMetadata().getNodes(); metadataControlNode = (DefaultNode) nodesMetadata.get(simulacronControlNode.inetSocketAddress()); metadataRegularNode = (DefaultNode) nodesMetadata.get(simulacronRegularNode.inetSocketAddress()); - // ClusterRule uses all nodes as contact points, so we only get onUp notifications for them (no + // SessionRule uses all nodes as contact points, so we only get onUp notifications for them (no // onAdd) inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataControlNode); inOrder.verify(nodeStateListener, timeout(500)).onUp(metadataRegularNode); @@ -290,22 +289,20 @@ public void should_ignore_down_topology_event_when_still_connected() throws Inte public void should_force_immediate_reconnection_when_up_topology_event() throws InterruptedException { // This test requires a longer reconnection interval, so create a separate driver instance - try (Cluster localCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( simulacron, "connection.reconnection-policy.base-delay = 1 hour", "connection.reconnection-policy.max-delay = 1 hour")) { - localCluster.connect(); - NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - localCluster.register(localNodeStateListener); + session.register(localNodeStateListener); BoundNode localSimulacronNode = simulacron.cluster().getNodes().iterator().next(); assertThat(localSimulacronNode).isNotNull(); DefaultNode localMetadataNode = (DefaultNode) - localCluster.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); + session.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); localSimulacronNode.stop(); @@ -319,7 +316,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, localMetadataNode)); localSimulacronNode.acceptConnections(); - ((InternalDriverContext) localCluster.getContext()) + ((InternalDriverContext) session.getContext()) .eventBus() .fire(TopologyEvent.suggestUp(localMetadataNode.getConnectAddress())); @@ -419,23 +416,25 @@ public void should_force_down_when_ignored() throws InterruptedException { @Test public void should_signal_non_contact_points_as_added() { - // Since we need to observe the behavior of non-contact points, build a dedicated cluster with + // Since we need to observe the behavior of non-contact points, build a dedicated session with // just one contact point. Iterator contactPoints = simulacron.getContactPoints().iterator(); InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - try (Cluster localCluster = - Cluster.builder() + try (CqlSession localSession = + CqlSession.builder() .addContactPoint(address1) .addNodeStateListeners(localNodeStateListener) .withConfigLoader( new TestConfigLoader( "connection.reconnection-policy.base-delay = 1 hour", - "connection.reconnection-policy.max-delay = 1 hour")) + "connection.reconnection-policy.max-delay = 1 hour", + // Ensure we only have the control connection + "connection.pool.local.size = 0")) .build()) { - Map nodes = localCluster.getMetadata().getNodes(); + Map nodes = localSession.getMetadata().getNodes(); Node localMetadataNode1 = nodes.get(address1); Node localMetadataNode2 = nodes.get(address2); @@ -443,11 +442,6 @@ public void should_signal_non_contact_points_as_added() { Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); // Non-contact point only added since we don't have a connection or events for it yet Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); - - localCluster.connect(); - - // Non-contact point now has a connection opened => up - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode2); } } @@ -461,8 +455,8 @@ public void should_remove_invalid_contact_point() { // Initialize the driver with 1 wrong address and 1 valid address InetSocketAddress wrongContactPoint = withUnusedPort(address1); - try (Cluster localCluster = - Cluster.builder() + try (CqlSession localSession = + CqlSession.builder() .addContactPoint(address1) .addContactPoint(wrongContactPoint) .addNodeStateListeners(localNodeStateListener) @@ -472,7 +466,7 @@ public void should_remove_invalid_contact_point() { "connection.reconnection-policy.max-delay = 1 hour")) .build()) { - Map nodes = localCluster.getMetadata().getNodes(); + Map nodes = localSession.getMetadata().getNodes(); assertThat(nodes).doesNotContainKey(wrongContactPoint); Node localMetadataNode1 = nodes.get(address1); Node localMetadataNode2 = nodes.get(address2); @@ -508,8 +502,8 @@ public void should_mark_unreachable_contact_point_down() { // Since contact points are shuffled, we have a 50% chance that our bad contact point will be // hit first. So we retry the scenario a few times if needed. for (int i = 0; i < 10; i++) { - try (Cluster localCluster = - Cluster.builder() + try (CqlSession localSession = + CqlSession.builder() .addContactPoint(address1) .addContactPoint(address2) .addNodeStateListeners(localNodeStateListener) @@ -519,9 +513,9 @@ public void should_mark_unreachable_contact_point_down() { "connection.reconnection-policy.max-delay = 1 hour")) .build()) { - Mockito.verify(localNodeStateListener).onRegister(localCluster); + Mockito.verify(localNodeStateListener).onRegister(localSession); - Map nodes = localCluster.getMetadata().getNodes(); + Map nodes = localSession.getMetadata().getNodes(); Node localMetadataNode1 = nodes.get(address1); Node localMetadataNode2 = nodes.get(address2); if (localMetadataNode2.getState() == NodeState.DOWN) { @@ -548,27 +542,27 @@ public void should_mark_unreachable_contact_point_down() { @Test public void should_call_onRegister_and_onUnregister_implicitly() { NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - Cluster localClusterRef; + CqlSession localSessionRef; // onRegister should be called implicitly when added as a listener on builder. - try (Cluster localCluster = - Cluster.builder() + try (CqlSession localSession = + CqlSession.builder() .addContactPoints(simulacron.getContactPoints()) .addNodeStateListeners(localNodeStateListener) .build()) { - Mockito.verify(localNodeStateListener).onRegister(localCluster); - localClusterRef = localCluster; + Mockito.verify(localNodeStateListener).onRegister(localSession); + localSessionRef = localSession; } // onUnregister should be called implicitly when cluster is closed. - Mockito.verify(localNodeStateListener).onUnregister(localClusterRef); + Mockito.verify(localNodeStateListener).onUnregister(localSessionRef); } @Test public void should_call_onRegister_and_onUnregister_when_used() { NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); - cluster.cluster().register(localNodeStateListener); - Mockito.verify(localNodeStateListener, timeout(1000)).onRegister(cluster.cluster()); - cluster.cluster().unregister(localNodeStateListener); - Mockito.verify(localNodeStateListener, timeout(1000)).onUnregister(cluster.cluster()); + sessionRule.session().register(localNodeStateListener); + Mockito.verify(localNodeStateListener, timeout(1000)).onRegister(sessionRule.session()); + sessionRule.session().unregister(localNodeStateListener); + Mockito.verify(localNodeStateListener, timeout(1000)).onUnregister(sessionRule.session()); } private void expect(NodeStateEvent... expectedEvents) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index b071e049265..9dd964c07ea 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,26 +29,20 @@ public class RandomTokenIT extends TokenITBase { CustomCcmRule.builder().withNodes(3).withCreateOption("-p RandomPartitioner").build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public RandomTokenIT() { super(RandomToken.class, false); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index 01e66f2d60b..f1c2b586d5a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -34,26 +33,20 @@ public class RandomTokenVnodesIT extends TokenITBase { .build(); @ClassRule - public static ClusterRule clusterRule = - new ClusterRule( - ccmRule, false, true, new NodeStateListener[0], "request.timeout = 30 seconds"); + public static SessionRule sessionRule = + new SessionRule<>(ccmRule, false, new NodeStateListener[0], "request.timeout = 30 seconds"); public RandomTokenVnodesIT() { super(RandomToken.class, true); } - @Override - protected Cluster cluster() { - return clusterRule.cluster(); - } - @Override protected CqlSession session() { - return clusterRule.session(); + return sessionRule.session(); } @BeforeClass public static void createSchema() { - TokenITBase.createSchema(clusterRule.session()); + TokenITBase.createSchema(sessionRule.session()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index c19d77e2f4b..66c799f6199 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -15,12 +15,11 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import java.util.concurrent.atomic.AtomicInteger; import org.junit.ClassRule; import org.junit.Rule; @@ -33,15 +32,15 @@ public class SchemaAgreementIT { private static CustomCcmRule ccm = CustomCcmRule.builder().withNodes(3).build(); - private static ClusterRule clusterRule = - ClusterRule.builder(ccm) + private static SessionRule sessionRule = + SessionRule.builder(ccm) .withOptions( "request.timeout = 30s", "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", "connection.control-connection.schema-agreement.timeout = 3s") .build(); - @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(ccm).around(clusterRule); + @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public TestName name = new TestName(); @@ -50,7 +49,7 @@ public void should_succeed_when_all_nodes_agree() { ResultSet result = createTable(); assertThat(result.getExecutionInfo().isSchemaInAgreement()).isTrue(); - assertThat(clusterRule.cluster().checkSchemaAgreement()).isTrue(); + assertThat(sessionRule.session().checkSchemaAgreement()).isTrue(); } @Test @@ -61,7 +60,7 @@ public void should_fail_on_timeout() { ResultSet result = createTable(); assertThat(result.getExecutionInfo().isSchemaInAgreement()).isFalse(); - assertThat(clusterRule.cluster().checkSchemaAgreement()).isFalse(); + assertThat(sessionRule.session().checkSchemaAgreement()).isFalse(); } finally { ccm.getCcmBridge().resume(2); } @@ -75,7 +74,7 @@ public void should_agree_when_up_nodes_agree() { ResultSet result = createTable(); assertThat(result.getExecutionInfo().isSchemaInAgreement()).isTrue(); - assertThat(clusterRule.cluster().checkSchemaAgreement()).isTrue(); + assertThat(sessionRule.session().checkSchemaAgreement()).isTrue(); } finally { ccm.getCcmBridge().start(2); } @@ -83,22 +82,22 @@ public void should_agree_when_up_nodes_agree() { @Test public void should_fail_if_timeout_is_zero() { - try (Cluster cluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, + sessionRule.keyspace(), "request.timeout = 30s", "connection.control-connection.schema-agreement.timeout = 0s")) { - CqlSession session = cluster.connect(clusterRule.keyspace()); ResultSet result = createTable(session); // Should not agree because schema metadata is disabled assertThat(result.getExecutionInfo().isSchemaInAgreement()).isFalse(); - assertThat(cluster.checkSchemaAgreement()).isFalse(); + assertThat(session.checkSchemaAgreement()).isFalse(); } } private ResultSet createTable() { - return createTable(clusterRule.session()); + return createTable(sessionRule.session()); } private final AtomicInteger tableCounter = new AtomicInteger(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 0e867827841..db1e7280adb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -15,16 +15,15 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.collect.ImmutableList; @@ -48,22 +47,22 @@ public class SchemaChangesIT { // A client that we only use to set up the tests @Rule - public ClusterRule adminClusterRule = - new ClusterRule( + public SessionRule adminSessionRule = + new SessionRule<>( ccmRule, "request.timeout = 30 seconds", "metadata.schema.debouncer.window = 0 seconds"); @Before public void setup() { // Always drop and re-create the keyspace to start from a clean state - adminClusterRule + adminSessionRule .session() - .execute(String.format("DROP KEYSPACE %s", adminClusterRule.keyspace())); - ClusterUtils.createKeyspace(adminClusterRule.cluster(), adminClusterRule.keyspace()); + .execute(String.format("DROP KEYSPACE %s", adminSessionRule.keyspace())); + SessionUtils.createKeyspace(adminSessionRule.session(), adminSessionRule.keyspace()); } @Test public void should_handle_keyspace_creation() { - CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + CqlIdentifier newKeyspaceId = SessionUtils.uniqueKeyspaceId(); should_handle_creation( null, String.format( @@ -85,7 +84,7 @@ public void should_handle_keyspace_creation() { @Test public void should_handle_keyspace_drop() { - CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + CqlIdentifier newKeyspaceId = SessionUtils.uniqueKeyspaceId(); should_handle_drop( ImmutableList.of( String.format( @@ -100,7 +99,7 @@ public void should_handle_keyspace_drop() { @Test public void should_handle_keyspace_update() { - CqlIdentifier newKeyspaceId = ClusterUtils.uniqueKeyspaceId(); + CqlIdentifier newKeyspaceId = SessionUtils.uniqueKeyspaceId(); should_handle_update( ImmutableList.of( String.format( @@ -126,10 +125,10 @@ public void should_handle_table_creation() { "CREATE TABLE foo(k int primary key)", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getTable(CqlIdentifier.fromInternal("foo")), table -> { - assertThat(table.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(table.getKeyspace()).isEqualTo(adminSessionRule.keyspace()); assertThat(table.getName().asInternal()).isEqualTo("foo"); assertThat(table.getColumns()).containsOnlyKeys(CqlIdentifier.fromInternal("k")); ColumnMetadata k = table.getColumn(CqlIdentifier.fromInternal("k")); @@ -147,7 +146,7 @@ public void should_handle_table_drop() { "DROP TABLE foo", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getTable(CqlIdentifier.fromInternal("foo")), (listener, oldTable) -> Mockito.verify(listener).onTableDropped(oldTable)); } @@ -159,7 +158,7 @@ public void should_handle_table_update() { "ALTER TABLE foo ADD v int", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getTable(CqlIdentifier.fromInternal("foo")), newTable -> assertThat(newTable.getColumn(CqlIdentifier.fromInternal("v"))).isNotNull(), (listener, oldTable, newTable) -> @@ -173,10 +172,10 @@ public void should_handle_type_creation() { "CREATE TYPE t(i int)", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getUserDefinedType(CqlIdentifier.fromInternal("t")), type -> { - assertThat(type.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(type.getKeyspace()).isEqualTo(adminSessionRule.keyspace()); assertThat(type.getName().asInternal()).isEqualTo("t"); assertThat(type.getFieldNames()).containsExactly(CqlIdentifier.fromInternal("i")); assertThat(type.getFieldTypes()).containsExactly(DataTypes.INT); @@ -191,7 +190,7 @@ public void should_handle_type_drop() { "DROP TYPE t", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getUserDefinedType(CqlIdentifier.fromInternal("t")), (listener, oldType) -> Mockito.verify(listener).onUserDefinedTypeDropped(oldType)); } @@ -203,7 +202,7 @@ public void should_handle_type_update() { "ALTER TYPE t ADD j int", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getUserDefinedType(CqlIdentifier.fromInternal("t")), newType -> assertThat(newType.getFieldNames()) @@ -223,10 +222,10 @@ public void should_handle_view_creation() { + "WITH CLUSTERING ORDER BY (score DESC)", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getView(CqlIdentifier.fromInternal("highscores")), view -> { - assertThat(view.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(view.getKeyspace()).isEqualTo(adminSessionRule.keyspace()); assertThat(view.getName().asInternal()).isEqualTo("highscores"); assertThat(view.getBaseTable().asInternal()).isEqualTo("scores"); assertThat(view.includesAllColumns()).isFalse(); @@ -253,7 +252,7 @@ public void should_handle_view_drop() { "DROP MATERIALIZED VIEW highscores", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getView(CqlIdentifier.fromInternal("highscores")), (listener, oldView) -> Mockito.verify(listener).onViewDropped(oldView)); } @@ -271,7 +270,7 @@ public void should_handle_view_update() { "ALTER MATERIALIZED VIEW highscores WITH comment = 'The best score for each game'", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getView(CqlIdentifier.fromInternal("highscores")), newView -> assertThat(newView.getOptions().get(CqlIdentifier.fromInternal("comment"))) @@ -288,10 +287,10 @@ public void should_handle_function_creation() { + "LANGUAGE java AS 'return i;'", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), function -> { - assertThat(function.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(function.getKeyspace()).isEqualTo(adminSessionRule.keyspace()); assertThat(function.getSignature().getName().asInternal()).isEqualTo("id"); assertThat(function.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); assertThat(function.getReturnType()).isEqualTo(DataTypes.INT); @@ -312,7 +311,7 @@ public void should_handle_function_drop() { "DROP FUNCTION id", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), (listener, oldFunction) -> Mockito.verify(listener).onFunctionDropped(oldFunction)); } @@ -329,7 +328,7 @@ public void should_handle_function_update() { + "LANGUAGE java AS 'return j;'", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT), newFunction -> assertThat(newFunction.getBody()).isEqualTo("return j;"), (listener, oldFunction, newFunction) -> @@ -345,10 +344,10 @@ public void should_handle_aggregate_creation() { "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), aggregate -> { - assertThat(aggregate.getKeyspace()).isEqualTo(adminClusterRule.keyspace()); + assertThat(aggregate.getKeyspace()).isEqualTo(adminSessionRule.keyspace()); assertThat(aggregate.getSignature().getName().asInternal()).isEqualTo("sum"); assertThat(aggregate.getSignature().getParameterTypes()).containsExactly(DataTypes.INT); assertThat(aggregate.getStateType()).isEqualTo(DataTypes.INT); @@ -372,7 +371,7 @@ public void should_handle_aggregate_drop() { "DROP AGGREGATE sum", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), (listener, oldAggregate) -> Mockito.verify(listener).onAggregateDropped(oldAggregate)); } @@ -389,7 +388,7 @@ public void should_handle_aggregate_update() { "CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 1", metadata -> metadata - .getKeyspace(adminClusterRule.keyspace()) + .getKeyspace(adminSessionRule.keyspace()) .getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT), newAggregate -> assertThat(newAggregate.getInitCond()).isEqualTo(1), (listener, oldAggregate, newAggregate) -> @@ -400,7 +399,7 @@ private String keyspaceFilterOption(CqlIdentifier... keyspaces) { // create option to filter keyspace refreshes based on input keyspaces, if none are provided, assume the // one associated wiht the cluster rule. if (keyspaces.length == 0) { - keyspaces = new CqlIdentifier[] {adminClusterRule.keyspace()}; + keyspaces = new CqlIdentifier[] {adminSessionRule.keyspace()}; } String keyspaceStr = @@ -418,33 +417,35 @@ private void should_handle_creation( CqlIdentifier... keyspaces) { if (beforeStatement != null) { - adminClusterRule.session().execute(beforeStatement); + adminSessionRule.session().execute(beforeStatement); } // cluster1 executes the DDL query and gets a SCHEMA_CHANGE response. // cluster2 gets a SCHEMA_CHANGE push event on its control connection. - try (Cluster cluster1 = - ClusterUtils.newCluster( - ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); - Cluster cluster2 = - ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { + try (CqlSession session1 = + SessionUtils.newSession( + ccmRule, + adminSessionRule.keyspace(), + "request.timeout = 30 seconds", + keyspaceFilterOption(keyspaces)); + CqlSession session2 = SessionUtils.newSession(ccmRule, keyspaceFilterOption(keyspaces))) { SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - cluster1.register(listener1); + session1.register(listener1); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); - cluster2.register(listener2); + session2.register(listener2); - cluster1.connect(adminClusterRule.keyspace()).execute(createStatement); + session1.execute(createStatement); // Refreshes on a response are synchronous: - T newElement1 = extract.apply(cluster1.getMetadata()); + T newElement1 = extract.apply(session1.getMetadata()); verifyMetadata.accept(newElement1); verifyListener.accept(listener1, newElement1); // Refreshes on a server event are asynchronous: ConditionChecker.checkThat( () -> { - T newElement2 = extract.apply(cluster2.getMetadata()); + T newElement2 = extract.apply(session2.getMetadata()); verifyMetadata.accept(newElement2); verifyListener.accept(listener2, newElement2); }) @@ -460,31 +461,33 @@ private void should_handle_drop( CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { - adminClusterRule.session().execute(statement); + adminSessionRule.session().execute(statement); } - try (Cluster cluster1 = - ClusterUtils.newCluster( - ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); - Cluster cluster2 = - ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { + try (CqlSession session1 = + SessionUtils.newSession( + ccmRule, + adminSessionRule.keyspace(), + "request.timeout = 30 seconds", + keyspaceFilterOption(keyspaces)); + CqlSession session2 = SessionUtils.newSession(ccmRule, keyspaceFilterOption(keyspaces))) { - T oldElement = extract.apply(cluster1.getMetadata()); + T oldElement = extract.apply(session1.getMetadata()); assertThat(oldElement).isNotNull(); SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - cluster1.register(listener1); + session1.register(listener1); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); - cluster2.register(listener2); + session2.register(listener2); - cluster1.connect(adminClusterRule.keyspace()).execute(dropStatement); + session1.execute(dropStatement); - assertThat(extract.apply(cluster1.getMetadata())).isNull(); + assertThat(extract.apply(session1.getMetadata())).isNull(); verifyListener.accept(listener1, oldElement); ConditionChecker.checkThat( () -> { - assertThat(extract.apply(cluster2.getMetadata())).isNull(); + assertThat(extract.apply(session2.getMetadata())).isNull(); verifyListener.accept(listener2, oldElement); }) .becomesTrue(); @@ -500,32 +503,34 @@ private void should_handle_update( CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { - adminClusterRule.session().execute(statement); + adminSessionRule.session().execute(statement); } - try (Cluster cluster1 = - ClusterUtils.newCluster( - ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); - Cluster cluster2 = - ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { + try (CqlSession session1 = + SessionUtils.newSession( + ccmRule, + adminSessionRule.keyspace(), + "request.timeout = 30 seconds", + keyspaceFilterOption(keyspaces)); + CqlSession session2 = SessionUtils.newSession(ccmRule, keyspaceFilterOption(keyspaces))) { - T oldElement = extract.apply(cluster1.getMetadata()); + T oldElement = extract.apply(session1.getMetadata()); assertThat(oldElement).isNotNull(); SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - cluster1.register(listener1); + session1.register(listener1); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); - cluster2.register(listener2); + session2.register(listener2); - cluster1.connect(adminClusterRule.keyspace()).execute(updateStatement); + session1.execute(updateStatement); - T newElement = extract.apply(cluster1.getMetadata()); + T newElement = extract.apply(session1.getMetadata()); verifyNewMetadata.accept(newElement); verifyListener.accept(listener1, oldElement, newElement); ConditionChecker.checkThat( () -> { - verifyNewMetadata.accept(extract.apply(cluster2.getMetadata())); + verifyNewMetadata.accept(extract.apply(session2.getMetadata())); verifyListener.accept(listener2, oldElement, newElement); }) .becomesTrue(); @@ -544,35 +549,37 @@ private void should_handle_update_via_drop_and_recreate( CqlIdentifier... keyspaces) { for (String statement : beforeStatements) { - adminClusterRule.session().execute(statement); + adminSessionRule.session().execute(statement); } - try (Cluster cluster1 = - ClusterUtils.newCluster( - ccmRule, "request.timeout = 30 seconds", keyspaceFilterOption(keyspaces)); - Cluster cluster2 = - ClusterUtils.newCluster(ccmRule, keyspaceFilterOption(keyspaces))) { + try (CqlSession session1 = + SessionUtils.newSession( + ccmRule, + adminSessionRule.keyspace(), + "request.timeout = 30 seconds", + keyspaceFilterOption(keyspaces)); + CqlSession session2 = SessionUtils.newSession(ccmRule, keyspaceFilterOption(keyspaces))) { - T oldElement = extract.apply(cluster1.getMetadata()); + T oldElement = extract.apply(session1.getMetadata()); assertThat(oldElement).isNotNull(); SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - cluster1.register(listener1); + session1.register(listener1); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); - cluster2.register(listener2); + session2.register(listener2); - cluster1.setSchemaMetadataEnabled(false); - cluster2.setSchemaMetadataEnabled(false); + session1.setSchemaMetadataEnabled(false); + session2.setSchemaMetadataEnabled(false); - cluster1.connect(adminClusterRule.keyspace()).execute(dropStatement); - cluster1.connect(adminClusterRule.keyspace()).execute(recreateStatement); + session1.execute(dropStatement); + session1.execute(recreateStatement); - cluster1.setSchemaMetadataEnabled(true); - cluster2.setSchemaMetadataEnabled(true); + session1.setSchemaMetadataEnabled(true); + session2.setSchemaMetadataEnabled(true); ConditionChecker.checkThat( () -> { - T newElement = extract.apply(cluster1.getMetadata()); + T newElement = extract.apply(session1.getMetadata()); verifyNewMetadata.accept(newElement); verifyListener.accept(listener1, oldElement, newElement); }) @@ -580,8 +587,8 @@ private void should_handle_update_via_drop_and_recreate( ConditionChecker.checkThat( () -> { - T newElement = extract.apply(cluster2.getMetadata()); - verifyNewMetadata.accept(extract.apply(cluster2.getMetadata())); + T newElement = extract.apply(session2.getMetadata()); + verifyNewMetadata.accept(extract.apply(session2.getMetadata())); verifyListener.accept(listener2, oldElement, newElement); }) .becomesTrue(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 46aa278ebb5..33abefd01fd 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -15,15 +15,14 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Map; @@ -38,119 +37,116 @@ public class SchemaIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); - @Rule public ClusterRule clusterRule = new ClusterRule(ccmRule); + @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); @Test public void should_expose_system_and_test_keyspace() { Map keyspaces = - clusterRule.cluster().getMetadata().getKeyspaces(); + sessionRule.session().getMetadata().getKeyspaces(); assertThat(keyspaces) .containsKeys( // Don't test exhaustively because system keyspaces depend on the Cassandra version, and // keyspaces from other tests might also be present CqlIdentifier.fromInternal("system"), CqlIdentifier.fromInternal("system_traces"), - clusterRule.keyspace()); + sessionRule.keyspace()); assertThat(keyspaces.get(CqlIdentifier.fromInternal("system")).getTables()) .containsKeys(CqlIdentifier.fromInternal("local"), CqlIdentifier.fromInternal("peers")); } @Test public void should_filter_by_keyspaces() { - try (Cluster cluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccmRule, String.format( "metadata.schema.refreshed-keyspaces = [ \"%s\"] ", - clusterRule.keyspace().asInternal()))) { + sessionRule.keyspace().asInternal()))) { - assertThat(cluster.getMetadata().getKeyspaces()).containsOnlyKeys(clusterRule.keyspace()); + assertThat(session.getMetadata().getKeyspaces()).containsOnlyKeys(sessionRule.keyspace()); - CqlIdentifier otherKeyspace = ClusterUtils.uniqueKeyspaceId(); - ClusterUtils.createKeyspace(cluster, otherKeyspace); + CqlIdentifier otherKeyspace = SessionUtils.uniqueKeyspaceId(); + SessionUtils.createKeyspace(session, otherKeyspace); - assertThat(cluster.getMetadata().getKeyspaces()).containsOnlyKeys(clusterRule.keyspace()); + assertThat(session.getMetadata().getKeyspaces()).containsOnlyKeys(sessionRule.keyspace()); } } @Test public void should_not_load_schema_if_disabled_in_config() { - try (Cluster cluster = - ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (CqlSession session = SessionUtils.newSession(ccmRule, "metadata.schema.enabled = false")) { - assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); - assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + assertThat(session.isSchemaMetadataEnabled()).isFalse(); + assertThat(session.getMetadata().getKeyspaces()).isEmpty(); } } @Test public void should_enable_schema_programmatically_when_disabled_in_config() { - try (Cluster cluster = - ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (CqlSession session = SessionUtils.newSession(ccmRule, "metadata.schema.enabled = false")) { - assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); - assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + assertThat(session.isSchemaMetadataEnabled()).isFalse(); + assertThat(session.getMetadata().getKeyspaces()).isEmpty(); - cluster.setSchemaMetadataEnabled(true); - assertThat(cluster.isSchemaMetadataEnabled()).isTrue(); + session.setSchemaMetadataEnabled(true); + assertThat(session.isSchemaMetadataEnabled()).isTrue(); ConditionChecker.checkThat( - () -> assertThat(cluster.getMetadata().getKeyspaces()).isNotEmpty()) + () -> assertThat(session.getMetadata().getKeyspaces()).isNotEmpty()) .becomesTrue(); - assertThat(cluster.getMetadata().getKeyspaces()) + assertThat(session.getMetadata().getKeyspaces()) .containsKeys( CqlIdentifier.fromInternal("system"), CqlIdentifier.fromInternal("system_traces"), - clusterRule.keyspace()); + sessionRule.keyspace()); - cluster.setSchemaMetadataEnabled(null); - assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + session.setSchemaMetadataEnabled(null); + assertThat(session.isSchemaMetadataEnabled()).isFalse(); } } @Test public void should_disable_schema_programmatically_when_enabled_in_config() { - Cluster cluster = clusterRule.cluster(); - cluster.setSchemaMetadataEnabled(false); - assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); + CqlSession session = sessionRule.session(); + session.setSchemaMetadataEnabled(false); + assertThat(session.isSchemaMetadataEnabled()).isFalse(); // Create a table, metadata should not be updated - DriverConfigProfile slowProfile = ClusterUtils.slowProfile(cluster); - clusterRule + DriverConfigProfile slowProfile = SessionUtils.slowProfile(session); + sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE foo(k int primary key)") .withConfigProfile(slowProfile) .build()); - assertThat(cluster.getMetadata().getKeyspace(clusterRule.keyspace()).getTables()) + assertThat(session.getMetadata().getKeyspace(sessionRule.keyspace()).getTables()) .doesNotContainKey(CqlIdentifier.fromInternal("foo")); // Reset to config value (true), should refresh and load the new table - cluster.setSchemaMetadataEnabled(null); - assertThat(cluster.isSchemaMetadataEnabled()).isTrue(); + session.setSchemaMetadataEnabled(null); + assertThat(session.isSchemaMetadataEnabled()).isTrue(); ConditionChecker.checkThat( () -> - assertThat(cluster.getMetadata().getKeyspace(clusterRule.keyspace()).getTables()) + assertThat(session.getMetadata().getKeyspace(sessionRule.keyspace()).getTables()) .containsKey(CqlIdentifier.fromInternal("foo"))) .becomesTrue(); } @Test public void should_refresh_schema_manually() { - try (Cluster cluster = - ClusterUtils.newCluster(ccmRule, "metadata.schema.enabled = false")) { + try (CqlSession session = SessionUtils.newSession(ccmRule, "metadata.schema.enabled = false")) { - assertThat(cluster.isSchemaMetadataEnabled()).isFalse(); - assertThat(cluster.getMetadata().getKeyspaces()).isEmpty(); + assertThat(session.isSchemaMetadataEnabled()).isFalse(); + assertThat(session.getMetadata().getKeyspaces()).isEmpty(); - Metadata newMetadata = cluster.refreshSchema(); + Metadata newMetadata = session.refreshSchema(); assertThat(newMetadata.getKeyspaces()) .containsKeys( CqlIdentifier.fromInternal("system"), CqlIdentifier.fromInternal("system_traces"), - clusterRule.keyspace()); + sessionRule.keyspace()); - assertThat(cluster.getMetadata()).isSameAs(newMetadata); + assertThat(session.getMetadata()).isSameAs(newMetadata); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index 3ad48d97154..9d97f3e49cf 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -25,9 +25,9 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.nio.ByteBuffer; @@ -42,8 +42,8 @@ public abstract class TokenITBase { - protected static final CqlIdentifier KS1 = ClusterUtils.uniqueKeyspaceId(); - protected static final CqlIdentifier KS2 = ClusterUtils.uniqueKeyspaceId(); + protected static final CqlIdentifier KS1 = SessionUtils.uniqueKeyspaceId(); + protected static final CqlIdentifier KS2 = SessionUtils.uniqueKeyspaceId(); // Must be called in a @BeforeClass method in each subclass (unfortunately we can't do this // automatically because it requires the session, which is not available from a static context in @@ -78,8 +78,6 @@ protected TokenITBase(Class expectedTokenType, boolean useVnode this.tokensPerNode = useVnodes ? 256 : 1; } - protected abstract Cluster cluster(); - protected abstract CqlSession session(); /** @@ -93,12 +91,12 @@ protected TokenITBase(Class expectedTokenType, boolean useVnode */ @Test public void should_be_consistent_with_range_queries() { - Metadata metadata = cluster().getMetadata(); + Metadata metadata = session().getMetadata(); TokenMap tokenMap = metadata.getTokenMap().get(); // Find the replica for a given partition key of ks1.foo. int key = 1; - ProtocolVersion protocolVersion = cluster().getContext().protocolVersion(); + ProtocolVersion protocolVersion = session().getContext().protocolVersion(); ByteBuffer serializedKey = TypeCodecs.INT.encodePrimitive(key, protocolVersion); Set replicas = tokenMap.getReplicas(KS1, serializedKey); assertThat(replicas).hasSize(1); @@ -229,22 +227,22 @@ public void should_raise_exception_when_getting_token_on_non_token_column() { */ @Test public void should_expose_consistent_ranges() { - checkRanges(cluster()); - checkRanges(cluster(), KS1, 1); - checkRanges(cluster(), KS2, 2); + checkRanges(session()); + checkRanges(session(), KS1, 1); + checkRanges(session(), KS2, 2); } - private void checkRanges(Cluster cluster) { - TokenMap tokenMap = cluster.getMetadata().getTokenMap().get(); + private void checkRanges(Session session) { + TokenMap tokenMap = session.getMetadata().getTokenMap().get(); checkRanges(tokenMap.getTokenRanges()); } - private void checkRanges(Cluster cluster, CqlIdentifier keyspace, int replicationFactor) { - TokenMap tokenMap = cluster.getMetadata().getTokenMap().get(); + private void checkRanges(Session session, CqlIdentifier keyspace, int replicationFactor) { + TokenMap tokenMap = session.getMetadata().getTokenMap().get(); List allRangesWithDuplicates = Lists.newArrayList(); // Get each host's ranges, the count should match the replication factor - for (Node node : cluster.getMetadata().getNodes().values()) { + for (Node node : session.getMetadata().getNodes().values()) { Set hostRanges = tokenMap.getTokenRanges(keyspace, node); // Special case: When using vnodes the tokens are not evenly assigned to each replica. if (!useVnodes) { @@ -301,7 +299,7 @@ private void checkRanges(Collection ranges) { */ @Test public void should_have_only_one_wrapped_range() { - TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = session().getMetadata().getTokenMap().get(); TokenRange wrappedRange = null; for (TokenRange range : tokenMap.getTokenRanges()) { if (range.isWrappedAround()) { @@ -319,7 +317,7 @@ public void should_have_only_one_wrapped_range() { @Test public void should_create_tokens_and_ranges() { - TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = session().getMetadata().getTokenMap().get(); // Pick a random range TokenRange range = tokenMap.getTokenRanges().iterator().next(); @@ -332,12 +330,12 @@ public void should_create_tokens_and_ranges() { @Test public void should_create_token_from_partition_key() { - TokenMap tokenMap = cluster().getMetadata().getTokenMap().get(); + TokenMap tokenMap = session().getMetadata().getTokenMap().get(); Row row = session().execute("SELECT token(i) FROM foo WHERE i = 1").one(); Token expected = row.getToken(0); - ProtocolVersion protocolVersion = cluster().getContext().protocolVersion(); + ProtocolVersion protocolVersion = session().getContext().protocolVersion(); assertThat(tokenMap.newToken(TypeCodecs.INT.encodePrimitive(1, protocolVersion))) .isEqualTo(expected); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index 8f7be9ca289..d727acb1ae6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -25,7 +26,7 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -60,8 +61,8 @@ public class DefaultRetryPolicyIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); - public @Rule ClusterRule cluster = - new ClusterRule( + public @Rule SessionRule sessionRule = + new SessionRule<>( simulacron, "request.default-idempotence = true", "load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy"); @@ -108,7 +109,7 @@ public void should_not_retry_on_read_timeout_when_data_present() { try { // when executing a query - cluster.session().execute(query); + sessionRule.session().execute(query); fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown @@ -130,7 +131,7 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() try { // when executing a query - cluster.session().execute(query); + sessionRule.session().execute(query); fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown @@ -152,7 +153,7 @@ public void should_retry_on_read_timeout_when_enough_responses_and_data_not_pres try { // when executing a query. - cluster.session().execute(query); + sessionRule.session().execute(query); fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown. @@ -178,7 +179,7 @@ public void should_retry_on_next_host_on_connection_error_if_idempotent() { .then(closeConnection(DisconnectAction.Scope.CONNECTION, CloseType.DISCONNECT))); // when executing a query. - ResultSet result = cluster.session().execute(query); + ResultSet result = sessionRule.session().execute(query); // then we should get a response, and the execution info on the result set indicates there was an error on // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); @@ -209,7 +210,7 @@ public void should_keep_retrying_on_next_host_on_connection_error() { try { // when executing a query. - cluster.session().execute(query); + sessionRule.session().execute(query); fail("AllNodesFailedException expected"); } catch (AllNodesFailedException ex) { // then an AllNodesFailedException should be raised indicating that all nodes failed the request. @@ -238,7 +239,9 @@ public void should_not_retry_on_connection_error_if_non_idempotent() { try { // when executing a non-idempotent query. - cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + sessionRule + .session() + .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); fail("ClosedConnectionException expected"); } catch (ClosedConnectionException ex) { // then a ClosedConnectionException should be raised, indicating that the connection closed while handling @@ -262,7 +265,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { try { // when executing a query. - cluster.session().execute(queryStr); + sessionRule.session().execute(queryStr); fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown @@ -300,7 +303,7 @@ public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( try { // when executing a query. - cluster.session().execute(queryStr); + sessionRule.session().execute(queryStr); fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown @@ -323,7 +326,9 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id try { // when executing a non-idempotent query. - cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + sessionRule + .session() + .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown @@ -343,7 +348,7 @@ public void should_retry_on_next_host_on_unavailable() { simulacron.cluster().node(0).prime(when(queryStr).then(unavailable(LOCAL_QUORUM, 3, 0))); // when executing a query. - ResultSet result = cluster.session().execute(queryStr); + ResultSet result = sessionRule.session().execute(queryStr); // then we should get a response, and the execution info on the result set indicates there was an error on // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); @@ -369,7 +374,7 @@ public void should_only_retry_once_on_unavailable() { try { // when executing a query. - cluster.session().execute(queryStr); + sessionRule.session().execute(queryStr); } catch (UnavailableException ue) { // then we should get an unavailable exception with the host being node 1 (since it was second tried). assertThat(ue.getCoordinator().getConnectAddress()) @@ -392,7 +397,7 @@ public void should_keep_retrying_on_next_host_on_error_response() { try { // when executing a query. - cluster.session().execute(queryStr); + sessionRule.session().execute(queryStr); } catch (AllNodesFailedException e) { // then we should get an all nodes failed exception, indicating the query was tried each node. assertThat(e.getErrors()).hasSize(3); @@ -418,7 +423,9 @@ public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() try { // when executing a query that is not idempotent - cluster.session().execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + sessionRule + .session() + .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); } catch (ServerError e) { // then should get a server error from first host. assertThat(e.getMessage()).isEqualTo("this is a server error"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 73caf3bc7bb..1d4f56ec714 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -15,17 +15,17 @@ */ package com.datastax.oss.driver.api.core.session; -import com.datastax.oss.driver.api.core.Cluster; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; -import com.datastax.oss.driver.example.guava.api.GuavaClusterUtils; import com.datastax.oss.driver.example.guava.api.GuavaSession; -import com.datastax.oss.driver.example.guava.internal.DefaultGuavaCluster; +import com.datastax.oss.driver.example.guava.api.GuavaSessionUtils; +import com.datastax.oss.driver.example.guava.internal.DefaultGuavaSession; import com.datastax.oss.driver.example.guava.internal.GuavaDriverContext; import com.datastax.oss.driver.example.guava.internal.KeyRequest; import com.datastax.oss.driver.example.guava.internal.KeyRequestProcessor; @@ -48,7 +48,7 @@ * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations to add-in * additional request handling and response types. * - *

          Uses {@link DefaultGuavaCluster} which is a specialized cluster implementation that uses + *

          Uses {@link DefaultGuavaSession} which is a specialized session implementation that uses * {@link GuavaDriverContext} which overrides {@link * DefaultDriverContext#requestProcessorRegistry()} to provide its own {@link * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations for returning @@ -66,7 +66,7 @@ public class RequestProcessorIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -75,15 +75,15 @@ public class RequestProcessorIT { @BeforeClass public static void setupSchema() { // table with clustering key where v1 == v0 * 2. - cluster + sessionRule .session() .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v0 int, v1 int, PRIMARY KEY(k, v0))") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { - cluster + sessionRule .session() .execute( SimpleStatement.builder("INSERT INTO test (k, v0, v1) VALUES (?, ?, ?)") @@ -92,17 +92,17 @@ public static void setupSchema() { } } - private Cluster newCluster(String... options) { - return GuavaClusterUtils.builder() + private GuavaSession newSession(CqlIdentifier keyspace, String... options) { + return GuavaSessionUtils.builder() .addContactPoints(ccm.getContactPoints()) + .withKeyspace(keyspace) .withConfigLoader(new TestConfigLoader(options)) .build(); } @Test public void should_use_custom_request_processor_for_prepareAsync() throws Exception { - try (Cluster gCluster = newCluster()) { - GuavaSession session = gCluster.connect(cluster.keyspace()); + try (GuavaSession session = newSession(sessionRule.keyspace())) { ListenableFuture preparedFuture = session.prepareAsync("select * from test"); @@ -121,9 +121,7 @@ public void should_use_custom_request_processor_for_prepareAsync() throws Except @Test public void should_use_custom_request_processor_for_handling_special_request_type() throws Exception { - try (Cluster gCluster = newCluster()) { - GuavaSession session = gCluster.connect(cluster.keyspace()); - + try (GuavaSession session = newSession(sessionRule.keyspace())) { // RequestProcessor executes "select v from test where k = " and returns v as Integer. int v1 = session.execute(new KeyRequest(5), KeyRequestProcessor.INT_TYPE); assertThat(v1).isEqualTo(10); // v1 = v0 * 2 @@ -136,9 +134,7 @@ public void should_use_custom_request_processor_for_handling_special_request_typ @Test public void should_use_custom_request_processor_for_executeAsync() throws Exception { - try (Cluster gCluster = newCluster()) { - GuavaSession session = gCluster.connect(cluster.keyspace()); - + try (GuavaSession session = newSession(sessionRule.keyspace())) { ListenableFuture future = session.executeAsync("select * from test"); AsyncResultSet result = Uninterruptibles.getUninterruptibly(future); assertThat(Iterables.size(result.currentPage())).isEqualTo(100); @@ -151,7 +147,7 @@ public void should_throw_illegal_argument_exception_if_no_matching_processor_fou // Since cluster does not have a processor registered for returning ListenableFuture, an IllegalArgumentException // should be thrown. thrown.expect(IllegalArgumentException.class); - cluster + sessionRule .session() .execute(SimpleStatement.newInstance("select * from test"), GuavaSession.ASYNC); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index aca41a4db54..d23d24ca680 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -16,11 +16,10 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -60,8 +59,7 @@ public void should_not_start_speculative_executions_if_not_idempotent() { primeNode( 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(2, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY.setIdempotent(false)); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -79,8 +77,7 @@ public void should_complete_from_first_speculative_execution_if_faster() { 0, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); primeNode(1, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(2, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); @@ -99,8 +96,7 @@ public void should_complete_from_initial_execution_if_speculative_is_started_but primeNode( 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(2, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -120,8 +116,7 @@ public void should_complete_from_second_speculative_execution_if_faster() { 1, when(QUERY_STRING).then(noRows()).delay(3 * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); primeNode(2, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(3, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); @@ -139,8 +134,7 @@ public void should_retry_within_initial_execution() { primeNode(0, when(QUERY_STRING).then(isBootstrapping())); primeNode(1, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(3, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -160,8 +154,7 @@ public void should_retry_within_speculative_execution() { primeNode(1, when(QUERY_STRING).then(isBootstrapping())); primeNode(2, when(QUERY_STRING).then(noRows())); - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(3, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); @@ -182,8 +175,7 @@ public void should_wait_for_last_execution_to_complete() { primeNode(1, when(QUERY_STRING).then(isBootstrapping())); primeNode(2, when(QUERY_STRING).then(isBootstrapping())); - try (Cluster cluster = buildCluster(2, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(2, SPECULATIVE_DELAY)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); @@ -206,8 +198,7 @@ public void should_fail_if_all_executions_reach_end_of_query_plan() { .then(isBootstrapping()) .delay((3 - i) * SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); } - try (Cluster cluster = buildCluster(3, SPECULATIVE_DELAY)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(3, SPECULATIVE_DELAY)) { session.execute(QUERY); } finally { assertQueryCount(0, 1); @@ -225,8 +216,7 @@ public void should_allow_zero_delay() { } primeNode(2, when(QUERY_STRING).then(noRows()).delay(SPECULATIVE_DELAY, TimeUnit.MILLISECONDS)); - try (Cluster cluster = buildCluster(3, 0)) { - CqlSession session = cluster.connect(); + try (CqlSession session = buildSession(3, 0)) { ResultSet resultSet = session.execute(QUERY); assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); @@ -239,8 +229,8 @@ public void should_allow_zero_delay() { } // Build a new Cluster instance for each test, because we need different configurations - private Cluster buildCluster(int maxSpeculativeExecutions, long speculativeDelayMs) { - return ClusterUtils.newCluster( + private CqlSession buildSession(int maxSpeculativeExecutions, long speculativeDelayMs) { + return SessionUtils.newSession( simulacron, String.format("request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), "request.default-idempotence = true", diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index 5333f12caf8..d6c8cff185e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -15,11 +15,10 @@ */ package com.datastax.oss.driver.api.core.ssl; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -36,11 +35,10 @@ public void should_connect_with_ssl() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index 823b03ea3c6..26f46d54757 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -15,11 +15,10 @@ */ package com.datastax.oss.driver.api.core.ssl; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -40,11 +39,10 @@ public void should_connect_with_ssl_using_client_auth() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java index 3228b9912eb..d8e0e33e9c7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -16,11 +16,10 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -37,11 +36,10 @@ public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (Cluster sslCluster = - ClusterUtils.newCluster( + try (CqlSession session = + SessionUtils.newSession( ccm, "ssl-engine-factory.class = com.datastax.oss.driver.api.core.ssl.DefaultSslEngineFactory")) { - CqlSession session = sslCluster.connect(); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java index 37c697302f5..e64ce96d8b0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -16,10 +16,9 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; @@ -32,8 +31,7 @@ public class DefaultSslEngineFactoryWithTruststoreNotProvidedIT { @Test(expected = AllNodesFailedException.class) public void should_not_connect_if_not_using_ssl() { - try (Cluster plainCluster = ClusterUtils.newCluster(ccm)) { - CqlSession session = plainCluster.connect(); + try (CqlSession session = SessionUtils.newSession(ccm)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 4833e82f615..2e030d75678 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -15,14 +15,13 @@ */ package com.datastax.oss.driver.api.core.type.codec.registry; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; @@ -31,7 +30,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.api.core.type.reflect.GenericTypeParameter; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterRule; +import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.codec.IntCodec; import java.nio.ByteBuffer; @@ -56,7 +55,7 @@ public class CodecRegistryIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static ClusterRule cluster = new ClusterRule(ccm); + @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); @Rule public TestName name = new TestName(); @@ -65,19 +64,19 @@ public class CodecRegistryIT { @BeforeClass public static void createSchema() { // table with simple primary key, single cell. - cluster + sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k text primary key, v int)") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); // table with map value - cluster + sessionRule .session() .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test2 (k0 text, k1 int, v map, primary key (k0, k1))") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); } @@ -119,7 +118,8 @@ public Float parse(String value) { @Test public void should_throw_exception_if_no_codec_registered_for_type_set() { - PreparedStatement prepared = cluster.session().prepare("INSERT INTO test (k, v) values (?, ?)"); + PreparedStatement prepared = + sessionRule.session().prepare("INSERT INTO test (k, v) values (?, ?)"); thrown.expect(CodecNotFoundException.class); @@ -129,14 +129,15 @@ public void should_throw_exception_if_no_codec_registered_for_type_set() { @Test public void should_throw_exception_if_no_codec_registered_for_type_get() { - PreparedStatement prepared = cluster.session().prepare("INSERT INTO test (k, v) values (?, ?)"); + PreparedStatement prepared = + sessionRule.session().prepare("INSERT INTO test (k, v) values (?, ?)"); BoundStatement insert = prepared.boundStatementBuilder().setString(0, name.getMethodName()).setInt(1, 2).build(); - cluster.session().execute(insert); + sessionRule.session().execute(insert); ResultSet result = - cluster + sessionRule .session() .execute( SimpleStatement.builder("SELECT v from test where k = ?") @@ -156,13 +157,12 @@ public void should_throw_exception_if_no_codec_registered_for_type_get() { @Test public void should_be_able_to_register_and_use_custom_codec() { // create a cluster with a registered codec from Float <-> cql int. - try (Cluster codecCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .addTypeCodecs(new FloatCIntCodec()) .addContactPoints(ccm.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) .build()) { - CqlSession session = codecCluster.connect(cluster.keyspace()); - PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); // float value for int column should work. @@ -272,13 +272,12 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() TypeCodec>> mapWithOptionalValueCodec = TypeCodecs.mapOf(TypeCodecs.INT, new OptionalCodec<>(TypeCodecs.TEXT)); - try (Cluster codecCluster = - Cluster.builder() + try (CqlSession session = + CqlSession.builder() .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) .addContactPoints(ccm.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) .build()) { - CqlSession session = codecCluster.connect(cluster.keyspace()); - PreparedStatement prepared = session.prepare("INSERT INTO test2 (k0, k1, v) values (?, ?, ?)"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java similarity index 74% rename from integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java index e02f845a6c7..1202331d95c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterBuilder.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java @@ -15,18 +15,16 @@ */ package com.datastax.oss.driver.example.guava.api; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.cql.CqlSession; -import com.datastax.oss.driver.example.guava.internal.DefaultGuavaCluster; -import com.datastax.oss.driver.example.guava.internal.GuavaDriverContext; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.example.guava.internal.DefaultGuavaSession; +import com.datastax.oss.driver.example.guava.internal.GuavaDriverContext; import java.util.List; -public class GuavaClusterBuilder - extends ClusterBuilder> { +public class GuavaSessionBuilder extends SessionBuilder { @Override protected DriverContext buildContext( @@ -35,7 +33,7 @@ protected DriverContext buildContext( } @Override - protected Cluster wrap(Cluster defaultCluster) { - return new DefaultGuavaCluster(defaultCluster); + protected GuavaSession wrap(CqlSession defaultSession) { + return new DefaultGuavaSession(defaultSession); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionUtils.java similarity index 84% rename from integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java rename to integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionUtils.java index e44c8de328b..c379d313572 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaClusterUtils.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionUtils.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.example.guava.api; -public class GuavaClusterUtils { - public static GuavaClusterBuilder builder() { - return new GuavaClusterBuilder(); +public class GuavaSessionUtils { + public static GuavaSessionBuilder builder() { + return new GuavaSessionBuilder(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java deleted file mode 100644 index a443e6d282d..00000000000 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaCluster.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * Copyright (C) 2017-2017 DataStax Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.example.guava.internal; - -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; -import com.datastax.oss.driver.example.guava.api.GuavaClusterBuilder; -import com.datastax.oss.driver.example.guava.api.GuavaSession; -import com.datastax.oss.driver.internal.core.ClusterWrapper; - -public class DefaultGuavaCluster extends ClusterWrapper { - - public DefaultGuavaCluster(Cluster delegate) { - super(delegate); - } - - @Override - protected GuavaSession wrap(CqlSession session) { - return new DefaultGuavaSession(session); - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java index 17f891baa70..a63da30d69b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/DefaultGuavaSession.java @@ -21,7 +21,7 @@ public class DefaultGuavaSession extends SessionWrapper implements GuavaSession { - DefaultGuavaSession(Session delegate) { + public DefaultGuavaSession(Session delegate) { super(delegate); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index 7db05413643..a7f41f41064 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -55,7 +55,7 @@ public RequestProcessorRegistry requestProcessorRegistry() { CqlRequestSyncProcessor cqlRequestSyncProcessor = new CqlRequestSyncProcessor(); return new RequestProcessorRegistry( - clusterName(), + sessionName(), cqlRequestSyncProcessor, new CqlPrepareSyncProcessor(preparedStatementsCache), new GuavaRequestAsyncProcessor<>( diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf index bf6c9e7854b..5519bc99175 100644 --- a/integration-tests/src/test/resources/application.conf +++ b/integration-tests/src/test/resources/application.conf @@ -7,6 +7,7 @@ datastax-java-driver { control-connection.timeout = 5 seconds } request.trace.interval = 1 second + request.warn-if-set-keyspace = false load-balancing-policy { # Since our test infra always specifies explicit contact points, we need to set the local DC as # well. diff --git a/manual/core/README.md b/manual/core/README.md index a9e7a4d8a51..5d9899a0da6 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -16,46 +16,37 @@ following coordinates: Here's a short program that connects to Cassandra and executes a query: ```java -try (Cluster cluster = Cluster.builder().build()) { // (1) +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.*; - CqlSession session = cluster.connect(); // (2) - - ResultSet rs = session.execute("select release_version from system.local"); // (3) +try (CqlSession session = CqlSession.builder().build()) { // (1) + ResultSet rs = session.execute("select release_version from system.local"); // (2) Row row = rs.one(); - System.out.println(row.getString("release_version")); // (4) + System.out.println(row.getString("release_version")); // (3) } ``` -1. [Cluster] is the main entry point of the driver. It holds the known state of the actual Cassandra - cluster. It is thread-safe, you should create a single instance (per target Cassandra cluster), - and share it throughout your application; -2. [CqlSession] is what you use to execute queries. Likewise, it is thread-safe and should be - reused; -3. we use `execute` to send a query to Cassandra. This returns a [ResultSet], which is an iterable +1. [CqlSession] is the main entry point of the driver. It holds the known state of the actual + Cassandra cluster, and is what you use to execute queries. It is thread-safe, you should create a + single instance (per target Cassandra cluster), and share it throughout your application; +2. we use `execute` to send a query to Cassandra. This returns a [ResultSet], which is an iterable of [Row] objects. On the next line, we extract the first row (which is the only one in this case); -4. we extract the value of the first (and only) column from the row. +3. we extract the value of the first (and only) column from the row. -Always close the `Cluster` once you're done with it, in order to free underlying resources (TCP -connections, thread pools...). Closing a `Cluster` also closes any `CqlSession` that was created -from it. In this simple example, we can use a try-with-resources block because `Cluster` implements -`java.lang.AutoCloseable`; in a real application, you'll probably call one of the close methods -(`close`, `closeAsync`, `forceCloseAsync`) explicitly. +Always close the `CqlSession` once you're done with it, in order to free underlying resources (TCP +connections, thread pools...). In this simple example, we can use a try-with-resources block because +`CqlSession` implements `java.lang.AutoCloseable`; in a real application, you'll probably call one +of the close methods (`close`, `closeAsync`, `forceCloseAsync`) explicitly. This example uses the synchronous API. Most methods have asynchronous equivalents (look for `*Async` variants that return a `CompletionStage`). -Note to framework implementors: if you design an API that lets users provide their own cluster -instance, use a bounded type parameter, like `Cluster` (or even -`Cluster` if you don't use any CQL-specific method). This allows custom cluster -implementations to be used as a drop-in replacement (see `RequestProcessorIT` in the integration -tests for an example of what such a custom implementation looks like). - ### Setting up the driver -#### [Cluster] +#### [CqlSession] -[Cluster#builder()] provides a fluent API to create an instance programmatically. Most of the +[CqlSession#builder()] provides a fluent API to create an instance programmatically. Most of the customization is done through the driver configuration (refer to the [corresponding section](configuration/) of this manual for full details). @@ -63,23 +54,33 @@ We recommend that you take a look at the `reference.conf` file bundled with the of available options, and cross-reference with the sub-sections in this manual for more explanations. -#### [CqlSession] - By default, a session isn't tied to any specific keyspace. You'll need to prefix table names in your queries: ```java -CqlSession session = cluster.connect(); -session.execute("SELECT * FROM myKeyspace.myTable WHERE id = 1"); +session.execute("SELECT * FROM my_keyspace.my_table WHERE id = 1"); ``` -You can also specify a keyspace name at construction time, it will be used as the default when table -names are not qualified: +You can also specify a keyspace at construction time, either through the +[configuration](configuration/): + +``` +session-keyspace = my_keyspace +``` + +Or with the builder: ```java -CqlSession session = cluster.connect(CqlIdentifier.fromCql("myKeyspace")); -session.execute("SELECT * FROM myTable WHERE id = 1"); -session.execute("SELECT * FROM otherKeyspace.otherTable WHERE id = 1"); +CqlSession session = CqlSession.builder() + .withKeyspace(CqlIdentifier.fromCql("my_keyspace")) + .build(); +``` + +That keyspace will be used as the default when table names are not qualified: + +```java +session.execute("SELECT * FROM my_table WHERE id = 1"); +session.execute("SELECT * FROM other_keyspace.other_table WHERE id = 1"); ``` You might be tempted to open a separate session for each keyspace used in your application; however, @@ -87,30 +88,36 @@ connection pools are created at the session level, so each new session will cons system resources: ```java -// Warning: creating two sessions doubles the number of TCP connections opened by the driver -CqlSession session1 = cluster.connect(CqlIdentifier.fromCql("ks1")); -CqlSession session2 = cluster.connect(CqlIdentifier.fromCql("ks2")); +// Anti-pattern: creating two sessions doubles the number of TCP connections opened by the driver +CqlSession session1 = CqlSession.builder().withKeyspace(CqlIdentifier.fromCql("ks1")).build(); +CqlSession session2 = CqlSession.builder().withKeyspace(CqlIdentifier.fromCql("ks2")).build(); ``` If you issue a `USE` statement, it will change the default keyspace on that session: ```java -CqlSession session = cluster.connect(); +CqlSession session = CqlSession.builder().build(); // No default keyspace set, need to prefix: -session.execute("SELECT * FROM myKeyspace.myTable WHERE id = 1"); +session.execute("SELECT * FROM my_keyspace.my_table WHERE id = 1"); -session.execute("USE myKeyspace"); +session.execute("USE my_keyspace"); // Now the keyspace is set, unqualified query works: -session.execute("SELECT * FROM myTable WHERE id = 1"); +session.execute("SELECT * FROM my_table WHERE id = 1"); ``` Be very careful though: switching the keyspace at runtime is inherently thread-unsafe, so if the session is shared by multiple threads (and is usually is), it could easily cause unexpected query failures. -Finally, [CASSANDRA-10145] \(coming in Cassandra 4) will allow specifying the keyspace on a per -query basis instead of relying on session state, which should greatly simplify multiple keyspace -handling. +Finally, if you're connecting to Cassandra 4 or above, you can specify the keyspace independently +for each request: + +```java +CqlSession session = CqlSession.builder().build(); +session.execute( + SimpleStatement.newInstance("SELECT * FROM my_table WHERE id = 1") + .setKeyspace(CqlIdentifier.fromCql("my_keyspace"))); +``` ### Running queries @@ -252,13 +259,12 @@ for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) { } ``` -[Cluster]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html -[Cluster#builder()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#builder-- -[CqlSession]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html -[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html -[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html -[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html -[AccessibleByName]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html -[GenericType]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/reflect/GenericType.html +[CqlSession]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html +[CqlSession#builder()]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/CqlSession.html#builder-- +[ResultSet]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ResultSet.html +[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html +[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html +[AccessibleByName]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html +[GenericType]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/reflect/GenericType.html [CASSANDRA-10145]: https://issues.apache.org/jira/browse/CASSANDRA-10145 \ No newline at end of file diff --git a/manual/core/address_resolution/README.md b/manual/core/address_resolution/README.md index d4d2efb96f1..d46a060c572 100644 --- a/manual/core/address_resolution/README.md +++ b/manual/core/address_resolution/README.md @@ -3,7 +3,7 @@ Each node in the Cassandra cluster is uniquely identified by an IP address that the driver will use to establish connections. -* for contact points, these are provided as part of configuring the `Cluster` object; +* for contact points, these are provided as part of configuring the `CqlSession` object; * for other nodes, addresses will be discovered dynamically, either by inspecting `system.peers` on already connected nodes, or via push notifications received on the control connection when new nodes are discovered by gossip. @@ -85,7 +85,7 @@ Then reference this class from the [configuration](../configuration/): datastax-java-driver.address-translator.class = com.mycompany.MyAddressTranslator ``` -Note: the contact points provided while creating the `Cluster` are not translated, only addresses +Note: the contact points provided while creating the `CqlSession` are not translated, only addresses retrieved from or sent by Cassandra nodes are. diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 5da82c1e520..37d6cc82a35 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -126,7 +126,7 @@ You don't need the configuration API for everyday usage of the driver, but it ca The driver's context exposes a [DriverConfig] instance: ```java -DriverConfig config = cluster.getContext().config(); +DriverConfig config = session.getContext().config(); DriverConfigProfile defaultProfile = config.getDefaultProfile(); DriverConfigProfile olapProfile = config.getNamedProfile("olap"); @@ -155,7 +155,7 @@ To allow this, you start from an existing profile in the configuration and build that overrides a subset of options: ```java -DriverConfigProfile defaultProfile = cluster.getContext().config().getDefaultProfile(); +DriverConfigProfile defaultProfile = session.getContext().config().getDefaultProfile(); DriverConfigProfile dynamicProfile = defaultProfile.withConsistencyLevel( CoreDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.EACH_QUORUM); @@ -188,13 +188,13 @@ VM, but with different configurations. What you want instead is separate option ``` # application.conf -cluster1 { - cluster-name = "cluster1" +session1 { + session-name = "session1" protocol-version = V4 // etc. } -cluster2 { - cluster-name = "cluster2" +session2 { + session-name = "session2" protocol-version = V3 // etc. } @@ -233,17 +233,17 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; -DriverConfigLoader cluster1ConfigLoader = +DriverConfigLoader session1ConfigLoader = new DefaultDriverConfigLoader( - () -> loadConfig("cluster1"), CoreDriverOption.values()); + () -> loadConfig("session1"), CoreDriverOption.values()); ``` Finally, pass the config loader when building the driver: ```java -Cluster cluster1 = - Cluster.builder() - .withConfigLoader(cluster1ConfigLoader) +CqlSession session1 = + CqlSession.builder() + .withConfigLoader(session1ConfigLoader) .build(); ``` @@ -267,7 +267,7 @@ DriverConfigLoader loader = }, CoreDriverOption.values()); -Cluster cluster = Cluster.builder().withConfigLoader(loader).build(); +CqlSession session = CqlSession.builder().withConfigLoader(loader).build(); ``` #### Bypassing TypeSafe Config @@ -275,7 +275,7 @@ Cluster cluster = Cluster.builder().withConfigLoader(loader).build(); If TypeSafe Config doesn't work for you, it is possible to get rid of it entirely. You will need to provide your own implementations of [DriverConfig] and [DriverConfigProfile]. Then -write a [DriverConfigLoader] and pass it to the cluster at initialization, as shown in the previous +write a [DriverConfigLoader] and pass it to the session at initialization, as shown in the previous sections. Study the built-in implementation (package `com.datastax.oss.driver.internal.core.config.typesafe`) for reference. @@ -295,7 +295,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; // DANGER ZONE: this gives you access to the driver internals, which allow very nasty things. // Use responsibly. -InternalDriverContext context = (InternalDriverContext) cluster.getContext(); +InternalDriverContext context = (InternalDriverContext) session.getContext(); EventBus eventBus = context.eventBus(); eventBus.fire(ForceReloadConfigEvent.INSTANCE); @@ -338,7 +338,7 @@ with the following signature: ExponentialReconnectionPolicy(DriverContext context) ``` -Where `context` is the object returned by `Cluster.getContext()`, which allows the policy to access +Where `context` is the object returned by `session.getContext()`, which allows the policy to access other driver components (for example the configuration). If you write custom policy implementations, you should follow that same pattern; it provides an @@ -364,13 +364,13 @@ public class MyDriverContext extends DefaultDriverContext { } ``` -Then you'll need to pass an instance of this context to `DefaultCluster.init`. You can either do so -directly, or subclass `ClusterBuilder` and override the `buildAsync` method. +Then you'll need to pass an instance of this context to `DefaultSession.init`. You can either do so +directly, or subclass `SessionBuilder` and override the `buildContext` method. #### Custom options You can add your own options to the configuration. This is useful for custom components, or even as -a way to associate arbitrary key/value pairs with the cluster instance. +a way to associate arbitrary key/value pairs with the session instance. First, write an enum that implements [DriverOption]: @@ -405,7 +405,7 @@ public enum MyCustomOption implements DriverOption { Pass the options to the config loader: ```java -Cluster cluster = Cluster.builder() +CqlSession session = CqlSession.builder() .withConfigLoader(new DefaultDriverConfigLoader( DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values(), // don't forget to keep the core options @@ -428,7 +428,7 @@ datastax-java-driver { And access them from the code: ```java -DriverConfig config = cluster.getContext().config(); +DriverConfig config = session.getContext().config(); config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL); config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); ``` diff --git a/manual/core/metadata/README.md b/manual/core/metadata/README.md index f203e3fdef4..bfaeedcea2d 100644 --- a/manual/core/metadata/README.md +++ b/manual/core/metadata/README.md @@ -1,6 +1,6 @@ ## Metadata -The driver exposes metadata about the Cassandra cluster via the [Cluster#getMetadata] method. It +The driver exposes metadata about the Cassandra cluster via the [Session#getMetadata] method. It returns a [Metadata] object, which contains three types of information: * [node metadata](node/) @@ -12,7 +12,7 @@ link above for details). Each call to `getMetadata()` will return a **new copy** changed since the last call. Do not cache the result across usages: ```java -Metadata metadata = cluster.getMetadata(); +Metadata metadata = session.getMetadata(); session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); @@ -29,7 +29,7 @@ On the other hand, the advantage of immutability is that a `Metadata` instance p guaranteed to be in sync with the node and schema metadata: ```java -Metadata metadata = cluster.getMetadata(); +Metadata metadata = session.getMetadata(); // Pick up any node and keyspace: Node node = metadata.getNodes().values().iterator().next(); KeyspaceMetadata keyspace = metadata.getKeyspaces().values().iterator().next(); @@ -42,6 +42,6 @@ Set tokenRanges = tokenMap.getTokenRanges(keyspace.getName(), node); This is a big improvement over previous versions of the driver, where it was possible to observe a new keyspace in the schema metadata before the token metadata was updated. -[Cluster#getMetadata]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#getMetadata-- +[Session#getMetadata]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#getMetadata-- [Metadata]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html [Node]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Node.html \ No newline at end of file diff --git a/manual/core/metadata/node/README.md b/manual/core/metadata/node/README.md index 40f9cb1e128..ff0843e8781 100644 --- a/manual/core/metadata/node/README.md +++ b/manual/core/metadata/node/README.md @@ -5,7 +5,7 @@ includes down and ignored nodes (see below), so the fact that a node is in this necessarily mean that the driver is connected to it. ```java -Map nodes = cluster.getMetadata().getNodes(); +Map nodes = session.getMetadata().getNodes(); System.out.println("Nodes in the cluster:"); for (Node node : nodes.values()) { System.out.printf( @@ -52,7 +52,7 @@ balancing policy, etc). import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; -InternalDriverContext context = (InternalDriverContext) cluster.getContext(); +InternalDriverContext context = (InternalDriverContext) session.getContext(); context.eventBus().fire(TopologyEvent.forceDown(node1.getConnectAddress())); context.eventBus().fire(TopologyEvent.forceUp(node1.getConnectAddress())); ``` diff --git a/manual/core/metadata/schema/README.md b/manual/core/metadata/schema/README.md index 395bdb72602..b43be22006f 100644 --- a/manual/core/metadata/schema/README.md +++ b/manual/core/metadata/schema/README.md @@ -3,7 +3,7 @@ [Metadata#getKeyspaces] returns a client-side representation of the database schema: ```java -Map keyspaces = cluster.getMetadata().getKeyspaces(); +Map keyspaces = session.getMetadata().getKeyspaces(); KeyspaceMetadata system = keyspaces.get(CqlIdentifier.fromCql("system")); System.out.println("The system keyspace contains the following tables:"); for (TableMetadata table : system.getTables().values()) { @@ -16,7 +16,7 @@ Schema metadata is fully immutable (both the map and all the objects it contains snapshot of the database at the time of the last metadata refresh, and is consistent with the [token map](../token/) of its parent `Metadata` object. Keep in mind that `Metadata` is itself immutable; if you need to get the latest schema, be sure to call -`cluster.getMetadata().getKeyspaces()` again (and not just `getKeyspaces()` on a stale `Metadata` +`session.getMetadata().getKeyspaces()` again (and not just `getKeyspaces()` on a stale `Metadata` reference). @@ -33,7 +33,7 @@ SchemaChangeListener listener = System.out.println("New table: " + table.getName().asCql(true)); } }; -cluster.register(listener); +session.register(listener); session.execute("CREATE TABLE test.foo (k int PRIMARY KEY)"); ``` @@ -55,20 +55,20 @@ datastax-java-driver.metadata.schema.enabled = false If it is disabled at startup, [Metadata#getKeyspaces] will stay empty. If you disable it at runtime, it will keep the value of the last refresh. -You can achieve the same thing programmatically with [Cluster#setSchemaMetadataEnabled]: if you call +You can achieve the same thing programmatically with [Session#setSchemaMetadataEnabled]: if you call it with `true` or `false`, it overrides the configuration; if you pass `null`, it reverts to the value defined in the configuration. One case where that could come in handy is if you are sending a large number of DDL statements from your code: ```java // Disable temporarily, we'll do a single refresh once we're done -cluster.setSchemaMetadataEnabled(false); +session.setSchemaMetadataEnabled(false); for (int i = 0; i < 100; i++) { session.execute(String.format("CREATE TABLE test.foo%d (k int PRIMARY KEY)", i)); } -cluster.setSchemaMetadataEnabled(null); +session.setSchemaMetadataEnabled(null); ``` Whenever schema metadata was disabled and becomes enabled again (either through the configuration or @@ -175,11 +175,11 @@ if (rs.getExecutionInfo().isSchemaInAgreement()) { } ``` -You can also perform an on-demand check at any time with [Cluster#checkSchemaAgreementAsync] \(or +You can also perform an on-demand check at any time with [Session#checkSchemaAgreementAsync] \(or its synchronous counterpart): ```java -if (cluster.checkSchemaAgreement()) { +if (session.checkSchemaAgreement()) { ... } ``` @@ -210,8 +210,8 @@ unavailable for the excluded keyspaces. [Metadata#getKeyspaces]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/Metadata.html#getKeyspaces-- [SchemaChangeListener]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListener.html [SchemaChangeListenerBase]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/metadata/schema/SchemaChangeListenerBase.html -[Cluster#setSchemaMetadataEnabled]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#setSchemaMetadataEnabled-java.lang.Boolean- -[Cluster#checkSchemaAgreementAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/Cluster.html#checkSchemaAgreementAsync-- +[Session#setSchemaMetadataEnabled]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#setSchemaMetadataEnabled-java.lang.Boolean- +[Session#checkSchemaAgreementAsync]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#checkSchemaAgreementAsync-- [ExecutionInfo#isSchemaInAgreement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ExecutionInfo.html#isSchemaInAgreement-- [JAVA-750]: https://datastax-oss.atlassian.net/browse/JAVA-750 \ No newline at end of file diff --git a/manual/core/metadata/token/README.md b/manual/core/metadata/token/README.md index 5546071253f..bf81424260c 100644 --- a/manual/core/metadata/token/README.md +++ b/manual/core/metadata/token/README.md @@ -10,7 +10,7 @@ to access it, you can use either a functional pattern, or more traditionally tes `isPresent` and then unwrap: ```java -Metadata metadata = cluster.getMetadata(); +Metadata metadata = session.getMetadata(); metadata.getTokenMap().ifPresent(tokenMap -> { // do something with the map @@ -111,7 +111,7 @@ partitioner: ```java String pk = "johndoe@example.com"; // You need to manually encode the key as binary: -ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, cluster.getContext().protocolVersion()); +ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, session.getContext().protocolVersion()); Set nodes1 = tokenMap.getReplicas(CqlIdentifier.fromInternal("ks1"), encodedPk); // Assuming the key hashes to "1", it is in the ]12, 2] range diff --git a/manual/core/statements/simple/README.md b/manual/core/statements/simple/README.md index 0eac2e4c183..21d7dd632e2 100644 --- a/manual/core/statements/simple/README.md +++ b/manual/core/statements/simple/README.md @@ -157,9 +157,9 @@ In that situation, there is no way to hint at the correct type. Fortunately, you value manually as a workaround: ```java -TypeCodec codec = cluster.getContext().codecRegistry().codecFor(DataTypes.ASCII); +TypeCodec codec = session.getContext().codecRegistry().codecFor(DataTypes.ASCII); ByteBuffer bytes = - codec.encode("Touché sir, touché...", cluster.getContext().protocolVersion()); + codec.encode("Touché sir, touché...", session.getContext().protocolVersion()); session.execute( SimpleStatement.builder("INSERT INTO ascii_quotes (id, t) VALUES (?, ?)") diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java new file mode 100644 index 00000000000..105ffe254a7 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.cluster; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; + +public class CqlSessionRuleBuilder extends SessionRuleBuilder { + + public CqlSessionRuleBuilder(CassandraResourceRule cassandraResource) { + super(cassandraResource); + } + + @Override + public SessionRule build() { + return new SessionRule<>(cassandraResource, createKeyspace, nodeStateListeners, options); + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java similarity index 74% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java index ed0c82bd47e..bc63b6e2b2f 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultClusterBuilderInstantiator.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.ClusterBuilder; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.session.SessionBuilder; -public class DefaultClusterBuilderInstantiator { - public static ClusterBuilder builder() { - return Cluster.builder(); +public class DefaultSessionBuilderInstantiator { + public static SessionBuilder builder() { + return CqlSession.builder(); } public static String configPath() { diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java similarity index 60% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java index 82d2e9fceed..3c73ecf4ffd 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java @@ -15,17 +15,18 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; -import com.datastax.oss.driver.api.core.Cluster; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import org.junit.rules.ExternalResource; /** - * Creates and manages a {@link Cluster} instance for a test. + * Creates and manages a {@link Session} instance for a test. * *

          Use it in conjunction with a {@link CassandraResourceRule} that creates the server resource to * connect to: @@ -36,33 +37,29 @@ * // Or: public static @ClassRule SimulacronRule server = * // new SimulacronRule(ClusterSpec.builder().withNodes(3)); * - * public static @ClassRule ClusterRule cluster = new ClusterRule(server); + * public static @ClassRule SessionRule sessionRule = new SessionRule(server); * * public void @Test should_do_something() { - * cluster.session().execute("some query"); + * sessionRule.session().execute("some query"); * } * } * * Optionally, it can also create a dedicated keyspace (useful to isolate tests that share a common - * server), and initialize a session. + * server). * *

          If you would rather create a new keyspace manually in each test, see the utility methods in - * {@link ClusterUtils}. + * {@link SessionUtils}. */ -public class ClusterRule extends ExternalResource { +public class SessionRule extends ExternalResource { // the CCM or Simulacron rule to depend on private final CassandraResourceRule cassandraResource; private final NodeStateListener[] nodeStateListeners; private final CqlIdentifier keyspace; - private final boolean createDefaultSession; - private final String[] defaultClusterOptions; + private final String[] defaultOptions; - // the default cluster that is auto created for this rule. - private Cluster cluster; - - // the default session that is auto created for this rule and is tied to the given keyspace. - private T defaultSession; + // the session that is auto created for this rule and is tied to the given keyspace. + private SessionT session; private DriverConfigProfile slowProfile; @@ -71,21 +68,19 @@ public class ClusterRule extends ExternalResource { * * @param cassandraResource resource to create clusters for. */ - public static ClusterRuleBuilder builder( - CassandraResourceRule cassandraResource) { - return new ClusterRuleBuilder<>(cassandraResource); + public static CqlSessionRuleBuilder builder(CassandraResourceRule cassandraResource) { + return new CqlSessionRuleBuilder(cassandraResource); } /** @see #builder(CassandraResourceRule) */ - public ClusterRule(CassandraResourceRule cassandraResource, String... options) { - this(cassandraResource, true, true, new NodeStateListener[0], options); + public SessionRule(CassandraResourceRule cassandraResource, String... options) { + this(cassandraResource, true, new NodeStateListener[0], options); } /** @see #builder(CassandraResourceRule) */ - public ClusterRule( + public SessionRule( CassandraResourceRule cassandraResource, boolean createKeyspace, - boolean createDefaultSession, NodeStateListener[] nodeStateListeners, String... options) { this.cassandraResource = cassandraResource; @@ -93,46 +88,36 @@ public ClusterRule( this.keyspace = (cassandraResource instanceof SimulacronRule || !createKeyspace) ? null - : ClusterUtils.uniqueKeyspaceId(); - this.createDefaultSession = createDefaultSession; - this.defaultClusterOptions = options; + : SessionUtils.uniqueKeyspaceId(); + this.defaultOptions = options; } @Override protected void before() { - // ensure resource is initialized before initializing the defaultCluster. + // ensure resource is initialized before initializing the session. cassandraResource.setUp(); - cluster = ClusterUtils.newCluster(cassandraResource, nodeStateListeners, defaultClusterOptions); - - slowProfile = ClusterUtils.slowProfile(cluster); + session = SessionUtils.newSession(cassandraResource, null, nodeStateListeners, defaultOptions); + slowProfile = SessionUtils.slowProfile(session); if (keyspace != null) { - ClusterUtils.createKeyspace(cluster, keyspace, slowProfile); - } - if (createDefaultSession) { - defaultSession = cluster.connect(keyspace); + SessionUtils.createKeyspace(session, keyspace, slowProfile); + session.execute( + SimpleStatement.newInstance(String.format("USE %s", keyspace.asCql(false))), + Statement.SYNC); } } @Override protected void after() { if (keyspace != null) { - ClusterUtils.dropKeyspace(cluster, keyspace, slowProfile); + SessionUtils.dropKeyspace(session, keyspace, slowProfile); } - cluster.close(); + session.close(); } - /** @return the cluster created with this rule. */ - public Cluster cluster() { - return cluster; - } - - /** - * @return the default session created with this rule, or {@code null} if no default session was - * created. - */ - public T session() { - return defaultSession; + /** @return the session created with this rule. */ + public SessionT session() { + return session; } /** diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java similarity index 57% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java index 0f2480dc51d..69c9ec01b0e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java @@ -15,24 +15,24 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; -import com.datastax.oss.driver.api.core.cql.CqlSession; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -public class ClusterRuleBuilder { +public abstract class SessionRuleBuilder< + SelfT extends SessionRuleBuilder, SessionT extends Session> { - private final CassandraResourceRule cassandraResource; - private boolean createDefaultSession = true; - private boolean createKeyspace = true; - private String[] options = new String[] {}; - private NodeStateListener[] nodeStateListeners = new NodeStateListener[] {}; + protected final CassandraResourceRule cassandraResource; + protected boolean createKeyspace = true; + protected String[] options = new String[] {}; + protected NodeStateListener[] nodeStateListeners = new NodeStateListener[] {}; @SuppressWarnings("unchecked") protected final SelfT self = (SelfT) this; - public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { + public SessionRuleBuilder(CassandraResourceRule cassandraResource) { this.cassandraResource = cassandraResource; } @@ -41,35 +41,21 @@ public ClusterRuleBuilder(CassandraResourceRule cassandraResource) { * *

          If this is set, the rule will create a keyspace with a name unique to this test (this allows * multiple tests to run concurrently against the same server resource), and make the name - * available through {@link ClusterRule#keyspace()}. If a {@link #createDefaultSession default - * session} is created, it will be connected to this keyspace. + * available through {@link SessionRule#keyspace()}. The created session will be connected to this + * keyspace. * *

          If this method is not called, the default value is {@code true}. * *

          Note that this option is only valid with a {@link CcmRule}. If the server resource is a * {@link SimulacronRule}, this option is ignored, no keyspace gets created, and {@link - * ClusterRule#keyspace()} returns {@code null}. + * SessionRule#keyspace()} returns {@code null}. */ public SelfT withKeyspace(boolean createKeyspace) { this.createKeyspace = createKeyspace; return self; } - /** - * Whether to create a default session from the {@code Cluster}. - * - *

          If this is set, the rule will create a session and make it available through {@link - * ClusterRule#session()}. If a {@link #createKeyspace keyspace} was created, the session will be - * connected to it. - * - *

          If this method is not called, the default value is {@code true}. - */ - public SelfT withDefaultSession(boolean createDefaultSession) { - this.createDefaultSession = createDefaultSession; - return self; - } - - /** A set of options to override in the cluster configuration. */ + /** A set of options to override in the session configuration. */ public SelfT withOptions(String... options) { this.options = options; return self; @@ -80,8 +66,5 @@ public SelfT withNodeStateListeners(NodeStateListener... listeners) { return self; } - public ClusterRule build() { - return new ClusterRule<>( - cassandraResource, createKeyspace, createDefaultSession, nodeStateListeners, options); - } + public abstract SessionRule build(); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java similarity index 55% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java index 50000e7748e..d0d2f37c6ff 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/ClusterUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java @@ -15,26 +15,26 @@ */ package com.datastax.oss.driver.api.testinfra.cluster; -import com.datastax.oss.driver.api.core.Cluster; -import com.datastax.oss.driver.api.core.ClusterBuilder; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.cql.CqlSession; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Utility methods to manage {@link Cluster} instances manually. + * Utility methods to manage {@link Session} instances manually. * - *

          Use this if you need to initialize a new cluster instance in each test method: + *

          Use this if you need to initialize a new session instance in each test method: * *

          {@code
            * public static @ClassRule CcmRule server = CcmRule.getInstance();
          @@ -43,73 +43,79 @@
            * //    new SimulacronRule(ClusterSpec.builder().withNodes(3));
            *
            * public void @Test should_do_something() {
          - *   try (Cluster cluster = TestUtils.newCluster(server)) {
          - *     Session session = cluster.connect();
          + *   try (Session session = TestUtils.newSession(server)) {
            *     session.execute("some query");
            *   }
            * }
            * }
          * - * The instances returned by {@link #newCluster(CassandraResourceRule, NodeStateListener[], + * The instances returned by {@link #newSession(CassandraResourceRule, NodeStateListener[], * String...)} are not managed automatically, you need to close them yourself (this is done with a * try-with-resources block in the example above). * - *

          If you can share the same {@code Cluster} instance between all test methods, {@link - * ClusterRule} provides a simpler alternative. + *

          If you can share the same {@code Session} instance between all test methods, {@link + * SessionRule} provides a simpler alternative. */ -public class ClusterUtils { - private static final Logger LOG = LoggerFactory.getLogger(ClusterUtils.class); +public class SessionUtils { + private static final Logger LOG = LoggerFactory.getLogger(SessionUtils.class); private static final AtomicInteger keyspaceId = new AtomicInteger(); - private static final String CLUSTER_BUILDER_CLASS = + private static final String SESSION_BUILDER_CLASS = System.getProperty( - "cluster.builder", - "com.datastax.oss.driver.api.testinfra.cluster.DefaultClusterBuilderInstantiator"); + "session.builder", + "com.datastax.oss.driver.api.testinfra.cluster.DefaultSessionBuilderInstantiator"); @SuppressWarnings("unchecked") - public static ClusterBuilder> baseBuilder() { + public static SessionBuilder baseBuilder() { try { - Class clazz = Class.forName(CLUSTER_BUILDER_CLASS); + Class clazz = Class.forName(SESSION_BUILDER_CLASS); Method m = clazz.getMethod("builder"); - return (ClusterBuilder>) m.invoke(null); + return (SessionBuilder) m.invoke(null); } catch (Exception e) { LOG.warn( - "Could not construct ClusterBuilder from {}, using default implementation.", - CLUSTER_BUILDER_CLASS, + "Could not construct SessionBuilder from {}, using default implementation.", + SESSION_BUILDER_CLASS, e); - return (ClusterBuilder>) Cluster.builder(); + return (SessionBuilder) CqlSession.builder(); } } public static String getConfigPath() { try { - Class clazz = Class.forName(CLUSTER_BUILDER_CLASS); + Class clazz = Class.forName(SESSION_BUILDER_CLASS); Method m = clazz.getMethod("configPath"); return (String) m.invoke(null); } catch (Exception e) { - LOG.warn("Could not get config path from {}, using default.", CLUSTER_BUILDER_CLASS, e); + LOG.warn("Could not get config path from {}, using default.", SESSION_BUILDER_CLASS, e); return "datastax-java-driver"; } } /** - * Creates a new instance of the driver's default {@code Cluster} implementation, using the nodes + * Creates a new instance of the driver's default {@code Session} implementation, using the nodes * in the 0th DC of the provided Cassandra resource as contact points, and the default * configuration augmented with the provided options. */ - public static Cluster newCluster( + public static SessionT newSession( CassandraResourceRule cassandraResource, String... options) { - return newCluster(cassandraResource, new NodeStateListener[0], options); + return newSession(cassandraResource, null, new NodeStateListener[0], options); + } + + public static SessionT newSession( + CassandraResourceRule cassandraResource, CqlIdentifier keyspace, String... options) { + return newSession(cassandraResource, keyspace, new NodeStateListener[0], options); } @SuppressWarnings("unchecked") - public static Cluster newCluster( + public static SessionT newSession( CassandraResourceRule cassandraResource, + CqlIdentifier keyspace, NodeStateListener[] nodeStateListeners, String... options) { - return (Cluster) + return (SessionT) baseBuilder() .addContactPoints(cassandraResource.getContactPoints()) + .withKeyspace(keyspace) .addNodeStateListeners(nodeStateListeners) .withConfigLoader(new TestConfigLoader(options)) .build(); @@ -124,63 +130,59 @@ public static CqlIdentifier uniqueKeyspaceId() { return CqlIdentifier.fromCql("ks_" + keyspaceId.getAndIncrement()); } - /** Creates a keyspace through the given cluster instance, with the given profile. */ + /** Creates a keyspace through the given session instance, with the given profile. */ public static void createKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { - try (CqlSession session = cluster.connect()) { - SimpleStatement createKeyspace = - SimpleStatement.builder( - String.format( - "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", - keyspace.asCql(false))) - .withConfigProfile(profile) - .build(); - session.execute(createKeyspace); - } + Session session, CqlIdentifier keyspace, DriverConfigProfile profile) { + SimpleStatement createKeyspace = + SimpleStatement.builder( + String.format( + "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", + keyspace.asCql(false))) + .withConfigProfile(profile) + .build(); + session.execute(createKeyspace, Statement.SYNC); } /** - * Calls {@link #createKeyspace(Cluster, CqlIdentifier, DriverConfigProfile)} with {@link - * #slowProfile(Cluster)} as the third argument. + * Calls {@link #createKeyspace(Session, CqlIdentifier, DriverConfigProfile)} with {@link + * #slowProfile(Session)} as the third argument. * *

          Note that this creates a derived profile for each invocation, which has a slight performance - * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, + * overhead. Instead, consider building the profile manually with {@link #slowProfile(Session)}, * and storing it in a local variable so it can be reused. */ - public static void createKeyspace(Cluster cluster, CqlIdentifier keyspace) { - createKeyspace(cluster, keyspace, slowProfile(cluster)); + public static void createKeyspace(Session session, CqlIdentifier keyspace) { + createKeyspace(session, keyspace, slowProfile(session)); } - /** Drops a keyspace through the given cluster instance, with the given profile. */ + /** Drops a keyspace through the given session instance, with the given profile. */ public static void dropKeyspace( - Cluster cluster, CqlIdentifier keyspace, DriverConfigProfile profile) { - try (CqlSession session = cluster.connect()) { - session.execute( - SimpleStatement.builder( - String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql(false))) - .withConfigProfile(profile) - .build()); - } + Session session, CqlIdentifier keyspace, DriverConfigProfile profile) { + session.execute( + SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql(false))) + .withConfigProfile(profile) + .build(), + Statement.SYNC); } /** - * Calls {@link #dropKeyspace(Cluster, CqlIdentifier, DriverConfigProfile)} with {@link - * #slowProfile(Cluster)} as the third argument. + * Calls {@link #dropKeyspace(Session, CqlIdentifier, DriverConfigProfile)} with {@link + * #slowProfile(Session)} as the third argument. * *

          Note that this creates a derived profile for each invocation, which has a slight performance - * overhead. Instead, consider building the profile manually with {@link #slowProfile(Cluster)}, + * overhead. Instead, consider building the profile manually with {@link #slowProfile(Session)}, * and storing it in a local variable so it can be reused. */ - public static void dropKeyspace(Cluster cluster, CqlIdentifier keyspace) { - dropKeyspace(cluster, keyspace, slowProfile(cluster)); + public static void dropKeyspace(Session session, CqlIdentifier keyspace) { + dropKeyspace(session, keyspace, slowProfile(session)); } /** * Builds a profile derived from the given cluster's default profile, with a higher request * timeout (30 seconds) that is appropriate for DML operations. */ - public static DriverConfigProfile slowProfile(Cluster cluster) { - return cluster + public static DriverConfigProfile slowProfile(Session session) { + return session .getContext() .config() .getDefaultProfile() diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java index ef33367d975..e6f3ccb0b6d 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.testinfra.cluster; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.testinfra.cluster.ClusterUtils; +import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -41,6 +41,6 @@ private static Config buildConfig(String... customOptions) { public static Config getConfig() { ConfigFactory.invalidateCaches(); - return ConfigFactory.load().getConfig(ClusterUtils.getConfigPath()); + return ConfigFactory.load().getConfig(SessionUtils.getConfigPath()); } } diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index fd928699cdd..4b4297ec006 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -34,13 +34,37 @@ For more details, refer to the [manual](../manual/core/configuration). #### Expose interfaces, not classes -Most types in the public API are now interfaces (as opposed to 3.x: `Cluster`, statement classes, +Most types in the public API are now interfaces (as opposed to 3.x: `Session`, statement classes, etc). The actual implementations are part of the internal API. This provides more flexibility in client code (e.g. to wrap them and write delegates). Thanks to Java 8, factory methods can now be part of these interfaces directly, e.g. -`Cluster.builder()`, `SimpleStatement.newInstance`. +`CqlSession.builder()`, `SimpleStatement.newInstance`. +#### No more `Cluster` + +In previous driver versions, initialization was done in two steps: create a `Cluster`, and then call +its `connect` method to create a `Session`. + +Those two types have now been merged: there is only one `Session` object, that you initialize +directly. + +#### Generic session API + +`Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, +the driver exposes a more familiar subtype `CqlSession`, that provides familiar signatures for CQL +queries (`execute(Statement)`, `prepare(String)`, etc). + +However, the request execution logic is completely pluggable, and supports arbitrary request types +(as long as you write the boilerplate to convert them to protocol messages). In the future, we will +take advantage of that to provide: + +* a reactive API; +* a high-performance implementation that exposes bare Netty buffers; +* specialized requests in our DataStax Enterprise driver. + +If you're interested, take a look at `RequestProcessor`. + #### Immutable statement types Simple, bound and batch statements implementations are now all immutable. This makes them @@ -64,22 +88,6 @@ boundSelect = boundSelect.setInt("k", key); Note that, as indicated in the previous section, the public API exposes these types as interfaces: if for some reason you prefer a mutable implementation, it's possible to write your own. -#### Generic session API - -`Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, -the driver supports the same CQL queries as 3.x, and exposes familiar signatures -(`execute(Statement)`, `prepare(String)`, etc). - -However, the request execution logic is completely pluggable, and supports arbitrary request types -as long as you write the boilerplate to convert them to protocol messages. In the future, we will -take advantage of that to provide: - -* a reactive API; -* a high-performance implementation that exposes bare Netty buffers; -* specialized requests in our DataStax Enterprise driver. - -If you're interested, take a look at `RequestProcessor`. - #### Dual result set APIs In 3.x, both synchronous and asynchronous execution models shared a common result set @@ -120,7 +128,7 @@ the same rules as in 3.x (see `GettableById` and `GettableByName` for details). #### Atomic metadata updates -`Cluster.getMetadata()` is now immutable and updated atomically. The node list, schema metadata and +`Session.getMetadata()` is now immutable and updated atomically. The node list, schema metadata and token map exposed by a given `Metadata` instance are guaranteed to be in sync. On the other hand, this means you have to call `getMetadata()` again each time you need a fresh From b8819250faeba0b91f1e0e0c1dec84f4bfb958b9 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 23 Jan 2018 11:21:06 -0800 Subject: [PATCH 299/742] Enforce formatting conventions in CqlDuration --- .../oss/driver/api/core/data/CqlDuration.java | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java index 90c30fa775e..389cde6c65e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -123,10 +123,12 @@ public static CqlDuration from(String input) { String source = isNegative ? input.substring(1) : input; if (source.startsWith("P")) { - if (source.endsWith("W")) return parseIso8601WeekFormat(isNegative, source); - - if (source.contains("-")) return parseIso8601AlternativeFormat(isNegative, source); - + if (source.endsWith("W")) { + return parseIso8601WeekFormat(isNegative, source); + } + if (source.contains("-")) { + return parseIso8601AlternativeFormat(isNegative, source); + } return parseIso8601Format(isNegative, source); } return parseStandardFormat(isNegative, source); @@ -139,29 +141,36 @@ private static CqlDuration parseIso8601Format(boolean isNegative, String source) String.format("Unable to convert '%s' to a duration", source)); Builder builder = new Builder(isNegative); - if (matcher.group(1) != null) builder.addYears(groupAsLong(matcher, 2)); - - if (matcher.group(3) != null) builder.addMonths(groupAsLong(matcher, 4)); - - if (matcher.group(5) != null) builder.addDays(groupAsLong(matcher, 6)); - + if (matcher.group(1) != null) { + builder.addYears(groupAsLong(matcher, 2)); + } + if (matcher.group(3) != null) { + builder.addMonths(groupAsLong(matcher, 4)); + } + if (matcher.group(5) != null) { + builder.addDays(groupAsLong(matcher, 6)); + } // Checks if the String contains time information if (matcher.group(7) != null) { - if (matcher.group(8) != null) builder.addHours(groupAsLong(matcher, 9)); - - if (matcher.group(10) != null) builder.addMinutes(groupAsLong(matcher, 11)); - - if (matcher.group(12) != null) builder.addSeconds(groupAsLong(matcher, 13)); + if (matcher.group(8) != null) { + builder.addHours(groupAsLong(matcher, 9)); + } + if (matcher.group(10) != null) { + builder.addMinutes(groupAsLong(matcher, 11)); + } + if (matcher.group(12) != null) { + builder.addSeconds(groupAsLong(matcher, 13)); + } } return builder.build(); } private static CqlDuration parseIso8601AlternativeFormat(boolean isNegative, String source) { Matcher matcher = ISO8601_ALTERNATIVE_PATTERN.matcher(source); - if (!matcher.matches()) + if (!matcher.matches()) { throw new IllegalArgumentException( String.format("Unable to convert '%s' to a duration", source)); - + } return new Builder(isNegative) .addYears(groupAsLong(matcher, 1)) .addMonths(groupAsLong(matcher, 2)) @@ -174,19 +183,19 @@ private static CqlDuration parseIso8601AlternativeFormat(boolean isNegative, Str private static CqlDuration parseIso8601WeekFormat(boolean isNegative, String source) { Matcher matcher = ISO8601_WEEK_PATTERN.matcher(source); - if (!matcher.matches()) + if (!matcher.matches()) { throw new IllegalArgumentException( String.format("Unable to convert '%s' to a duration", source)); - + } return new Builder(isNegative).addWeeks(groupAsLong(matcher, 1)).build(); } private static CqlDuration parseStandardFormat(boolean isNegative, String source) { Matcher matcher = STANDARD_PATTERN.matcher(source); - if (!matcher.find()) + if (!matcher.find()) { throw new IllegalArgumentException( String.format("Unable to convert '%s' to a duration", source)); - + } Builder builder = new Builder(isNegative); boolean done; @@ -197,10 +206,10 @@ private static CqlDuration parseStandardFormat(boolean isNegative, String source done = matcher.end() == source.length(); } while (matcher.find()); - if (!done) + if (!done) { throw new IllegalArgumentException( String.format("Unable to convert '%s' to a duration", source)); - + } return builder.build(); } @@ -244,8 +253,9 @@ private static Builder add(Builder builder, long number, String symbol) { * @return the remainder of the division */ private static long append(StringBuilder builder, long dividend, long divisor, String unit) { - if (dividend == 0 || dividend < divisor) return dividend; - + if (dividend == 0 || dividend < divisor) { + return dividend; + } builder.append(dividend / divisor).append(unit); return dividend % divisor; } @@ -300,8 +310,9 @@ public int hashCode() { public String toString() { StringBuilder builder = new StringBuilder(); - if (months < 0 || days < 0 || nanoseconds < 0) builder.append('-'); - + if (months < 0 || days < 0 || nanoseconds < 0) { + builder.append('-'); + } long remainder = append(builder, Math.abs(months), MONTHS_PER_YEAR, "y"); append(builder, remainder, 1, "mo"); @@ -512,17 +523,17 @@ private void validate(long units, long limit, String unitName) { * @param unitIndex the unit index (e.g. years=1, months=2, ...) */ private void validateOrder(int unitIndex) { - if (unitIndex == currentUnitIndex) + if (unitIndex == currentUnitIndex) { throw new IllegalArgumentException( String.format( "Invalid duration. The %s are specified multiple times", getUnitName(unitIndex))); - - if (unitIndex <= currentUnitIndex) + } + if (unitIndex <= currentUnitIndex) { throw new IllegalArgumentException( String.format( "Invalid duration. The %s should be after %s", getUnitName(currentUnitIndex), getUnitName(unitIndex))); - + } currentUnitIndex = unitIndex; } From 6a51aab120a92931da7be1a5fb6de9966176ff70 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 23 Jan 2018 12:54:53 -0800 Subject: [PATCH 300/742] Configure ErrorProne in build --- pom.xml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index e6f574e72be..ce1c7bdf0cb 100644 --- a/pom.xml +++ b/pom.xml @@ -216,14 +216,28 @@ maven-compiler-plugin + javac-with-errorprone + true 1.8 1.8 - true - true + + -Xep:FutureReturnValueIgnored:OFF + true - - false + true + + + org.codehaus.plexus + plexus-compiler-javac-errorprone + 2.8 + + + com.google.errorprone + error_prone_core + 2.2.0 + + com.coveo From adebb9215d4342fd569c60b59f28f66bf3e3948a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 23 Jan 2018 12:55:54 -0800 Subject: [PATCH 301/742] Address ErrorProne errors --- .../oss/driver/api/core/CassandraVersion.java | 2 +- .../metadata/schema/parsing/TableParser.java | 5 ++++ .../metadata/schema/parsing/ViewParser.java | 2 ++ .../metadata/token/Murmur3TokenFactory.java | 15 +++++++++- .../codec/registry/DefaultCodecRegistry.java | 30 +++++++++++-------- .../core/util/concurrent/CycleDetector.java | 2 +- .../core/type/codec/TupleCodecTest.java | 2 +- .../core/type/codec/UdtCodecTest.java | 2 +- .../oss/driver/api/core/data/DataTypeIT.java | 3 ++ .../type/codec/registry/CodecRegistryIT.java | 2 +- 10 files changed, 46 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java index 79d65894cb7..0d7773fc4a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CassandraVersion.java @@ -271,7 +271,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(major, minor, patch, dsePatch, preReleases, build); + return Objects.hash(major, minor, patch, dsePatch, Arrays.hashCode(preReleases), build); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java index 8bd530ee556..c983dff5394 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -179,6 +179,8 @@ TableMetadata parseTable( clusteringColumnsBuilder.put( column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); break; + default: + // nothing to do } allColumnsBuilder.put(column.getName(), column); @@ -237,6 +239,9 @@ private void pruneStaticCompactTableColumns(List columns) { break; case STATIC: column.kind = RawColumn.Kind.REGULAR; + break; + default: + // nothing to do } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java index c79b0d5c774..209046c2d1d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java @@ -116,6 +116,8 @@ ViewMetadata parseView( clusteringColumnsBuilder.put( column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); break; + default: + // nothing to do } allColumnsBuilder.put(column.getName(), column); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java index 1f0c27d06b2..d1a846795a2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java @@ -106,37 +106,50 @@ private long murmur(ByteBuffer data) { switch (length & 15) { case 15: k2 ^= ((long) data.get(offset + 14)) << 48; + // fall through case 14: k2 ^= ((long) data.get(offset + 13)) << 40; + // fall through case 13: k2 ^= ((long) data.get(offset + 12)) << 32; + // fall through case 12: k2 ^= ((long) data.get(offset + 11)) << 24; + // fall through case 11: k2 ^= ((long) data.get(offset + 10)) << 16; + // fall through case 10: k2 ^= ((long) data.get(offset + 9)) << 8; + // fall through case 9: k2 ^= ((long) data.get(offset + 8)); k2 *= c2; k2 = rotl64(k2, 33); k2 *= c1; h2 ^= k2; - + // fall through case 8: k1 ^= ((long) data.get(offset + 7)) << 56; + // fall through case 7: k1 ^= ((long) data.get(offset + 6)) << 48; + // fall through case 6: k1 ^= ((long) data.get(offset + 5)) << 40; + // fall through case 5: k1 ^= ((long) data.get(offset + 4)) << 32; + // fall through case 4: k1 ^= ((long) data.get(offset + 3)) << 24; + // fall through case 3: k1 ^= ((long) data.get(offset + 2)) << 16; + // fall through case 2: k1 ^= ((long) data.get(offset + 1)) << 8; + // fall through case 1: k1 ^= ((long) data.get(offset)); k1 *= c1; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java index 87baa794a80..24b9566f32b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java @@ -67,21 +67,25 @@ public DefaultCodecRegistry( if (cacheWeigher != null) { cacheBuilder.weigher(cacheWeigher::apply).maximumWeight(maximumCacheWeight); } + CacheLoader> cacheLoader = + new CacheLoader>() { + @Override + public TypeCodec load(CacheKey key) throws Exception { + return createCodec(key.cqlType, key.javaType); + } + }; if (cacheRemovalListener != null) { - //noinspection ResultOfMethodCallIgnored - cacheBuilder.removalListener( - (RemovalListener>) - notification -> - cacheRemovalListener.accept(notification.getKey(), notification.getValue())); + this.cache = + cacheBuilder + .removalListener( + (RemovalListener>) + notification -> + cacheRemovalListener.accept( + notification.getKey(), notification.getValue())) + .build(cacheLoader); + } else { + this.cache = cacheBuilder.build(cacheLoader); } - this.cache = - cacheBuilder.build( - new CacheLoader>() { - @Override - public TypeCodec load(CacheKey key) throws Exception { - return createCodec(key.cqlType, key.javaType); - } - }); } public DefaultCodecRegistry(String logPrefix, TypeCodec... userCodecs) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java index 30fec870fb0..b2c73d7aac9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetector.java @@ -74,7 +74,7 @@ void onReleaseLock(LazyReference reference) { synchronized (this) { Thread me = Thread.currentThread(); LOG.debug("{} is done initializing {}", me, reference.getName()); - graph.removeEdge(reference.getName(), me); + graph.removeEdge(reference.getName(), me.getName()); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index 900b61d72a8..55c5c18688b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -150,7 +150,7 @@ public void should_parse_tuple() { TupleValue tuple = parse("(1,NULL,'a')"); assertThat(tuple.getInt(0)).isEqualTo(1); - assertThat(tuple.isNull(1)); + assertThat(tuple.isNull(1)).isTrue(); assertThat(tuple.getString(2)).isEqualTo("a"); Mockito.verify(intCodec).parse("1"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 74a769963bb..51daa2da56e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -159,7 +159,7 @@ public void should_parse_udt() { UdtValue udt = parse("{field1:1,field2:NULL,field3:'a'}"); assertThat(udt.getInt(0)).isEqualTo(1); - assertThat(udt.isNull(1)); + assertThat(udt.isNull(1)).isTrue(); assertThat(udt.getString(2)).isEqualTo("a"); Mockito.verify(intCodec).parse("1"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 04914c48e04..a050af1e406 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -503,6 +503,7 @@ private static > S setValue( bs = bs.setCqlDuration(index, (CqlDuration) value); break; } + // fall through case ProtocolConstants.DataType.LIST: case ProtocolConstants.DataType.SET: case ProtocolConstants.DataType.MAP: @@ -593,6 +594,7 @@ private static > S setValue( bs = bs.setCqlDuration(name, (CqlDuration) value); break; } + // fall through case ProtocolConstants.DataType.LIST: case ProtocolConstants.DataType.SET: case ProtocolConstants.DataType.MAP: @@ -702,6 +704,7 @@ private void readValue( assertThat(row.getCqlDuration(0)).isEqualTo(expectedValue); break; } + // fall through case ProtocolConstants.DataType.LIST: case ProtocolConstants.DataType.MAP: case ProtocolConstants.DataType.SET: diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 2e030d75678..e549229e9a2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -340,7 +340,7 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() // next row (at key 1) should be absent (null value). row = rows.next(); // value should be null. - assertThat(row.isNull(0)); + assertThat(row.isNull(0)).isTrue(); // getting with codec should return Optional.empty() assertThat(row.get(0, optionalMapCodec.getJavaType())).isEqualTo(absentMap); // getting with map should return an empty map. From 3423e01496763490b7ec8782765cbc72d60457b2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 23 Jan 2018 14:18:54 -0800 Subject: [PATCH 302/742] Address ErrorProne warnings --- .../driver/api/core/CoreProtocolVersion.java | 1 + .../api/core/config/DriverConfigLoader.java | 1 + .../ExponentialReconnectionPolicy.java | 2 +- .../api/core/cql/SimpleStatementBuilder.java | 1 + .../oss/driver/api/core/data/CqlDuration.java | 11 ++++++---- .../oss/driver/api/core/type/CustomType.java | 1 + .../oss/driver/api/core/type/ListType.java | 1 + .../oss/driver/api/core/type/MapType.java | 1 + .../oss/driver/api/core/type/SetType.java | 1 + .../oss/driver/api/core/type/TupleType.java | 1 + .../driver/api/core/type/UserDefinedType.java | 1 + .../core/channel/ChannelHandlerRequest.java | 3 ++- .../internal/core/channel/DriverChannel.java | 15 ++++++++----- .../internal/core/cql/DefaultTraceEvent.java | 1 + .../internal/core/cql/MultiPageResultSet.java | 5 +++-- .../internal/core/metadata/DefaultNode.java | 1 + .../metadata/schema/DefaultIndexMetadata.java | 1 + .../schema/parsing/RelationParser.java | 2 +- .../metadata/schema/parsing/TableParser.java | 6 ++--- .../queries/DefaultSchemaQueriesFactory.java | 1 + .../internal/core/session/ReprepareOnUp.java | 4 ++-- .../internal/core/type/util/VIntCoding.java | 4 ++-- .../internal/core/util/DirectedGraph.java | 4 ++-- .../driver/internal/core/util/Strings.java | 2 +- .../api/core/CassandraVersionAssert.java | 1 + .../ChannelFactoryAvailableIdsTest.java | 1 + .../core/channel/ChannelFactoryTestBase.java | 2 +- .../core/channel/DriverChannelTest.java | 4 ++-- .../channel/MockChannelFactoryHelper.java | 5 +++-- .../core/channel/MockResponseCallback.java | 4 ++-- .../core/channel/ProtocolInitHandlerTest.java | 2 +- .../core/cql/CqlRequestHandlerTestBase.java | 4 ++-- .../core/cql/DefaultAsyncResultSetTest.java | 18 +++++++-------- .../DefaultLoadBalancingPolicyEventsTest.java | 1 + ...faultLoadBalancingPolicyQueryPlanTest.java | 1 + .../metadata/DefaultTopologyMonitorTest.java | 4 ++-- .../core/metadata/MetadataManagerTest.java | 2 +- .../metadata/SchemaAgreementCheckerTest.java | 4 ++-- .../schema/parsing/TableParserTest.java | 6 ++--- .../schema/parsing/ViewParserTest.java | 2 +- .../queries/Cassandra3SchemaQueriesTest.java | 1 + .../session/MockChannelPoolFactoryHelper.java | 5 +++-- .../core/session/ReprepareOnUpTest.java | 6 ++--- .../util/concurrent/CycleDetectorTest.java | 1 + .../ProtocolVersionInitialNegotiationIT.java | 2 ++ .../config/DriverConfigProfileReloadIT.java | 7 +++--- .../oss/driver/api/core/data/DataTypeIT.java | 22 +++++++++++-------- .../api/core/heartbeat/HeartbeatIT.java | 2 +- .../driver/api/core/metadata/NodeStateIT.java | 14 ++---------- .../api/core/retry/DefaultRetryPolicyIT.java | 3 +++ .../type/codec/registry/CodecRegistryIT.java | 4 ++-- .../guava/internal/KeyRequestProcessor.java | 2 +- .../SortingLoadBalancingPolicy.java | 5 ++--- 53 files changed, 118 insertions(+), 88 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java index f8304dd6e94..ae56e403d64 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java @@ -47,6 +47,7 @@ public enum CoreProtocolVersion implements ProtocolVersion { this.beta = beta; } + @Override public int getCode() { return code; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java index 768cf9b59fb..137d4189cf9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java @@ -35,5 +35,6 @@ public interface DriverConfigLoader extends AutoCloseable { * Called when the cluster closes. This is a good time to release any external resource, for * example cancel a scheduled reloading task. */ + @Override void close(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index c65688706e8..8fd943f4738 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -58,7 +58,7 @@ public ExponentialReconnectionPolicy(DriverContext context) { // Maximum number of attempts after which we overflow int ceil = (baseDelayMs & (baseDelayMs - 1)) == 0 ? 0 : 1; - this.maxAttempts = 64 - Long.numberOfLeadingZeros(Long.MAX_VALUE / baseDelayMs) - ceil; + this.maxAttempts = 64L - Long.numberOfLeadingZeros(Long.MAX_VALUE / baseDelayMs) - ceil; } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index dc0252249e4..4077096ca23 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -121,6 +121,7 @@ public SimpleStatementBuilder clearNamedValues() { return this; } + @Override public SimpleStatement build() { return new DefaultSimpleStatement( query, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java index 389cde6c65e..1b52670473f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -351,7 +351,10 @@ public Builder(boolean isNegative) { public Builder addYears(long numberOfYears) { validateOrder(1); validateMonths(numberOfYears, MONTHS_PER_YEAR); - months += numberOfYears * MONTHS_PER_YEAR; + // Cast to avoid http://errorprone.info/bugpattern/NarrowingCompoundAssignment + // We could also change the method to accept an int, but keeping long allows us to keep the + // calling code generic. + months += (int) numberOfYears * MONTHS_PER_YEAR; return this; } @@ -364,7 +367,7 @@ public Builder addYears(long numberOfYears) { public Builder addMonths(long numberOfMonths) { validateOrder(2); validateMonths(numberOfMonths, 1); - months += numberOfMonths; + months += (int) numberOfMonths; return this; } @@ -377,7 +380,7 @@ public Builder addMonths(long numberOfMonths) { public Builder addWeeks(long numberOfWeeks) { validateOrder(3); validateDays(numberOfWeeks, DAYS_PER_WEEK); - days += numberOfWeeks * DAYS_PER_WEEK; + days += (int) numberOfWeeks * DAYS_PER_WEEK; return this; } @@ -390,7 +393,7 @@ public Builder addWeeks(long numberOfWeeks) { public Builder addDays(long numberOfDays) { validateOrder(4); validateDays(numberOfDays, 1); - days += numberOfDays; + days += (int) numberOfDays; return this; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java index 67beef478ab..0a22b61f5a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/CustomType.java @@ -29,6 +29,7 @@ default String asCql(boolean includeFrozen, boolean pretty) { return String.format("'%s'", getClassName()); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.CUSTOM; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java index 7aa880aa231..32c362e3b55 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/ListType.java @@ -28,6 +28,7 @@ default String asCql(boolean includeFrozen, boolean pretty) { return String.format(template, getElementType().asCql(includeFrozen, pretty)); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.LIST; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java index c5be9a3947b..0475bf4632d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/MapType.java @@ -33,6 +33,7 @@ default String asCql(boolean includeFrozen, boolean pretty) { getValueType().asCql(includeFrozen, pretty)); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.MAP; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java index 9e42d25254a..34a2ca6f0d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/SetType.java @@ -28,6 +28,7 @@ default String asCql(boolean includeFrozen, boolean pretty) { return String.format(template, getElementType().asCql(includeFrozen, pretty)); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.SET; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java index 15b49c4cece..7587997b98a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/TupleType.java @@ -47,6 +47,7 @@ default String asCql(boolean includeFrozen, boolean pretty) { return builder.toString(); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.TUPLE; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java index 1d87e38cc14..63f8c802ad7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java @@ -91,6 +91,7 @@ default String describeWithChildren(boolean pretty) { return describe(pretty); } + @Override default int getProtocolCode() { return ProtocolConstants.DataType.UDT; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java index f0969487cac..212d43232e1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerRequest.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.response.Error; @@ -91,7 +92,7 @@ private void onTimeout() { fail(new DriverTimeoutException(describe() + ": timed out after " + timeoutMillis + " ms")); if (!channel.closeFuture().isDone()) { // Cancel the response callback - channel.writeAndFlush(this); + channel.writeAndFlush(this).addListener(UncaughtExceptions::log); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index da5e9a9e175..4666f8cc086 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -82,7 +83,7 @@ public Future write( public void cancel(ResponseCallback responseCallback) { // To avoid creating an extra message, we adopt the convention that writing the callback // directly means cancellation - writeCoalescer.writeAndFlush(channel, responseCallback); + writeCoalescer.writeAndFlush(channel, responseCallback).addListener(UncaughtExceptions::log); } /** @@ -149,10 +150,12 @@ public SocketAddress localAddress() { * be allowed to complete before the underlying channel is closed. */ public Future close() { - if (closing.compareAndSet(false, true)) { + if (closing.compareAndSet(false, true) && channel.isOpen()) { // go through the coalescer: this guarantees that we won't reject writes that were submitted // before, but had not been coalesced yet. - writeCoalescer.writeAndFlush(channel, GRACEFUL_CLOSE_MESSAGE); + writeCoalescer + .writeAndFlush(channel, GRACEFUL_CLOSE_MESSAGE) + .addListener(UncaughtExceptions::log); } return channel.closeFuture(); } @@ -163,8 +166,10 @@ public Future close() { */ public Future forceClose() { this.close(); - if (forceClosing.compareAndSet(false, true)) { - writeCoalescer.writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE); + if (forceClosing.compareAndSet(false, true) && channel.isOpen()) { + writeCoalescer + .writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE) + .addListener(UncaughtExceptions::log); } return channel.closeFuture(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java index 198cf5019e2..7dffbe31f3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java @@ -56,6 +56,7 @@ public InetAddress getSource() { return source; } + @Override public int getSourceElapsedMicros() { return sourceElapsedMicros; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index 8a205733d74..b0b0bf69128 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -24,9 +24,10 @@ import com.datastax.oss.driver.internal.core.util.CountingIterator; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; public class MultiPageResultSet implements ResultSet { @@ -81,7 +82,7 @@ public boolean wasApplied() { private class RowIterator extends CountingIterator { // The pages fetched so far. The first is the one we're currently iterating. - private LinkedList pages = new LinkedList<>(); + private Deque pages = new ArrayDeque<>(); private Iterator currentRows; private RowIterator(AsyncResultSet firstPage) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 2e975cc00a1..20f649aaa5f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -98,6 +98,7 @@ public NodeState getState() { return state; } + @Override public int getOpenConnections() { return openConnections; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java index 01346006cc5..d1a538942af 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java @@ -70,6 +70,7 @@ public String getTarget() { return target; } + @Override public Map getOptions() { return options; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java index 7902ee6a7c8..8876d871ab4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -114,7 +114,7 @@ public static void appendOptions(Map options, ScriptBuild * The columns of the system table that are turned into entries in {@link * RelationMetadata#getOptions()}. */ - public static final Map> OPTION_CODECS = + public static final ImmutableMap> OPTION_CODECS = ImmutableMap.>builder() .put("bloom_filter_fp_chance", TypeCodecs.DOUBLE) // In C* <= 2.2, this is a string, not a map (this is special-cased in parseOptions): diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java index c983dff5394..7f81aa7c582 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -292,9 +292,9 @@ private static String buildLegacyIndexTarget(ColumnMetadata column, Map refreshFuture) { String logPrefix = context.sessionName(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index b27fb11d6fe..e18046a41e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -33,9 +33,9 @@ import com.google.common.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.time.Duration; +import java.util.ArrayDeque; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; @@ -161,7 +161,7 @@ private void gatherServerIds(AdminResult rows, Throwable error) { private void gatherPayloadsToReprepare() { assert channel.eventLoop().inEventLoop(); - toReprepare = new LinkedList<>(); + toReprepare = new ArrayDeque<>(); for (RepreparePayload payload : repreparePayloads.values()) { if (serverKnownIds.contains(payload.id)) { LOG.trace( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java index e3a508452b6..731d1139046 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java @@ -96,9 +96,9 @@ private static int firstByteValueMask(int extraBytesToRead) { return 0xff >> extraBytesToRead; } - private static int encodeExtraBytesToRead(int extraBytesToRead) { + private static byte encodeExtraBytesToRead(int extraBytesToRead) { // because we have an extra bit in the value mask, we just need to invert it - return ~firstByteValueMask(extraBytesToRead); + return (byte) ~firstByteValueMask(extraBytesToRead); } private static int numberOfExtraBytesToRead(int firstByte) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java index 6fc24bf8f2a..a0939eb3bc7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java @@ -21,9 +21,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -67,7 +67,7 @@ public List topologicalSort() { Preconditions.checkState(!wasSorted); wasSorted = true; - Queue queue = new LinkedList(); + Queue queue = new ArrayDeque<>(); for (Map.Entry entry : vertices.entrySet()) { if (entry.getValue() == 0) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java index 196e4d9a1df..1444d6ca9fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java @@ -255,7 +255,7 @@ public static boolean isLongLiteral(String str) { private Strings() {} - private static final Set RESERVED_KEYWORDS = + private static final ImmutableSet RESERVED_KEYWORDS = ImmutableSet.of( // See https://github.com/apache/cassandra/blob/trunk/doc/cql3/CQL.textile#appendixA "add", diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java index a97af4b694c..268092ad68f 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java @@ -59,6 +59,7 @@ public CassandraVersionAssert hasNextStable(String version) { return this; } + @Override public CassandraVersionAssert hasToString(String string) { Assertions.assertThat(actual.toString()).isEqualTo(string); return this; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index c356305cf8d..3f4a66ea6d9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -36,6 +36,7 @@ public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Mock private ResponseCallback responseCallback; @Before + @Override public void setup() throws InterruptedException { super.setup(); Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index bc25095b5df..9689ce80083 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -121,7 +121,7 @@ public void setup() throws InterruptedException { Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) - .thenReturn(Duration.ofMillis(30000)); + .thenReturn(Duration.ofSeconds(30)); Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 6d3c4d2ee3a..cf874b3c8fe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -25,7 +25,7 @@ import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Future; import java.util.AbstractMap; -import java.util.LinkedList; +import java.util.ArrayDeque; import java.util.Map; import java.util.Queue; import org.junit.Before; @@ -142,7 +142,7 @@ public void should_wait_for_coalesced_writes_when_closing_forcefully() { // Simple implementation that holds all the writes, and flushes them when it's explicitly // triggered. private class MockWriteCoalescer implements WriteCoalescer { - private Queue> messages = new LinkedList<>(); + private Queue> messages = new ArrayDeque<>(); @Override public ChannelFuture writeAndFlush(Channel channel, Object message) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index 08578459b0f..d6fd9de2640 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -20,8 +20,9 @@ import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; import java.net.SocketAddress; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -148,7 +149,7 @@ public MockChannelFactoryHelper build() { private void stub() { for (SocketAddress address : invocations.keySet()) { - LinkedList> results = new LinkedList<>(); + Deque> results = new ArrayDeque<>(); for (Object object : invocations.get(address)) { if (object instanceof DriverChannel) { results.add(CompletableFuture.completedFuture(((DriverChannel) object))); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java index c0bb98ee640..d5360d63120 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java @@ -16,12 +16,12 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.protocol.internal.Frame; -import java.util.LinkedList; +import java.util.ArrayDeque; import java.util.Queue; class MockResponseCallback implements ResponseCallback { private final boolean holdStreamId; - private final Queue responses = new LinkedList<>(); + private final Queue responses = new ArrayDeque<>(); volatile int streamId = -1; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index a9ee0a8e621..d75bad658fa 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -93,7 +93,7 @@ public void setup() { Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) - .thenReturn(Duration.ofMillis(30000)); + .thenReturn(Duration.ofSeconds(30)); Mockito.when( defaultConfigProfile.getBoolean(CoreDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH)) .thenReturn(true); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index b3c2ded3481..a1b3a55095f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -31,8 +31,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.junit.Before; @@ -83,7 +83,7 @@ protected static Message singleRow() { null, new int[] {}, null); - Queue> data = new LinkedList<>(); + Queue> data = new ArrayDeque<>(); data.add(ImmutableList.of(Bytes.fromHexString("0x68656C6C6F2C20776F726C64"))); return new DefaultRows(metadata, data); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 3fa049f9b8a..12906c0543f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -16,12 +16,12 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -29,7 +29,7 @@ import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.Lists; import java.nio.ByteBuffer; -import java.util.LinkedList; +import java.util.ArrayDeque; import java.util.List; import java.util.Queue; import java.util.concurrent.CompletableFuture; @@ -67,7 +67,7 @@ public void should_fail_to_fetch_next_page_if_last() { // When DefaultAsyncResultSet resultSet = new DefaultAsyncResultSet( - columnDefinitions, executionInfo, new LinkedList<>(), session, context); + columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); // Then assertThat(resultSet.hasMorePages()).isFalse(); @@ -89,7 +89,7 @@ public void should_invoke_session_to_fetch_next_page() { // When DefaultAsyncResultSet resultSet = new DefaultAsyncResultSet( - columnDefinitions, executionInfo, new LinkedList<>(), session, context); + columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); assertThat(resultSet.hasMorePages()).isTrue(); CompletionStage nextPageFuture = resultSet.fetchNextPage(); @@ -107,7 +107,7 @@ public void should_report_applied_if_column_not_present_and_empty() { // When DefaultAsyncResultSet resultSet = new DefaultAsyncResultSet( - columnDefinitions, executionInfo, new LinkedList<>(), session, context); + columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); // Then assertThat(resultSet.wasApplied()).isTrue(); @@ -117,7 +117,7 @@ public void should_report_applied_if_column_not_present_and_empty() { public void should_report_applied_if_column_not_present_and_not_empty() { // Given Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(false); - Queue> data = new LinkedList<>(); + Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(Bytes.fromHexString("0xffff"))); // When @@ -138,7 +138,7 @@ public void should_report_not_applied_if_column_present_and_false() { Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); - Queue> data = new LinkedList<>(); + Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(false, CoreProtocolVersion.DEFAULT))); // When @@ -159,7 +159,7 @@ public void should_report_not_applied_if_column_present_and_true() { Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); - Queue> data = new LinkedList<>(); + Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(true, CoreProtocolVersion.DEFAULT))); // When @@ -181,7 +181,7 @@ public void should_fail_to_report_if_applied_if_column_present_but_empty() { // When DefaultAsyncResultSet resultSet = new DefaultAsyncResultSet( - columnDefinitions, executionInfo, new LinkedList<>(), session, context); + columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); // Then resultSet.wasApplied(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index 4e4960f8801..84fe778dbf5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -35,6 +35,7 @@ public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPo private DefaultLoadBalancingPolicy policy; @Before + @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 01a5eaa31d7..7e2fe170802 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -60,6 +60,7 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin private DefaultLoadBalancingPolicy policy; @Before + @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index c3f500b3a4b..9bc5b22a2af 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -32,10 +32,10 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.time.Duration; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; -import java.util.LinkedList; import java.util.Map; import java.util.Optional; import java.util.Queue; @@ -245,7 +245,7 @@ public void should_stop_executing_queries_once_closed() throws Exception { /** Mocks the query execution logic. */ private static class TestTopologyMonitor extends DefaultTopologyMonitor { - private final Queue queries = new LinkedList<>(); + private final Queue queries = new ArrayDeque<>(); private TestTopologyMonitor(InternalDriverContext context) { super(context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index af03857003f..944e2a89194 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -243,7 +243,7 @@ public void should_remove_node() { assertThat(refresh.toRemove).isEqualTo(ADDRESS1); } - private class TestMetadataManager extends MetadataManager { + private static class TestMetadataManager extends MetadataManager { private List refreshes = new CopyOnWriteArrayList<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index da645b5205c..df75f64f9c5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -33,8 +33,8 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; +import java.util.ArrayDeque; import java.util.Arrays; -import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.UUID; @@ -273,7 +273,7 @@ public void should_fail_if_versions_do_not_match_after_timeout() { /** Extend to mock the query execution logic. */ private static class TestSchemaAgreementChecker extends SchemaAgreementChecker { - private final Queue queries = new LinkedList<>(); + private final Queue queries = new ArrayDeque<>(); TestSchemaAgreementChecker(DriverChannel channel, InternalDriverContext context) { super(channel, context, 9042, "test"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java index 1284465f8f3..f7bcbf7e691 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java @@ -40,7 +40,7 @@ public class TableParserTest extends SchemaParserTestBase { "ks", "foo", "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)"); - private static final Iterable COLUMN_ROWS_2_2 = + private static final ImmutableList COLUMN_ROWS_2_2 = ImmutableList.of( mockLegacyColumnRow( "ks", "foo", "k2", "partition_key", "org.apache.cassandra.db.marshal.UTF8Type", 1), @@ -67,14 +67,14 @@ public class TableParserTest extends SchemaParserTestBase { "{}")); static final AdminRow TABLE_ROW_3_0 = mockModernTableRow("ks", "foo"); - static final Iterable COLUMN_ROWS_3_0 = + static final ImmutableList COLUMN_ROWS_3_0 = ImmutableList.of( mockModernColumnRow("ks", "foo", "k2", "partition_key", "text", "none", 1), mockModernColumnRow("ks", "foo", "k1", "partition_key", "int", "none", 0), mockModernColumnRow("ks", "foo", "cc1", "clustering", "int", "asc", 0), mockModernColumnRow("ks", "foo", "cc2", "clustering", "int", "desc", 1), mockModernColumnRow("ks", "foo", "v", "regular", "int", "none", -1)); - static final Iterable INDEX_ROWS_3_0 = + static final ImmutableList INDEX_ROWS_3_0 = ImmutableList.of( mockIndexRow("ks", "foo", "foo_v_idx", "COMPOSITES", ImmutableMap.of("target", "v"))); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java index 12ae0402b7c..1abf65bb960 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java @@ -32,7 +32,7 @@ public class ViewParserTest extends SchemaParserTestBase { static final AdminRow VIEW_ROW_3_0 = mockViewRow("ks", "alltimehigh", "scores", false, "game IS NOT NULL"); - static final Iterable COLUMN_ROWS_3_0 = + static final ImmutableList COLUMN_ROWS_3_0 = ImmutableList.of( mockModernColumnRow("ks", "alltimehigh", "game", "partition_key", "text", "none", 0), mockModernColumnRow("ks", "alltimehigh", "score", "clustering", "int", "desc", 0), diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index dc11e1028f9..b2456c24953 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -34,6 +34,7 @@ public class Cassandra3SchemaQueriesTest extends SchemaQueriesTest { @Before + @Override public void setup() { super.setup(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 3b28cf35c3f..1ebd54b8664 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -25,8 +25,9 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -157,7 +158,7 @@ public MockChannelPoolFactoryHelper build() { private void stub() { for (Params params : invocations.keySet()) { - LinkedList> results = new LinkedList<>(); + Deque> results = new ArrayDeque<>(); for (Object object : invocations.get(params)) { if (object instanceof ChannelPool) { results.add(CompletableFuture.completedFuture(((ChannelPool) object))); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index ff7aa0c9e9d..eaf234ddf89 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -40,8 +40,8 @@ import io.netty.channel.EventLoop; import java.nio.ByteBuffer; import java.time.Duration; +import java.util.ArrayDeque; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -312,7 +312,7 @@ private Map getMockPayloads(char... values) { /** Bypasses the channel to make testing easier. */ private static class MockReprepareOnUp extends ReprepareOnUp { - private Queue queries = new LinkedList<>(); + private Queue queries = new ArrayDeque<>(); MockReprepareOnUp( String logPrefix, @@ -352,7 +352,7 @@ private Rows preparedIdRows(char... values) { RawType.PRIMITIVES.get(ProtocolConstants.DataType.BLOB)); RowsMetadata rowsMetadata = new RowsMetadata(ImmutableList.of(preparedIdSpec), null, null, null); - Queue> data = new LinkedList<>(); + Queue> data = new ArrayDeque<>(); for (char value : values) { data.add(ImmutableList.of(Bytes.fromHexString("0x0" + value))); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java index 1e28170886d..f5be850f1f7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java @@ -36,6 +36,7 @@ public void should_detect_cycle_within_same_thread() { CyclicContext context = new CyclicContext(checker, false); try { context.a.get(); + fail("Expected an exception"); } catch (Exception e) { assertThat(e) .isInstanceOf(IllegalStateException.class) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 633fee11622..d9bd50a9055 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -24,6 +24,7 @@ import org.junit.experimental.categories.Category; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; /** Covers protocol negotiation for the initial connection to the first contact point. */ @Category(ParallelizableTests.class) @@ -54,6 +55,7 @@ public void should_fail_if_provided_version_isnt_supported() { try (CqlSession session = SessionUtils.newSession(ccm, "protocol.version = V4")) { assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); + fail("Expected an AllNodesFailedException"); } catch (AllNodesFailedException anfe) { Throwable cause = anfe.getErrors().values().iterator().next(); assertThat(cause).isInstanceOf(UnsupportedProtocolVersionException.class); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index b361106bcb6..79649594ce4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.config; -import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -147,7 +147,8 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { configSource.set("profiles.slow.request.timeout = 2s"); waitForConfigChange(session, 3, TimeUnit.SECONDS); - // Execute again, should expect to fail again because doesn't allow to dynamically define profile. + // Execute again, should expect to fail again because doesn't allow to dynamically define + // profile. thrown.expect(IllegalArgumentException.class); session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); } @@ -200,7 +201,7 @@ private void waitForConfigChange(CqlSession session, long timeout, TimeUnit unit boolean success = latch.await(timeout, unit); assertThat(success).isTrue(); } catch (InterruptedException e) { - fail("Interrupted while waiting for config change event"); + throw new AssertionError("Interrupted while waiting for config change event", e); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index a050af1e406..c4ecd629711 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -17,10 +17,10 @@ import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -100,11 +100,11 @@ public class DataTypeIT { @DataProvider public static Object[][] primitiveTypeSamples() { - InetAddress address = null; + InetAddress address; try { address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); } catch (UnknownHostException uhae) { - fail("Could not get address from 127.0.0.1 for some reason"); + throw new AssertionError("Could not get address from 127.0.0.1", uhae); } Object[][] samples = @@ -254,7 +254,8 @@ public static Object[][] typeSamples() { @BeforeClass public static void createTable() { // Create a table with all types being tested with. - // This is a bit more lenient than creating a table for each sample, which would put a lot of burden on C* and + // This is a bit more lenient than creating a table for each sample, which would put a lot of + // burden on C* and // the filesystem. int counter = 0; @@ -437,7 +438,8 @@ private static > S setValue( ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) : null; - // set to null if value is null instead of getting possible NPE when casting from null to primitive. + // set to null if value is null instead of getting possible NPE when casting from null to + // primitive. if (value == null) { return bs.setToNull(index); } @@ -528,7 +530,8 @@ private static > S setValue( ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) : null; - // set to null if value is null instead of getting possible NPE when casting from null to primitive. + // set to null if value is null instead of getting possible NPE when casting from null to + // primitive. if (value == null) { return bs.setToNull(name); } @@ -725,9 +728,10 @@ private void readValue( assertThat(returnedValue.get(i, typeCodec)).isEqualTo(exValue.get(i, typeCodec)); } - //assertThat(row.getTupleValue(columnName)).isEqualTo(expectedValue); - //assertThat(row.getTupleValue(0)).isEqualTo(expectedValue); - return; // return instead of break here since we don't want to compare using decode output since it has same problem. + // assertThat(row.getTupleValue(columnName)).isEqualTo(expectedValue); + // assertThat(row.getTupleValue(0)).isEqualTo(expectedValue); + return; // return instead of break here since we don't want to compare using decode output + // since it has same problem. case ProtocolConstants.DataType.UDT: // TODO: Replace this when JAVA-1572 is fixed UdtValue returnedUdtValue = row.getUdtValue(columnName); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index c2b252245e9..8619867092c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -232,7 +232,7 @@ private AtomicInteger countHeartbeats(boolean regularConnection) { false, (l) -> IS_OPTION_REQUEST.test(l) - && regularConnection ^ l.getConnection().equals(controlConnectionAddress)); + && (regularConnection ^ l.getConnection().equals(controlConnectionAddress))); return count; } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 86b009e210e..8e2b49432b2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -586,22 +586,12 @@ private InetSocketAddress withUnusedPort(InetSocketAddress address) { * AvailablePortFinder. */ private static synchronized int findAvailablePort() throws RuntimeException { - ServerSocket ss = null; - try { - // let the system pick an ephemeral port - ss = new ServerSocket(0); + // let the system pick an ephemeral port + try (ServerSocket ss = new ServerSocket(0)) { ss.setReuseAddress(true); return ss.getLocalPort(); } catch (IOException e) { throw new AssertionError(e); - } finally { - if (ss != null) { - try { - ss.close(); - } catch (IOException e) { - throw new AssertionError(e); - } - } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index d727acb1ae6..43745e71188 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -375,6 +375,7 @@ public void should_only_retry_once_on_unavailable() { try { // when executing a query. sessionRule.session().execute(queryStr); + fail("Expected an UnavailableException"); } catch (UnavailableException ue) { // then we should get an unavailable exception with the host being node 1 (since it was second tried). assertThat(ue.getCoordinator().getConnectAddress()) @@ -398,6 +399,7 @@ public void should_keep_retrying_on_next_host_on_error_response() { try { // when executing a query. sessionRule.session().execute(queryStr); + fail("Expected an AllNodesFailedException"); } catch (AllNodesFailedException e) { // then we should get an all nodes failed exception, indicating the query was tried each node. assertThat(e.getErrors()).hasSize(3); @@ -426,6 +428,7 @@ public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() sessionRule .session() .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + fail("Expected a ServerError"); } catch (ServerError e) { // then should get a server error from first host. assertThat(e.getMessage()).isEqualTo("this is a server error"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index e549229e9a2..27879c3499e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -243,8 +243,8 @@ private static class OptionalCodec extends MappingCodec, T> { Predicate isAbsent = (i) -> i == null - || (i instanceof Collection && ((Collection) i).isEmpty()) - || (i instanceof Map) && ((Map) i).isEmpty(); + || ((i instanceof Collection && ((Collection) i).isEmpty())) + || ((i instanceof Map) && ((Map) i).isEmpty()); OptionalCodec(TypeCodec innerCodec) { super( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java index e1037dfe282..8390f661589 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java @@ -62,7 +62,7 @@ public RequestHandler newHandler( return new KeyRequestHandler(subHandler); } - class KeyRequestHandler implements RequestHandler { + static class KeyRequestHandler implements RequestHandler { private final RequestHandler, ResultSet> subHandler; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index 3b66ed96ef1..774f7e5cb25 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.testinfra.loadbalancing; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -24,7 +23,7 @@ import com.datastax.oss.driver.api.core.session.Session; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.util.LinkedList; +import java.util.ArrayDeque; import java.util.Map; import java.util.Queue; import java.util.Set; @@ -75,7 +74,7 @@ public void init( @Override public Queue newQueryPlan(Request request, Session session) { - return new LinkedList<>(nodes); + return new ArrayDeque<>(nodes); } @Override From 9ec5ee6419567819f36390c4fae808f3ed9090b3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 24 Jan 2018 10:18:15 -0800 Subject: [PATCH 303/742] Suppress irrelevant ErrorProne warnings --- .../java/com/datastax/oss/driver/api/core/cql/Bindable.java | 3 +++ .../oss/driver/internal/core/channel/StreamIdGenerator.java | 2 ++ .../oss/driver/internal/core/metadata/NodeStateManager.java | 2 ++ .../internal/core/util/concurrent/UncaughtExceptions.java | 1 + .../com/datastax/oss/driver/internal/SerializationHelper.java | 2 ++ .../core/util/concurrent/ScheduledTaskCapturingEventLoop.java | 1 + .../oss/driver/api/testinfra/cluster/SessionUtils.java | 4 +++- 7 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java index 41a5841f7e7..77ecdb7920d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java @@ -31,6 +31,7 @@ public interface Bindable> * * @throws IndexOutOfBoundsException if the index is invalid. */ + @SuppressWarnings("ReferenceEquality") default boolean isSet(int i) { return getBytesUnsafe(i) != ProtocolConstants.UNSET_VALUE; } @@ -43,6 +44,7 @@ default boolean isSet(int i) { * * @throws IndexOutOfBoundsException if the id is invalid. */ + @SuppressWarnings("ReferenceEquality") default boolean isSet(CqlIdentifier id) { return getBytesUnsafe(id) != ProtocolConstants.UNSET_VALUE; } @@ -55,6 +57,7 @@ default boolean isSet(CqlIdentifier id) { * * @throws IndexOutOfBoundsException if the name is invalid. */ + @SuppressWarnings("ReferenceEquality") default boolean isSet(String name) { return getBytesUnsafe(name) != ProtocolConstants.UNSET_VALUE; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java index 05a07d9f537..f3e693fa13d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/StreamIdGenerator.java @@ -37,6 +37,7 @@ class StreamIdGenerator { this.availableIds = this.maxAvailableIds; } + @SuppressWarnings("NonAtomicVolatileUpdate") // see explanation in class Javadoc int acquire() { int id = ids.nextClearBit(0); if (id >= maxAvailableIds) { @@ -47,6 +48,7 @@ int acquire() { return id; } + @SuppressWarnings("NonAtomicVolatileUpdate") void release(int id) { if (ids.get(id)) { availableIds++; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 1d935fbe631..29b3dff976b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -115,6 +115,8 @@ private void markInitialized() { isInitialized = true; } + // Updates to DefaultNode's volatile fields are confined to the admin thread + @SuppressWarnings("NonAtomicVolatileUpdate") private void onChannelEvent(ChannelEvent event) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java index 54cbf3b7eb1..1fc9060b710 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/UncaughtExceptions.java @@ -50,6 +50,7 @@ public static void log(Future future) { } } + @SuppressWarnings("TypeParameterUnusedInFormals") // type parameter is only needed for chaining public static T log(Throwable t) { Loggers.warnWithException(LOG, "Uncaught exception in scheduled task", t); return null; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java index c790e419e33..d5cb46278bd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java @@ -36,6 +36,8 @@ public static byte[] serialize(T t) { } } + // the calling code performs validations on the result, so this doesn't matter + @SuppressWarnings("TypeParameterUnusedInFormals") public static T deserialize(byte[] bytes) { try { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index 1c418cdce45..4f0447bdd4e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -41,6 +41,7 @@ * *

          This is used to make unit tests independent of time. */ +@SuppressWarnings("FunctionalInterfaceClash") // does not matter for test code public class ScheduledTaskCapturingEventLoop extends DefaultEventLoop { private final BlockingQueue capturedTasks = new ArrayBlockingQueue<>(100); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java index d0d2f37c6ff..6629801caff 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java @@ -96,17 +96,19 @@ public static String getConfigPath() { * in the 0th DC of the provided Cassandra resource as contact points, and the default * configuration augmented with the provided options. */ + @SuppressWarnings("TypeParameterUnusedInFormals") public static SessionT newSession( CassandraResourceRule cassandraResource, String... options) { return newSession(cassandraResource, null, new NodeStateListener[0], options); } + @SuppressWarnings("TypeParameterUnusedInFormals") public static SessionT newSession( CassandraResourceRule cassandraResource, CqlIdentifier keyspace, String... options) { return newSession(cassandraResource, keyspace, new NodeStateListener[0], options); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public static SessionT newSession( CassandraResourceRule cassandraResource, CqlIdentifier keyspace, From 2681bd7393306192fe3422d8ab1e0ddf17787233 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 25 Jan 2018 17:18:25 -0800 Subject: [PATCH 304/742] Rename Session#getDriverVersion to getCoreDriverVersion --- .../com/datastax/oss/driver/api/core/session/Session.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 82c392e7b50..07117789639 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -18,9 +18,9 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.CqlSessionBuilder; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -50,12 +50,13 @@ public interface Session extends AsyncAutoCloseable { /** - * The current version of the driver. + * The current version of the core driver (in other words, the version of the {@code + * com.datastax.oss:java-driver-core} artifact). * *

          This is intended for products that wrap or extend the driver, as a way to check * compatibility if end-users override the driver version in their application. */ - static String getDriverVersion() { + static String getCoreDriverVersion() { // Note: getBundle caches its result return ResourceBundle.getBundle("com.datastax.oss.driver.Driver").getString("driver.version"); } From fd17fe09b68f1765481e9832972ba54314411d16 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 26 Jan 2018 09:16:45 -0800 Subject: [PATCH 305/742] Fix package of session test infra --- .../java/com/datastax/oss/driver/api/core/ConnectIT.java | 4 ++-- .../api/core/ProtocolVersionInitialNegotiationIT.java | 2 +- .../oss/driver/api/core/ProtocolVersionMixedClusterIT.java | 2 +- .../oss/driver/api/core/auth/PlainTextAuthProviderIT.java | 2 +- .../driver/api/core/compression/DirectCompressionIT.java | 4 ++-- .../oss/driver/api/core/compression/HeapCompressionIT.java | 4 ++-- .../oss/driver/api/core/config/DriverConfigProfileIT.java | 2 +- .../oss/driver/api/core/connection/FrameLengthIT.java | 2 +- .../datastax/oss/driver/api/core/cql/AsyncResultSetIT.java | 2 +- .../datastax/oss/driver/api/core/cql/BatchStatementIT.java | 4 ++-- .../datastax/oss/driver/api/core/cql/BoundStatementIT.java | 4 ++-- .../oss/driver/api/core/cql/PerRequestKeyspaceIT.java | 4 ++-- .../api/core/cql/PreparedStatementInvalidationIT.java | 4 ++-- .../com/datastax/oss/driver/api/core/cql/QueryTraceIT.java | 2 +- .../datastax/oss/driver/api/core/cql/SimpleStatementIT.java | 2 +- .../com/datastax/oss/driver/api/core/data/DataTypeIT.java | 2 +- .../oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java | 2 +- .../datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java | 2 +- .../core/loadbalancing/DefaultLoadBalancingPolicyIT.java | 2 +- .../oss/driver/api/core/metadata/ByteOrderedTokenIT.java | 2 +- .../driver/api/core/metadata/ByteOrderedTokenVnodesIT.java | 2 +- .../datastax/oss/driver/api/core/metadata/DescribeIT.java | 4 ++-- .../oss/driver/api/core/metadata/Murmur3TokenIT.java | 2 +- .../oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java | 2 +- .../datastax/oss/driver/api/core/metadata/NodeStateIT.java | 6 +++--- .../oss/driver/api/core/metadata/RandomTokenIT.java | 2 +- .../oss/driver/api/core/metadata/RandomTokenVnodesIT.java | 2 +- .../oss/driver/api/core/metadata/SchemaAgreementIT.java | 4 ++-- .../oss/driver/api/core/metadata/SchemaChangesIT.java | 4 ++-- .../com/datastax/oss/driver/api/core/metadata/SchemaIT.java | 4 ++-- .../datastax/oss/driver/api/core/metadata/TokenITBase.java | 2 +- .../oss/driver/api/core/retry/DefaultRetryPolicyIT.java | 2 +- .../oss/driver/api/core/session/RequestProcessorIT.java | 4 ++-- .../oss/driver/api/core/specex/SpeculativeExecutionIT.java | 2 +- .../oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java | 2 +- .../core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java | 2 +- .../DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java | 2 +- .../DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java | 2 +- .../api/core/type/codec/registry/CodecRegistryIT.java | 2 +- .../{cluster => session}/CqlSessionRuleBuilder.java | 2 +- .../DefaultSessionBuilderInstantiator.java | 2 +- .../api/testinfra/{cluster => session}/SessionRule.java | 2 +- .../testinfra/{cluster => session}/SessionRuleBuilder.java | 2 +- .../api/testinfra/{cluster => session}/SessionUtils.java | 6 +++--- .../testinfra/{cluster => session}/TestConfigLoader.java | 4 ++-- 45 files changed, 62 insertions(+), 62 deletions(-) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/{cluster => session}/CqlSessionRuleBuilder.java (95%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/{cluster => session}/DefaultSessionBuilderInstantiator.java (94%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/{cluster => session}/SessionRule.java (98%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/{cluster => session}/SessionRuleBuilder.java (97%) rename test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/{cluster => session}/SessionUtils.java (97%) rename test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/{cluster => session}/TestConfigLoader.java (93%) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 9ad9aadf110..fafe7e4e5f3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -17,8 +17,8 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.ClassRule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index d9bd50a9055..aa30876bce9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Rule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 1e5286c7f60..a12d4d1ca77 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.DataCenterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index 4032b2b0fe6..ba8f55d7f60 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.google.common.util.concurrent.Uninterruptibles; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 17674351a4e..ba88cc877c1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index b6575d5402f..eaf439e68a6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 44d0ceaf35a..555aca93df3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index e1c275c9af1..f84230264fb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 8aa36953639..1dea4831dbb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import java.util.concurrent.CompletableFuture; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 3f52511dc53..de3daa47e3e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -19,8 +19,8 @@ import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import org.junit.Before; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 427e793e8c6..85f8f99b8b7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; import org.junit.Rule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 0bd8e739f0a..284c7ccb212 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import org.junit.Before; import org.junit.Rule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index abe0e3d8700..b1d487ca060 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index c416dacb3a1..05139765bfa 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 6259e7f58b5..fcfc72ac972 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index c4ecd629711..c32d782431f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -36,7 +36,7 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.DefaultListType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java index f8568cffda5..643bbb49b21 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.heartbeat; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import java.util.concurrent.atomic.AtomicInteger; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index 8619867092c..f8dc38c1ccc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.protocol.internal.request.Register; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 934819feac0..08bbda05395 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index 2836de0373f..8909ef7c965 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index ec2cf268ae5..5ecc2953a99 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 5b60ba56509..e7eb63efc96 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.io.ByteStreams; import com.google.common.io.Closer; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index 7418211a040..e5ea8cee9a0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index 55d72f9aaf9..8da254d41f3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 8e2b49432b2..20a46ae53e6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; @@ -27,7 +27,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.NodeConnectionReport; import com.datastax.oss.simulacron.common.stubbing.CloseType; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index 9dd964c07ea..c0eb1931f54 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index f1c2b586d5a..be5ea90db33 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index 66c799f6199..3a093590a25 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import java.util.concurrent.atomic.AtomicInteger; import org.junit.ClassRule; import org.junit.Rule; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index db1e7280adb..8ee748d35ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -22,8 +22,8 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.collect.ImmutableList; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 33abefd01fd..a90aa01a98a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -21,8 +21,8 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Map; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index 9d97f3e49cf..dd90f5b834f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.nio.ByteBuffer; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index 43745e71188..8f7591edf1a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 1d4f56ec714..5c648b2d84d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.example.guava.api.GuavaSession; import com.datastax.oss.driver.example.guava.api.GuavaSessionUtils; @@ -30,7 +30,7 @@ import com.datastax.oss.driver.example.guava.internal.KeyRequest; import com.datastax.oss.driver.example.guava.internal.KeyRequestProcessor; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index d23d24ca680..c0da337bd16 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index d6c8cff185e..826d3689726 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index 26f46d54757..c836e79ea2e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java index d8e0e33e9c7..707a84e7d12 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java index e64ce96d8b0..0348f60c707 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 27879c3499e..ee7c44be4c5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -30,7 +30,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.api.core.type.reflect.GenericTypeParameter; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.cluster.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.codec.IntCodec; import java.nio.ByteBuffer; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java similarity index 95% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java index 105ffe254a7..e352d242ff9 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/CqlSessionRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.testinfra.cluster; +package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java similarity index 94% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java index bc63b6e2b2f..67898e10e07 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/DefaultSessionBuilderInstantiator.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.testinfra.cluster; +package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.session.SessionBuilder; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java similarity index 98% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java index 3c73ecf4ffd..8e29b6c44b4 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.testinfra.cluster; +package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java similarity index 97% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java index 69c9ec01b0e..b73d12aa017 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.testinfra.cluster; +package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.session.Session; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java similarity index 97% rename from test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java rename to test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 6629801caff..b3111e0a9aa 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/cluster/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.testinfra.cluster; +package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -25,7 +25,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; -import com.datastax.oss.driver.internal.testinfra.cluster.TestConfigLoader; +import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; @@ -63,7 +63,7 @@ public class SessionUtils { private static final String SESSION_BUILDER_CLASS = System.getProperty( "session.builder", - "com.datastax.oss.driver.api.testinfra.cluster.DefaultSessionBuilderInstantiator"); + "com.datastax.oss.driver.api.testinfra.session.DefaultSessionBuilderInstantiator"); @SuppressWarnings("unchecked") public static SessionBuilder baseBuilder() { diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java similarity index 93% rename from test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java rename to test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java index e6f3ccb0b6d..a7af47000e7 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/cluster/TestConfigLoader.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.internal.testinfra.cluster; +package com.datastax.oss.driver.internal.testinfra.session; import com.datastax.oss.driver.api.core.config.CoreDriverOption; -import com.datastax.oss.driver.api.testinfra.cluster.SessionUtils; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; From 01e47b5b7ee9bdab6d6afe50aafad006269c24c7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 26 Jan 2018 09:19:33 -0800 Subject: [PATCH 306/742] Fix bug in ArrayUtils.shuffleHead If n is too large, but the random generator produced the same index for all out-of-bound indices, swap() is a no-op and no exception would be thrown. --- .../datastax/oss/driver/internal/core/util/ArrayUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 896b854c1a5..4b28091f424 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -55,6 +55,11 @@ public static void bubbleDown(T[] elements, int sourceIndex, int targetIndex * Fisher-Yates shuffle */ public static void shuffleHead(T[] elements, int n) { + if (n > elements.length) { + throw new ArrayIndexOutOfBoundsException( + String.format( + "Can't shuffle the first %d elements, there are only %d", n, elements.length)); + } if (n > 1) { ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = n - 1; i > 0; i--) { From a2c2b9eccd5b60a585e8a65335b79354cc54e96a Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 26 Jan 2018 13:34:55 -0800 Subject: [PATCH 307/742] Add note on loopback aliases to contributing guide --- CONTRIBUTING.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f8fecd592e..27d87cba22f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -220,7 +220,22 @@ classified into categories that determine how they will be run during the build: * No annotation: for tests that use `CustomCcmRule`. They will be run one after the other. * `@Category(IsolatedTests.class)`: for tests that require specific environment tweaks, typically system properties that need to be set before initialization. They will be run one after the other, - each in their its JVM fork. + each in its own JVM fork. + +Simulacron relies on loopback aliases to simulate multiple nodes. On Linux or Windows, you shouldn't +have anything to do. On MacOS, run this script: + +``` +#!/bin/bash +for sub in {0..4}; do + echo "Opening for 127.0.$sub" + for i in {0..255}; do sudo ifconfig lo0 alias 127.0.$sub.$i up; done +done +``` + +Note that this is known to cause temporary increased CPU usage in OS X initially while mDNSResponder +acclimates itself to the presence of added IP addresses. This lasts several minutes. Also, this does +not survive reboots. ## Running the tests From e746b152255731d2ea7d9f52b4bd65561d201f3c Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 26 Jan 2018 13:41:33 -0800 Subject: [PATCH 308/742] Turn RequestProcessorRegistry into a singleton in the context Now that there is only one Session, we don't need to create a new one on each call. This is functionally equivalent, but we don't rely anymore on the fact that the method is called exactly once. --- .../internal/core/context/DefaultDriverContext.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 1c0054428a2..a770a78dc81 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -131,6 +131,9 @@ public class DefaultDriverContext implements InternalDriverContext { "loadBalancingPolicyWrapper", this::buildLoadBalancingPolicyWrapper, cycleDetector); private final LazyReference controlConnectionRef = new LazyReference<>("controlConnection", this::buildControlConnection, cycleDetector); + private final LazyReference requestProcessorRegistryRef = + new LazyReference<>( + "requestProcessorRegistry", this::buildRequestProcessorRegistry, cycleDetector); private final LazyReference timestampGeneratorRef = new LazyReference<>("timestampGenerator", this::buildTimestampGenerator, cycleDetector); private final LazyReference schemaQueriesFactoryRef = @@ -292,6 +295,10 @@ protected ControlConnection buildControlConnection() { return new ControlConnection(this); } + protected RequestProcessorRegistry buildRequestProcessorRegistry() { + return RequestProcessorRegistry.defaultCqlProcessors(sessionName()); + } + protected CodecRegistry buildCodecRegistry(String logPrefix, List> codecs) { TypeCodec[] array = new TypeCodec[codecs.size()]; return new DefaultCodecRegistry(logPrefix, codecs.toArray(array)); @@ -445,7 +452,7 @@ public ControlConnection controlConnection() { @Override public RequestProcessorRegistry requestProcessorRegistry() { - return RequestProcessorRegistry.defaultCqlProcessors(sessionName()); + return requestProcessorRegistryRef.get(); } @Override From 1eb20d787ac967a80342985bf352da15908c705d Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Jan 2018 15:54:30 -0800 Subject: [PATCH 309/742] Improve extensibility of DefaultTopologyMonitor --- .../core/metadata/DefaultNodeInfo.java | 8 +++-- .../core/metadata/DefaultTopologyMonitor.java | 36 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java index 04f47eb9a97..349f28b60e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -151,10 +151,12 @@ public Builder withTokens(Set tokens) { } public Builder withExtra(String key, Object value) { - if (this.extras == null) { - this.extras = new HashMap<>(); + if (value != null) { + if (this.extras == null) { + this.extras = new HashMap<>(); + } + this.extras.put(key, value); } - this.extras.put(key, value); return this; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index f8d71b7b3d1..c066839903b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -98,7 +98,7 @@ public CompletionStage> refreshNode(Node node) { channel, "SELECT * FROM system.peers WHERE peer = :address", ImmutableMap.of("address", node.getBroadcastAddress().get())) - .thenApply(this::buildNodeInfoFromFirstRow); + .thenApply(this::firstRowAsNodeInfo); } else { return query(channel, "SELECT * FROM system.peers") .thenApply(result -> this.findInPeers(result, node.getConnectAddress())); @@ -140,9 +140,10 @@ public CompletionStage> refreshNodeList() { // Don't rely on system.local.rpc_address for the control row, because it mistakenly // reports the normal RPC address instead of the broadcast one (CASSANDRA-11181). We // already know the address since we've just used it to query. - nodeInfos.add(buildNodeInfo(controlNodeResult.iterator().next(), controlAddress)); + nodeInfos.add( + nodeInfoBuilder(controlNodeResult.iterator().next(), controlAddress).build()); for (AdminRow row : peersResult) { - nodeInfos.add(buildNodeInfo(row)); + nodeInfos.add(asNodeInfo(row)); } return nodeInfos; }); @@ -185,17 +186,27 @@ private CompletionStage query(DriverChannel channel, String querySt return query(channel, queryString, Collections.emptyMap()); } - private NodeInfo buildNodeInfo(AdminRow row) { + private NodeInfo asNodeInfo(AdminRow row) { InetAddress broadcastRpcAddress = row.getInetAddress("rpc_address"); if (broadcastRpcAddress == null) { throw new IllegalArgumentException("Missing rpc_address in system row, can't refresh node"); } InetSocketAddress connectAddress = addressTranslator.translate(new InetSocketAddress(broadcastRpcAddress, port)); - return buildNodeInfo(row, connectAddress); + return nodeInfoBuilder(row, connectAddress).build(); } - private NodeInfo buildNodeInfo(AdminRow row, InetSocketAddress connectAddress) { + private Optional firstRowAsNodeInfo(AdminResult result) { + Iterator iterator = result.iterator(); + if (iterator.hasNext()) { + return Optional.of(asNodeInfo(iterator.next())); + } else { + return Optional.empty(); + } + } + + protected DefaultNodeInfo.Builder nodeInfoBuilder( + AdminRow row, InetSocketAddress connectAddress) { DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder().withConnectAddress(connectAddress); InetAddress broadcastAddress = row.getInetAddress("broadcast_address"); // in system.local @@ -211,16 +222,7 @@ private NodeInfo buildNodeInfo(AdminRow row, InetSocketAddress connectAddress) { builder.withTokens(row.getSetOfString("tokens")); builder.withPartitioner(row.getString("partitioner")); - return builder.build(); - } - - private Optional buildNodeInfoFromFirstRow(AdminResult result) { - Iterator iterator = result.iterator(); - if (iterator.hasNext()) { - return Optional.of(buildNodeInfo(iterator.next())); - } else { - return Optional.empty(); - } + return builder; } private Optional findInPeers(AdminResult result, InetSocketAddress connectAddress) { @@ -232,7 +234,7 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con && addressTranslator .translate(new InetSocketAddress(broadcastRpcAddress, port)) .equals(connectAddress)) { - return Optional.of(buildNodeInfo(row, connectAddress)); + return Optional.of(nodeInfoBuilder(row, connectAddress).build()); } } LOG.debug("[{}] Could not find any peer row matching {}", logPrefix, connectAddress); From aaa56037419f32f9bd0b5f3003135cb3b62864c5 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Feb 2018 15:47:38 -0800 Subject: [PATCH 310/742] Handle session wrappers in NodeStateIT It the test suite is run with a different `session.builder` (see SessionUtils), the session could be a wrapper. --- .../internal/core/session/SessionWrapper.java | 4 ++++ .../driver/api/core/metadata/NodeStateIT.java | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java index 8250739ff38..44c3afa1e4b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -46,6 +46,10 @@ public SessionWrapper(Session delegate) { this.delegate = delegate; } + public Session getDelegate() { + return delegate; + } + @Override public String getName() { return delegate.getName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 20a46ae53e6..f17d36f5bf3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -15,9 +15,10 @@ */ package com.datastax.oss.driver.api.core.metadata; -import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -27,6 +28,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import com.datastax.oss.driver.internal.core.session.SessionWrapper; import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.NodeConnectionReport; @@ -560,9 +562,17 @@ public void should_call_onRegister_and_onUnregister_implicitly() { public void should_call_onRegister_and_onUnregister_when_used() { NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); sessionRule.session().register(localNodeStateListener); - Mockito.verify(localNodeStateListener, timeout(1000)).onRegister(sessionRule.session()); + Mockito.verify(localNodeStateListener, timeout(1000)).onRegister(unwrap(sessionRule.session())); sessionRule.session().unregister(localNodeStateListener); - Mockito.verify(localNodeStateListener, timeout(1000)).onUnregister(sessionRule.session()); + Mockito.verify(localNodeStateListener, timeout(1000)) + .onUnregister(unwrap(sessionRule.session())); + } + + private Session unwrap(Session session) { + while (session instanceof SessionWrapper) { + session = ((SessionWrapper) session).getDelegate(); + } + return session; } private void expect(NodeStateEvent... expectedEvents) { From b15dc894d7f355dc6621a07b086f0d15a4e12def Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 2 Feb 2018 10:03:08 -0800 Subject: [PATCH 311/742] Check that tests using CcmRule are properly annotated --- .../oss/driver/api/testinfra/ccm/CcmRule.java | 29 +++++++++++++++++++ .../oss/driver/categories/IsolatedTests.java | 0 .../categories/ParallelizableTests.java | 0 3 files changed, 29 insertions(+) rename {integration-tests/src/test => test-infra/src/main}/java/com/datastax/oss/driver/categories/IsolatedTests.java (100%) rename {integration-tests/src/test => test-infra/src/main}/java/com/datastax/oss/driver/categories/ParallelizableTests.java (100%) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index 014aad0e03c..0e16b06b081 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.api.testinfra.ccm; +import com.datastax.oss.driver.categories.ParallelizableTests; +import org.junit.AssumptionViolatedException; +import org.junit.experimental.categories.Category; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + /** * A rule that creates a globally shared single node Ccm cluster that is only shut down after the * JVM exists. @@ -46,6 +52,29 @@ protected void after() { // override after so we don't remove when done. } + @Override + public Statement apply(Statement base, Description description) { + + Category categoryAnnotation = description.getAnnotation(Category.class); + if (categoryAnnotation == null + || categoryAnnotation.value().length != 1 + || categoryAnnotation.value()[0] != ParallelizableTests.class) { + return new Statement() { + @Override + public void evaluate() { + throw new AssumptionViolatedException( + String.format( + "Tests using %s must be annotated with `@Category(%s.class)`. Description: %s", + CcmRule.class.getSimpleName(), + ParallelizableTests.class.getSimpleName(), + description)); + } + }; + } + + return super.apply(base, description); + } + public static CcmRule getInstance() { return INSTANCE; } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java b/test-infra/src/main/java/com/datastax/oss/driver/categories/IsolatedTests.java similarity index 100% rename from integration-tests/src/test/java/com/datastax/oss/driver/categories/IsolatedTests.java rename to test-infra/src/main/java/com/datastax/oss/driver/categories/IsolatedTests.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/categories/ParallelizableTests.java b/test-infra/src/main/java/com/datastax/oss/driver/categories/ParallelizableTests.java similarity index 100% rename from integration-tests/src/test/java/com/datastax/oss/driver/categories/ParallelizableTests.java rename to test-infra/src/main/java/com/datastax/oss/driver/categories/ParallelizableTests.java From af996e67b67dce01e3e1756045b04b52be6b0c1b Mon Sep 17 00:00:00 2001 From: GregBestland Date: Mon, 29 Jan 2018 16:25:17 -0600 Subject: [PATCH 312/742] JAVA-1729: Override DefaultTupleValue.equals --- changelog/README.md | 2 +- .../internal/core/data/DefaultTupleValue.java | 55 ++++++++++++++++++- .../core/data/DefaultTupleValueTest.java | 23 ++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 9125af39fa3..ab5e5061193 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [bug] JAVA-1729: Override DefaultTupleValue.equals - [improvement] JAVA-1720: Merge Cluster and Session into a single interface - [improvement] JAVA-1713: Use less nodes in DefaultLoadBalancingPolicyIT - [improvement] JAVA-1707: Add test infrastructure for running DSE clusters with CCM @@ -23,7 +24,6 @@ - [improvement] JAVA-1566: Enforce API rules automatically - [bug] JAVA-1584: Validate that no bound values are unset in protocol v3 - ### 4.0.0-alpha2 - [new feature] JAVA-1525: Handle token metadata diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index f6be80202c6..690ceb3a7c5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -26,11 +26,11 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; +import java.util.Objects; public class DefaultTupleValue implements TupleValue { private static final long serialVersionUID = 1; - private final TupleType type; private final ByteBuffer[] values; @@ -93,6 +93,59 @@ private void readObject(ObjectInputStream stream) throws InvalidObjectException throw new InvalidObjectException("Proxy required"); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof TupleValue)) { + return false; + } + TupleValue that = (TupleValue) o; + + if (!this.protocolVersion().equals(that.protocolVersion())) { + return false; + } + + for (int i = 0; i < values.length; i++) { + DataType innerThisType = type.getComponentTypes().get(i); + DataType innerThatType = that.getType().getComponentTypes().get(i); + if (!innerThisType.equals(innerThatType)) { + return false; + } + Object thisValue = + this.codecRegistry() + .codecFor(innerThisType) + .decode(this.getBytesUnsafe(i), this.protocolVersion()); + Object thatValue = + that.codecRegistry() + .codecFor(innerThatType) + .decode(that.getBytesUnsafe(i), that.protocolVersion()); + if (!Objects.equals(thisValue, thatValue)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + + int result = type.hashCode(); + + for (int i = 0; i < values.length; i++) { + DataType innerThisType = type.getComponentTypes().get(i); + Object thisValue = + this.codecRegistry() + .codecFor(innerThisType) + .decode(this.values[i], this.protocolVersion()); + result = 31 * result + thisValue.hashCode(); + } + + return result; + } + private static class SerializationProxy implements Serializable { private static final long serialVersionUID = 1; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index dd0f64b665d..937563b0f54 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.internal.SerializationHelper; import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.protocol.internal.util.Bytes; @@ -51,4 +52,26 @@ public void should_serialize_and_deserialize() { assertThat(Bytes.toHexString(out.getBytesUnsafe(0))).isEqualTo("0x00000001"); assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); } + + @Test + public void should_equate_instances_with_same_values_but_different_binary_representations() { + TupleType tupleType = DataTypes.tupleOf(DataTypes.VARINT); + + TupleValue tuple1 = tupleType.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x01")); + TupleValue tuple2 = tupleType.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x0001")); + + assertThat(tuple1).isEqualTo(tuple2); + assertThat(tuple1.hashCode()).isEqualTo(tuple2.hashCode()); + } + + @Test + public void should_not_equate_instances_with_same_binary_representation_but_different_types() { + TupleType tupleType1 = DataTypes.tupleOf(DataTypes.INT); + TupleType tupleType2 = DataTypes.tupleOf(DataTypes.VARINT); + + TupleValue tuple1 = tupleType1.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + TupleValue tuple2 = tupleType2.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + + assertThat(tuple1).isNotEqualTo(tuple2); + } } From f0da92dea98d8ad570b5a46f8a2ff5c1cb02c933 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 2 Feb 2018 17:07:46 -0800 Subject: [PATCH 313/742] Add javadoc to CodecRegistry --- .../api/core/type/codec/registry/CodecRegistry.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java index a4a819aaddd..9c612d7bb84 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java @@ -15,12 +15,25 @@ */ package com.datastax.oss.driver.api.core.type.codec.registry; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; +/** + * Provides codecs to convert CQL types to their Java equivalent, and vice-versa. + * + *

          Implementations MUST provide a default mapping for all CQL types (primitive types, and + * all the collections, tuples or user-defined types that can recursively be built from them — + * see {@link DataTypes}). + * + *

          They may also provide additional mappings to other Java types (for use with methods such as + * {@link Row#get(int, Class)}, {@link TupleValue#set(int, Object, Class)}, etc.) + */ public interface CodecRegistry { /** * An immutable instance, that only handles built-in driver types (that is, primitive types, and From f70c27ea9278766f59ebe3aa88b0f9c828561a2e Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 4 Feb 2018 16:47:22 -0800 Subject: [PATCH 314/742] Check type first in DefaultTupleValue.equals --- .../oss/driver/internal/core/data/DefaultTupleValue.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index 690ceb3a7c5..d2580c9ade5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -108,6 +108,10 @@ public boolean equals(Object o) { return false; } + if (!type.equals(that.getType())) { + return false; + } + for (int i = 0; i < values.length; i++) { DataType innerThisType = type.getComponentTypes().get(i); DataType innerThatType = that.getType().getComponentTypes().get(i); From bb0f325da93a88e841b1a0739578f327292776d7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 4 Feb 2018 16:49:04 -0800 Subject: [PATCH 315/742] Override DefaultTupleValue.toString --- .../oss/driver/internal/core/data/DefaultTupleValue.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index d2580c9ade5..685611fcd18 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -150,6 +150,11 @@ public int hashCode() { return result; } + @Override + public String toString() { + return codecRegistry().codecFor(type).format(this); + } + private static class SerializationProxy implements Serializable { private static final long serialVersionUID = 1; From 44c4799b8cfb94a317b90098b2ced2a68bf8797f Mon Sep 17 00:00:00 2001 From: GregBestland Date: Tue, 23 Jan 2018 17:42:40 -0600 Subject: [PATCH 316/742] JAVA-1727: Override DefaultUdtValue.equals --- changelog/README.md | 1 + .../internal/core/data/DefaultUdtValue.java | 66 ++++++++++++++++++- .../api/core/type/UserDefinedTypeTest.java | 9 ++- .../core/data/DefaultUdtValueTest.java | 29 ++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index ab5e5061193..b8d0f6f879b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [bug] JAVA-1727: Override DefaultUdtValue.equals - [bug] JAVA-1729: Override DefaultTupleValue.equals - [improvement] JAVA-1720: Merge Cluster and Session into a single interface - [improvement] JAVA-1713: Use less nodes in DefaultLoadBalancingPolicyIT diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index 784420068c0..b8a365b506c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.base.Preconditions; @@ -27,9 +28,13 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DefaultUdtValue implements UdtValue { - + private static final Logger LOG = LoggerFactory.getLogger(DefaultUdtValue.class); private static final long serialVersionUID = 1; private final UserDefinedType type; @@ -91,6 +96,65 @@ public ProtocolVersion protocolVersion() { return type.getAttachmentPoint().protocolVersion(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof UdtValue)) { + return false; + } + UdtValue that = (UdtValue) o; + + if (!this.protocolVersion().equals(that.protocolVersion())) { + return false; + } + + if (!type.equals(that.getType())) { + return false; + } + + for (int i = 0; i < values.length; i++) { + + DataType innerThisType = type.getFieldTypes().get(i); + DataType innerThatType = that.getType().getFieldTypes().get(i); + + Object thisValue = + that.codecRegistry() + .codecFor(innerThisType) + .decode(this.getBytesUnsafe(i), this.protocolVersion()); + Object thatValue = + this.codecRegistry() + .codecFor(innerThatType) + .decode(that.getBytesUnsafe(i), that.protocolVersion()); + + if (!Objects.equals(thisValue, thatValue)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int result = type.hashCode(); + for (int i = 0; i < values.length; i++) { + DataType innerThisType = type.getFieldTypes().get(i); + Object thisValue = + this.codecRegistry() + .codecFor(innerThisType) + .decode(this.values[i], this.protocolVersion()); + result = 31 * result + thisValue.hashCode(); + } + return result; + } + + @Override + public String toString() { + return codecRegistry().codecFor(type).format(this); + } + /** * @serialData The type of the tuple, followed by an array of byte arrays representing the values * (null values are represented by {@code null}). diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java index 5406bd5f740..05416857057 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/UserDefinedTypeTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.api.core.type; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class UserDefinedTypeTest { private static final UserDefinedType ADDRESS_TYPE = @@ -62,4 +62,9 @@ public void should_describe_as_pretty_cql() { + " list_of_map list>>\n" + ");"); } + + @Test + public void should_evaluate_equality() { + assertThat(ACCOUNT_TYPE.newValue()).isEqualTo(ACCOUNT_TYPE.newValue()); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index 5ff36d5aab2..a420dfb333e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -63,4 +63,33 @@ public void should_serialize_and_deserialize() { assertThat(Bytes.toHexString(out.getBytesUnsafe(0))).isEqualTo("0x00000001"); assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); } + + @Test + public void should_equate_instances_with_same_values_but_different_binary_representations() { + UserDefinedType type = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("f"), DataTypes.VARINT) + .build(); + + UdtValue udt1 = type.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x01")); + UdtValue udt2 = type.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x0001")); + + assertThat(udt1).isEqualTo(udt2); + } + + @Test + public void should_format_to_string() { + UserDefinedType type = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("t"), DataTypes.TEXT) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("d"), DataTypes.DOUBLE) + .build(); + + UdtValue udt = type.newValue().setString("t", "foobar").setDouble("d", 3.14); + + assertThat(udt.toString()).isEqualTo("{t:'foobar',i:NULL,d:3.14}"); + } } From 94a345a987ddfacc3edde102b1112e371be92c66 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 5 Feb 2018 09:52:57 -0800 Subject: [PATCH 317/742] Always look up the Category annotation on the test class This ensures that the check introduced in datastax/java-driver@b15dc894d works even when CcmRule is used as a method Rule. --- .../java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index 0e16b06b081..9657250a521 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -55,7 +55,7 @@ protected void after() { @Override public Statement apply(Statement base, Description description) { - Category categoryAnnotation = description.getAnnotation(Category.class); + Category categoryAnnotation = description.getTestClass().getAnnotation(Category.class); if (categoryAnnotation == null || categoryAnnotation.value().length != 1 || categoryAnnotation.value()[0] != ParallelizableTests.class) { From ba71dc166924a395d0bf1153b5abb9a25abad71c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 5 Feb 2018 10:13:24 -0800 Subject: [PATCH 318/742] Upgrade Maven formatter plugin --- CONTRIBUTING.md | 10 ++-- .../api/core/cql/BatchStatementBuilder.java | 1 - .../oss/driver/api/core/data/CqlDuration.java | 5 +- .../driver/api/core/detach/Detachable.java | 4 +- .../api/core/session/SessionBuilder.java | 3 +- .../type/reflect/GenericTypeParameter.java | 4 +- .../oss/driver/api/core/uuid/Uuids.java | 4 +- .../core/channel/ProtocolInitHandler.java | 3 +- .../config/typesafe/TypeSafeDriverConfig.java | 4 +- .../core/control/ControlConnection.java | 3 +- .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/DefaultAsyncResultSet.java | 2 +- .../internal/core/cql/QueryTraceFetcher.java | 2 +- .../internal/core/data/DefaultUdtValue.java | 2 - .../internal/core/metadata/TopologyEvent.java | 1 - .../metadata/token/ByteOrderedTokenRange.java | 6 ++- .../core/metadata/token/KeyspaceTokenMap.java | 3 +- .../metadata/token/Murmur3TokenFactory.java | 6 +-- .../core/metadata/token/RandomTokenRange.java | 4 +- .../metadata/token/ReplicationStrategy.java | 1 - .../internal/core/pool/ChannelPool.java | 3 +- .../core/protocol/ByteBufPrimitiveCodec.java | 3 +- .../internal/core/time/NativeClock.java | 6 +-- .../internal/core/type/codec/DateCodec.java | 4 +- .../core/type/codec/TimestampCodec.java | 4 +- .../codec/registry/CachingCodecRegistry.java | 3 +- .../internal/core/type/util/VIntCoding.java | 8 ++-- .../driver/internal/core/util/Strings.java | 1 - .../util/concurrent/CompletableFutures.java | 1 - .../datastax/oss/driver/ByteBufAssert.java | 4 +- .../oss/driver/DriverRunListener.java | 4 +- .../api/core/CassandraVersionAssert.java | 4 +- .../driver/api/core/CassandraVersionTest.java | 4 +- .../driver/api/core/CqlIdentifierTest.java | 4 +- .../driver/api/core/data/CqlDurationTest.java | 4 +- .../core/retry/DefaultRetryPolicyTest.java | 14 +++--- .../api/core/retry/RetryPolicyTestBase.java | 4 +- ...onstantSpeculativeExecutionPolicyTest.java | 4 +- .../time/AtomicTimestampGeneratorTest.java | 6 +-- .../MonotonicTimestampGeneratorTestBase.java | 4 +- .../ThreadLocalTimestampGeneratorTest.java | 6 +-- .../core/type/reflect/GenericTypeTest.java | 4 +- .../oss/driver/api/core/uuid/UuidsTest.java | 4 +- .../driver/internal/SerializationHelper.java | 4 +- ...tocolVersionRegistryHighestCommonTest.java | 4 +- .../CassandraProtocolVersionRegistryTest.java | 4 +- .../internal/core/CompletionStageAssert.java | 6 +-- .../internal/core/DriverConfigAssert.java | 4 +- .../internal/core/NettyFutureAssert.java | 6 +-- .../ChannelFactoryAvailableIdsTest.java | 8 ++-- .../ChannelFactoryClusterNameTest.java | 4 +- ...ChannelFactoryProtocolNegotiationTest.java | 4 +- .../core/channel/ChannelFactoryTestBase.java | 6 +-- .../core/channel/ChannelHandlerTestBase.java | 4 +- .../core/channel/ConnectInitHandlerTest.java | 4 +- .../core/channel/DriverChannelTest.java | 4 +- .../core/channel/InFlightHandlerTest.java | 6 +-- .../channel/MockChannelFactoryHelper.java | 12 ++--- .../core/channel/ProtocolInitHandlerTest.java | 12 +++-- .../core/channel/StreamIdGeneratorTest.java | 4 +- .../DefaultDriverConfigLoaderTest.java | 6 +-- .../typesafe/TypeSafeDriverConfigTest.java | 4 +- .../core/context/bus/EventBusTest.java | 4 +- .../control/ControlConnectionEventsTest.java | 6 +-- .../core/control/ControlConnectionTest.java | 6 +-- .../control/ControlConnectionTestBase.java | 6 +-- .../core/cql/CqlPrepareHandlerTest.java | 8 ++-- .../core/cql/CqlRequestHandlerRetryTest.java | 8 ++-- ...equestHandlerSpeculativeExecutionTest.java | 10 ++-- .../core/cql/CqlRequestHandlerTest.java | 5 +- .../core/cql/DefaultAsyncResultSetTest.java | 4 +- .../internal/core/cql/PoolBehavior.java | 10 ++-- .../core/cql/QueryTraceFetcherTest.java | 12 ++--- .../core/cql/RequestHandlerTestHarness.java | 8 ++-- .../internal/core/cql/ResultSetsTest.java | 4 +- .../core/data/AccessibleByIdTestBase.java | 8 ++-- .../core/data/AccessibleByIndexTestBase.java | 8 ++-- .../core/data/DefaultTupleValueTest.java | 4 +- .../core/data/DefaultUdtValueTest.java | 4 +- .../core/data/IdentifierIndexTest.java | 4 +- .../DefaultLoadBalancingPolicyEventsTest.java | 10 ++-- .../DefaultLoadBalancingPolicyInitTest.java | 8 ++-- ...faultLoadBalancingPolicyQueryPlanTest.java | 19 ++++---- .../DefaultLoadBalancingPolicyTestBase.java | 4 +- .../core/metadata/AddNodeRefreshTest.java | 4 +- .../metadata/DefaultMetadataTokenMapTest.java | 4 +- .../metadata/DefaultTopologyMonitorTest.java | 10 ++-- .../metadata/FullNodeListRefreshTest.java | 4 +- .../InitContactPointsRefreshTest.java | 4 +- .../LoadBalancingPolicyWrapperTest.java | 12 ++--- .../core/metadata/MetadataManagerTest.java | 8 ++-- .../core/metadata/NodeStateManagerTest.java | 12 ++--- .../core/metadata/RemoveNodeRefreshTest.java | 4 +- .../metadata/SchemaAgreementCheckerTest.java | 10 ++-- .../schema/parsing/AggregateParserTest.java | 4 +- .../parsing/DataTypeClassNameParserTest.java | 4 +- .../parsing/DataTypeCqlNameParserTest.java | 4 +- .../schema/parsing/FunctionParserTest.java | 4 +- .../schema/parsing/SchemaParserTest.java | 4 +- .../schema/parsing/SchemaParserTestBase.java | 4 +- .../schema/parsing/TableParserTest.java | 4 +- .../UserDefinedTypeListParserTest.java | 4 +- .../schema/parsing/ViewParserTest.java | 4 +- .../queries/Cassandra21SchemaQueriesTest.java | 4 +- .../queries/Cassandra22SchemaQueriesTest.java | 4 +- .../queries/Cassandra3SchemaQueriesTest.java | 4 +- .../schema/queries/SchemaQueriesTest.java | 4 +- .../schema/refresh/SchemaRefreshTest.java | 4 +- .../token/ByteOrderedTokenRangeTest.java | 4 +- .../metadata/token/DefaultTokenMapTest.java | 4 +- .../metadata/token/Murmur3TokenRangeTest.java | 4 +- ...etworkTopologyReplicationStrategyTest.java | 10 ++-- .../metadata/token/RandomTokenRangeTest.java | 4 +- .../token/SimpleReplicationStrategyTest.java | 4 +- .../core/metadata/token/TokenRangeAssert.java | 4 +- .../core/metadata/token/TokenRangeTest.java | 6 +-- .../core/pool/ChannelPoolInitTest.java | 8 ++-- .../core/pool/ChannelPoolKeyspaceTest.java | 4 +- .../core/pool/ChannelPoolReconnectTest.java | 10 ++-- .../core/pool/ChannelPoolResizeTest.java | 8 ++-- .../core/pool/ChannelPoolShutdownTest.java | 8 ++-- .../core/pool/ChannelPoolTestBase.java | 6 +-- .../internal/core/pool/ChannelSetTest.java | 6 +-- .../protocol/ByteBufPrimitiveCodecTest.java | 4 +- .../core/protocol/FrameDecoderTest.java | 6 +-- .../core/session/DefaultSessionPoolsTest.java | 14 +++--- .../session/MockChannelPoolFactoryHelper.java | 12 ++--- .../core/session/ReprepareOnUpTest.java | 4 +- .../core/type/DataTypeDetachableTest.java | 7 +-- .../core/type/DataTypeSerializationTest.java | 7 +-- .../core/type/codec/BigIntCodecTest.java | 4 +- .../core/type/codec/BlobCodecTest.java | 4 +- .../core/type/codec/BooleanCodecTest.java | 4 +- .../core/type/codec/CodecTestBase.java | 4 +- .../core/type/codec/CounterCodecTest.java | 4 +- .../core/type/codec/CqlDurationCodecTest.java | 4 +- .../core/type/codec/CustomCodecTest.java | 4 +- .../core/type/codec/DateCodecTest.java | 4 +- .../core/type/codec/DecimalCodecTest.java | 4 +- .../core/type/codec/DoubleCodecTest.java | 4 +- .../core/type/codec/FloatCodecTest.java | 4 +- .../core/type/codec/InetCodecTest.java | 6 +-- .../core/type/codec/IntCodecTest.java | 4 +- .../core/type/codec/ListCodecTest.java | 4 +- .../core/type/codec/MapCodecTest.java | 4 +- .../core/type/codec/SetCodecTest.java | 4 +- .../core/type/codec/SmallIntCodecTest.java | 4 +- .../core/type/codec/StringCodecTest.java | 4 +- .../core/type/codec/TimeCodecTest.java | 4 +- .../core/type/codec/TimeUuidCodecTest.java | 4 +- .../core/type/codec/TimestampCodecTest.java | 4 +- .../core/type/codec/TinyIntCodecTest.java | 4 +- .../core/type/codec/TupleCodecTest.java | 4 +- .../core/type/codec/UdtCodecTest.java | 4 +- .../core/type/codec/UuidCodecTest.java | 4 +- .../core/type/codec/VarintCodecTest.java | 4 +- .../registry/CachingCodecRegistryTest.java | 6 +-- .../internal/core/util/ArrayUtilsTest.java | 4 +- .../internal/core/util/DirectedGraphTest.java | 4 +- .../util/concurrent/CycleDetectorTest.java | 6 +-- .../core/util/concurrent/DebouncerTest.java | 8 ++-- .../util/concurrent/ReconnectionTest.java | 6 +-- .../concurrent/ReplayingEventFilterTest.java | 4 +- .../ScheduledTaskCapturingEventLoop.java | 8 ++-- .../ScheduledTaskCapturingEventLoopTest.java | 4 +- .../oss/driver/api/core/ConnectIT.java | 4 +- .../ProtocolVersionInitialNegotiationIT.java | 6 +-- .../core/ProtocolVersionMixedClusterIT.java | 6 +-- .../core/compression/DirectCompressionIT.java | 6 +-- .../core/compression/HeapCompressionIT.java | 6 +-- .../core/config/DriverConfigProfileIT.java | 14 +++--- .../config/DriverConfigProfileReloadIT.java | 12 ++--- .../api/core/connection/FrameLengthIT.java | 14 +++--- .../driver/api/core/cql/AsyncResultSetIT.java | 7 +-- .../driver/api/core/cql/BatchStatementIT.java | 13 +++-- .../driver/api/core/cql/BoundStatementIT.java | 7 +-- .../api/core/cql/PerRequestKeyspaceIT.java | 4 +- .../cql/PreparedStatementInvalidationIT.java | 12 +++-- .../oss/driver/api/core/cql/QueryTraceIT.java | 4 +- .../api/core/cql/SimpleStatementIT.java | 10 ++-- .../oss/driver/api/core/data/DataTypeIT.java | 10 ++-- .../core/heartbeat/HeartbeatDisabledIT.java | 9 ++-- .../api/core/heartbeat/HeartbeatIT.java | 20 ++++---- .../DefaultLoadBalancingPolicyIT.java | 8 ++-- .../driver/api/core/metadata/DescribeIT.java | 6 +-- .../driver/api/core/metadata/NodeStateIT.java | 10 ++-- .../api/core/metadata/SchemaAgreementIT.java | 4 +- .../api/core/metadata/SchemaChangesIT.java | 7 +-- .../driver/api/core/metadata/SchemaIT.java | 6 +-- .../driver/api/core/metadata/TokenITBase.java | 6 +-- .../api/core/retry/DefaultRetryPolicyIT.java | 48 +++++++++++-------- .../api/core/session/RequestProcessorIT.java | 9 ++-- .../core/specex/SpeculativeExecutionIT.java | 10 ++-- .../type/codec/registry/CodecRegistryIT.java | 12 +++-- .../guava/api/GuavaSessionBuilder.java | 2 +- pom.xml | 8 ++-- .../driver/api/testinfra/ccm/BaseCcmRule.java | 3 +- .../driver/api/testinfra/ccm/CcmBridge.java | 6 +-- .../api/testinfra/session/SessionUtils.java | 2 +- .../api/testinfra/utils/ConditionChecker.java | 4 +- .../driver/api/testinfra/utils/NodeUtils.java | 6 +-- .../driver/assertions/NodeMetadataAssert.java | 4 +- 202 files changed, 606 insertions(+), 586 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27d87cba22f..31c40c0f884 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,18 +8,18 @@ https://github.com/google/google-java-format for IDE plugins. The rules are not The build will fail if the code is not formatted. To format all files from the command line, run: ``` -mvn fmt:format -Dformat.validateOnly=false +mvn fmt:format ``` Some aspects are not covered by the formatter: -* imports: please configure your IDE to follow the guide (no wildcard imports, normal imports - in ASCII sort order come first, followed by a blank line, followed by static imports in ASCII - sort order). + * braces must be used with `if`, `else`, `for`, `do` and `while` statements, even when the body is empty or contains only a single statement. -* implementation comments: wrap them to respect the column limit of 100 characters. * XML files: indent with two spaces and wrap to respect the column limit of 100 characters. +Also, if your IDE sorts import statements automatically, make sure it follows the same order as the +formatter: all static imports in ASCII sort order, followed by a blank line, followed by all regular +imports in ASCII sort order. ## Coding style -- production code diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index cc0ac0092af..9cabcdcf1c1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.Arrays; -import java.util.Collections; public class BatchStatementBuilder extends StatementBuilder { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java index 1b52670473f..5e678da7066 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.api.core.data; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.google.common.base.Preconditions.checkArgument; - /** * A duration, as defined in CQL. * @@ -111,7 +111,6 @@ public static CqlDuration newInstance(int months, int days, long nanoseconds) { *

        • {@code us} or {@code µs}: microseconds *
        • {@code ns}: nanoseconds * - * *
        • ISO 8601 format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W *
        • ISO 8601 alternative format: P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java b/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java index db191522c0c..8dacd8da2bc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/detach/Detachable.java @@ -32,10 +32,10 @@ * type). * *
            - *
          • When a data container was obtained from a driver instance (for example, reading a row from + *
          • When a data container was obtained from a driver instance (for example, reading a row from * a result set, or reading a value from a UDT column), it is attached: its protocol * version and registry are those of the driver. - *
          • When it is created manually by the user (for example, creating an instance from a manually + *
          • When it is created manually by the user (for example, creating an instance from a manually * created {@link TupleType}), it is detached: it uses {@link * ProtocolVersion#DEFAULT} and {@link CodecRegistry#DEFAULT}. *
          diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 38600744cde..b5da8fcc7e5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -16,11 +16,11 @@ package com.datastax.oss.driver.api.core.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.internal.core.ContactPoints; @@ -73,7 +73,6 @@ public abstract class SessionBuilder { *
        • {@code application.properties} (all resources on classpath with this name) *
        • {@code reference.conf} (all resources on classpath with this name) * - * *
        • the resulting configuration is expected to contain a {@code datastax-java-driver} * section. *
        • that section is validated against the {@link CoreDriverOption core driver options}. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java index ace6dbc4ee7..357049cd305 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeParameter.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.api.core.type.reflect; +import static com.google.common.base.Preconditions.checkArgument; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import static com.google.common.base.Preconditions.checkArgument; - /** * Captures a free type variable that can be used in {@link GenericType#where(GenericTypeParameter, * GenericType)}. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java index 3e12e895ad3..ff8f3ca9c6c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/uuid/Uuids.java @@ -63,7 +63,6 @@ *
        • If all of the above fail, a random integer will be generated and used as a surrogate * PID. * - * * * * @see JAVA-444 @@ -353,7 +352,8 @@ private static Set getAllLocalAddresses() { try { InetAddress localhost = InetAddress.getLocalHost(); allIps.add(localhost.toString()); - // Also return the hostname if available, it won't hurt (this does a dns lookup, it's only done once at startup) + // Also return the hostname if available, it won't hurt (this does a dns lookup, it's only + // done once at startup) allIps.add(localhost.getCanonicalHostName()); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); if (allMyIps != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 3d07fd22372..48788aaaead 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -272,7 +272,8 @@ void onResponse(Message response) { setConnectSuccess(); } else if (response instanceof Error) { Error error = (Error) response; - // Testing for a specific string is a tad fragile but Cassandra doesn't give us a more precise error + // Testing for a specific string is a tad fragile but Cassandra doesn't give us a more + // precise error // code. // C* 2.1 reports a server error instead of protocol error, see CASSANDRA-9451. if (step == Step.STARTUP diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java index 54ac3a73f62..13d31e662a9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; +import static com.typesafe.config.ConfigValueType.OBJECT; + import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; @@ -31,8 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.typesafe.config.ConfigValueType.OBJECT; - public class TypeSafeDriverConfig implements DriverConfig { private static final Logger LOG = LoggerFactory.getLogger(TypeSafeDriverConfig.class); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index d6c84fde6e8..04acd8622be 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -324,7 +324,8 @@ private void connect( connect(nodes, errors, onSuccess, onFailure); } else { LOG.debug("[{}] Connection established to {}", logPrefix, node); - // Make sure previous channel gets closed (it may still be open if reconnection was forced) + // Make sure previous channel gets closed (it may still be open if + // reconnection was forced) DriverChannel previousChannel = ControlConnection.this.channel; if (previousChannel != null) { previousChannel.forceClose(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 877a71efdfa..f687f1123d3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -27,7 +28,6 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 293ef51ba4d..e8556a1ab98 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.CountingIterator; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index 69a870d9ddd..d988918e094 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index b8a365b506c..ccbfb5ca914 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.base.Preconditions; @@ -28,7 +27,6 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java index d467fc6e9ed..111e6d058d9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java @@ -52,7 +52,6 @@ public enum Type { * com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule} * to be reset, and the next reconnection attempt to happen immediately. * - * * */ public static TopologyEvent suggestUp(InetSocketAddress address) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java index 89a2e1207c3..4b4572a5c8c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRange.java @@ -99,7 +99,8 @@ protected List split(Token rawStartToken, Token rawEndToken, int numberOf } // Convert a token's byte array to a number in order to perform computations. - // This depends on the number of "significant bytes" that we use to normalize all tokens to the same size. + // This depends on the number of "significant bytes" that we use to normalize all tokens to the + // same size. // For example if the token is 0x01 but significantBytes is 2, the result is 8 (0x0100). private BigInteger toBigInteger(ByteBuffer bb, int significantBytes) { byte[] bytes = Bytes.getArray(bb); @@ -114,7 +115,8 @@ private BigInteger toBigInteger(ByteBuffer bb, int significantBytes) { } // Convert a numeric representation back to a byte array. - // Again, the number of significant bytes matters: if the input value is 1 but significantBytes is 2, the + // Again, the number of significant bytes matters: if the input value is 1 but significantBytes is + // 2, the // expected result is 0x0001 (a simple conversion would produce 0x01). protected ByteBuffer toBytes(BigInteger value, int significantBytes) { byte[] rawBytes = value.toByteArray(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java index 2d6e597430e..11c67e04c22 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/KeyspaceTokenMap.java @@ -54,7 +54,8 @@ static KeyspaceTokenMap build( strategy.computeReplicasByToken(tokenToPrimary, ring); SetMultimap tokenRangesByNode; if (ring.size() == 1) { - // We forced the single range to ]minToken,minToken], make sure to use that instead of relying + // We forced the single range to ]minToken,minToken], make sure to use that instead of + // relying // on the node's token ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); for (Node node : tokenToPrimary.values()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java index d1a846795a2..bfda1c40178 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java @@ -71,7 +71,7 @@ private long murmur(ByteBuffer data) { long c1 = 0x87c37b91114253d5L; long c2 = 0x4cf5ad432745937fL; - //---------- + // ---------- // body for (int i = 0; i < nblocks; i++) { @@ -94,7 +94,7 @@ private long murmur(ByteBuffer data) { h2 = h2 * 5 + 0x38495ab5; } - //---------- + // ---------- // tail // Advance offset to the unprocessed tail of the data. @@ -158,7 +158,7 @@ private long murmur(ByteBuffer data) { h1 ^= k1; } - //---------- + // ---------- // finalization h1 ^= length; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java index 39e1d524240..c08b980d3e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRange.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static com.datastax.oss.driver.internal.core.metadata.token.RandomTokenFactory.MAX_VALUE; + import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.google.common.collect.Lists; import java.math.BigInteger; import java.util.List; -import static com.datastax.oss.driver.internal.core.metadata.token.RandomTokenFactory.MAX_VALUE; - public class RandomTokenRange extends TokenRangeBase { private static final BigInteger RING_LENGTH = MAX_VALUE.add(BigInteger.ONE); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java index 30b4d5c0867..5a82fabb0e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ReplicationStrategy.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.token.Token; -import com.google.common.base.Preconditions; import com.google.common.collect.SetMultimap; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 68efca3ad99..b6e3ceb4147 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -273,7 +273,8 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { if (future.isCompletedExceptionally()) { Throwable error = CompletableFutures.getFailed(future); LOG.debug("[{}] Error while opening new channel", logPrefix, error); - // TODO we don't log at a higher level because it's not a fatal error, but this should probably be recorded somewhere (metric?) + // TODO we don't log at a higher level because it's not a fatal error, but this should + // probably be recorded somewhere (metric?) // TODO auth exception => WARN and keep reconnecting // TODO protocol error => WARN and force down diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java index 1ec09ce2789..a62add533ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodec.java @@ -183,7 +183,8 @@ public void writeShortBytes(byte[] bytes, ByteBuf dest) { } // Reads *all* readable bytes from a buffer and return them. - // If the buffer is backed by an array, this will return the underlying array directly, without copy. + // If the buffer is backed by an array, this will return the underlying array directly, without + // copy. private static byte[] readRawBytes(ByteBuf buffer) { if (buffer.hasArray() && buffer.readableBytes() == buffer.array().length) { // Move the readerIndex just so we consistently consume the input diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java index 41a5fa4de2f..99794ddaeae 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/NativeClock.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.time; -import com.datastax.oss.driver.internal.core.os.Native; -import java.util.concurrent.atomic.AtomicReference; - import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import com.datastax.oss.driver.internal.core.os.Native; +import java.util.concurrent.atomic.AtomicReference; + /** * Provides the current time with microseconds precision with some reasonable accuracy through the * use of {@link Native#currentTimeMicros()}. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java index f5cca6f9499..eb4f75f7f7a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DateCodec.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static java.lang.Long.parseLong; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -27,8 +29,6 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import static java.lang.Long.parseLong; - public class DateCodec implements TypeCodec { private static final LocalDate EPOCH = LocalDate.of(1970, 1, 1); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java index 19fa3e1ff31..686c0e5e330 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static java.lang.Long.parseLong; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -29,8 +31,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; -import static java.lang.Long.parseLong; - public class TimestampCodec implements TypeCodec { /** A {@link DateTimeFormatter} that parses (most) of the ISO formats accepted in CQL. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index 0b8f4cfc669..1ee7a9fd7af 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -158,7 +158,8 @@ public TypeCodec codecFor(T value) { return safeCast(getCachedCodec(null, javaType)); } - // Not exposed publicly, this is only used for the recursion from createCovariantCodec(GenericType) + // Not exposed publicly, this is only used for the recursion from + // createCovariantCodec(GenericType) private TypeCodec covariantCodecFor(GenericType javaType) { LOG.trace("[{}] Looking up codec for Java type {}", logPrefix, javaType); for (TypeCodec primitiveCodec : PRIMITIVE_CODECS) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java index 731d1139046..918949a13fc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/util/VIntCoding.java @@ -70,7 +70,7 @@ public class VIntCoding { private static long readUnsignedVInt(DataInput input) throws IOException { int firstByte = input.readByte(); - //Bail out early if this is one byte, necessary or it fails later + // Bail out early if this is one byte, necessary or it fails later if (firstByte >= 0) { return firstByte; } @@ -90,7 +90,8 @@ public static long readVInt(DataInput input) throws IOException { return decodeZigZag64(readUnsignedVInt(input)); } - // & this with the first byte to give the value part for a given extraBytesToRead encoded in the byte + // & this with the first byte to give the value part for a given extraBytesToRead encoded in the + // byte private static int firstByteValueMask(int extraBytesToRead) { // by including the known 0bit in the mask, we can use this for encodeExtraBytesToRead return 0xff >> extraBytesToRead; @@ -103,7 +104,8 @@ private static byte encodeExtraBytesToRead(int extraBytesToRead) { private static int numberOfExtraBytesToRead(int firstByte) { // we count number of set upper bits; so if we simply invert all of the bits, we're golden - // this is aided by the fact that we only work with negative numbers, so when upcast to an int all + // this is aided by the fact that we only work with negative numbers, so when upcast to an int + // all // of the new upper bits are also set, so by inverting we set all of them to zero return Integer.numberOfLeadingZeros(~firstByte) - 24; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java index 1444d6ca9fb..28199b5127d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Strings.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.util; import com.google.common.collect.ImmutableSet; -import java.util.Set; public class Strings { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index f6c40e4d6db..8a1d9c89c0e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.google.common.base.Preconditions; - import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; diff --git a/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java b/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java index 2558b57b589..917d572e721 100644 --- a/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/ByteBufAssert.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.protocol.internal.util.Bytes; import io.netty.buffer.ByteBuf; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - public class ByteBufAssert extends AbstractAssert { public ByteBufAssert(ByteBuf actual) { super(actual, ByteBufAssert.class); diff --git a/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java b/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java index ac867f8d036..67c22c56eeb 100644 --- a/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java +++ b/core/src/test/java/com/datastax/oss/driver/DriverRunListener.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver; +import static org.assertj.core.api.Assertions.fail; + import org.junit.runner.Description; import org.junit.runner.notification.RunListener; -import static org.assertj.core.api.Assertions.fail; - /** * Common parent of all driver tests, to store common configuration and perform sanity checks. * diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java index 268092ad68f..06261fc5271 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionAssert.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.api.core; +import static com.datastax.oss.driver.Assertions.assertThat; + import org.assertj.core.api.AbstractComparableAssert; import org.assertj.core.api.Assertions; -import static com.datastax.oss.driver.Assertions.assertThat; - public class CassandraVersionAssert extends AbstractComparableAssert { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java index b2d32210efa..969e3bae11e 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CassandraVersionTest.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.api.core; -import org.junit.Test; - import static com.datastax.oss.driver.Assertions.assertThat; +import org.junit.Test; + public class CassandraVersionTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java index 5e5f4705d3a..db440007e92 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/CqlIdentifierTest.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.api.core; -import org.junit.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + public class CqlIdentifierTest { @Test public void should_build_from_internal() { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java index ae4d10e7d21..c983735c739 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.api.core.data; -import org.junit.Test; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import org.junit.Test; + public class CqlDurationTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index 65e936db338..252649b4e72 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -15,6 +15,13 @@ */ package com.datastax.oss.driver.api.core.retry; +import static com.datastax.oss.driver.api.core.ConsistencyLevel.QUORUM; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; +import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; +import static com.datastax.oss.driver.api.core.retry.WriteType.BATCH_LOG; +import static com.datastax.oss.driver.api.core.retry.WriteType.SIMPLE; + import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.servererrors.OverloadedException; @@ -24,13 +31,6 @@ import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import org.junit.Test; -import static com.datastax.oss.driver.api.core.ConsistencyLevel.QUORUM; -import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; -import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; -import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; -import static com.datastax.oss.driver.api.core.retry.WriteType.BATCH_LOG; -import static com.datastax.oss.driver.api.core.retry.WriteType.SIMPLE; - public class DefaultRetryPolicyTest extends RetryPolicyTestBase { public DefaultRetryPolicyTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java index 10c6097013a..dc55d9c7d17 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.retry; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.session.Request; @@ -24,8 +26,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static org.assertj.core.api.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public abstract class RetryPolicyTestBase { private final RetryPolicy policy; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index 69e1abb410b..f1469978c53 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.specex; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -28,8 +30,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class ConstantSpeculativeExecutionPolicyTest { @Mock private DriverContext context; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java index ac91b8da502..1212f7201de 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/AtomicTimestampGeneratorTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.time; +import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.fail; + import com.datastax.oss.driver.internal.core.time.Clock; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; @@ -25,9 +28,6 @@ import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; -import static com.datastax.oss.driver.Assertions.assertThat; -import static com.datastax.oss.driver.Assertions.fail; - public class AtomicTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @Override protected MonotonicTimestampGenerator newInstance(Clock clock) { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java index a5d4aa77770..a4b886a8ef7 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.time; +import static org.assertj.core.api.Assertions.assertThat; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -36,8 +38,6 @@ import org.mockito.stubbing.OngoingStubbing; import org.slf4j.LoggerFactory; -import static org.assertj.core.api.Assertions.assertThat; - abstract class MonotonicTimestampGeneratorTestBase { @Mock protected Clock clock; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java index 6165d23d055..9816a1ef473 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/ThreadLocalTimestampGeneratorTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.time; +import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.fail; + import com.datastax.oss.driver.internal.core.time.Clock; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.List; @@ -28,9 +31,6 @@ import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; -import static com.datastax.oss.driver.Assertions.assertThat; -import static com.datastax.oss.driver.Assertions.fail; - public class ThreadLocalTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @Override protected MonotonicTimestampGenerator newInstance(Clock clock) { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java index 99746225848..062980f45d4 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.api.core.type.reflect; +import static org.assertj.core.api.Assertions.assertThat; + import com.google.common.reflect.TypeToken; import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class GenericTypeTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java index 0a09a7343ae..7540584d322 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.uuid; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.nio.ByteBuffer; @@ -25,8 +27,6 @@ import java.util.concurrent.ConcurrentSkipListSet; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class UuidsTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java index d5cb46278bd..0494aeaf539 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/SerializationHelper.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal; +import static org.assertj.core.api.Assertions.fail; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import static org.assertj.core.api.Assertions.fail; - public abstract class SerializationHelper { public static byte[] serialize(T t) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java index b5f67fab9db..b9b0fde7631 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -25,8 +27,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - /** * Covers {@link CassandraProtocolVersionRegistry#highestCommon(Collection)} separately, because it * relies explicitly on {@link CoreProtocolVersion} as the version implementation. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java index 23bbffdeedb..548251275cd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.datastax.oss.driver.Assertions.assertThat; - /** * Covers the method that are agnostic to the actual {@link ProtocolVersion} implementation (using a * mock implementation). diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java index 2bc05725207..aaf8049e909 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -22,9 +25,6 @@ import java.util.function.Consumer; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class CompletionStageAssert extends AbstractAssert, CompletionStage> { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java index fbace1fc3b1..0f420bb002b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/DriverConfigAssert.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverOption; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - public class DriverConfigAssert extends AbstractAssert { public DriverConfigAssert(DriverConfig actual) { super(actual, DriverConfigAssert.class); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java index 48b9bbec5dd..0e572b89f93 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/NettyFutureAssert.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import io.netty.util.concurrent.Future; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -22,9 +25,6 @@ import java.util.function.Consumer; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class NettyFutureAssert extends AbstractAssert, Future> { public NettyFutureAssert(Future actual) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 3f4a66ea6d9..937039d99ba 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.protocol.internal.Frame; @@ -27,10 +31,6 @@ import org.mockito.Mock; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.timeout; - public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Mock private ResponseCallback responseCallback; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 038e834a4c1..da6a1f5c605 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.internal.core.TestResponses; @@ -23,8 +25,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ChannelFactoryClusterNameTest extends ChannelFactoryTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index a9a6aa4c2b7..1665b065174 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -30,8 +32,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ChannelFactoryProtocolNegotiationTest extends ChannelFactoryTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 9689ce80083..4970fee23f3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -59,9 +62,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.fail; - /** * Sets up the infrastructure for channel factory tests. * diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java index 1f25ddf8414..d0da5377c5f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelHandlerTestBase.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.embedded.EmbeddedChannel; import java.util.Collections; import org.junit.Before; -import static org.assertj.core.api.Assertions.assertThat; - /** * Infrastructure for channel handler test. * diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java index 4ae34fc84d2..ba5fd28aeb5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ConnectInitHandlerTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; + import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; import org.junit.Before; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ConnectInitHandlerTest extends ChannelHandlerTestBase { private TestHandler handler; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index cf874b3c8fe..fea55b2ed24 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.protocol.internal.Frame; @@ -33,8 +35,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; - public class DriverChannelTest extends ChannelHandlerTestBase { public static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 81976872c93..2ed3fe08fe8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; @@ -39,9 +42,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; - public class InFlightHandlerTest extends ChannelHandlerTestBase { private static final Query QUERY = new Query("select * from foo"); private static final int SET_KEYSPACE_TIMEOUT_MILLIS = 100; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index d6fd9de2640..05f697b07b1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; @@ -33,12 +39,6 @@ import org.mockito.internal.util.MockUtil; import org.mockito.stubbing.OngoingStubbing; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.timeout; - /** * Helper class to set up and verify a sequence of invocations on a ChannelFactory mock. * diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index d75bad658fa..c3bf61eed77 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.channel; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.atLeast; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -64,9 +67,6 @@ import org.mockito.MockitoAnnotations; import org.slf4j.LoggerFactory; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.atLeast; - public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { private static final long QUERY_TIMEOUT_MILLIS = 100L; @@ -292,7 +292,8 @@ public void should_initialize_with_authentication() { assertThat(Bytes.toHexString(authResponse.token)).isEqualTo(MockAuthenticator.INITIAL_RESPONSE); assertThat(connectFuture).isNotDone(); - // As long as the server sends an auth challenge, the client should reply with another auth_response + // As long as the server sends an auth challenge, the client should reply with another + // auth_response String mockToken = "0xabcd"; for (int i = 0; i < 5; i++) { writeInboundFrame(requestFrame, new AuthChallenge(Bytes.fromHexString(mockToken))); @@ -305,7 +306,8 @@ public void should_initialize_with_authentication() { assertThat(connectFuture).isNotDone(); } - // When the server finally sends back a success message, should proceed to the cluster name check and succeed + // When the server finally sends back a success message, should proceed to the cluster name + // check and succeed writeInboundFrame(requestFrame, new AuthSuccess(Bytes.fromHexString(mockToken))); assertThat(authenticator.successToken).isEqualTo(mockToken); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java index 03d91ba0acd..7bbbf23c329 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/StreamIdGeneratorTest.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.internal.core.channel; -import org.junit.Test; - import static com.datastax.oss.driver.Assertions.assertThat; +import org.junit.Test; + public class StreamIdGeneratorTest { @Test public void should_have_all_available_upon_creation() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index 6a915bbeb2e..6723752282d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -36,9 +39,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; - public class DefaultDriverConfigLoaderTest { @Mock private InternalDriverContext context; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index ff9cebced26..96908e4e70b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; @@ -23,8 +25,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.datastax.oss.driver.Assertions.assertThat; - public class TypeSafeDriverConfigTest { @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java index 92855c91133..1bf71a7ce6c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/bus/EventBusTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.context.bus; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.context.EventBus; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class EventBusTest { private EventBus bus; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index fe8f63398b5..0676dccae8a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.control; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; + import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.channel.EventCallback; @@ -29,9 +32,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; - public class ControlConnectionEventsTest extends ControlConnectionTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index ddc697a6367..34ab1f9100e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.control; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -34,9 +37,6 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; - @RunWith(DataProviderRunner.class) public class ControlConnectionTest extends ControlConnectionTestBase { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 58f9e6a4529..11e8c97b1a0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.control; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; + import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; @@ -48,9 +51,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; - abstract class ControlConnectionTestBase { protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 6c52e173b65..c72441c233a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -44,10 +48,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; - public class CqlPrepareHandlerTest { private static final DefaultPrepareRequest PREPARE_REQUEST = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 78e6378621a..de57fddc2a3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.connection.HeartbeatException; @@ -44,10 +48,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; - public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 344420c9303..bd543d1fc33 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -32,8 +34,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class CqlRequestHandlerSpeculativeExecutionTest extends CqlRequestHandlerTestBase { @Test @@ -318,7 +318,8 @@ public void should_retry_in_speculative_executions( .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); - // do not simulate a response from node1. The request will stay hanging for the rest of this test + // do not simulate a response from node1. The request will stay hanging for the rest of this + // test harness.nextScheduledTask(); // Discard the timeout task @@ -379,7 +380,8 @@ public void should_stop_retrying_other_executions_if_result_complete( node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); assertThat(resultSetFuture).isSuccess(); - // node2 replies with a response that would trigger a RETRY_NEXT if the request was still running + // node2 replies with a response that would trigger a RETRY_NEXT if the request was still + // running node2Behavior.setResponseSuccess( defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 376f42f83ca..c7bae13455f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -15,13 +15,14 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BoundStatement; -import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -44,8 +45,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class CqlRequestHandlerTest extends CqlRequestHandlerTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 12906c0543f..15e1e80e50c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -40,8 +42,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; - public class DefaultAsyncResultSetTest { @Mock private ColumnDefinitions columnDefinitions; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 8c74b706c86..9cf17f0c9ad 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; @@ -26,11 +31,6 @@ import java.util.concurrent.CompletableFuture; import org.mockito.Mockito; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.Mockito.never; - /** * The simulated behavior of the connection pool for a given node in a {@link * RequestHandlerTestHarness}. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 4a403dca32a..97e98b76c9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -15,11 +15,16 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; @@ -53,11 +58,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.times; - @RunWith(MockitoJUnitRunner.class) public class QueryTraceFetcherTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 6bb4754559e..e5b878eb343 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; + import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -53,10 +57,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; - /** * Provides the environment to test a request handler, where a query plan can be defined, and the * behavior of each successive node simulated. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index 43cac08bc04..4f63ea7b0f5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -29,8 +31,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ResultSetsTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index 4f5ff536356..0762f08c237 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.data; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.GettableById; @@ -30,10 +34,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; - public abstract class AccessibleByIdTestBase< T extends GettableById & SettableById & GettableByName & SettableByName> extends AccessibleByIndexTestBase { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index e7af1344fa0..9e4432be5b4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.data; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.GettableByIndex; import com.datastax.oss.driver.api.core.data.SettableByIndex; @@ -37,10 +41,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; - public abstract class AccessibleByIndexTestBase> { protected abstract T newInstance(List dataTypes, AttachmentPoint attachmentPoint); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index 937563b0f54..54c2cd6a8eb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.data; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.type.DataType; @@ -27,8 +29,6 @@ import java.util.List; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DefaultTupleValueTest extends AccessibleByIndexTestBase { @Override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index a420dfb333e..9a1375b4a55 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.data; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; @@ -27,8 +29,6 @@ import java.util.List; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DefaultUdtValueTest extends AccessibleByIdTestBase { @Override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java index b6ac0fa0c04..46a1ed6432b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/IdentifierIndexTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.data; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.google.common.collect.ImmutableList; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class IdentifierIndexTest { private static final CqlIdentifier Foo = CqlIdentifier.fromInternal("Foo"); private static final CqlIdentifier foo = CqlIdentifier.fromInternal("foo"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index 84fe778dbf5..2e0f34798e4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -24,11 +29,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; - @RunWith(MockitoJUnitRunner.Silent.class) public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPolicyTestBase { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index 785cb21b5b2..0c6a17d6d97 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.filter; +import static org.mockito.Mockito.atLeast; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -25,10 +29,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.filter; -import static org.mockito.Mockito.atLeast; - public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPolicyTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 7e2fe170802..a2015ef1c95 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -15,6 +15,14 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -22,15 +30,12 @@ import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; -import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.nio.ByteBuffer; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import org.junit.Before; @@ -38,14 +43,6 @@ import org.mockito.Mock; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancingPolicyTestBase { private static final CqlIdentifier KEYSPACE = CqlIdentifier.fromInternal("ks"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java index 52098815da3..a67e6ce2c1a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; +import static org.mockito.ArgumentMatchers.any; + import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; @@ -36,8 +38,6 @@ import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.LoggerFactory; -import static org.mockito.ArgumentMatchers.any; - @RunWith(MockitoJUnitRunner.class) public abstract class DefaultLoadBalancingPolicyTestBase { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 9f52f10a583..61e772fcad2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableMap; @@ -25,8 +27,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class AddNodeRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index 6cb09579abc..b86cf1e833c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; @@ -32,8 +34,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class DefaultMetadataTokenMapTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 9bc5b22a2af..8fc050c6d6a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -47,11 +52,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.never; - public class DefaultTopologyMonitorTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index ef05e3ae61c..2e014151726 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -24,8 +26,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class FullNodeListRefreshTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index b6fcea129de..44838cab078 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableSet; import java.net.InetSocketAddress; @@ -23,8 +25,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class InitContactPointsRefreshTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index a1afca7cbb7..bf8d848f217 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -39,12 +45,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; - public class LoadBalancingPolicyWrapperTest { private DefaultNode node1; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 944e2a89194..6893a686a6f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -47,10 +51,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.timeout; - public class MetadataManagerTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 930093187ba..f630dac20a9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -44,12 +50,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class NodeStateManagerTest { private static final InetSocketAddress NEW_ADDRESS = new InetSocketAddress("127.0.0.3", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 549c64835f5..543ab74c0c0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; @@ -23,8 +25,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class RemoveNodeRefreshTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index df75f64f9c5..760bb96dead 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -48,11 +53,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.never; - @RunWith(MockitoJUnitRunner.class) public class SchemaAgreementCheckerTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java index 4067da842e3..67d8ed25562 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; @@ -28,8 +30,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class AggregateParserTest extends SchemaParserTestBase { private static final AdminRow SUM_AND_TO_STRING_ROW_2_2 = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java index 4e17ef01a22..4d4cc6ce508 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -31,8 +33,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class DataTypeClassNameParserTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java index 4707567534d..dfc40365402 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -30,8 +32,6 @@ import org.mockito.Mock; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class DataTypeCqlNameParserTest { private static final CqlIdentifier KEYSPACE_ID = CqlIdentifier.fromInternal("ks"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java index 2f98db2d95d..ace79bf9ae0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -23,8 +25,6 @@ import java.util.Collections; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class FunctionParserTest extends SchemaParserTestBase { private static final AdminRow ID_ROW_2_2 = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java index 58909273922..7822e074668 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; @@ -29,8 +31,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class SchemaParserTest extends SchemaParserTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java index d19b49bf66a..9416376d456 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -28,8 +30,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static org.assertj.core.api.Assertions.fail; - @RunWith(MockitoJUnitRunner.Silent.class) public abstract class SchemaParserTestBase { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java index f7bcbf7e691..d35d09d362f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; @@ -31,8 +33,6 @@ import java.util.Map; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class TableParserTest extends SchemaParserTestBase { private static final AdminRow TABLE_ROW_2_2 = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java index e606a7db737..8a6fbaed6e8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeListParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.ListType; @@ -27,8 +29,6 @@ import java.util.Map; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class UserDefinedTypeListParserTest extends SchemaParserTestBase { private static final AdminRow PERSON_ROW_2_2 = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java index 1abf65bb960..f8bb088313b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; @@ -26,8 +28,6 @@ import java.util.Iterator; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ViewParserTest extends SchemaParserTestBase { static final AdminRow VIEW_ROW_3_0 = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index 96758f8c268..e9a9d2f43a7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -27,8 +29,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - // Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is // shared, this class just validates the query strings. public class Cassandra21SchemaQueriesTest extends SchemaQueriesTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index a7539d1ac52..0480f8944e2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -27,8 +29,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - // Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is // shared, this class just validates the query strings. public class Cassandra22SchemaQueriesTest extends SchemaQueriesTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index b2456c24953..99f47c34f0e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -29,8 +31,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class Cassandra3SchemaQueriesTest extends SchemaQueriesTest { @Before diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java index c80f1babec6..e7c1cecd9bd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -32,8 +34,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static org.assertj.core.api.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public abstract class SchemaQueriesTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java index 8c5af1cc01f..7816ecfb9a0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.refresh; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; @@ -33,8 +35,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class SchemaRefreshTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java index 19501b228d5..392a801c789 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenRangeTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.protocol.internal.util.Bytes; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - /** @see TokenRangeTest */ public class ByteOrderedTokenRangeTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java index 886b76fb73d..fe3cd551274 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Node; @@ -38,8 +40,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class DefaultTokenMapTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java index 31e70148961..3d14f21c741 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenRangeTest.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; -import org.junit.Test; - import static com.datastax.oss.driver.Assertions.assertThat; +import org.junit.Test; + /** @see TokenRangeTest */ public class Murmur3TokenRangeTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java index 73381f2d1c8..022053ac58e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -36,11 +41,6 @@ import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.LoggerFactory; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.never; - @RunWith(MockitoJUnitRunner.class) public class NetworkTopologyReplicationStrategyTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java index c221f05139d..57a22f463cf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenRangeTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static org.assertj.core.api.Assertions.assertThat; + import java.math.BigInteger; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class RandomTokenRangeTest { private static final String MIN = "-1"; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java index e5295946f5e..b08c839111f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/SimpleReplicationStrategyTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.google.common.collect.ImmutableList; @@ -27,8 +29,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static com.datastax.oss.driver.Assertions.assertThat; - @RunWith(MockitoJUnitRunner.class) public class SimpleReplicationStrategyTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java index ba3cc1fea10..174fa69519a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeAssert.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import java.util.List; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - public class TokenRangeAssert extends AbstractAssert { public TokenRangeAssert(TokenRange actual) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java index f638e561966..a912bf7b16f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/TokenRangeTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.metadata.token; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.junit.Assert.fail; + import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.junit.Assert.fail; - /** * Covers the methods that don't depend on the underlying factory (we use Murmur3 as the * implementation here). diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index abcbbd1b626..94fa2797cd7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -30,10 +34,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class ChannelPoolInitTest extends ChannelPoolTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index bcfa13e3a77..bdefb377f13 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -27,8 +29,6 @@ import org.junit.Test; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ChannelPoolKeyspaceTest extends ChannelPoolTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 0a721b555eb..6ca59661dbc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -29,11 +34,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class ChannelPoolReconnectTest extends ChannelPoolTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index 08af8945475..d29d9373e74 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -28,10 +32,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class ChannelPoolResizeTest extends ChannelPoolTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 1231989fa28..54140ce0fd1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -28,10 +32,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class ChannelPoolShutdownTest extends ChannelPoolTestBase { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 99635ac04f8..9adda1f0ca5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -42,9 +45,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; - abstract class ChannelPoolTestBase { static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java index 633c328c296..ab732d76fbd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.pool; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; + import com.datastax.oss.driver.internal.core.channel.DriverChannel; import org.junit.Before; import org.junit.Test; @@ -22,9 +25,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; - public class ChannelSetTest { @Mock private DriverChannel channel1, channel2, channel3; private ChannelSet set; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java index 2b5ab173f09..b6eb23be387 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/ByteBufPrimitiveCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.util.ByteBufs; import com.datastax.oss.protocol.internal.util.Bytes; import io.netty.buffer.ByteBuf; @@ -26,8 +28,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.datastax.oss.driver.Assertions.assertThat; - /** * Note: we don't test trivial methods that simply delegate to ByteBuf, nor default implementations * inherited from {@link com.datastax.oss.protocol.internal.PrimitiveCodec}. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java index 16863b32ef6..c223cb15462 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/protocol/FrameDecoderTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.protocol; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.internal.core.channel.ChannelHandlerTestBase; import com.datastax.oss.driver.internal.core.util.ByteBufs; @@ -27,9 +30,6 @@ import org.junit.Before; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class FrameDecoderTest extends ChannelHandlerTestBase { // A valid binary payload for a response frame. private static final ByteBuf VALID_PAYLOAD = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index fb201c9e592..f7800a1cd41 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -15,14 +15,20 @@ */ package com.datastax.oss.driver.internal.core.session; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; @@ -63,12 +69,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.timeout; - public class DefaultSessionPoolsTest { private static final CqlIdentifier KEYSPACE = CqlIdentifier.fromInternal("ks"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 1ebd54b8664..b18293d6c74 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.internal.core.session; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; @@ -39,12 +45,6 @@ import org.mockito.internal.util.MockUtil; import org.mockito.stubbing.OngoingStubbing; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.timeout; - public class MockChannelPoolFactoryHelper { public static MockChannelPoolFactoryHelper.Builder builder( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index eaf234ddf89..53ed50d3ace 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.session; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -53,8 +55,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ReprepareOnUpTest { @Mock private ChannelPool pool; @Mock private DriverChannel channel; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java index b76bc4e7a7f..dab43a2e0e1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeDetachableTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.type.DataType; @@ -31,8 +33,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class DataTypeDetachableTest { @Mock private AttachmentPoint attachmentPoint; @@ -44,7 +44,8 @@ public void setup() { @Test public void simple_types_should_never_be_detached() { - // Because simple types don't need the codec registry, we consider them as always attached by default + // Because simple types don't need the codec registry, we consider them as always attached by + // default for (DataType simpleType : ImmutableList.of(DataTypes.INT, DataTypes.custom("some.class"))) { assertThat(simpleType.isDetached()).isFalse(); assertThat(SerializationHelper.serializeAndDeserialize(simpleType).isDetached()).isFalse(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java index 251c60f2527..ed53b0b4e65 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/DataTypeSerializationTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -23,8 +25,6 @@ import com.datastax.oss.driver.internal.SerializationHelper; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DataTypeSerializationTest { @Test @@ -37,7 +37,8 @@ public void should_serialize_and_deserialize() { .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) .build(); - // Because primitive and custom types never use the codec registry, we consider them always attached + // Because primitive and custom types never use the codec registry, we consider them always + // attached should_serialize_and_deserialize(DataTypes.INT, false); should_serialize_and_deserialize(DataTypes.custom("some.class.name"), false); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java index 0a6f61cd8f8..1f1107727c8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class BigIntCodecTest extends CodecTestBase { public BigIntCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java index 74d10d54d3c..dfba69f2596 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class BlobCodecTest extends CodecTestBase { private static final ByteBuffer BUFFER = Bytes.fromHexString("0xcafebabe"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java index 4279f978a2c..b23c9ee098f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class BooleanCodecTest extends CodecTestBase { public BooleanCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java index 5c8a0e6df43..576072ce911 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; -import static org.assertj.core.api.Assertions.assertThat; - public class CodecTestBase { protected TypeCodec codec; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java index ef8510939fb..b413e9e50bf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class CounterCodecTest extends CodecTestBase { public CounterCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java index e4b25ccaaef..606c670560b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class CqlDurationCodecTest extends CodecTestBase { private static final CqlDuration DURATION = CqlDuration.newInstance(1, 2, 3); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java index 4de6ee48fb6..834ee426b42 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; @@ -22,8 +24,6 @@ import java.nio.ByteBuffer; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class CustomCodecTest extends CodecTestBase { private static final ByteBuffer BUFFER = Bytes.fromHexString("0xcafebabe"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java index 707590a984a..3f8bfc32bbe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalDate; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DateCodecTest extends CodecTestBase { private static final LocalDate EPOCH = LocalDate.ofEpochDay(0); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java index 7bbd65df113..349a8c54899 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigDecimal; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DecimalCodecTest extends CodecTestBase { public DecimalCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java index 4633d9bf4fe..4fa761c24dc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class DoubleCodecTest extends CodecTestBase { public DoubleCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java index b0334655463..7b2a92d6f18 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class FloatCodecTest extends CodecTestBase { public FloatCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java index 27b354d9eb2..0393a0e83d4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java @@ -15,15 +15,15 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.google.common.base.Strings; import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class InetCodecTest extends CodecTestBase { private static final InetAddress V4_ADDRESS; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java index a31cdb6fdca..154c2dfe939 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class IntCodecTest extends CodecTestBase { public IntCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java index f142bea451a..9aad5dd0dd0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -30,8 +32,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class ListCodecTest extends CodecTestBase> { @Mock private TypeCodec elementCodec; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java index 774e89ea2ac..c9702cbfa1e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -30,8 +32,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class MapCodecTest extends CodecTestBase> { @Mock private TypeCodec keyCodec; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java index 6a0fec401d4..3ef1563e496 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; @@ -30,8 +32,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class SetCodecTest extends CodecTestBase> { @Mock private TypeCodec elementCodec; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java index ae46f1ce89d..8057607a424 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class SmallIntCodecTest extends CodecTestBase { public SmallIntCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java index 6bd46f3e177..28cab6892d3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class StringCodecTest extends CodecTestBase { public StringCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java index 1f2f7554f2d..234396ab89e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class TimeCodecTest extends CodecTestBase { public TimeCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java index 063971ef5b3..809fdc59a51 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class TimeUuidCodecTest extends CodecTestBase { public static final UUID TIME_BASED = new UUID(6342305776366260711L, -5736720392086604862L); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java index 2281db117db..d158d01c491 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.time.Instant; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class TimestampCodecTest extends CodecTestBase { public TimestampCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java index a1f64cf8727..80a291f5853 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java @@ -15,11 +15,11 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class TinyIntCodecTest extends CodecTestBase { public TinyIntCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index 55c5c18688b..84493ee4708 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; @@ -33,8 +35,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class TupleCodecTest extends CodecTestBase { @Mock private AttachmentPoint attachmentPoint; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 51daa2da56e..00cee68e0e1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.UdtValue; @@ -34,8 +36,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; - public class UdtCodecTest extends CodecTestBase { @Mock private AttachmentPoint attachmentPoint; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java index ea392f2f386..ebe5f004c5a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.util.UUID; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class UuidCodecTest extends CodecTestBase { private final UUID MOCK_UUID = new UUID(2L, 1L); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java index 2412d998b1e..0aed7a0ddc7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java @@ -15,12 +15,12 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.math.BigInteger; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class VarintCodecTest extends CodecTestBase { public VarintCodecTest() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index d8277b7b5e2..4fb360589ff 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.type.codec.registry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.CqlDuration; @@ -59,9 +62,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class CachingCodecRegistryTest { @Mock private BiConsumer> onCacheLookup; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java index 26bce21c2a7..8a0cb4429fe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.util; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.google.common.collect.ImmutableList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ArrayUtilsTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java index 003051163e1..b673edcf1d2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/DirectedGraphTest.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.internal.core.util; -import org.junit.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + public class DirectedGraphTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java index f5be850f1f7..e9edaa7a85c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CycleDetectorTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.Uninterruptibles; @@ -25,9 +28,6 @@ import java.util.concurrent.Future; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - public class CycleDetectorTest { @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java index c9c80ece836..a53ac00af49 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + import com.google.common.base.Joiner; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.ScheduledFuture; @@ -30,10 +34,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; - public class DebouncerTest { private static final Duration DEFAULT_WINDOW = Duration.ofSeconds(1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index f2ce86075ad..687bacf6f07 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; + import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; @@ -34,9 +37,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; - @RunWith(DataProviderRunner.class) public class ReconnectionTest { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java index 00c66c9bf83..6dcda9119f1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilterTest.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class ReplayingEventFilterTest { private ReplayingEventFilter filter; private List filteredEvents; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index 4f0447bdd4e..a58fa6e0750 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; + import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultEventLoop; import io.netty.channel.EventLoopGroup; @@ -28,10 +32,6 @@ import java.util.concurrent.TimeoutException; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.anyBoolean; - /** * Extend Netty's default event loop to capture scheduled tasks instead of running them. The tasks * can be checked later, and run manually. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java index 6826079e2b7..61bc52c99e1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoopTest.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; +import static com.datastax.oss.driver.Assertions.assertThat; + import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop.CapturedTask; import io.netty.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; -import static com.datastax.oss.driver.Assertions.assertThat; - public class ScheduledTaskCapturingEventLoopTest { @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index fafe7e4e5f3..4722da53f82 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -24,8 +26,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class ConnectIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index aa30876bce9..f97987c076a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -23,9 +26,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - /** Covers protocol negotiation for the initial connection to the first contact point. */ @Category(ParallelizableTests.class) public class ProtocolVersionInitialNegotiationIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index a12d4d1ca77..55e398950b3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core; +import static com.datastax.oss.driver.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -33,9 +36,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static com.datastax.oss.driver.assertions.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - /** * Covers protocol re-negotiation with a mixed cluster: if, after the initial connection and the * first node list refresh, we find out that some nodes only support a lower version, reconnect the diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index ba88cc877c1..ebe9e9c9cf8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.compression; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -28,9 +31,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.offset; - @Category(ParallelizableTests.class) public class DirectCompressionIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index eaf439e68a6..b991bfb3cdb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.compression; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -28,9 +31,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.offset; - @Category(IsolatedTests.class) public class HeapCompressionIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 555aca93df3..63616b4951b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -15,13 +15,19 @@ */ package com.datastax.oss.driver.api.core.config; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; import com.datastax.oss.driver.api.core.cql.BatchType; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -39,12 +45,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - @Category(ParallelizableTests.class) public class DriverConfigProfileIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index 79649594ce4..43b0261976a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -15,6 +15,12 @@ */ package com.datastax.oss.driver.api.core.config; +import static com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -32,12 +38,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - public class DriverConfigProfileReloadIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index f84230264fb..ec64be948b6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -15,10 +15,16 @@ */ package com.datastax.oss.driver.api.core.connection; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.retry.DefaultRetryPolicy; import com.datastax.oss.driver.api.core.retry.RetryDecision; @@ -36,12 +42,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Category(ParallelizableTests.class) public class FrameLengthIT { public static @ClassRule SimulacronRule simulacron = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 1dea4831dbb..7d9dc742476 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -28,8 +30,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class AsyncResultSetIT { @@ -72,7 +72,8 @@ public static void setupSchema() { @Test public void should_only_iterate_over_rows_in_current_page() throws Exception { - // very basic test that just ensures that iterating over an AsyncResultSet only visits the first page. + // very basic test that just ensures that iterating over an AsyncResultSet only visits the first + // page. CompletionStage result = sessionRule .session() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index de3daa47e3e..b06ed1ba257 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; @@ -29,8 +31,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class BatchStatementIT { @@ -83,7 +83,8 @@ public void should_execute_batch_of_simple_statements_with_variables() { @Test public void should_execute_batch_of_bound_statements_with_variables() { - // Build a batch of batchCount statements with bound statements, each with their own positional variables. + // Build a batch of batchCount statements with bound statements, each with their own positional + // variables. BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( @@ -105,7 +106,8 @@ public void should_execute_batch_of_bound_statements_with_variables() { @Test @CassandraRequirement(min = "2.2") public void should_execute_batch_of_bound_statements_with_unset_values() { - // Build a batch of batchCount statements with bound statements, each with their own positional variables. + // Build a batch of batchCount statements with bound statements, each with their own positional + // variables. BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( @@ -159,7 +161,8 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { @Test public void should_execute_batch_of_bound_statements_with_named_variables() { - // Build a batch of batchCount statements with bound statements, each with their own named variable values. + // Build a batch of batchCount statements with bound statements, each with their own named + // variable values. BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); PreparedStatement preparedStatement = sessionRule.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 85f8f99b8b7..dcb6d41d785 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -27,8 +29,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class BoundStatementIT { @@ -130,7 +130,8 @@ public void should_have_empty_result_definitions_for_update_query() { private void verifyUnset(BoundStatement boundStatement) { sessionRule.session().execute(boundStatement.unset(1)); - // Verify that no tombstone was written by reading data back and ensuring initial value is retained. + // Verify that no tombstone was written by reading data back and ensuring initial value is + // retained. ResultSet result = sessionRule .session() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 284c7ccb212..931cfdd77fe 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -28,8 +30,6 @@ import org.junit.rules.ExpectedException; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - /** * Note: at the time of writing, this test exercises features of an unreleased Cassandra version. To * test against a local build, run with diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index b1d487ca060..4b3eb1b23ac 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import static junit.framework.TestCase.fail; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -32,9 +35,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static junit.framework.TestCase.fail; -import static org.assertj.core.api.Assertions.assertThat; - /** * Note: at the time of writing, some of these tests exercises features of an unreleased Cassandra * version. To test against a local build, run with @@ -131,7 +131,8 @@ public void should_update_metadata_when_schema_changed_across_pages() { .build()); // Then - // this should trigger a background fetch of the second page, and therefore update the definitions + // this should trigger a background fetch of the second page, and therefore update the + // definitions for (Row row : rows) { assertThat(row.isNull("d")).isTrue(); } @@ -235,7 +236,8 @@ private void should_not_store_metadata_for_conditional_updates(CqlSession sessio session.prepare( "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (?, ?, ?) IF NOT EXISTS"); - // Never store metadata in the prepared statement for conditional updates, since the result set can change + // Never store metadata in the prepared statement for conditional updates, since the result set + // can change // depending on the outcome. assertThat(ps.getResultSetDefinitions()).hasSize(0); ByteBuffer idBefore = ps.getResultMetadataId(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index 05139765bfa..b33fac20368 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverExecutionException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -28,8 +30,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class QueryTraceIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index fcfc72ac972..04f7a0a4397 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -29,8 +31,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class SimpleStatementIT { @@ -77,7 +77,8 @@ public void should_use_paging_state_when_copied() { SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); ResultSet result = cluster.session().execute(st); - // given a query created from a copy of a previous query with paging state from previous queries response. + // given a query created from a copy of a previous query with paging state from previous queries + // response. st = st.copy(result.getExecutionInfo().getPagingState()); // when executing that query. @@ -93,7 +94,8 @@ public void should_use_paging_state_when_provided_to_new_statement() { SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); ResultSet result = cluster.session().execute(st); - // given a query created from a copy of a previous query with paging state from previous queries response. + // given a query created from a copy of a previous query with paging state from previous queries + // response. st = SimpleStatement.builder(String.format("SELECT v FROM test where k='%s'", KEY)) .withPagingState(result.getExecutionInfo().getPagingState()) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index c32d782431f..5088e59b6de 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.api.core.data; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeThat; + import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -74,11 +79,6 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeThat; - @Category(ParallelizableTests.class) @RunWith(DataProviderRunner.class) public class DataTypeIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java index 643bbb49b21..888758346be 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.heartbeat; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -23,9 +26,6 @@ import org.junit.ClassRule; import org.junit.Test; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; - /** This test is separate from {@link HeartbeatIT} because it can't be parallelized. */ public class HeartbeatDisabledIT { @@ -34,7 +34,8 @@ public class HeartbeatDisabledIT { @Test public void should_not_send_heartbeat_when_disabled() throws InterruptedException { - // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't receive any + // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't + // receive any try (CqlSession session = SessionUtils.newSession(simulacron, "connection.heartbeat.interval = 0 second")) { AtomicInteger heartbeats = registerHeartbeatListener(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index f8dc38c1ccc..2beb2bb6898 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -15,6 +15,16 @@ */ package com.datastax.oss.driver.api.core.heartbeat; +import static com.datastax.oss.driver.api.testinfra.utils.ConditionChecker.checkThat; +import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForDown; +import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForUp; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noResult; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -41,16 +51,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.datastax.oss.driver.api.testinfra.utils.ConditionChecker.checkThat; -import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForDown; -import static com.datastax.oss.driver.api.testinfra.utils.NodeUtils.waitForUp; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noResult; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Category(ParallelizableTests.class) public class HeartbeatIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 08bbda05395..9969e721108 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -15,9 +15,12 @@ */ package com.datastax.oss.driver.api.core.loadbalancing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -42,9 +45,6 @@ import org.junit.ClassRule; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.withinPercentage; - public class DefaultLoadBalancingPolicyIT { private static final String LOCAL_DC = "dc1"; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index e7eb63efc96..725878d4ed9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.CassandraVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -35,9 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Category(ParallelizableTests.class) public class DescribeIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index f17d36f5bf3..c4906312cfe 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static com.datastax.oss.driver.assertions.Assertions.assertThat; +import static com.datastax.oss.driver.assertions.Assertions.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -55,11 +60,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; -import static com.datastax.oss.driver.assertions.Assertions.assertThat; -import static com.datastax.oss.driver.assertions.Assertions.fail; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; - @Category(ParallelizableTests.class) public class NodeStateIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index 3a093590a25..ee45c43f885 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; @@ -27,8 +29,6 @@ import org.junit.rules.RuleChain; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - public class SchemaAgreementIT { private static CustomCcmRule ccm = CustomCcmRule.builder().withNodes(3).build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 8ee748d35ec..9490f19a4b8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; @@ -38,8 +40,6 @@ import org.junit.experimental.categories.Category; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class SchemaChangesIT { @@ -396,7 +396,8 @@ public void should_handle_aggregate_update() { } private String keyspaceFilterOption(CqlIdentifier... keyspaces) { - // create option to filter keyspace refreshes based on input keyspaces, if none are provided, assume the + // create option to filter keyspace refreshes based on input keyspaces, if none are provided, + // assume the // one associated wiht the cluster rule. if (keyspaces.length == 0) { keyspaces = new CqlIdentifier[] {adminSessionRule.keyspace()}; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index a90aa01a98a..26989a2b3aa 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -30,8 +32,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class SchemaIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index dd90f5b834f..e9a022ad970 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.core.metadata; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -38,8 +40,6 @@ import java.util.TreeSet; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - public abstract class TokenITBase { protected static final CqlIdentifier KS1 = SessionUtils.uniqueKeyspaceId(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index 8f7591edf1a..f5ab37097eb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -15,10 +15,21 @@ */ package com.datastax.oss.driver.api.core.retry; +import static com.datastax.oss.simulacron.common.codec.ConsistencyLevel.LOCAL_QUORUM; +import static com.datastax.oss.simulacron.common.codec.WriteType.BATCH_LOG; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.readTimeout; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.unavailable; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.writeTimeout; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; -import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -44,17 +55,6 @@ import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; -import static com.datastax.oss.simulacron.common.codec.ConsistencyLevel.LOCAL_QUORUM; -import static com.datastax.oss.simulacron.common.codec.WriteType.BATCH_LOG; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.closeConnection; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.readTimeout; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.unavailable; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.writeTimeout; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Category(ParallelizableTests.class) @RunWith(DataProviderRunner.class) public class DefaultRetryPolicyIT { @@ -125,7 +125,8 @@ public void should_not_retry_on_read_timeout_when_data_present() { @Test public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() { - // given a node that will respond to a query with a read timeout where 2 out of 3 responses are received. + // given a node that will respond to a query with a read timeout where 2 out of 3 responses are + // received. // in this case, digest requests succeeded, but not the data request. simulacron.cluster().node(0).prime(when(queryStr).then(readTimeout(LOCAL_QUORUM, 2, 3, false))); @@ -147,7 +148,8 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() @Test public void should_retry_on_read_timeout_when_enough_responses_and_data_not_present() { - // given a node that will respond to a query with a read timeout where 3 out of 3 responses are received, + // given a node that will respond to a query with a read timeout where 3 out of 3 responses are + // received, // but data is not present. simulacron.cluster().node(0).prime(when(queryStr).then(readTimeout(LOCAL_QUORUM, 3, 3, false))); @@ -180,7 +182,8 @@ public void should_retry_on_next_host_on_connection_error_if_idempotent() { // when executing a query. ResultSet result = sessionRule.session().execute(query); - // then we should get a response, and the execution info on the result set indicates there was an error on + // then we should get a response, and the execution info on the result set indicates there was + // an error on // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); Map.Entry error = result.getExecutionInfo().getErrors().get(0); @@ -213,7 +216,8 @@ public void should_keep_retrying_on_next_host_on_connection_error() { sessionRule.session().execute(query); fail("AllNodesFailedException expected"); } catch (AllNodesFailedException ex) { - // then an AllNodesFailedException should be raised indicating that all nodes failed the request. + // then an AllNodesFailedException should be raised indicating that all nodes failed the + // request. assertThat(ex.getErrors()).hasSize(3); } @@ -244,7 +248,8 @@ public void should_not_retry_on_connection_error_if_non_idempotent() { .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); fail("ClosedConnectionException expected"); } catch (ClosedConnectionException ex) { - // then a ClosedConnectionException should be raised, indicating that the connection closed while handling + // then a ClosedConnectionException should be raised, indicating that the connection closed + // while handling // the request on that node. // this clearly indicates that the request wasn't retried. // Exception should indicate that node 0 was the failing node. @@ -295,7 +300,8 @@ public static Object[] nonBatchLogWriteTypes() { @Test public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( com.datastax.oss.simulacron.common.codec.WriteType writeType) { - // given a node that will respond to query with a write timeout with write type that is not batch log. + // given a node that will respond to query with a write timeout with write type that is not + // batch log. simulacron .cluster() .node(0) @@ -349,7 +355,8 @@ public void should_retry_on_next_host_on_unavailable() { // when executing a query. ResultSet result = sessionRule.session().execute(queryStr); - // then we should get a response, and the execution info on the result set indicates there was an error on + // then we should get a response, and the execution info on the result set indicates there was + // an error on // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); Map.Entry error = result.getExecutionInfo().getErrors().get(0); @@ -377,7 +384,8 @@ public void should_only_retry_once_on_unavailable() { sessionRule.session().execute(queryStr); fail("Expected an UnavailableException"); } catch (UnavailableException ue) { - // then we should get an unavailable exception with the host being node 1 (since it was second tried). + // then we should get an unavailable exception with the host being node 1 (since it was second + // tried). assertThat(ue.getCoordinator().getConnectAddress()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); assertThat(ue.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 5c648b2d84d..34dbeb4a148 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.core.session; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -41,8 +43,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static org.assertj.core.api.Assertions.assertThat; - /** * A suite of tests for exercising registration of custom {@link * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations to add-in @@ -144,7 +144,8 @@ public void should_use_custom_request_processor_for_executeAsync() throws Except @Test public void should_throw_illegal_argument_exception_if_no_matching_processor_found() throws Exception { - // Since cluster does not have a processor registered for returning ListenableFuture, an IllegalArgumentException + // Since cluster does not have a processor registered for returning ListenableFuture, an + // IllegalArgumentException // should be thrown. thrown.expect(IllegalArgumentException.class); sessionRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index c0da337bd16..7286d22b916 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -15,6 +15,11 @@ */ package com.datastax.oss.driver.api.core.specex; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.isBootstrapping; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; @@ -30,11 +35,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.isBootstrapping; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class SpeculativeExecutionIT { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index ee7c44be4c5..917b8864481 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.core.type.codec.registry; +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.cql.BoundStatement; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -48,8 +50,6 @@ import org.junit.rules.ExpectedException; import org.junit.rules.TestName; -import static org.assertj.core.api.Assertions.assertThat; - @Category(ParallelizableTests.class) public class CodecRegistryIT { @@ -182,7 +182,8 @@ public void should_be_able_to_register_and_use_custom_codec() { assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); - // should be able to retrieve value back as float, some precision is lost due to going from int -> float. + // should be able to retrieve value back as float, some precision is lost due to going from + // int -> float. Row row = result.iterator().next(); assertThat(row.getFloat("v")).isEqualTo(3.0f); assertThat(row.getFloat(0)).isEqualTo(3.0f); @@ -309,7 +310,8 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() session.execute(insert); // map with optional value should work - note that you can't have null values in collections, - // so this is not technically practical but want to validate that custom codec resolution works + // so this is not technically practical but want to validate that custom codec resolution + // works // when it's composed in a collection codec. Map> v2Map = Maps.newHashMap(1, Optional.of("hello")); insert = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java index 1202331d95c..d6cea876ae5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.example.guava.api; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.example.guava.internal.DefaultGuavaSession; diff --git a/pom.xml b/pom.xml index ce1c7bdf0cb..a90845edf44 100644 --- a/pom.xml +++ b/pom.xml @@ -140,7 +140,7 @@ com.coveo fmt-maven-plugin - 1.5.0 + 2.2.0 com.mycila @@ -242,14 +242,12 @@ com.coveo fmt-maven-plugin - - ${format.validateOnly} - - format + check + process-sources diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java index 04315367e12..45f390036d5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java @@ -21,12 +21,11 @@ import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.driver.api.testinfra.DseRequirement; +import java.util.Optional; import org.junit.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import java.util.Optional; - public abstract class BaseCcmRule extends CassandraResourceRule { protected final CcmBridge ccmBridge; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 94611bb4ae7..d8b5b355fd5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -15,8 +15,9 @@ */ package com.datastax.oss.driver.api.testinfra.ccm; -import com.datastax.oss.driver.api.core.CassandraVersion; +import static io.netty.util.internal.PlatformDependent.isWindows; +import com.datastax.oss.driver.api.core.CassandraVersion; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -35,7 +36,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; - import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteStreamHandler; @@ -46,8 +46,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.netty.util.internal.PlatformDependent.isWindows; - public class CcmBridge implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(CcmBridge.class); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index b3111e0a9aa..6ef54d07708 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java index cfb8dbec89a..2b590845ec2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.testinfra.utils; +import static org.assertj.core.api.Fail.fail; + import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; @@ -23,8 +25,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.BooleanSupplier; -import static org.assertj.core.api.Fail.fail; - public class ConditionChecker { private static final int DEFAULT_PERIOD_MILLIS = 500; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java index 93cfa4387f0..300befe71d4 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/NodeUtils.java @@ -15,14 +15,14 @@ */ package com.datastax.oss.driver.api.testinfra.utils; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; - public class NodeUtils { private static final Logger logger = LoggerFactory.getLogger(NodeUtils.class); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java index de6982254b2..904f60f9793 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/assertions/NodeMetadataAssert.java @@ -15,13 +15,13 @@ */ package com.datastax.oss.driver.assertions; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - public class NodeMetadataAssert extends AbstractAssert { public NodeMetadataAssert(Node actual) { From cf9fed2277ca2c9ba291bd5dfec400a10d4a9df1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Jan 2018 10:53:41 -0800 Subject: [PATCH 319/742] JAVA-1738: Make ConsistencyLevel pluggable --- .../oss/driver/api/core/ConsistencyLevel.java | 57 ++++------------ .../driver/api/core/CoreConsistencyLevel.java | 66 +++++++++++++++++++ .../oss/driver/api/core/ProtocolVersion.java | 2 +- .../api/core/config/DriverConfigProfile.java | 5 -- .../core/ConsistencyLevelRegistry.java | 34 ++++++++++ .../core/DefaultConsistencyLevelRegistry.java | 41 ++++++++++++ .../typesafe/TypesafeDriverConfigProfile.java | 16 ----- .../core/context/DefaultDriverContext.java | 14 ++++ .../core/context/InternalDriverContext.java | 3 + .../driver/internal/core/cql/Conversions.java | 24 ++++--- .../core/cql/CqlPrepareHandlerBase.java | 2 +- .../core/cql/CqlRequestHandlerBase.java | 4 +- .../internal/core/cql/QueryTraceFetcher.java | 14 ++-- .../core/retry/DefaultRetryPolicyTest.java | 2 +- .../core/cql/CqlRequestHandlerRetryTest.java | 8 +-- .../core/cql/QueryTraceFetcherTest.java | 15 +++-- .../core/cql/RequestHandlerTestHarness.java | 15 +++-- .../api/core/retry/DefaultRetryPolicyIT.java | 16 ++--- 18 files changed, 231 insertions(+), 107 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java index 4033aaa7bb0..9dcc2dd2f4d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java @@ -15,51 +15,18 @@ */ package com.datastax.oss.driver.api.core; -import com.datastax.oss.protocol.internal.ProtocolConstants; -import com.google.common.collect.ImmutableMap; -import java.util.Map; - -/** The consistency level of a request. */ -public enum ConsistencyLevel { - ANY(ProtocolConstants.ConsistencyLevel.ANY), - ONE(ProtocolConstants.ConsistencyLevel.ONE), - TWO(ProtocolConstants.ConsistencyLevel.TWO), - THREE(ProtocolConstants.ConsistencyLevel.THREE), - QUORUM(ProtocolConstants.ConsistencyLevel.QUORUM), - ALL(ProtocolConstants.ConsistencyLevel.ALL), - LOCAL_ONE(ProtocolConstants.ConsistencyLevel.LOCAL_ONE), - LOCAL_QUORUM(ProtocolConstants.ConsistencyLevel.LOCAL_QUORUM), - EACH_QUORUM(ProtocolConstants.ConsistencyLevel.EACH_QUORUM), - - SERIAL(ProtocolConstants.ConsistencyLevel.SERIAL), - LOCAL_SERIAL(ProtocolConstants.ConsistencyLevel.LOCAL_SERIAL), - ; - - private final int protocolCode; - - ConsistencyLevel(int protocolCode) { - this.protocolCode = protocolCode; - } - - public int getProtocolCode() { - return protocolCode; - } - - public static ConsistencyLevel fromCode(int code) { - ConsistencyLevel level = BY_CODE.get(code); - if (level == null) { - throw new IllegalArgumentException("Unknown code: " + code); - } - return level; - } +/** + * The consistency level of a request. + * + *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate + * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all + * {@code ConsistencyLevel}s are {@link CoreConsistencyLevel} instances. + */ +public interface ConsistencyLevel { - private static Map BY_CODE = mapByCode(values()); + /** The numerical value that the level is encoded to. */ + int getProtocolCode(); - private static Map mapByCode(ConsistencyLevel[] levels) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (ConsistencyLevel level : levels) { - builder.put(level.protocolCode, level); - } - return builder.build(); - } + /** The textual representation of the level in configuration files. */ + String name(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java new file mode 100644 index 00000000000..062870d898f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java @@ -0,0 +1,66 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +/** A default consistency level supported by the driver out of the box. */ +public enum CoreConsistencyLevel implements ConsistencyLevel { + ANY(ProtocolConstants.ConsistencyLevel.ANY), + ONE(ProtocolConstants.ConsistencyLevel.ONE), + TWO(ProtocolConstants.ConsistencyLevel.TWO), + THREE(ProtocolConstants.ConsistencyLevel.THREE), + QUORUM(ProtocolConstants.ConsistencyLevel.QUORUM), + ALL(ProtocolConstants.ConsistencyLevel.ALL), + LOCAL_ONE(ProtocolConstants.ConsistencyLevel.LOCAL_ONE), + LOCAL_QUORUM(ProtocolConstants.ConsistencyLevel.LOCAL_QUORUM), + EACH_QUORUM(ProtocolConstants.ConsistencyLevel.EACH_QUORUM), + + SERIAL(ProtocolConstants.ConsistencyLevel.SERIAL), + LOCAL_SERIAL(ProtocolConstants.ConsistencyLevel.LOCAL_SERIAL), + ; + + private final int protocolCode; + + CoreConsistencyLevel(int protocolCode) { + this.protocolCode = protocolCode; + } + + @Override + public int getProtocolCode() { + return protocolCode; + } + + public static CoreConsistencyLevel fromCode(int code) { + CoreConsistencyLevel level = BY_CODE.get(code); + if (level == null) { + throw new IllegalArgumentException("Unknown code: " + code); + } + return level; + } + + private static Map BY_CODE = mapByCode(values()); + + private static Map mapByCode(CoreConsistencyLevel[] levels) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (CoreConsistencyLevel level : levels) { + builder.put(level.protocolCode, level); + } + return builder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java index 4df5f854f7e..1b3c31e8756 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java @@ -22,7 +22,7 @@ * *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all - * {@code ProtocolVersion}s are {@code CoreProtocolVersion} instances. + * {@code ProtocolVersion}s are {@link CoreProtocolVersion} instances. */ public interface ProtocolVersion { /** The default version used for {@link Detachable detached} objects. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 5dd8dac8e97..844db27898a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.config; -import com.datastax.oss.driver.api.core.ConsistencyLevel; import java.time.Duration; import java.util.List; @@ -61,8 +60,4 @@ public interface DriverConfigProfile { Duration getDuration(DriverOption option); DriverConfigProfile withDuration(DriverOption option, Duration value); - - ConsistencyLevel getConsistencyLevel(DriverOption option); - - DriverConfigProfile withConsistencyLevel(DriverOption option, ConsistencyLevel value); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java new file mode 100644 index 00000000000..ca1c93fb1ea --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java @@ -0,0 +1,34 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; + +/** + * Extension point to plug custom consistency levels. + * + *

          This is overridable through {@link InternalDriverContext}. + */ +public interface ConsistencyLevelRegistry { + + ConsistencyLevel fromCode(int code); + + ConsistencyLevel fromName(String name); + + /** @return all the values known to this driver instance. */ + Iterable getValues(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java new file mode 100644 index 00000000000..086e8ecab9c --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java @@ -0,0 +1,41 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CoreConsistencyLevel; +import com.google.common.collect.ImmutableList; + +public class DefaultConsistencyLevelRegistry implements ConsistencyLevelRegistry { + + private static final ImmutableList values = + ImmutableList.builder().add(CoreConsistencyLevel.values()).build(); + + @Override + public ConsistencyLevel fromCode(int code) { + return CoreConsistencyLevel.fromCode(code); + } + + @Override + public ConsistencyLevel fromName(String name) { + return CoreConsistencyLevel.valueOf(name); + } + + @Override + public Iterable getValues() { + return values; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 5efb04caeb5..be6f346a512 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; -import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.google.common.collect.MapMaker; @@ -108,21 +107,6 @@ public DriverConfigProfile withBytes(DriverOption option, long value) { return with(option, value); } - @Override - public ConsistencyLevel getConsistencyLevel(DriverOption option) { - return getCached( - option.getPath(), - path -> { - String name = getEffectiveOptions().getString(path); - return ConsistencyLevel.valueOf(name); - }); - } - - @Override - public DriverConfigProfile withConsistencyLevel(DriverOption option, ConsistencyLevel value) { - return with(option, value.toString()); - } - private T getCached(String path, Function compute) { // compute's signature guarantees we get a T, and this is the only place where we mutate the // entry diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index a770a78dc81..e33e97685ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -32,6 +32,8 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; +import com.datastax.oss.driver.internal.core.ConsistencyLevelRegistry; +import com.datastax.oss.driver.internal.core.DefaultConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; @@ -114,6 +116,9 @@ public class DefaultDriverContext implements InternalDriverContext { private final LazyReference protocolVersionRegistryRef = new LazyReference<>( "protocolVersionRegistry", this::buildProtocolVersionRegistry, cycleDetector); + private final LazyReference consistencyLevelRegistryRef = + new LazyReference<>( + "consistencyLevelRegistry", this::buildConsistencyLevelRegistry, cycleDetector); private final LazyReference nettyOptionsRef = new LazyReference<>("nettyOptions", this::buildNettyOptions, cycleDetector); private final LazyReference writeCoalescerRef = @@ -253,6 +258,10 @@ protected ProtocolVersionRegistry buildProtocolVersionRegistry() { return new CassandraProtocolVersionRegistry(sessionName()); } + protected ConsistencyLevelRegistry buildConsistencyLevelRegistry() { + return new DefaultConsistencyLevelRegistry(); + } + protected NettyOptions buildNettyOptions() { return new DefaultNettyOptions(this); } @@ -405,6 +414,11 @@ public ProtocolVersionRegistry protocolVersionRegistry() { return protocolVersionRegistryRef.get(); } + @Override + public ConsistencyLevelRegistry consistencyLevelRegistry() { + return consistencyLevelRegistryRef.get(); + } + @Override public NettyOptions nettyOptions() { return nettyOptionsRef.get(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 8672621e487..233b0e431e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.ConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; @@ -48,6 +49,8 @@ public interface InternalDriverContext extends DriverContext { ProtocolVersionRegistry protocolVersionRegistry(); + ConsistencyLevelRegistry consistencyLevelRegistry(); + NettyOptions nettyOptions(); WriteCoalescer writeCoalescer(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index f687f1123d3..4f23255d799 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.cql; -import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -98,10 +97,16 @@ class Conversions { static Message toMessage( Statement statement, DriverConfigProfile config, InternalDriverContext context) { int consistency = - config.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY).getProtocolCode(); + context + .consistencyLevelRegistry() + .fromName(config.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + .getProtocolCode(); int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); int serialConsistency = - config.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY).getProtocolCode(); + context + .consistencyLevelRegistry() + .fromName(config.getString(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) + .getProtocolCode(); long timestamp = statement.getTimestamp(); if (timestamp == Long.MIN_VALUE) { timestamp = context.timestampGenerator().next(); @@ -354,7 +359,8 @@ private static List asList(int[] pkIndices) { } } - static CoordinatorException toThrowable(Node node, Error errorMessage) { + static CoordinatorException toThrowable( + Node node, Error errorMessage, InternalDriverContext context) { switch (errorMessage.code) { case ProtocolConstants.ErrorCode.UNPREPARED: throw new AssertionError( @@ -372,7 +378,7 @@ static CoordinatorException toThrowable(Node node, Error errorMessage) { Unavailable unavailable = (Unavailable) errorMessage; return new UnavailableException( node, - ConsistencyLevel.fromCode(unavailable.consistencyLevel), + context.consistencyLevelRegistry().fromCode(unavailable.consistencyLevel), unavailable.required, unavailable.alive); case ProtocolConstants.ErrorCode.OVERLOADED: @@ -385,7 +391,7 @@ static CoordinatorException toThrowable(Node node, Error errorMessage) { WriteTimeout writeTimeout = (WriteTimeout) errorMessage; return new WriteTimeoutException( node, - ConsistencyLevel.fromCode(writeTimeout.consistencyLevel), + context.consistencyLevelRegistry().fromCode(writeTimeout.consistencyLevel), writeTimeout.received, writeTimeout.blockFor, WriteType.valueOf(writeTimeout.writeType)); @@ -393,7 +399,7 @@ static CoordinatorException toThrowable(Node node, Error errorMessage) { ReadTimeout readTimeout = (ReadTimeout) errorMessage; return new ReadTimeoutException( node, - ConsistencyLevel.fromCode(readTimeout.consistencyLevel), + context.consistencyLevelRegistry().fromCode(readTimeout.consistencyLevel), readTimeout.received, readTimeout.blockFor, readTimeout.dataPresent); @@ -401,7 +407,7 @@ static CoordinatorException toThrowable(Node node, Error errorMessage) { ReadFailure readFailure = (ReadFailure) errorMessage; return new ReadFailureException( node, - ConsistencyLevel.fromCode(readFailure.consistencyLevel), + context.consistencyLevelRegistry().fromCode(readFailure.consistencyLevel), readFailure.received, readFailure.blockFor, readFailure.numFailures, @@ -413,7 +419,7 @@ static CoordinatorException toThrowable(Node node, Error errorMessage) { WriteFailure writeFailure = (WriteFailure) errorMessage; return new WriteFailureException( node, - ConsistencyLevel.fromCode(writeFailure.consistencyLevel), + context.consistencyLevelRegistry().fromCode(writeFailure.consistencyLevel), writeFailure.received, writeFailure.blockFor, WriteType.valueOf(writeFailure.writeType), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index c82c5998518..fe7d3354dec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -376,7 +376,7 @@ private void processErrorResponse(Error errorMessage) { "Unexpected server error for a PREPARE query" + errorMessage)); return; } - CoordinatorException error = Conversions.toThrowable(node, errorMessage); + CoordinatorException error = Conversions.toThrowable(node, errorMessage, context); if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 10c49ee119e..e9b59daf424 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -461,7 +461,7 @@ private void processErrorResponse(Error errorMessage) { ((UnexpectedResponseException) exception).message; if (prepareErrorMessage instanceof Error) { CoordinatorException prepareError = - Conversions.toThrowable(node, (Error) prepareErrorMessage); + Conversions.toThrowable(node, (Error) prepareErrorMessage, context); if (prepareError instanceof QueryValidationException || prepareError instanceof FunctionFailureException || prepareError instanceof ProtocolError) { @@ -482,7 +482,7 @@ private void processErrorResponse(Error errorMessage) { }); return; } - CoordinatorException error = Conversions.toThrowable(node, errorMessage); + CoordinatorException error = Conversions.toThrowable(node, errorMessage, context); if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index d988918e094..95d4fea5c10 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -54,14 +54,18 @@ class QueryTraceFetcher { this.session = session; ConsistencyLevel regularConsistency = - configProfile.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY); + context + .consistencyLevelRegistry() + .fromName(configProfile.getString(CoreDriverOption.REQUEST_CONSISTENCY)); ConsistencyLevel traceConsistency = - configProfile.getConsistencyLevel(CoreDriverOption.REQUEST_TRACE_CONSISTENCY); + context + .consistencyLevelRegistry() + .fromName(configProfile.getString(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)); this.configProfile = - (traceConsistency == regularConsistency) + (traceConsistency.equals(regularConsistency)) ? configProfile - : configProfile.withConsistencyLevel( - CoreDriverOption.REQUEST_CONSISTENCY, traceConsistency); + : configProfile.withString( + CoreDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); this.maxAttempts = configProfile.getInt(CoreDriverOption.REQUEST_TRACE_ATTEMPTS); this.intervalNanos = diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index 252649b4e72..6b54fd46ac2 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.retry; -import static com.datastax.oss.driver.api.core.ConsistencyLevel.QUORUM; +import static com.datastax.oss.driver.api.core.CoreConsistencyLevel.QUORUM; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index de57fddc2a3..cfeca160707 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -20,7 +20,7 @@ import static org.mockito.ArgumentMatchers.eq; import com.datastax.oss.driver.TestDataProviders; -import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -308,7 +308,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onReadTimeout( any(SimpleStatement.class), - eq(ConsistencyLevel.LOCAL_ONE), + eq(CoreConsistencyLevel.LOCAL_ONE), eq(2), eq(1), eq(true), @@ -335,7 +335,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onWriteTimeout( any(SimpleStatement.class), - eq(ConsistencyLevel.LOCAL_ONE), + eq(CoreConsistencyLevel.LOCAL_ONE), eq(WriteType.SIMPLE), eq(2), eq(1), @@ -358,7 +358,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onUnavailable( any(SimpleStatement.class), - eq(ConsistencyLevel.LOCAL_ONE), + eq(CoreConsistencyLevel.LOCAL_ONE), eq(2), eq(1), eq(0))) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 97e98b76c9a..b8ca844a395 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -20,7 +20,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.TraceEvent; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.datastax.oss.driver.internal.core.DefaultConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -94,12 +95,18 @@ public void setup() { // Doesn't really matter since we mock the scheduler Mockito.when(config.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL)) .thenReturn(Duration.ZERO); - Mockito.when(config.getConsistencyLevel(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)) - .thenReturn(ConsistencyLevel.ONE); + Mockito.when(config.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + .thenReturn(CoreConsistencyLevel.LOCAL_ONE.name()); + Mockito.when(config.getString(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)) + .thenReturn(CoreConsistencyLevel.ONE.name()); Mockito.when( - config.withConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.ONE)) + config.withString( + CoreDriverOption.REQUEST_CONSISTENCY, CoreConsistencyLevel.ONE.name())) .thenReturn(traceConfig); + + Mockito.when(context.consistencyLevelRegistry()) + .thenReturn(new DefaultConsistencyLevelRegistry()); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index e5b878eb343..e984f17ff2b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -19,7 +19,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.internal.core.DefaultConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.ProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -92,12 +93,11 @@ private RequestHandlerTestHarness(Builder builder) { // TODO make configurable in the test, also handle profiles Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultConfigProfile.getConsistencyLevel(CoreDriverOption.REQUEST_CONSISTENCY)) - .thenReturn(ConsistencyLevel.LOCAL_ONE); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + .thenReturn(CoreConsistencyLevel.LOCAL_ONE.name()); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); - Mockito.when( - defaultConfigProfile.getConsistencyLevel(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) - .thenReturn(ConsistencyLevel.SERIAL); + Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) + .thenReturn(CoreConsistencyLevel.SERIAL.name()); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)) @@ -141,6 +141,9 @@ private RequestHandlerTestHarness(Builder builder) { protocolVersionRegistry.supports( any(ProtocolVersion.class), any(ProtocolFeature.class))) .thenReturn(true); + + Mockito.when(context.consistencyLevelRegistry()) + .thenReturn(new DefaultConsistencyLevelRegistry()); } public DefaultSession getSession() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index f5ab37097eb..117c2fff44f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -27,7 +27,7 @@ import static org.assertj.core.api.Assertions.fail; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.cql.ResultSet; @@ -113,7 +113,7 @@ public void should_not_retry_on_read_timeout_when_data_present() { fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown - assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(1); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isTrue(); @@ -136,7 +136,7 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown - assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(2); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isFalse(); @@ -159,7 +159,7 @@ public void should_retry_on_read_timeout_when_enough_responses_and_data_not_pres fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown. - assertThat(rte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(3); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isFalse(); @@ -274,7 +274,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); @@ -313,7 +313,7 @@ public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); } @@ -338,7 +338,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); @@ -388,7 +388,7 @@ public void should_only_retry_once_on_unavailable() { // tried). assertThat(ue.getCoordinator().getConnectAddress()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); - assertThat(ue.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_QUORUM); + assertThat(ue.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(ue.getRequired()).isEqualTo(3); assertThat(ue.getAlive()).isEqualTo(0); } From 3366ea5e6b814930cfd36cdabccfeaee903f33ba Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Jan 2018 11:13:17 -0800 Subject: [PATCH 320/742] JAVA-1738: Make BatchType pluggable --- .../oss/driver/api/core/cql/BatchType.java | 31 +++++------ .../driver/api/core/cql/CoreBatchType.java | 52 +++++++++++++++++++ .../driver/internal/core/cql/Conversions.java | 16 +----- .../core/config/DriverConfigProfileIT.java | 4 +- .../driver/api/core/cql/AsyncResultSetIT.java | 4 +- .../driver/api/core/cql/BatchStatementIT.java | 22 ++++---- .../api/core/cql/PerRequestKeyspaceIT.java | 8 +-- 7 files changed, 84 insertions(+), 53 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java index c38078fa5c4..fe4f1536efa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java @@ -15,25 +15,18 @@ */ package com.datastax.oss.driver.api.core.cql; -/** The type of a batch. */ -public enum BatchType { - /** - * A logged batch: Cassandra will first write the batch to its distributed batch log to ensure the - * atomicity of the batch (atomicity meaning that if any statement in the batch succeeds, all will - * eventually succeed). - */ - LOGGED, +/** + * The type of a batch. + * + *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate + * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all + * {@code BatchType}s are {@link CoreBatchType} instances. + */ +public interface BatchType { - /** - * A batch that doesn't use Cassandra's distributed batch log. Such batch are not guaranteed to be - * atomic. - */ - UNLOGGED, + /** The numerical value that the level is encoded to. */ + byte getProtocolCode(); - /** - * A counter batch. Note that such batch is the only type that can contain counter operations and - * it can only contain these. - */ - COUNTER, - ; + // Implementation note: we don't have a "BatchTypeRegistry" because we never decode batch types. + // This can be added later if needed (see ConsistencyLevelRegistry for an example). } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java new file mode 100644 index 00000000000..88b4c4f3f1b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java @@ -0,0 +1,52 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.cql; + +import com.datastax.oss.protocol.internal.ProtocolConstants; + +/** A default batch type supported by the driver out of the box. */ +public enum CoreBatchType implements BatchType { + /** + * A logged batch: Cassandra will first write the batch to its distributed batch log to ensure the + * atomicity of the batch (atomicity meaning that if any statement in the batch succeeds, all will + * eventually succeed). + */ + LOGGED(ProtocolConstants.BatchType.LOGGED), + + /** + * A batch that doesn't use Cassandra's distributed batch log. Such batch are not guaranteed to be + * atomic. + */ + UNLOGGED(ProtocolConstants.BatchType.UNLOGGED), + + /** + * A counter batch. Note that such batch is the only type that can contain counter operations and + * it can only contain these. + */ + COUNTER(ProtocolConstants.BatchType.COUNTER), + ; + + private final byte code; + + CoreBatchType(byte code) { + this.code = code; + } + + @Override + public byte getProtocolCode() { + return code; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 4f23255d799..4a9fa64bd63 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -22,7 +22,6 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BatchStatement; -import com.datastax.oss.driver.api.core.cql.BatchType; import com.datastax.oss.driver.api.core.cql.BatchableStatement; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; @@ -198,7 +197,7 @@ static Message toMessage( } } return new Batch( - toProtocol(batchStatement.getBatchType()), + batchStatement.getBatchType().getProtocolCode(), queriesOrIds, values, consistency, @@ -440,17 +439,4 @@ static CoordinatorException toThrowable( return new ProtocolError(node, "Unknown error code: " + errorMessage.code); } } - - private static byte toProtocol(BatchType batchType) { - switch (batchType) { - case LOGGED: - return ProtocolConstants.BatchType.LOGGED; - case UNLOGGED: - return ProtocolConstants.BatchType.UNLOGGED; - case COUNTER: - return ProtocolConstants.BatchType.COUNTER; - default: - throw new IllegalArgumentException("Unsupported batch type: " + batchType); - } - } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index 63616b4951b..ae832c8c93a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; -import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.CoreBatchType; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -186,7 +186,7 @@ public void should_use_profile_page_size() { .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(BatchType.UNLOGGED).withConfigProfile(slowProfile); + BatchStatement.builder(CoreBatchType.UNLOGGED).withConfigProfile(slowProfile); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 7d9dc742476..15a236e5a01 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -58,8 +58,8 @@ public static void setupSchema() { PreparedStatement prepared = sessionRule.session().prepare("INSERT INTO test (k0, k1, v) VALUES (?, ?, ?)"); - BatchStatementBuilder batchPart1 = BatchStatement.builder(BatchType.UNLOGGED); - BatchStatementBuilder batchPart2 = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder batchPart1 = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder batchPart2 = BatchStatement.builder(CoreBatchType.UNLOGGED); for (int i = 0; i < ROWS_PER_PARTITION; i++) { batchPart1.addStatement(prepared.bind(PARTITION_KEY1, i, i)); batchPart2.addStatement( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index b06ed1ba257..2fa42051e52 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -64,7 +64,7 @@ public void createTable() { @Test public void should_execute_batch_of_simple_statements_with_variables() { // Build a batch of batchCount simple statements, each with their own positional variables. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); for (int i = 0; i < batchCount; i++) { SimpleStatement insert = SimpleStatement.builder( @@ -85,7 +85,7 @@ public void should_execute_batch_of_simple_statements_with_variables() { public void should_execute_batch_of_bound_statements_with_variables() { // Build a batch of batchCount statements with bound statements, each with their own positional // variables. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -108,7 +108,7 @@ public void should_execute_batch_of_bound_statements_with_variables() { public void should_execute_batch_of_bound_statements_with_unset_values() { // Build a batch of batchCount statements with bound statements, each with their own positional // variables. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -125,7 +125,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { verifyBatchInsert(); - BatchStatementBuilder builder2 = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder2 = BatchStatement.builder(CoreBatchType.UNLOGGED); for (int i = 0; i < batchCount; i++) { BoundStatement boundStatement = preparedStatement.bind(i, i + 2); // unset v every 20 statements. @@ -163,7 +163,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { public void should_execute_batch_of_bound_statements_with_named_variables() { // Build a batch of batchCount statements with bound statements, each with their own named // variable values. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); PreparedStatement preparedStatement = sessionRule.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); @@ -186,7 +186,7 @@ public void should_execute_batch_of_bound_statements_with_named_variables() { @Test public void should_execute_batch_of_bound_and_simple_statements_with_variables() { // Build a batch of batchCount statements with simple and bound statements alternating. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -217,7 +217,7 @@ public void should_execute_batch_of_bound_and_simple_statements_with_variables() @Test public void should_execute_cas_batch() { // Build a batch with CAS operations on the same partition. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -244,7 +244,7 @@ public void should_execute_cas_batch() { @Test public void should_execute_counter_batch() { // should be able to do counter increments in a counter batch. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.COUNTER); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.COUNTER); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -277,7 +277,7 @@ public void should_execute_counter_batch() { @Test(expected = InvalidQueryException.class) public void should_fail_logged_batch_with_counter_increment() { // should not be able to do counter inserts in a unlogged batch. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.LOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.LOGGED); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -296,7 +296,7 @@ public void should_fail_logged_batch_with_counter_increment() { @Test(expected = InvalidQueryException.class) public void should_fail_counter_batch_with_non_counter_increment() { // should not be able to do a counter batch if it contains a non-counter increment statement. - BatchStatementBuilder builder = BatchStatement.builder(BatchType.COUNTER); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.COUNTER); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -328,7 +328,7 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { PreparedStatement prepared = v3Session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); - BatchStatementBuilder builder = BatchStatement.builder(BatchType.LOGGED); + BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.LOGGED); builder.addStatements( // All set => OK prepared.bind(name.getMethodName(), 1, 1), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 931cfdd77fe..909272daa64 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -73,7 +73,7 @@ public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1); should_reject_statement_with_keyspace_in_protocol_v4( - BatchStatement.builder(BatchType.LOGGED) + BatchStatement.builder(CoreBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatement(statementWithoutKeyspace) .build()); @@ -87,7 +87,7 @@ public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) .setKeyspace(sessionRule.keyspace()); should_reject_statement_with_keyspace_in_protocol_v4( - BatchStatement.builder(BatchType.LOGGED).addStatement(statementWithKeyspace).build()); + BatchStatement.builder(CoreBatchType.LOGGED).addStatement(statementWithKeyspace).build()); } private void should_reject_statement_with_keyspace_in_protocol_v4(Statement statement) { @@ -121,7 +121,7 @@ public void should_execute_simple_statement_with_keyspace() { public void should_execute_batch_with_explicit_keyspace() { CqlSession session = sessionRule.session(); session.execute( - BatchStatement.builder(BatchType.LOGGED) + BatchStatement.builder(CoreBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( @@ -145,7 +145,7 @@ public void should_execute_batch_with_explicit_keyspace() { public void should_execute_batch_with_inferred_keyspace() { CqlSession session = sessionRule.session(); session.execute( - BatchStatement.builder(BatchType.LOGGED) + BatchStatement.builder(CoreBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( From bbd2d530055cb3949248f1b2095e79f0af284fa0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Jan 2018 11:17:24 -0800 Subject: [PATCH 321/742] JAVA-1738: Make WriteType pluggable --- changelog/README.md | 1 + .../oss/driver/api/core/ConsistencyLevel.java | 2 +- .../api/core/retry/DefaultRetryPolicy.java | 4 ++- .../driver/api/core/retry/RetryPolicy.java | 1 + .../CoreWriteType.java} | 12 +++---- .../servererrors/WriteFailureException.java | 1 - .../servererrors/WriteTimeoutException.java | 1 - .../api/core/servererrors/WriteType.java | 32 +++++++++++++++++ .../core/context/DefaultDriverContext.java | 13 +++++++ .../core/context/InternalDriverContext.java | 3 ++ .../driver/internal/core/cql/Conversions.java | 5 ++- .../DefaultWriteTypeRegistry.java | 36 +++++++++++++++++++ .../core/servererrors/WriteTypeRegistry.java | 25 +++++++++++++ .../core/retry/DefaultRetryPolicyTest.java | 4 +-- .../api/core/retry/RetryPolicyTestBase.java | 1 + .../core/cql/CqlRequestHandlerRetryTest.java | 4 +-- .../core/cql/RequestHandlerTestHarness.java | 3 ++ .../api/core/retry/DefaultRetryPolicyIT.java | 5 +-- 18 files changed, 132 insertions(+), 21 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/{retry/WriteType.java => servererrors/CoreWriteType.java} (86%) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/WriteTypeRegistry.java diff --git a/changelog/README.md b/changelog/README.md index b8d0f6f879b..374237544e9 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1738: Convert enums to allow extensibility - [bug] JAVA-1727: Override DefaultUdtValue.equals - [bug] JAVA-1729: Override DefaultTupleValue.equals - [improvement] JAVA-1720: Merge Cluster and Session into a single interface diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java index 9dcc2dd2f4d..672c48a7a33 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java @@ -24,7 +24,7 @@ */ public interface ConsistencyLevel { - /** The numerical value that the level is encoded to. */ + /** The numerical value that the level is encoded to in protocol frames. */ int getProtocolCode(); /** The textual representation of the level in configuration files. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index 80591c4b376..0b3a30b7982 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -20,8 +20,10 @@ import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; +import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; +import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; /** @@ -82,7 +84,7 @@ public RetryDecision onWriteTimeout( int received, int retryCount) { - return (retryCount == 0 && writeType == WriteType.BATCH_LOG) + return (retryCount == 0 && writeType == CoreWriteType.BATCH_LOG) ? RetryDecision.RETRY_SAME : RetryDecision.RETHROW; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java index b62b3132fe6..0a5228ea112 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.TruncateException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; +import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java similarity index 86% rename from core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java index 9a72faf5c85..7e2637e590a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/WriteType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datastax.oss.driver.api.core.retry; +package com.datastax.oss.driver.api.core.servererrors; + +/** A default write type supported by the driver out of the box. */ +public enum CoreWriteType implements WriteType { -/** - * The type of a Cassandra write query. - * - *

          This information is returned by Cassandra when a write timeout is raised, to indicate what - * type of write timed out. It is useful to decide which retry decision to adopt. - */ -public enum WriteType { /** A write to a single partition key. Such writes are guaranteed to be atomic and isolated. */ SIMPLE, /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java index ab9a9d13338..b364b12fe8a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; -import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.session.Request; import java.net.InetAddress; import java.util.Map; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java index 1a837d0ecb3..5eff46da03b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; -import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.session.Request; /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java new file mode 100644 index 00000000000..84e038d8247 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java @@ -0,0 +1,32 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.servererrors; + +/** + * The type of a Cassandra write query. + * + *

          This information is returned by Cassandra when a write timeout is raised, to indicate what + * type of write timed out. It is useful to decide which retry decision to adopt. + * + *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate + * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all + * {@code WriteType}s are {@link CoreWriteType} instances. + */ +public interface WriteType { + + /** The textual representation that the write type is encoded to in protocol frames. */ + String name(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index e33e97685ce..6e44fbe1918 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -52,6 +52,8 @@ import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; +import com.datastax.oss.driver.internal.core.servererrors.DefaultWriteTypeRegistry; +import com.datastax.oss.driver.internal.core.servererrors.WriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; @@ -119,6 +121,8 @@ public class DefaultDriverContext implements InternalDriverContext { private final LazyReference consistencyLevelRegistryRef = new LazyReference<>( "consistencyLevelRegistry", this::buildConsistencyLevelRegistry, cycleDetector); + private final LazyReference writeTypeRegistryRef = + new LazyReference<>("writeTypeRegistry", this::buildWriteTypeRegistry, cycleDetector); private final LazyReference nettyOptionsRef = new LazyReference<>("nettyOptions", this::buildNettyOptions, cycleDetector); private final LazyReference writeCoalescerRef = @@ -262,6 +266,10 @@ protected ConsistencyLevelRegistry buildConsistencyLevelRegistry() { return new DefaultConsistencyLevelRegistry(); } + protected WriteTypeRegistry buildWriteTypeRegistry() { + return new DefaultWriteTypeRegistry(); + } + protected NettyOptions buildNettyOptions() { return new DefaultNettyOptions(this); } @@ -419,6 +427,11 @@ public ConsistencyLevelRegistry consistencyLevelRegistry() { return consistencyLevelRegistryRef.get(); } + @Override + public WriteTypeRegistry writeTypeRegistry() { + return writeTypeRegistryRef.get(); + } + @Override public NettyOptions nettyOptions() { return nettyOptionsRef.get(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 233b0e431e6..788c9a621ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; +import com.datastax.oss.driver.internal.core.servererrors.WriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; @@ -51,6 +52,8 @@ public interface InternalDriverContext extends DriverContext { ConsistencyLevelRegistry consistencyLevelRegistry(); + WriteTypeRegistry writeTypeRegistry(); + NettyOptions nettyOptions(); WriteCoalescer writeCoalescer(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 4a9fa64bd63..52e9fd031f7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -33,7 +33,6 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.token.Token; -import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.servererrors.AlreadyExistsException; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; @@ -393,7 +392,7 @@ static CoordinatorException toThrowable( context.consistencyLevelRegistry().fromCode(writeTimeout.consistencyLevel), writeTimeout.received, writeTimeout.blockFor, - WriteType.valueOf(writeTimeout.writeType)); + context.writeTypeRegistry().fromName(writeTimeout.writeType)); case ProtocolConstants.ErrorCode.READ_TIMEOUT: ReadTimeout readTimeout = (ReadTimeout) errorMessage; return new ReadTimeoutException( @@ -421,7 +420,7 @@ static CoordinatorException toThrowable( context.consistencyLevelRegistry().fromCode(writeFailure.consistencyLevel), writeFailure.received, writeFailure.blockFor, - WriteType.valueOf(writeFailure.writeType), + context.writeTypeRegistry().fromName(writeFailure.writeType), writeFailure.numFailures, writeFailure.reasonMap); case ProtocolConstants.ErrorCode.SYNTAX_ERROR: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java new file mode 100644 index 00000000000..88bb06947dd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java @@ -0,0 +1,36 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.servererrors; + +import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; +import com.datastax.oss.driver.api.core.servererrors.WriteType; +import com.google.common.collect.ImmutableList; + +public class DefaultWriteTypeRegistry implements WriteTypeRegistry { + + private static final ImmutableList values = + ImmutableList.builder().add(CoreWriteType.values()).build(); + + @Override + public WriteType fromName(String name) { + return CoreWriteType.valueOf(name); + } + + @Override + public ImmutableList getValues() { + return values; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/WriteTypeRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/WriteTypeRegistry.java new file mode 100644 index 00000000000..1634b3066bf --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/WriteTypeRegistry.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.servererrors; + +import com.datastax.oss.driver.api.core.servererrors.WriteType; + +public interface WriteTypeRegistry { + WriteType fromName(String name); + + /** @return all the values known to this driver instance. */ + Iterable getValues(); +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index 6b54fd46ac2..b8a4efc9158 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -19,8 +19,8 @@ import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; -import static com.datastax.oss.driver.api.core.retry.WriteType.BATCH_LOG; -import static com.datastax.oss.driver.api.core.retry.WriteType.SIMPLE; +import static com.datastax.oss.driver.api.core.servererrors.CoreWriteType.BATCH_LOG; +import static com.datastax.oss.driver.api.core.servererrors.CoreWriteType.SIMPLE; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java index dc55d9c7d17..142352747f3 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; +import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; import org.assertj.core.api.Assert; import org.junit.runner.RunWith; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index cfeca160707..3ee4ca0fa82 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -29,8 +29,8 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; -import com.datastax.oss.driver.api.core.retry.WriteType; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; import com.datastax.oss.driver.api.core.servererrors.ServerError; @@ -336,7 +336,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) policy.onWriteTimeout( any(SimpleStatement.class), eq(CoreConsistencyLevel.LOCAL_ONE), - eq(WriteType.SIMPLE), + eq(CoreWriteType.SIMPLE), eq(2), eq(1), eq(0))) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index e984f17ff2b..49683da8763 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -39,6 +39,7 @@ import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.servererrors.DefaultWriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; @@ -144,6 +145,8 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.consistencyLevelRegistry()) .thenReturn(new DefaultConsistencyLevelRegistry()); + + Mockito.when(context.writeTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); } public DefaultSession getSession() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index 117c2fff44f..f261e3f091f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; @@ -277,7 +278,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); - assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); + assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); } // there should have been a retry, and it should have been executed on the same host. @@ -341,7 +342,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); - assertThat(wte.getWriteType()).isEqualTo(WriteType.BATCH_LOG); + assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); } // should not have been retried. From 25017dd48410d91016eb2d18bccebff17a7b5a05 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 8 Feb 2018 13:57:01 -0800 Subject: [PATCH 322/742] Add ProtocolVersionRegistry.getValues() --- .../internal/core/CassandraProtocolVersionRegistry.java | 7 +++++++ .../oss/driver/internal/core/ProtocolVersionRegistry.java | 3 +++ 2 files changed, 10 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 7dabb741566..1e0180530c5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -45,6 +45,8 @@ public class CassandraProtocolVersionRegistry implements ProtocolVersionRegistry { private static final Logger LOG = LoggerFactory.getLogger(CassandraProtocolVersionRegistry.class); + private static final ImmutableList values = + ImmutableList.builder().add(CoreProtocolVersion.values()).build(); private final String logPrefix; private final NavigableMap versionsByCode; @@ -177,6 +179,11 @@ public boolean supports(ProtocolVersion version, ProtocolFeature feature) { } } + @Override + public ImmutableList getValues() { + return values; + } + private NavigableMap byCode(ProtocolVersion[][] versionRanges) { NavigableMap map = new TreeMap<>(); for (ProtocolVersion[] versionRange : versionRanges) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java index 63a96015205..0f9e46d6fe0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java @@ -69,4 +69,7 @@ public interface ProtocolVersionRegistry { /** Whether a given version supports a given feature. */ boolean supports(ProtocolVersion version, ProtocolFeature feature); + + /** @return all the values known to this driver instance. */ + Iterable getValues(); } From 8e9c2f26f0234eb42f80aa73d0204fa29d3cac3e Mon Sep 17 00:00:00 2001 From: GregBestland Date: Wed, 7 Feb 2018 14:40:44 -0600 Subject: [PATCH 323/742] JAVA-1739: Add host_id and schema_version to node metadata --- changelog/README.md | 1 + .../oss/driver/api/core/metadata/Node.java | 10 +++ .../internal/core/metadata/DefaultNode.java | 13 ++++ .../core/metadata/DefaultNodeInfo.java | 27 ++++++++ .../core/metadata/DefaultTopologyMonitor.java | 3 +- .../internal/core/metadata/NodeInfo.java | 10 +++ .../internal/core/metadata/NodesRefresh.java | 2 + .../core/metadata/AddNodeRefreshTest.java | 8 +++ .../metadata/FullNodeListRefreshTest.java | 15 +++++ .../api/core/metadata/NodeMetadataIT.java | 62 +++++++++++++++++++ 10 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java diff --git a/changelog/README.md b/changelog/README.md index 374237544e9..7f4f875daaf 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [improvement] JAVA-1739: Add host_id and schema_version to node metadata - [improvement] JAVA-1738: Convert enums to allow extensibility - [bug] JAVA-1727: Override DefaultUdtValue.equals - [bug] JAVA-1729: Override DefaultTupleValue.equals diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index 3fabacfd204..ab8c5f89017 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.util.Map; import java.util.Optional; +import java.util.UUID; /** * Metadata about a Cassandra node in the cluster. @@ -98,4 +99,13 @@ public interface Node { * driver. */ NodeDistance getDistance(); + + /** + * The host ID that is assigned to this node by Cassandra. This value can be used to uniquely + * identify a node even when the underling IP address changes. + */ + UUID getHostId(); + + /** The current version that is associated with the node's schema. */ + UUID getSchemaVersion(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 20f649aaa5f..a63f1bf9ba5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; /** * Implementation note: all the mutable state in this class is read concurrently, but only mutated @@ -42,6 +43,8 @@ public class DefaultNode implements Node { // Keep a copy of the raw tokens, to detect if they have changed when we refresh the node volatile Set rawTokens; volatile Map extras; + volatile UUID hostId; + volatile UUID schemaVersion; // These 3 fields are read concurrently, but only mutated on NodeStateManager's admin thread volatile NodeState state; @@ -88,6 +91,16 @@ public CassandraVersion getCassandraVersion() { return cassandraVersion; } + @Override + public UUID getHostId() { + return hostId; + } + + @Override + public UUID getSchemaVersion() { + return schemaVersion; + } + @Override public Map getExtras() { return extras; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java index 349f28b60e3..cead0e70aea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; public class DefaultNodeInfo implements NodeInfo { public static Builder builder() { @@ -37,6 +38,8 @@ public static Builder builder() { private final String partitioner; private final Set tokens; private final Map extras; + private final UUID hostId; + private final UUID schemaVersion; private DefaultNodeInfo(Builder builder) { this.connectAddress = builder.connectAddress; @@ -47,6 +50,8 @@ private DefaultNodeInfo(Builder builder) { this.cassandraVersion = builder.cassandraVersion; this.partitioner = builder.partitioner; this.tokens = (builder.tokens == null) ? Collections.emptySet() : builder.tokens; + this.hostId = builder.hostId; + this.schemaVersion = builder.schemaVersion; this.extras = (builder.extras == null) ? Collections.emptyMap() : builder.extras; } @@ -95,6 +100,16 @@ public Map getExtras() { return extras; } + @Override + public UUID getHostId() { + return hostId; + } + + @Override + public UUID getSchemaVersion() { + return schemaVersion; + } + public static class Builder { private InetSocketAddress connectAddress; private Optional broadcastAddress = Optional.empty(); @@ -105,6 +120,8 @@ public static class Builder { private String partitioner; private Set tokens; private Map extras; + private UUID hostId; + private UUID schemaVersion; public Builder withConnectAddress(InetSocketAddress address) { this.connectAddress = address; @@ -150,6 +167,16 @@ public Builder withTokens(Set tokens) { return this; } + public Builder withHostId(UUID hostId) { + this.hostId = hostId; + return this; + } + + public Builder withSchemaVersion(UUID schemaVersion) { + this.schemaVersion = schemaVersion; + return this; + } + public Builder withExtra(String key, Object value) { if (value != null) { if (this.extras == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index c066839903b..58a93be3da3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -221,7 +221,8 @@ protected DefaultNodeInfo.Builder nodeInfoBuilder( builder.withCassandraVersion(row.getString("release_version")); builder.withTokens(row.getSetOfString("tokens")); builder.withPartitioner(row.getString("partitioner")); - + builder.withHostId(row.getUuid("host_id")); + builder.withSchemaVersion(row.getUuid("schema_version")); return builder; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java index 5b639fbb8f5..d6a2f4a99a2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; /** * Information about a node, returned by the {@link TopologyMonitor}. @@ -111,4 +112,13 @@ public interface NodeInfo { * will be copied as-is into {@link Node#getExtras()}. */ Map getExtras(); + + /** + * The host ID that is assigned to this host by cassandra. This value can be used to uniquely + * identify a host even when the underling ip address changes. + */ + UUID getHostId(); + + /** The current version that is associated with the nodes schema. */ + UUID getSchemaVersion(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 1836c77d892..e3f86de4d82 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -36,6 +36,8 @@ protected static boolean copyInfos( node.listenAddress = nodeInfo.getListenAddress(); node.datacenter = nodeInfo.getDatacenter(); node.rack = nodeInfo.getRack(); + node.hostId = nodeInfo.getHostId(); + node.schemaVersion = nodeInfo.getSchemaVersion(); String versionString = nodeInfo.getCassandraVersion(); try { node.cassandraVersion = CassandraVersion.parse(versionString); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 61e772fcad2..56c353669b6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -18,10 +18,12 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; +import java.util.UUID; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -40,11 +42,15 @@ public class AddNodeRefreshTest { public void should_add_new_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + UUID hostId = Uuids.random(); + UUID schemaVersion = Uuids.random(); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS2) .withDatacenter("dc1") .withRack("rack2") + .withHostId(hostId) + .withSchemaVersion(schemaVersion) .build(); AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo); @@ -57,6 +63,8 @@ public void should_add_new_node() { Node node2 = newNodes.get(ADDRESS2); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); + assertThat(node2.getHostId()).isEqualTo(hostId); + assertThat(node2.getSchemaVersion()).isEqualTo(schemaVersion); assertThat(result.events).containsExactly(NodeStateEvent.added((DefaultNode) node2)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 2e014151726..bcd0de9bc5f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -17,10 +17,12 @@ import static com.datastax.oss.driver.Assertions.assertThat; +import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import java.util.UUID; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -64,17 +66,26 @@ public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + + UUID hostId1 = Uuids.random(); + UUID hostId2 = Uuids.random(); + UUID schemaVersion1 = Uuids.random(); + UUID schemaVersion2 = Uuids.random(); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder() .withConnectAddress(ADDRESS1) .withDatacenter("dc1") .withRack("rack1") + .withHostId(hostId1) + .withSchemaVersion(schemaVersion1) .build(), DefaultNodeInfo.builder() .withConnectAddress(ADDRESS2) .withDatacenter("dc1") .withRack("rack2") + .withHostId(hostId2) + .withSchemaVersion(schemaVersion2) .build()); FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos); @@ -85,8 +96,12 @@ public void should_update_existing_nodes() { assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); assertThat(node1.getDatacenter()).isEqualTo("dc1"); assertThat(node1.getRack()).isEqualTo("rack1"); + assertThat(node1.getHostId()).isEqualTo(hostId1); + assertThat(node1.getSchemaVersion()).isEqualTo(schemaVersion1); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); + assertThat(node2.getHostId()).isEqualTo(hostId2); + assertThat(node2.getSchemaVersion()).isEqualTo(schemaVersion2); assertThat(result.events).isEmpty(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java new file mode 100644 index 00000000000..c99e6179376 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -0,0 +1,62 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import java.util.Collection; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class NodeMetadataIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); + + @Test + public void should_expose_node_metadata() { + Collection values = sessionRule.session().getMetadata().getNodes().values(); + assertThat(values).hasSize(1); + + Node node = values.iterator().next(); + + // Run a few basic checks given what we know about our test environment: + assertThat(node.getConnectAddress()).isNotNull(); + node.getBroadcastAddress() + .ifPresent( + broadcastAddress -> + assertThat(broadcastAddress).isEqualTo(node.getConnectAddress().getAddress())); + assertThat(node.getListenAddress().get()).isEqualTo(node.getConnectAddress().getAddress()); + assertThat(node.getDatacenter()).isEqualTo("dc1"); + assertThat(node.getRack()).isEqualTo("r1"); + assertThat(node.getCassandraVersion()).isEqualTo(ccmRule.getCassandraVersion()); + assertThat(node.getState()).isSameAs(NodeState.UP); + assertThat(node.getDistance()).isSameAs(NodeDistance.LOCAL); + assertThat(node.getHostId()).isNotNull(); + assertThat(node.getSchemaVersion()).isNotNull(); + + // Note: open connections and reconnection status are covered in NodeStateIT + } +} From d6893f26c63975d4ba5f197cc0c0a8d904536672 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 4 Dec 2017 11:25:42 -0800 Subject: [PATCH 324/742] Keep track of the node that caused a speculative execution This allows us to pass that information to the policy (like in 3.x), and possibly to keep internal stats for each node. --- .../ConstantSpeculativeExecutionPolicy.java | 7 +- .../specex/NoSpeculativeExecutionPolicy.java | 5 +- .../specex/SpeculativeExecutionPolicy.java | 5 +- .../core/cql/CqlRequestHandlerBase.java | 117 +++++++++--------- ...onstantSpeculativeExecutionPolicyTest.java | 6 +- ...equestHandlerSpeculativeExecutionTest.java | 57 +++++---- .../core/cql/RequestHandlerTestHarness.java | 2 +- 7 files changed, 114 insertions(+), 85 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java index 3d7050e2be7..920b9e67eab 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; /** @@ -47,7 +48,11 @@ public ConstantSpeculativeExecutionPolicy(DriverContext context) { } @Override - public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { + public long nextExecution( + @SuppressWarnings("unused") Node node, + @SuppressWarnings("unused") CqlIdentifier keyspace, + @SuppressWarnings("unused") Request request, + int runningExecutions) { assert runningExecutions >= 1; return (runningExecutions < maxExecutions) ? constantDelayMillis : -1; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java index 6b277de7a25..95771d22dac 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/NoSpeculativeExecutionPolicy.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; /** A policy that never triggers speculative executions. */ @@ -27,7 +28,9 @@ public NoSpeculativeExecutionPolicy(@SuppressWarnings("unused") DriverContext co } @Override - public long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions) { + @SuppressWarnings("unused") + public long nextExecution( + Node node, CqlIdentifier keyspace, Request request, int runningExecutions) { // never start speculative executions return -1; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java index 47182ce89a1..847dd252886 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionPolicy.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.SessionBuilder; @@ -26,6 +27,8 @@ public interface SpeculativeExecutionPolicy extends AutoCloseable { /** + * @param node the node that caused the speculative execution (that is, the node that was queried + * previously but was too slow to answer) * @param keyspace the CQL keyspace currently associated to the session. This is set either * through the configuration, by calling {@link SessionBuilder#withKeyspace(CqlIdentifier)}, * or by manually executing a {@code USE} CQL statement. It can be {@code null} if the session @@ -38,7 +41,7 @@ public interface SpeculativeExecutionPolicy extends AutoCloseable { * @return the time (in milliseconds) until a speculative request is sent to the next node, or 0 * to send it immediately, or a negative value to stop sending requests. */ - long nextExecution(CqlIdentifier keyspace, Request request, int runningExecutions); + long nextExecution(Node node, CqlIdentifier keyspace, Request request, int runningExecutions); /** Called when the cluster that this policy is associated with closes. */ @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index e9b59daf424..9678175e14c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -166,30 +166,10 @@ protected CqlRequestHandlerBase( this.speculativeExecutionPolicy = context.speculativeExecutionPolicy(); this.activeExecutionsCount = new AtomicInteger(1); this.startedSpeculativeExecutionsCount = new AtomicInteger(0); - - if (isIdempotent) { - // Schedule the first speculative execution if applicable - long nextDelay = - context.speculativeExecutionPolicy().nextExecution(keyspace, this.statement, 1); - if (nextDelay >= 0) { - LOG.debug("[{}] Scheduling speculative execution 1 in {} ms", logPrefix, nextDelay); - this.scheduledExecutions = new CopyOnWriteArrayList<>(); - this.scheduledExecutions.add( - scheduler.schedule(() -> startExecution(1), nextDelay, TimeUnit.MILLISECONDS)); - } else { - LOG.debug( - "[{}] Speculative execution policy returned {}, no next execution", - logPrefix, - nextDelay); - this.scheduledExecutions = null; // we'll never need this so avoid allocation - } - } else { - LOG.debug("[{}] Request is not idempotent, no speculative executions", logPrefix); - this.scheduledExecutions = null; - } + this.scheduledExecutions = isIdempotent ? new CopyOnWriteArrayList<>() : null; this.inFlightCallbacks = new CopyOnWriteArrayList<>(); // Start the initial execution - sendRequest(null, 0, 0); + sendRequest(null, 0, 0, true); } private ScheduledFuture scheduleTimeout(Duration timeout) { @@ -203,39 +183,18 @@ private ScheduledFuture scheduleTimeout(Duration timeout) { } } - private void startExecution(int currentExecutionIndex) { - if (!result.isDone()) { - LOG.trace("[{}] Starting speculative execution {}", logPrefix, currentExecutionIndex); - activeExecutionsCount.incrementAndGet(); - startedSpeculativeExecutionsCount.incrementAndGet(); - long nextDelay = - speculativeExecutionPolicy.nextExecution(keyspace, statement, currentExecutionIndex + 1); - if (nextDelay >= 0) { - LOG.trace( - "[{}] Scheduling speculative execution {} in {} ms", - logPrefix, - currentExecutionIndex + 1, - nextDelay); - scheduledExecutions.add( - scheduler.schedule( - () -> startExecution(currentExecutionIndex + 1), nextDelay, TimeUnit.MILLISECONDS)); - } else { - LOG.trace( - "[{}] Speculative execution policy returned {}, no next execution", - logPrefix, - nextDelay); - } - sendRequest(null, currentExecutionIndex, 0); - } - } - /** * Sends the request to the next available node. * * @param node if not null, it will be attempted first before the rest of the query plan. * @param currentExecutionIndex 0 for the initial execution, 1 for the first speculative one, etc. + * @param retryCount the number of times that the retry policy was invoked for this execution + * already (note that some internal retries don't go through the policy, and therefore don't + * increment this counter) + * @param scheduleNextExecution whether to schedule the next speculative execution */ - private void sendRequest(Node node, int currentExecutionIndex, int retryCount) { + private void sendRequest( + Node node, int currentExecutionIndex, int retryCount, boolean scheduleNextExecution) { if (result.isDone()) { return; } @@ -256,7 +215,8 @@ private void sendRequest(Node node, int currentExecutionIndex, int retryCount) { } } else { NodeResponseCallback nodeResponseCallback = - new NodeResponseCallback(node, channel, currentExecutionIndex, retryCount, logPrefix); + new NodeResponseCallback( + node, channel, currentExecutionIndex, retryCount, scheduleNextExecution, logPrefix); channel .write(message, statement.isTracing(), statement.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); @@ -353,14 +313,21 @@ private class NodeResponseCallback // How many times we've invoked the retry policy and it has returned a "retry" decision (0 for // the first attempt of each execution). private final int retryCount; + private final boolean scheduleNextExecution; private final String logPrefix; private NodeResponseCallback( - Node node, DriverChannel channel, int execution, int retryCount, String logPrefix) { + Node node, + DriverChannel channel, + int execution, + int retryCount, + boolean scheduleNextExecution, + String logPrefix) { this.node = node; this.channel = channel; this.execution = execution; this.retryCount = retryCount; + this.scheduleNextExecution = scheduleNextExecution; this.logPrefix = logPrefix + "|" + execution; } @@ -379,7 +346,7 @@ public void operationComplete(Future future) throws Exception { channel, error); recordError(node, error); - sendRequest(null, execution, retryCount); // try next node + sendRequest(null, execution, retryCount, scheduleNextExecution); // try next node } } else { LOG.debug("[{}] Request sent on {}", logPrefix, channel); @@ -389,6 +356,42 @@ public void operationComplete(Future future) throws Exception { cancel(); } else { inFlightCallbacks.add(this); + if (scheduleNextExecution && isIdempotent) { + int nextExecution = execution + 1; + // Note that `node` is the first node of the execution, it might not be the "slow" one + // if there were retries, but in practice retries are rare. + long nextDelay = + context + .speculativeExecutionPolicy() + .nextExecution(node, keyspace, statement, nextExecution); + if (nextDelay >= 0) { + LOG.debug( + "[{}] Scheduling speculative execution {} in {} ms", + logPrefix, + nextExecution, + nextDelay); + scheduledExecutions.add( + scheduler.schedule( + () -> { + if (!result.isDone()) { + LOG.trace( + "[{}] Starting speculative execution {}", + CqlRequestHandlerBase.this.logPrefix, + nextExecution); + activeExecutionsCount.incrementAndGet(); + startedSpeculativeExecutionsCount.incrementAndGet(); + sendRequest(null, nextExecution, 0, true); + } + }, + nextDelay, + TimeUnit.MILLISECONDS)); + } else { + LOG.debug( + "[{}] Speculative execution policy returned {}, no next execution", + logPrefix, + nextDelay); + } + } } } } @@ -473,10 +476,10 @@ private void processErrorResponse(Error errorMessage) { } recordError(node, exception); LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); - sendRequest(null, execution, retryCount); + sendRequest(null, execution, retryCount, false); } else { LOG.debug("[{}] Reprepare sucessful, retrying", logPrefix); - sendRequest(node, execution, retryCount); + sendRequest(node, execution, retryCount, false); } return null; }); @@ -486,7 +489,7 @@ private void processErrorResponse(Error errorMessage) { if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); - sendRequest(null, execution, retryCount); + sendRequest(null, execution, retryCount, false); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { @@ -540,11 +543,11 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { switch (decision) { case RETRY_SAME: recordError(node, error); - sendRequest(node, execution, retryCount + 1); + sendRequest(node, execution, retryCount + 1, false); break; case RETRY_NEXT: recordError(node, error); - sendRequest(null, execution, retryCount + 1); + sendRequest(null, execution, retryCount + 1, false); break; case RETHROW: setFinalError(error); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index f1469978c53..aba6a944800 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -68,10 +68,10 @@ public void should_return_delay_until_max() { SpeculativeExecutionPolicy policy = new ConstantSpeculativeExecutionPolicy(context); // Initial execution starts, schedule first speculative execution - assertThat(policy.nextExecution(null, request, 1)).isEqualTo(10); + assertThat(policy.nextExecution(null, null, request, 1)).isEqualTo(10); // First speculative execution starts, schedule second one - assertThat(policy.nextExecution(null, request, 2)).isEqualTo(10); + assertThat(policy.nextExecution(null, null, request, 2)).isEqualTo(10); // Second speculative execution starts, we're at 3 => stop - assertThat(policy.nextExecution(null, request, 3)).isNegative(); + assertThat(policy.nextExecution(null, null, request, 3)).isNegative(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index bd543d1fc33..c5ea31c7bdc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; @@ -75,16 +77,24 @@ public void should_schedule_speculative_executions( harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; long secondExecutionDelay = 200L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 2)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(2))) .thenReturn(secondExecutionDelay); - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 3)).thenReturn(-1L); + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(3))) + .thenReturn(-1L); new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); harness.nextScheduledTask(); // Discard the timeout task @@ -94,6 +104,7 @@ public void should_schedule_speculative_executions( .isEqualTo(firstExecutionDelay); firstExecutionTask.run(); node2Behavior.verifyWrite(); + node2Behavior.setWriteSuccess(); ScheduledTaskCapturingEventLoop.CapturedTask secondExecutionTask = harness.nextScheduledTask(); @@ -101,6 +112,7 @@ public void should_schedule_speculative_executions( .isEqualTo(secondExecutionDelay); secondExecutionTask.run(); node3Behavior.verifyWrite(); + node3Behavior.setWriteSuccess(); // No more scheduled tasks since the policy returns 0 on the third call. assertThat(harness.nextScheduledTask()).isNull(); @@ -123,13 +135,16 @@ public void should_not_start_execution_if_result_complete( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); harness.nextScheduledTask(); // Discard the timeout task @@ -140,7 +155,6 @@ public void should_not_start_execution_if_result_complete( .isEqualTo(firstExecutionDelay); // Complete the request from the initial execution - node1Behavior.setWriteSuccess(); node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); assertThat(resultSetFuture).isSuccess(); @@ -166,7 +180,9 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -175,17 +191,8 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement harness.nextScheduledTask(); // Discard the timeout task - // We schedule a first speculative execution before even detecting that the query plan is - // empty - ScheduledTaskCapturingEventLoop.CapturedTask task = harness.nextScheduledTask(); - assertThat(task).isNotNull(); - assertThat(task.getInitialDelay(TimeUnit.MILLISECONDS)).isEqualTo(100); - assertThat(resultSetFuture) - .isFailed( - error -> { - assertThat(error).isInstanceOf(NoNodeAvailableException.class); - }); + .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); } } @@ -204,7 +211,9 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -255,7 +264,9 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -310,7 +321,9 @@ public void should_retry_in_speculative_executions( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -357,13 +370,16 @@ public void should_stop_retrying_other_executions_if_result_complete( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().speculativeExecutionPolicy(); long firstExecutionDelay = 100L; - Mockito.when(speculativeExecutionPolicy.nextExecution(null, statement, 1)) + Mockito.when( + speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); + node1Behavior.setWriteSuccess(); harness.nextScheduledTask(); // Discard the timeout task @@ -376,7 +392,6 @@ public void should_stop_retrying_other_executions_if_result_complete( node2Behavior.setWriteSuccess(); // Complete the request from the initial execution - node1Behavior.setWriteSuccess(); node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); assertThat(resultSetFuture).isSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 49683da8763..72bddda47cc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -116,7 +116,7 @@ private RequestHandlerTestHarness(Builder builder) { // Disable speculative executions by default Mockito.when( speculativeExecutionPolicy.nextExecution( - any(CqlIdentifier.class), any(Request.class), anyInt())) + any(Node.class), any(CqlIdentifier.class), any(Request.class), anyInt())) .thenReturn(-1L); Mockito.when(context.speculativeExecutionPolicy()).thenReturn(speculativeExecutionPolicy); From ec28433d6559cca9233ce12b5ce060d90adcc905 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 29 Jan 2018 17:43:26 -0800 Subject: [PATCH 325/742] Improve error handling in pool --- .../api/core/config/CoreDriverOption.java | 1 + .../internal/core/pool/ChannelPool.java | 37 +++++++++++++------ .../driver/internal/core/util/Loggers.java | 3 +- core/src/main/resources/reference.conf | 16 ++++++++ 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index aef6a80ea5f..d1fad205dd9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -37,6 +37,7 @@ public enum CoreDriverOption implements DriverOption { CONNECTION_HEARTBEAT_INTERVAL("connection.heartbeat.interval", true), CONNECTION_HEARTBEAT_TIMEOUT("connection.heartbeat.timeout", true), CONNECTION_MAX_ORPHAN_REQUESTS("connection.max-orphan-requests", true), + CONNECTION_WARN_INIT_ERROR("connection.warn-on-init-error", true), CONNECTION_POOL_LOCAL_SIZE("connection.pool.local.size", true), CONNECTION_POOL_REMOTE_SIZE("connection.pool.remote.size", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index b6e3ceb4147..4c99ab0f24a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; +import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; +import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -265,25 +267,32 @@ private CompletionStage addMissingChannels() { private boolean onAllConnected(@SuppressWarnings("unused") Void v) { assert adminExecutor.inEventLoop(); - ClusterNameMismatchException clusterNameMismatch = null; + Throwable fatalError = null; int invalidKeyspaceErrors = 0; for (CompletionStage pendingChannel : pendingChannels) { CompletableFuture future = pendingChannel.toCompletableFuture(); assert future.isDone(); if (future.isCompletedExceptionally()) { Throwable error = CompletableFutures.getFailed(future); - LOG.debug("[{}] Error while opening new channel", logPrefix, error); - // TODO we don't log at a higher level because it's not a fatal error, but this should - // probably be recorded somewhere (metric?) - - // TODO auth exception => WARN and keep reconnecting - // TODO protocol error => WARN and force down - - if (error instanceof ClusterNameMismatchException) { + if (error instanceof ClusterNameMismatchException + || error instanceof UnsupportedProtocolVersionException) { // This will likely be thrown by all channels, but finish the loop cleanly - clusterNameMismatch = (ClusterNameMismatchException) error; + fatalError = error; + } else if (error instanceof AuthenticationException) { + // Always warn because this is most likely something the operator needs to fix. + // Keep going to reconnect if it can be fixed without bouncing the client. + Loggers.warnWithException(LOG, "[{}] Authentication error", logPrefix, error); } else if (error instanceof InvalidKeyspaceException) { invalidKeyspaceErrors += 1; + } else { + if (config + .getDefaultProfile() + .getBoolean(CoreDriverOption.CONNECTION_WARN_INIT_ERROR)) { + Loggers.warnWithException( + LOG, "[{}] Error while opening new channel", logPrefix, error); + } else { + LOG.debug("[{}] Error while opening new channel", logPrefix, error); + } } } else { DriverChannel channel = CompletableFutures.getCompleted(future); @@ -320,8 +329,12 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { pendingChannels.clear(); - if (clusterNameMismatch != null) { - LOG.warn("[{}] {}", logPrefix, clusterNameMismatch.getMessage()); + if (fatalError != null) { + Loggers.warnWithException( + LOG, + "[{}] Fatal error while initializing pool, forcing the node down", + logPrefix, + fatalError); eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); // Don't bother continuing, the pool will get shut down soon anyway return true; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java index f8618c64933..eeb753830bc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Loggers.java @@ -29,7 +29,8 @@ public static void warnWithException(Logger logger, String format, Object... arg } else { Object last = arguments[arguments.length - 1]; if (last instanceof Throwable) { - arguments[arguments.length - 1] = ((Throwable) last).getMessage(); + Throwable t = (Throwable) last; + arguments[arguments.length - 1] = t.getClass().getSimpleName() + ": " + t.getMessage(); logger.warn(format + " ({})", arguments); } else { // Should only be called with an exception as last argument, but handle gracefully anyway diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 28cb7e378fa..ae14c5135c6 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -190,6 +190,22 @@ datastax-java-driver { # after the change. max-orphan-requests = 24576 + # Whether to log non-fatal errors when the driver tries to open a new connection. + # + # This error as recoverable, as the driver will try to reconnect according to the reconnection + # policy. Therefore some users see them as unnecessary clutter in the logs. On the other hand, + # those logs can be handy to debug a misbehaving node. + # + # This option can be changed at runtime, the new value will be used for new connections created + # after the change. + # + # Note that some type of errors are always logged, regardless of this option: + # - protocol version mismatches (the node gets forced down) + # - when the cluster name in system.local doesn't match the other nodes (the node gets forced + # down) + # - authentication errors (will be retried) + warn-on-init-error = true + heartbeat { # The heartbeat interval. If a connection stays idle for that duration (no reads), the driver # sends a dummy message on it to make sure it's still alive. If not, the connection is From 94f0e111cb8ac1fabc90289259fb9063f2096ed1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 17 Jan 2018 09:18:40 -0800 Subject: [PATCH 326/742] JAVA-1518: Expose metrics --- changelog/README.md | 1 + core/pom.xml | 12 +- .../api/core/config/CoreDriverOption.java | 9 + .../api/core/context/DriverContext.java | 3 + .../api/core/metrics/CoreNodeMetric.java | 79 ++++++ .../api/core/metrics/CoreSessionMetric.java | 56 ++++ .../driver/api/core/metrics/NodeMetric.java | 46 ++++ .../api/core/metrics/SessionMetric.java | 38 +++ .../oss/driver/api/core/session/Session.java | 12 + .../internal/core/channel/DriverChannel.java | 17 ++ .../core/channel/InFlightHandler.java | 16 +- .../core/context/DefaultDriverContext.java | 25 ++ .../core/context/InternalDriverContext.java | 3 + .../core/cql/CqlRequestHandlerBase.java | 78 ++++++ .../core/metadata/AddNodeRefresh.java | 2 +- .../internal/core/metadata/DefaultNode.java | 12 +- .../core/metadata/FullNodeListRefresh.java | 2 +- .../metadata/InitContactPointsRefresh.java | 2 +- .../metrics/DefaultMetricUpdaterFactory.java | 85 ++++++ .../metrics/DefaultNodeMetricUpdater.java | 121 ++++++++ .../metrics/DefaultSessionMetricUpdater.java | 63 +++++ .../internal/core/metrics/HdrReservoir.java | 259 ++++++++++++++++++ .../internal/core/metrics/MetricUpdater.java | 37 +++ .../core/metrics/MetricUpdaterBase.java | 110 ++++++++ .../core/metrics/MetricUpdaterFactory.java | 25 ++ .../core/metrics/NodeMetricUpdater.java | 20 ++ .../core/metrics/SessionMetricUpdater.java | 20 ++ .../internal/core/pool/ChannelPool.java | 21 ++ .../driver/internal/core/pool/ChannelSet.java | 18 ++ .../internal/core/session/DefaultSession.java | 13 + .../internal/core/session/SessionWrapper.java | 6 + core/src/main/resources/reference.conf | 192 +++++++++++++ .../core/control/ControlConnectionTest.java | 117 ++++---- .../control/ControlConnectionTestBase.java | 13 +- .../core/cql/CqlRequestHandlerRetryTest.java | 94 ++++++- ...equestHandlerSpeculativeExecutionTest.java | 11 + .../core/cql/CqlRequestHandlerTestBase.java | 21 +- .../core/metadata/AddNodeRefreshTest.java | 14 +- .../metadata/DefaultTopologyMonitorTest.java | 9 +- .../metadata/FullNodeListRefreshTest.java | 21 +- .../InitContactPointsRefreshTest.java | 9 + .../LoadBalancingPolicyWrapperTest.java | 10 +- .../core/metadata/MetadataManagerTest.java | 6 +- .../core/metadata/NodeStateManagerTest.java | 7 +- .../core/metadata/RemoveNodeRefreshTest.java | 17 +- .../core/pool/ChannelPoolInitTest.java | 38 ++- .../core/pool/ChannelPoolKeyspaceTest.java | 8 +- .../core/pool/ChannelPoolReconnectTest.java | 36 +-- .../core/pool/ChannelPoolResizeTest.java | 74 ++--- .../core/pool/ChannelPoolShutdownTest.java | 24 +- .../core/pool/ChannelPoolTestBase.java | 18 +- .../core/session/DefaultSessionPoolsTest.java | 4 + .../driver/api/core/metadata/NodeStateIT.java | 11 +- .../driver/api/core/metrics/MetricsIT.java | 76 +++++ .../src/test/resources/application.conf | 5 + pom.xml | 10 + 56 files changed, 1863 insertions(+), 193 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/HdrReservoir.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdater.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/NodeMetricUpdater.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metrics/SessionMetricUpdater.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java diff --git a/changelog/README.md b/changelog/README.md index 7f4f875daaf..deeac5cd259 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha3 (in progress) +- [new feature] JAVA-1518: Expose metrics - [improvement] JAVA-1739: Add host_id and schema_version to node metadata - [improvement] JAVA-1738: Convert enums to allow extensibility - [bug] JAVA-1727: Override DefaultUdtValue.equals diff --git a/core/pom.xml b/core/pom.xml index 37799aa22fc..32007abf24d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 @@ -75,6 +77,14 @@ org.slf4j slf4j-api + + io.dropwizard.metrics + metrics-core + + + org.hdrhistogram + HdrHistogram + ch.qos.logback logback-classic diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java index d1fad205dd9..a37297779aa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java @@ -109,6 +109,15 @@ public enum CoreDriverOption implements DriverOption { TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL( "request.timestamp-generator.drift-warning.interval", false), + METRICS_SESSION_ENABLED("metrics.session.enabled", false), + METRICS_NODE_ENABLED("metrics.node.enabled", false), + METRICS_SESSION_CQL_REQUESTS_HIGHEST("metrics.session.cql-requests.highest-latency", false), + METRICS_SESSION_CQL_REQUESTS_DIGITS("metrics.session.cql-requests.significant-digits", false), + METRICS_SESSION_CQL_REQUESTS_INTERVAL("metrics.session.cql-requests.refresh-interval", false), + METRICS_NODE_CQL_MESSAGES_HIGHEST("metrics.node.cql-messages.highest-latency", false), + METRICS_NODE_CQL_MESSAGES_DIGITS("metrics.node.cql-messages.significant-digits", false), + METRICS_NODE_CQL_MESSAGES_INTERVAL("metrics.node.cql-messages.refresh-interval", false), + NETTY_IO_SIZE("netty.io-group.size", false), NETTY_IO_SHUTDOWN_QUIET_PERIOD("netty.io-group.shutdown.quiet-period", false), NETTY_IO_SHUTDOWN_TIMEOUT("netty.io-group.shutdown.timeout", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 571c6c30ffb..4a4f9320d94 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.context; +import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -56,4 +57,6 @@ public interface DriverContext extends AttachmentPoint { Optional sslEngineFactory(); TimestampGenerator timestampGenerator(); + + MetricRegistry metricRegistry(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java new file mode 100644 index 00000000000..8afef5601cb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java @@ -0,0 +1,79 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metrics; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +/** See {@code reference.conf} for a description of each metric. */ +public enum CoreNodeMetric implements NodeMetric { + OPEN_CONNECTIONS("pool.open-connections"), + AVAILABLE_STREAMS("pool.available-streams"), + IN_FLIGHT("pool.in-flight"), + ORPHANED_STREAMS("pool.orphaned-streams"), + CQL_MESSAGES("cql-messages"), + UNSENT_REQUESTS("errors.request.unsent"), + ABORTED_REQUESTS("errors.request.aborted"), + WRITE_TIMEOUTS("errors.request.write-timeouts"), + READ_TIMEOUTS("errors.request.read-timeouts"), + UNAVAILABLES("errors.request.unavailables"), + OTHER_ERRORS("errors.request.others"), + RETRIES("retries.total"), + RETRIES_ON_ABORTED("retries.aborted"), + RETRIES_ON_READ_TIMEOUT("retries.read-timeout"), + RETRIES_ON_WRITE_TIMEOUT("retries.write-timeout"), + RETRIES_ON_UNAVAILABLE("retries.unavailable"), + RETRIES_ON_OTHER_ERROR("retries.other"), + IGNORES("ignores.total"), + IGNORES_ON_ABORTED("ignores.aborted"), + IGNORES_ON_READ_TIMEOUT("ignores.read-timeout"), + IGNORES_ON_WRITE_TIMEOUT("ignores.write-timeout"), + IGNORES_ON_UNAVAILABLE("ignores.unavailable"), + IGNORES_ON_OTHER_ERROR("ignores.other"), + SPECULATIVE_EXECUTIONS("speculative-executions"), + CONNECTION_INIT_ERRORS("errors.connection.init"), + AUTHENTICATION_ERRORS("errors.connection.auth"), + ; + + private static final Map BY_PATH = sortByPath(); + + private final String path; + + CoreNodeMetric(String path) { + this.path = path; + } + + @Override + public String getPath() { + return path; + } + + public static CoreNodeMetric fromPath(String path) { + CoreNodeMetric metric = BY_PATH.get(path); + if (metric == null) { + throw new IllegalArgumentException("Unknown node metric path " + path); + } + return metric; + } + + private static Map sortByPath() { + ImmutableMap.Builder result = ImmutableMap.builder(); + for (CoreNodeMetric value : values()) { + result.put(value.getPath(), value); + } + return result.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java new file mode 100644 index 00000000000..380b58f5603 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java @@ -0,0 +1,56 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metrics; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +/** See {@code reference.conf} for a description of each metric. */ +public enum CoreSessionMetric implements SessionMetric { + CONNECTED_NODES("connected-nodes"), + CQL_REQUESTS("cql-requests"), + CQL_CLIENT_TIMEOUTS("cql-client-timeouts"), + ; + + private static final Map BY_PATH = sortByPath(); + + private final String path; + + CoreSessionMetric(String path) { + this.path = path; + } + + @Override + public String getPath() { + return path; + } + + public static CoreSessionMetric fromPath(String path) { + CoreSessionMetric metric = BY_PATH.get(path); + if (metric == null) { + throw new IllegalArgumentException("Unknown session metric path " + path); + } + return metric; + } + + private static Map sortByPath() { + ImmutableMap.Builder result = ImmutableMap.builder(); + for (CoreSessionMetric value : values()) { + result.put(value.getPath(), value); + } + return result.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java new file mode 100644 index 00000000000..869f6df3b91 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java @@ -0,0 +1,46 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metrics; + +import com.datastax.oss.driver.api.core.session.Session; +import java.net.InetAddress; + +/** + * A node-level metric exposed through {@link Session#getMetricRegistry()}. + * + *

          Note that the actual key in the registry is composed of the {@link Session#getName() name of + * the session}, followed by "nodes", followed by a textual representation of the address of the + * node, followed by the path of the metric. IPv4 addresses are represented as the decimal + * components followed by the port, separated with underscores; IPv6 addresses are represented as + * the result of {@link InetAddress#getHostAddress()}, followed by an underscore, followed by the + * port. For example: + * + *

          + * // Retrieve the `retries.total` metric for node 127.0.0.1:9042 in session `s0`:
          + * Meter retriesMeter = session.getMetricRegistry().meter("s0.nodes.127_0_0_1_9042.retries.total");
          + *
          + * // Retrieve the `retries.total` metric for node ::1:9042 in session `s0`:
          + * Meter retriesMeter = session.getMetricRegistry().meter("s0.nodes.0:0:0:0:0:0:0:1_9042.retries.total");
          + * 
          + * + *

          All metrics exposed out of the box by the driver are instances of {@link CoreNodeMetric} (this + * interface only exists to allow custom metrics in driver extensions). + * + * @see SessionMetric + */ +public interface NodeMetric { + String getPath(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java new file mode 100644 index 00000000000..74289609ec7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java @@ -0,0 +1,38 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metrics; + +import com.datastax.oss.driver.api.core.session.Session; + +/** + * A session-level metric exposed through {@link Session#getMetricRegistry()}. + * + *

          Note that the actual key in the registry is composed of the {@link Session#getName() name of + * the session} followed by the path of the metric, for example: + * + *

          + * // Retrieve the `cql_requests` metric for session `s0`:
          + * Timer requestsTimer = session.getMetricRegistry().timer("s0.cql_requests");
          + * 
          + * + *

          All metrics exposed out of the box by the driver are instances of {@link CoreSessionMetric} + * (this interface only exists to allow custom metrics in driver extensions). + * + * @see NodeMetric + */ +public interface SessionMetric { + String getPath(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 07117789639..a4827bb241f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.session; +import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -178,6 +179,17 @@ default boolean checkSchemaAgreement() { */ CqlIdentifier getKeyspace(); + /** + * The registry of driver metrics. + * + *

          The driver is instrumented with DropWizard metrics, use this object to register metric + * reporters. + * + * @see Reporters + * (DropWizard Metrics manual) + */ + MetricRegistry getMetricRegistry(); + /** * Executes an arbitrary request. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 4666f8cc086..3496c9d34ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -129,6 +129,23 @@ public int getAvailableIds() { return inFlightHandler.getAvailableIds(); } + /** + * @return the number of requests currently executing on this channel (including {@link + * #getOrphanedIds() orphaned ids}). + */ + public int getInFlight() { + return inFlightHandler.getInFlight(); + } + + /** + * @return the number of stream ids for requests that have either timed out or been cancelled, but + * for which we can't release the stream id because a request might still come from the + * server. + */ + public int getOrphanedIds() { + return inFlightHandler.getOrphanIds(); + } + public EventLoop eventLoop() { return channel.eventLoop(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 4a34ec9254b..be5f1e442bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -56,7 +56,7 @@ public class InFlightHandler extends ChannelDuplexHandler { private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; private String logPrefix; - private int orphanStreamIds; + private volatile int orphanStreamIds; // volatile only for metrics InFlightHandler( ProtocolVersion protocolVersion, @@ -147,6 +147,7 @@ private void write(ChannelHandlerContext ctx, RequestMessage message, ChannelPro }); } + @SuppressWarnings("NonAtomicVolatileUpdate") private void cancel( ChannelHandlerContext ctx, ResponseCallback responseCallback, ChannelPromise promise) { Integer streamId = inFlight.inverse().remove(responseCallback); @@ -165,7 +166,7 @@ private void cancel( // We can't release the stream id, because a response might still come back from the server. // Keep track of how many of those ids are held, because we want to replace the channel if // it becomes too high. - orphanStreamIds += 1; + orphanStreamIds += 1; // safe because the method is confined to the I/O thread if (orphanStreamIds > maxOrphanStreamIds) { LOG.debug( "[{}] Orphan stream ids exceeded the configured threshold ({}), closing gracefully", @@ -195,6 +196,7 @@ private void startGracefulShutdown(ChannelHandlerContext ctx) { } @Override + @SuppressWarnings("NonAtomicVolatileUpdate") public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Frame responseFrame = (Frame) msg; int streamId = responseFrame.streamId; @@ -217,7 +219,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (responseCallback == null) { LOG.debug("[{}] Got response on orphan stream id {}, releasing", logPrefix, streamId); release(streamId, ctx); - orphanStreamIds -= 1; + orphanStreamIds -= 1; // safe because the method is confined to the I/O thread } else { LOG.debug( "[{}] Got response on stream id {}, completing {}", @@ -337,6 +339,14 @@ int getAvailableIds() { return streamIds.getAvailableIds(); } + int getInFlight() { + return streamIds.getMaxAvailableIds() - streamIds.getAvailableIds(); + } + + int getOrphanIds() { + return orphanStreamIds; + } + private class SetKeyspaceRequest extends ChannelHandlerRequest { private final CqlIdentifier keyspaceName; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 6e44fbe1918..bc462d1f143 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.context; +import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; @@ -50,6 +51,8 @@ import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenFactoryRegistry; import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; +import com.datastax.oss.driver.internal.core.metrics.DefaultMetricUpdaterFactory; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec; import com.datastax.oss.driver.internal.core.servererrors.DefaultWriteTypeRegistry; @@ -156,6 +159,10 @@ public class DefaultDriverContext implements InternalDriverContext { "replicationStrategyFactory", this::buildReplicationStrategyFactory, cycleDetector); private final LazyReference poolManagerRef = new LazyReference<>("poolManager", this::buildPoolManager, cycleDetector); + private final LazyReference metricRegistryRef = + new LazyReference<>("metricRegistry", this::buildMetricRegistry, cycleDetector); + private final LazyReference metricUpdaterFactoryRef = + new LazyReference<>("metricUpdaterFactory", this::buildMetricUpdaterFactory, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -352,6 +359,14 @@ protected PoolManager buildPoolManager() { return new PoolManager(this); } + protected MetricRegistry buildMetricRegistry() { + return new MetricRegistry(); + } + + protected MetricUpdaterFactory buildMetricUpdaterFactory() { + return new DefaultMetricUpdaterFactory(this); + } + @Override public String sessionName() { return sessionName; @@ -512,6 +527,16 @@ public PoolManager poolManager() { return poolManagerRef.get(); } + @Override + public MetricRegistry metricRegistry() { + return metricRegistryRef.get(); + } + + @Override + public MetricUpdaterFactory metricUpdaterFactory() { + return metricUpdaterFactoryRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 788c9a621ec..051be55a758 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; import com.datastax.oss.driver.internal.core.metadata.token.ReplicationStrategyFactory; import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.servererrors.WriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.PoolManager; @@ -85,4 +86,6 @@ public interface InternalDriverContext extends DriverContext { ReplicationStrategyFactory replicationStrategyFactory(); PoolManager poolManager(); + + MetricUpdaterFactory metricUpdaterFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 9678175e14c..da6a08f499c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -26,6 +26,8 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -42,6 +44,8 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.util.Loggers; @@ -80,6 +84,7 @@ public abstract class CqlRequestHandlerBase { private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandlerBase.class); + private final long startTimeNanos; private final String logPrefix; private final Statement statement; private final DefaultSession session; @@ -121,6 +126,7 @@ protected CqlRequestHandlerBase( InternalDriverContext context, String sessionLogPrefix) { + this.startTimeNanos = System.nanoTime(); this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); LOG.debug("[{}] Creating new handler for request {}", logPrefix, statement); @@ -264,6 +270,12 @@ private void setFinalResult( Conversions.toResultSet(resultMessage, executionInfo, session, context); if (result.complete(resultSet)) { cancelScheduledTasks(); + session + .getMetricUpdater() + .updateTimer( + CoreSessionMetric.CQL_REQUESTS, + System.nanoTime() - startTimeNanos, + TimeUnit.NANOSECONDS); } } catch (Throwable error) { setFinalError(error); @@ -294,6 +306,9 @@ private ExecutionInfo buildExecutionInfo( private void setFinalError(Throwable error) { if (result.completeExceptionally(error)) { cancelScheduledTasks(); + if (error instanceof DriverTimeoutException) { + session.getMetricUpdater().incrementCounter(CoreSessionMetric.CQL_CLIENT_TIMEOUTS); + } } } @@ -305,6 +320,7 @@ private void setFinalError(Throwable error) { private class NodeResponseCallback implements ResponseCallback, GenericFutureListener> { + private final long start = System.nanoTime(); private final Node node; private final DriverChannel channel; // The identifier of the current execution (0 for the initial execution, 1 for the first @@ -346,6 +362,7 @@ public void operationComplete(Future future) throws Exception { channel, error); recordError(node, error); + ((DefaultNode) node).getMetricUpdater().incrementCounter(CoreNodeMetric.UNSENT_REQUESTS); sendRequest(null, execution, retryCount, scheduleNextExecution); // try next node } } else { @@ -380,6 +397,9 @@ public void operationComplete(Future future) throws Exception { nextExecution); activeExecutionsCount.incrementAndGet(); startedSpeculativeExecutionsCount.incrementAndGet(); + ((DefaultNode) node) + .getMetricUpdater() + .incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); sendRequest(null, nextExecution, 0, true); } }, @@ -398,6 +418,10 @@ public void operationComplete(Future future) throws Exception { @Override public void onResponse(Frame responseFrame) { + ((DefaultNode) node) + .getMetricUpdater() + .updateTimer( + CoreNodeMetric.CQL_MESSAGES, System.nanoTime() - start, TimeUnit.NANOSECONDS); inFlightCallbacks.remove(this); if (result.isDone()) { return; @@ -486,6 +510,7 @@ private void processErrorResponse(Error errorMessage) { return; } CoordinatorException error = Conversions.toThrowable(node, errorMessage, context); + NodeMetricUpdater metricUpdater = ((DefaultNode) node).getMetricUpdater(); if (error instanceof BootstrappingException) { LOG.debug("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); @@ -494,6 +519,7 @@ private void processErrorResponse(Error errorMessage) { || error instanceof FunctionFailureException || error instanceof ProtocolError) { LOG.debug("[{}] Unrecoverable error, rethrowing", logPrefix); + metricUpdater.incrementCounter(CoreNodeMetric.OTHER_ERRORS); setFinalError(error); } else { RetryDecision decision; @@ -507,6 +533,12 @@ private void processErrorResponse(Error errorMessage) { readTimeout.getReceived(), readTimeout.wasDataPresent(), retryCount); + updateErrorMetrics( + metricUpdater, + decision, + CoreNodeMetric.READ_TIMEOUTS, + CoreNodeMetric.RETRIES_ON_READ_TIMEOUT, + CoreNodeMetric.IGNORES_ON_READ_TIMEOUT); } else if (error instanceof WriteTimeoutException) { WriteTimeoutException writeTimeout = (WriteTimeoutException) error; decision = @@ -519,6 +551,12 @@ private void processErrorResponse(Error errorMessage) { writeTimeout.getReceived(), retryCount) : RetryDecision.RETHROW; + updateErrorMetrics( + metricUpdater, + decision, + CoreNodeMetric.WRITE_TIMEOUTS, + CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT, + CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT); } else if (error instanceof UnavailableException) { UnavailableException unavailable = (UnavailableException) error; decision = @@ -528,11 +566,23 @@ private void processErrorResponse(Error errorMessage) { unavailable.getRequired(), unavailable.getAlive(), retryCount); + updateErrorMetrics( + metricUpdater, + decision, + CoreNodeMetric.UNAVAILABLES, + CoreNodeMetric.RETRIES_ON_UNAVAILABLE, + CoreNodeMetric.IGNORES_ON_UNAVAILABLE); } else { decision = isIdempotent ? retryPolicy.onErrorResponse(statement, error, retryCount) : RetryDecision.RETHROW; + updateErrorMetrics( + metricUpdater, + decision, + CoreNodeMetric.OTHER_ERRORS, + CoreNodeMetric.RETRIES_ON_OTHER_ERROR, + CoreNodeMetric.IGNORES_ON_OTHER_ERROR); } processRetryDecision(decision, error); } @@ -558,6 +608,28 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { } } + private void updateErrorMetrics( + NodeMetricUpdater metricUpdater, + RetryDecision decision, + CoreNodeMetric error, + CoreNodeMetric retriesOnError, + CoreNodeMetric ignoresOnError) { + metricUpdater.incrementCounter(error); + switch (decision) { + case RETRY_SAME: + case RETRY_NEXT: + metricUpdater.incrementCounter(CoreNodeMetric.RETRIES); + metricUpdater.incrementCounter(retriesOnError); + break; + case IGNORE: + metricUpdater.incrementCounter(CoreNodeMetric.IGNORES); + metricUpdater.incrementCounter(ignoresOnError); + break; + case RETHROW: + // nothing do do + } + } + @Override public void onFailure(Throwable error) { inFlightCallbacks.remove(this); @@ -572,6 +644,12 @@ public void onFailure(Throwable error) { decision = retryPolicy.onRequestAborted(statement, error, retryCount); } processRetryDecision(decision, error); + updateErrorMetrics( + ((DefaultNode) node).getMetricUpdater(), + decision, + CoreNodeMetric.ABORTED_REQUESTS, + CoreNodeMetric.RETRIES_ON_ABORTED, + CoreNodeMetric.IGNORES_ON_ABORTED); } public void cancel() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index e12f457492e..cb2523d803b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -38,7 +38,7 @@ public Result compute( if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { return new Result(oldMetadata); } else { - DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress()); + DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress(), context); copyInfos(newNodeInfo, newNode, null, context.sessionName()); Map newNodes = ImmutableMap.builder() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index a63f1bf9ba5..6a606fc9978 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collections; @@ -34,6 +36,7 @@ public class DefaultNode implements Node { private final InetSocketAddress connectAddress; + private final NodeMetricUpdater metricUpdater; volatile Optional broadcastAddress; volatile Optional listenAddress; @@ -53,12 +56,15 @@ public class DefaultNode implements Node { volatile NodeDistance distance; - public DefaultNode(InetSocketAddress connectAddress) { + public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext context) { this.connectAddress = connectAddress; this.state = NodeState.UNKNOWN; this.distance = NodeDistance.IGNORED; this.rawTokens = Collections.emptySet(); this.extras = Collections.emptyMap(); + // We leak a reference to a partially constructed object (this), but in practice this won't be a + // problem because the node updater only needs the connect address to initialize. + this.metricUpdater = context.metricUpdaterFactory().newNodeUpdater(this); } @Override @@ -126,6 +132,10 @@ public NodeDistance getDistance() { return distance; } + public NodeMetricUpdater getMetricUpdater() { + return metricUpdater; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 44c2844c05c..1df258cdd41 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -67,7 +67,7 @@ public Result compute( seen.add(address); DefaultNode node = (DefaultNode) oldNodes.get(address); if (node == null) { - node = new DefaultNode(address); + node = new DefaultNode(address, context); LOG.debug("[{}] Adding new node {}", logPrefix, node); added.put(address, node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index aafde53793e..cd4daa1a607 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -43,7 +43,7 @@ public Result compute( ImmutableMap.Builder newNodes = ImmutableMap.builder(); for (InetSocketAddress address : contactPoints) { - newNodes.put(address, new DefaultNode(address)); + newNodes.put(address, new DefaultNode(address, context)); } return new Result(new DefaultMetadata(newNodes.build())); // No token map refresh, because we don't have enough information yet diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java new file mode 100644 index 00000000000..f6495fa2409 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; +import com.datastax.oss.driver.api.core.metrics.NodeMetric; +import com.datastax.oss.driver.api.core.metrics.SessionMetric; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultMetricUpdaterFactory implements MetricUpdaterFactory { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultMetricUpdaterFactory.class); + + private final String logPrefix; + private final InternalDriverContext context; + private final Set enabledSessionMetrics; + private final Set enabledNodeMetrics; + + public DefaultMetricUpdaterFactory(InternalDriverContext context) { + this.logPrefix = context.sessionName(); + this.context = context; + DriverConfigProfile config = context.config().getDefaultProfile(); + this.enabledSessionMetrics = + parseSessionMetricPaths(config.getStringList(CoreDriverOption.METRICS_SESSION_ENABLED)); + this.enabledNodeMetrics = + parseNodeMetricPaths(config.getStringList(CoreDriverOption.METRICS_NODE_ENABLED)); + } + + @Override + public SessionMetricUpdater newSessionUpdater() { + return new DefaultSessionMetricUpdater(enabledSessionMetrics, context); + } + + @Override + public NodeMetricUpdater newNodeUpdater(Node node) { + return new DefaultNodeMetricUpdater(node, enabledNodeMetrics, context); + } + + private Set parseSessionMetricPaths(List paths) { + EnumSet result = EnumSet.noneOf(CoreSessionMetric.class); + for (String path : paths) { + try { + result.add(CoreSessionMetric.fromPath(path)); + } catch (IllegalArgumentException e) { + LOG.warn("[{}] Unknown session metric {}, skipping", logPrefix, path); + } + } + return Collections.unmodifiableSet(result); + } + + private Set parseNodeMetricPaths(List paths) { + EnumSet result = EnumSet.noneOf(CoreNodeMetric.class); + for (String path : paths) { + try { + result.add(CoreNodeMetric.fromPath(path)); + } catch (IllegalArgumentException e) { + LOG.warn("[{}] Unknown node metric {}, skipping", logPrefix, path); + } + } + return Collections.unmodifiableSet(result); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java new file mode 100644 index 00000000000..ad5cb5bcfc1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java @@ -0,0 +1,121 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.codahale.metrics.Gauge; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.NodeMetric; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.function.Function; + +public class DefaultNodeMetricUpdater extends MetricUpdaterBase + implements NodeMetricUpdater { + + private final String metricNamePrefix; + + public DefaultNodeMetricUpdater( + Node node, Set enabledMetrics, InternalDriverContext context) { + super(enabledMetrics, context.metricRegistry()); + this.metricNamePrefix = buildPrefix(context.sessionName(), node.getConnectAddress()); + + DriverConfigProfile config = context.config().getDefaultProfile(); + + if (enabledMetrics.contains(CoreNodeMetric.OPEN_CONNECTIONS)) { + metricRegistry.register( + buildFullName(CoreNodeMetric.OPEN_CONNECTIONS), + (Gauge) node::getOpenConnections); + } + initializePoolGauge( + CoreNodeMetric.AVAILABLE_STREAMS, node, ChannelPool::getAvailableIds, context); + initializePoolGauge(CoreNodeMetric.IN_FLIGHT, node, ChannelPool::getInFlight, context); + initializePoolGauge( + CoreNodeMetric.ORPHANED_STREAMS, node, ChannelPool::getOrphanedIds, context); + initializeHdrTimer( + CoreNodeMetric.CQL_MESSAGES, + config, + CoreDriverOption.METRICS_NODE_CQL_MESSAGES_HIGHEST, + CoreDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS, + CoreDriverOption.METRICS_NODE_CQL_MESSAGES_INTERVAL); + initializeDefaultCounter(CoreNodeMetric.UNSENT_REQUESTS); + initializeDefaultCounter(CoreNodeMetric.ABORTED_REQUESTS); + initializeDefaultCounter(CoreNodeMetric.WRITE_TIMEOUTS); + initializeDefaultCounter(CoreNodeMetric.READ_TIMEOUTS); + initializeDefaultCounter(CoreNodeMetric.UNAVAILABLES); + initializeDefaultCounter(CoreNodeMetric.OTHER_ERRORS); + initializeDefaultCounter(CoreNodeMetric.RETRIES); + initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_ABORTED); + initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_READ_TIMEOUT); + initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT); + initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_UNAVAILABLE); + initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_OTHER_ERROR); + initializeDefaultCounter(CoreNodeMetric.IGNORES); + initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_ABORTED); + initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_READ_TIMEOUT); + initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT); + initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_UNAVAILABLE); + initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_OTHER_ERROR); + initializeDefaultCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); + initializeDefaultCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + initializeDefaultCounter(CoreNodeMetric.AUTHENTICATION_ERRORS); + } + + @Override + protected String buildFullName(NodeMetric metric) { + return metricNamePrefix + metric.getPath(); + } + + private String buildPrefix(String sessionName, InetSocketAddress addressAndPort) { + StringBuilder prefix = new StringBuilder(sessionName).append(".nodes."); + InetAddress address = addressAndPort.getAddress(); + int port = addressAndPort.getPort(); + if (address instanceof Inet4Address) { + // Metrics use '.' as a delimiter, replace so that the IP is a single path component + // (127.0.0.1 => 127_0_0_1) + prefix.append(address.getHostAddress().replace('.', '_')); + } else { + assert address instanceof Inet6Address; + // IPv6 only uses '%' and ':' as separators, so no replacement needed + prefix.append(address.getHostAddress()); + } + // Append the port in anticipation of when C* will support nodes on different ports + return prefix.append('_').append(port).append('.').toString(); + } + + private void initializePoolGauge( + NodeMetric metric, + Node node, + Function reading, + InternalDriverContext context) { + if (enabledMetrics.contains(metric)) { + metricRegistry.register( + buildFullName(metric), + (Gauge) + () -> { + ChannelPool pool = context.poolManager().getPools().get(node); + return (pool == null) ? 0 : reading.apply(pool); + }); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java new file mode 100644 index 00000000000..c7f3a0f4d85 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java @@ -0,0 +1,63 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.codahale.metrics.Gauge; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; +import com.datastax.oss.driver.api.core.metrics.SessionMetric; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.util.Set; + +public class DefaultSessionMetricUpdater extends MetricUpdaterBase + implements SessionMetricUpdater { + + private final String metricNamePrefix; + + public DefaultSessionMetricUpdater( + Set enabledMetrics, InternalDriverContext context) { + super(enabledMetrics, context.metricRegistry()); + this.metricNamePrefix = context.sessionName() + "."; + + if (enabledMetrics.contains(CoreSessionMetric.CONNECTED_NODES)) { + metricRegistry.register( + buildFullName(CoreSessionMetric.CONNECTED_NODES), + (Gauge) + () -> { + int count = 0; + for (Node node : context.metadataManager().getMetadata().getNodes().values()) { + if (node.getOpenConnections() > 0) { + count += 1; + } + } + return count; + }); + } + initializeHdrTimer( + CoreSessionMetric.CQL_REQUESTS, + context.config().getDefaultProfile(), + CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST, + CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, + CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); + initializeDefaultCounter(CoreSessionMetric.CQL_CLIENT_TIMEOUTS); + } + + @Override + protected String buildFullName(SessionMetric metric) { + return metricNamePrefix + metric.getPath(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/HdrReservoir.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/HdrReservoir.java new file mode 100644 index 00000000000..bdf6d6c5438 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/HdrReservoir.java @@ -0,0 +1,259 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.codahale.metrics.Reservoir; +import com.codahale.metrics.Snapshot; +import java.io.OutputStream; +import java.time.Duration; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.HdrHistogram.Histogram; +import org.HdrHistogram.Recorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A reservoir implementation backed by the HdrHistogram library. + * + *

          It uses a {@link Recorder} to capture snapshots at a configurable interval: calls to {@link + * #update(long)} are recorded in a "live" histogram, while {@link #getSnapshot()} is based on a + * "cached", read-only histogram. Each time the cached histogram becomes older than the interval, + * the two histograms are switched (therefore statistics won't be available during the first + * interval after initialization, since we don't have a cached histogram yet). + * + *

          Note that this class does not implement {@link #size()}. + * + * @see HdrHistogram + */ +public class HdrReservoir implements Reservoir { + + private static final Logger LOG = LoggerFactory.getLogger(HdrReservoir.class); + + private final String logPrefix; + private final Recorder recorder; + private final long refreshIntervalNanos; + + // The lock only orchestrates `getSnapshot()` calls; `update()` is fed directly to the recorder, + // which is lock-free. `getSnapshot()` calls are comparatively rare, so locking is not a + // bottleneck. + private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); + private Histogram cachedHistogram; // Guarded by cacheLock + private long cachedHistogramTimestampNanos; // Guarded by cacheLock + private Snapshot cachedSnapshot; // Guarded by cacheLock + + public HdrReservoir( + Duration highestTrackableLatency, + int numberOfSignificantValueDigits, + Duration refreshInterval, + String logPrefix) { + this.logPrefix = logPrefix; + // The Reservoir interface is supposed to be agnostic to the unit. However, the Metrics library + // heavily leans towards nanoseconds (for example, Timer feeds nanoseconds to update(); JmxTimer + // assumes that the snapshot results are in nanoseconds). + // In our case, microseconds are precise enough for request metrics, and we don't want to waste + // space unnecessarily. So we simply use microseconds for our internal storage, and do the + // conversion when needed. + this.recorder = + new Recorder(highestTrackableLatency.toNanos() / 1000, numberOfSignificantValueDigits); + this.refreshIntervalNanos = refreshInterval.toNanos(); + this.cachedHistogramTimestampNanos = System.nanoTime(); + this.cachedSnapshot = EMPTY_SNAPSHOT; + } + + @Override + public void update(long value) { + try { + recorder.recordValue(value / 1000); + } catch (ArrayIndexOutOfBoundsException e) { + LOG.warn("[{}] Recorded value ({}) is out of bounds, discarding", logPrefix, value); + } + } + + /** + * Not implemented: this reservoir implementation is intended for use with a {@link + * com.codahale.metrics.Histogram}, which doesn't use this method. + * + *

          (original description: {@inheritDoc}) + */ + @Override + public int size() { + throw new UnsupportedOperationException("HdrReservoir does not implement size()"); + } + + /** + * {@inheritDoc} + * + *

          Note that the snapshots returned from this method do not implement {@link + * Snapshot#getValues()} nor {@link Snapshot#dump(OutputStream)}. In addition, due to the way that + * internal data structures are recycled, you should not hold onto a snapshot for more than the + * refresh interval; one way to ensure this is to never cache the result of this method. + */ + @Override + public Snapshot getSnapshot() { + long now = System.nanoTime(); + + cacheLock.readLock().lock(); + try { + if (now - cachedHistogramTimestampNanos < refreshIntervalNanos) { + return cachedSnapshot; + } + } finally { + cacheLock.readLock().unlock(); + } + + cacheLock.writeLock().lock(); + try { + // Might have raced with another writer => re-check the timestamp + if (now - cachedHistogramTimestampNanos >= refreshIntervalNanos) { + LOG.debug("Cached snapshot is too old, refreshing"); + cachedHistogram = recorder.getIntervalHistogram(cachedHistogram); + cachedSnapshot = new HdrSnapshot(cachedHistogram); + cachedHistogramTimestampNanos = now; + } + return cachedSnapshot; + } finally { + cacheLock.writeLock().unlock(); + } + } + + private class HdrSnapshot extends Snapshot { + + private final Histogram histogram; + private final double meanNanos; + private final double stdDevNanos; + + private HdrSnapshot(Histogram histogram) { + this.histogram = histogram; + + // Cache those values because they rely on HdrHistogram's internal iterators, which are not + // safe if the snapshot is accessed by concurrent reporters. + // In contrast, getMin(), getMax() and getValue() are safe. + this.meanNanos = histogram.getMean() * 1000; + this.stdDevNanos = histogram.getStdDeviation() * 1000; + } + + @Override + public double getValue(double quantile) { + return histogram.getValueAtPercentile(quantile * 100) * 1000; + } + + /** + * Not implemented: this reservoir implementation is intended for use with a {@link + * com.codahale.metrics.Histogram}, which doesn't use this method. + * + *

          (original description: {@inheritDoc}) + */ + @Override + public long[] getValues() { + // This can be implemented, but we ran into issues when accessed by concurrent reporters + // because HdrHistogram uses an unsafe shared iterator. + // So throwing instead since this method should be seldom used anyway. + throw new UnsupportedOperationException( + "HdrReservoir's snapshots do not implement getValues()"); + } + + @Override + public int size() { + long longSize = histogram.getTotalCount(); + // The Metrics API requires an int. It's very unlikely that we get an overflow here, unless + // the refresh interval is ridiculously high (at 10k requests/s, it would have to be more than + // 59 hours). However handle gracefully just in case. + int size; + if (longSize > Integer.MAX_VALUE) { + LOG.warn("[{}] Too many recorded values, truncating", logPrefix); + size = Integer.MAX_VALUE; + } else { + size = (int) longSize; + } + return size; + } + + @Override + public long getMax() { + return histogram.getMaxValue() * 1000; + } + + @Override + public double getMean() { + return meanNanos; + } + + @Override + public long getMin() { + return histogram.getMinValue() * 1000; + } + + @Override + public double getStdDev() { + return stdDevNanos; + } + + /** + * Not implemented: this reservoir implementation is intended for use with a {@link + * com.codahale.metrics.Histogram}, which doesn't use this method. + * + *

          (original description: {@inheritDoc}) + */ + @Override + public void dump(OutputStream output) { + throw new UnsupportedOperationException("HdrReservoir's snapshots do not implement dump()"); + } + } + + private static final Snapshot EMPTY_SNAPSHOT = + new Snapshot() { + @Override + public double getValue(double quantile) { + return 0; + } + + @Override + public long[] getValues() { + return new long[0]; + } + + @Override + public int size() { + return 0; + } + + @Override + public long getMax() { + return 0; + } + + @Override + public double getMean() { + return 0; + } + + @Override + public long getMin() { + return 0; + } + + @Override + public double getStdDev() { + return 0; + } + + @Override + public void dump(OutputStream output) { + // nothing to do + } + }; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdater.java new file mode 100644 index 00000000000..c4e2777db5f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdater.java @@ -0,0 +1,37 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import java.util.concurrent.TimeUnit; + +public interface MetricUpdater { + + void incrementCounter(MetricT metric, long amount); + + default void incrementCounter(MetricT metric) { + incrementCounter(metric, 1); + } + + void updateHistogram(MetricT metric, long value); + + void markMeter(MetricT metric, long amount); + + default void markMeter(MetricT metric) { + markMeter(metric, 1); + } + + void updateTimer(MetricT metric, long duration, TimeUnit unit); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java new file mode 100644 index 00000000000..70d6f41c448 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java @@ -0,0 +1,110 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import java.time.Duration; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class MetricUpdaterBase implements MetricUpdater { + + private static final Logger LOG = LoggerFactory.getLogger(MetricUpdaterBase.class); + + protected final Set enabledMetrics; + protected final MetricRegistry metricRegistry; + + protected MetricUpdaterBase(Set enabledMetrics, MetricRegistry metricRegistry) { + this.enabledMetrics = enabledMetrics; + this.metricRegistry = metricRegistry; + } + + protected abstract String buildFullName(MetricT metric); + + @Override + public void incrementCounter(MetricT metric, long amount) { + if (enabledMetrics.contains(metric)) { + metricRegistry.counter(buildFullName(metric)).inc(amount); + } + } + + @Override + public void updateHistogram(MetricT metric, long value) { + if (enabledMetrics.contains(metric)) { + metricRegistry.histogram(buildFullName(metric)).update(value); + } + } + + @Override + public void markMeter(MetricT metric, long amount) { + if (enabledMetrics.contains(metric)) { + metricRegistry.meter(buildFullName(metric)).mark(amount); + } + } + + @Override + public void updateTimer(MetricT metric, long duration, TimeUnit unit) { + if (enabledMetrics.contains(metric)) { + metricRegistry.timer(buildFullName(metric)).update(duration, unit); + } + } + + protected void initializeDefaultCounter(MetricT metric) { + if (enabledMetrics.contains(metric)) { + // Just initialize eagerly so that the metric appears even when it has no data yet + metricRegistry.counter(buildFullName(metric)); + } + } + + protected void initializeHdrTimer( + MetricT metric, + DriverConfigProfile config, + CoreDriverOption highestLatencyOption, + CoreDriverOption significantDigitsOption, + CoreDriverOption intervalOption) { + if (enabledMetrics.contains(metric)) { + String fullName = buildFullName(metric); + + Duration highestLatency = config.getDuration(highestLatencyOption); + final int significantDigits; + int d = config.getInt(significantDigitsOption); + if (d >= 0 && d <= 5) { + significantDigits = d; + } else { + LOG.warn( + "[{}] Configuration option {} is out of range (expected between 0 and 5, found {}); " + + "using 3 instead.", + fullName, + significantDigitsOption, + d); + significantDigits = 3; + } + Duration refreshInterval = config.getDuration(intervalOption); + + // Initialize eagerly to use the custom implementation + metricRegistry.timer( + fullName, + () -> + new Timer( + new HdrReservoir(highestLatency, significantDigits, refreshInterval, fullName))); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java new file mode 100644 index 00000000000..10fcbc0a76a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.datastax.oss.driver.api.core.metadata.Node; + +public interface MetricUpdaterFactory { + + SessionMetricUpdater newSessionUpdater(); + + NodeMetricUpdater newNodeUpdater(Node node); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/NodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/NodeMetricUpdater.java new file mode 100644 index 00000000000..4a145124d6a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/NodeMetricUpdater.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.datastax.oss.driver.api.core.metrics.NodeMetric; + +public interface NodeMetricUpdater extends MetricUpdater {} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/SessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/SessionMetricUpdater.java new file mode 100644 index 00000000000..8aaf82dcb71 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/SessionMetricUpdater.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metrics; + +import com.datastax.oss.driver.api.core.metrics.SessionMetric; + +public interface SessionMetricUpdater extends MetricUpdater {} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 4c99ab0f24a..2f287f4cda4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; @@ -32,6 +33,7 @@ import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -143,10 +145,23 @@ public DriverChannel next() { return channels.next(); } + /** @return the number of active channels in the pool. */ + public int size() { + return channels.size(); + } + public int getAvailableIds() { return channels.getAvailableIds(); } + public int getInFlight() { + return channels.getInFlight(); + } + + public int getOrphanedIds() { + return channels.getOrphanedIds(); + } + public void resize(NodeDistance newDistance) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newDistance)); } @@ -274,6 +289,12 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { assert future.isDone(); if (future.isCompletedExceptionally()) { Throwable error = CompletableFutures.getFailed(future); + ((DefaultNode) node) + .getMetricUpdater() + .incrementCounter( + error instanceof AuthenticationException + ? CoreNodeMetric.AUTHENTICATION_ERRORS + : CoreNodeMetric.CONNECTION_INIT_ERRORS); if (error instanceof ClusterNameMismatchException || error instanceof UnsupportedProtocolVersionException) { // This will likely be thrown by all channels, but finish the loop cleanly diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java index 6452dfa67e6..aa6caacf2ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -104,6 +104,24 @@ int getAvailableIds() { return availableIds; } + int getInFlight() { + int inFlight = 0; + DriverChannel[] snapshot = this.channels; + for (DriverChannel channel : snapshot) { + inFlight += channel.getInFlight(); + } + return inFlight; + } + + int getOrphanedIds() { + int orphanedIds = 0; + DriverChannel[] snapshot = this.channels; + for (DriverChannel channel : snapshot) { + orphanedIds += channel.getOrphanedIds(); + } + return orphanedIds; + } + int size() { return this.channels.length; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f70357dfc86..de88337fa8a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.session; +import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -35,6 +36,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateManager; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -88,6 +90,7 @@ public static CompletionStage init( private final MetadataManager metadataManager; private final RequestProcessorRegistry processorRegistry; private final PoolManager poolManager; + private final SessionMetricUpdater metricUpdater; private DefaultSession( InternalDriverContext context, @@ -101,6 +104,7 @@ private DefaultSession( this.processorRegistry = context.requestProcessorRegistry(); this.poolManager = context.poolManager(); this.logPrefix = context.sessionName(); + this.metricUpdater = context.metricUpdaterFactory().newSessionUpdater(); } private CompletionStage init(CqlIdentifier keyspace) { @@ -148,6 +152,11 @@ public CqlIdentifier getKeyspace() { return poolManager.getKeyspace(); } + @Override + public MetricRegistry getMetricRegistry() { + return context.metricRegistry(); + } + /** * INTERNAL USE ONLY -- switches the session to a new keyspace. * @@ -195,6 +204,10 @@ public ConcurrentMap getRepreparePayloads() { return poolManager.getRepreparePayloads(); } + public SessionMetricUpdater getMetricUpdater() { + return metricUpdater; + } + @Override public void register(SchemaChangeListener listener) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.register(listener)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java index 44c3afa1e4b..0d14d6ac777 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.session; +import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -90,6 +91,11 @@ public CqlIdentifier getKeyspace() { return delegate.getKeyspace(); } + @Override + public MetricRegistry getMetricRegistry() { + return delegate.getMetricRegistry(); + } + @Override public ResultT execute( RequestT request, GenericType resultType) { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index ae14c5135c6..fc6e0bbcada 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -566,6 +566,198 @@ datastax-java-driver { token-map.enabled = true } + metrics { + # The session-level metrics (all disabled by default). + session { + enabled = [ + # The number of nodes to which the driver has at least one active connection (exposed as a + # Gauge). + // connected-nodes, + + # The throughput and latency percentiles of CQL requests (exposed as a Timer). + # + # This corresponds to the overall duration of the session.execute() call, including any + # retry. + // cql-requests, + + # The number of CQL requests that timed out -- that is, the session.execute() call failed + # with a DriverTimeoutException (exposed as a Counter). + // cql-client-timeouts, + ] + + # Extra configuration (for the metrics that need it) + cql-requests { + # The largest latency that we expect to record. + # + # This should be slightly higher than request.timeout (in theory, readings can't be higher + # than the timeout, but there might be a small overhead due to internal scheduling). + # + # This is used to scale internal data structures. If a higher recording is encountered at + # runtime, it is discarded and a warning is logged. + highest-latency = 3 seconds + + # The number of significant decimal digits to which internal structures will maintain value + # resolution and separation (for example, 3 means that recordings up to 1 second will be + # recorded with a resolution of 1 millisecond or better). + # + # This must be between 0 and 5. If the value is out of range, it defaults to 3 and a warning + # is logged. + significant-digits = 3 + + # The interval at which percentile data is refreshed. + # + # The driver records latency data in a "live" histogram, and serves results from a cached + # snapshot. Each time the snapshot gets older than the interval, the two are switched. Note + # that this switch happens upon fetching the metrics, so if you never fetch the recording + # interval might grow higher (that shouldn't be an issue in a production environment because + # you would typically have a metrics reporter that exports to a monitoring tool at a regular + # interval). + # + # In practice, this means that if you set this to 5 minutes, you're looking at data from a + # 5-minute interval in the past, that is at most 5 minutes old. If you fetch the metrics at + # a faster pace, you will observe the same data for 5 minutes until the interval expires. + # + # Note that this does not apply to the total count and rates (those are updated in real + # time). + refresh-interval = 5 minutes + } + } + # The node-level metrics (all disabled by default). + node { + enabled = [ + # The number of connections open to this node for regular requests (exposed as a + # Gauge). + # + # This includes the control connection (which uses at most one extra connection to a random + # node in the cluster). + // pool.open-connections, + + # The number of stream ids available on the connections to this node (exposed as a + # Gauge). + # + # Stream ids are used to multiplex requests on each connection, so this is an indication of + # how many more requests the node could handle concurrently before becoming saturated (note + # that this is a driver-side only consideration, there might be other limitations on the + # server that prevent reaching that theoretical limit). + // pool.available-streams, + + # The number of requests currently executing on the connections to this node (exposed as a + # Gauge). This includes orphaned streams. + // pool.in-flight, + + # The number of "orphaned" stream ids on the connections to this node (exposed as a + # Gauge). + # + # See the description of the connection.max-orphan-requests option for more details. + // pool.orphaned-streams, + + # The throughput and latency percentiles of individual CQL messages sent to this node as + # part of an overall request (exposed as a Timer). + # + # Note that this does not necessarily correspond to the overall duration of the + # session.execute() call, since the driver might query multiple nodes because of retries and + # speculative executions. Therefore a single "request" (as seen from a client of the driver) + # can be composed of more than one of the "messages" measured by this metric. + # + # Therefore this metric is intended as an insight into the performance of this particular + # node. For statistics on overall request completion, use the session-level cql-requests. + // cql-messages, + + # The number of times the driver failed to send a request to this node (exposed as a + # Counter). + # + # In those case we know the request didn't even reach the coordinator, so they are retried + # on the next node automatically (without going through the retry policy). + // errors.request.unsent, + + # The number of times a request was aborted before the driver even received a response from + # this node (exposed as a Counter). + # + # This can happen in two cases: if the connection was closed due to an external event (such + # as a network error or heartbeat failure); or if there was an unexpected error while + # decoding the response (this can only be a driver bug). + // errors.request.aborted, + + # The number of times this node replied with a WRITE_TIMEOUT error (exposed as a Counter). + # + # Whether this error is rethrown directly to the client, rethrown or ignored is determined + # by the RetryPolicy. + // errors.request.write-timeouts, + + # The number of times this node replied with a READ_TIMEOUT error (exposed as a Counter). + # + # Whether this error is rethrown directly to the client, rethrown or ignored is determined + # by the RetryPolicy. + // errors.request.read-timeouts, + + # The number of times this node replied with an UNAVAILABLE error (exposed as a Counter). + # + # Whether this error is rethrown directly to the client, rethrown or ignored is determined + # by the RetryPolicy. + // errors.request.unavailables, + + # The number of times this node replied with an error that doesn't fall under other errors.* + # metrics (exposed as a Counter). + // errors.request.others, + + # The total number of errors on this node that caused the RetryPolicy to trigger a retry + # (exposed as a Counter). + # + # This is a sum of all the other retries.* metrics. + // retries.total, + + # The number of errors on this node that caused the RetryPolicy to trigger a retry, broken + # down by error type (exposed as Counters). + // retries.aborted, + // retries.read-timeout, + // retries.write-timeout, + // retries.unavailable, + // retries.other, + + # The total number of errors on this node that were ignored by the RetryPolicy (exposed as a + # Counter). + # + # This is a sum of all the other ignores.* metrics. + // ignores.total, + + # The number of errors on this node that were ignored by the RetryPolicy, broken down by + # error type (exposed as Counters). + // ignores.aborted, + // ignores.read-timeout, + // ignores.write-timeout, + // ignores.unavailable, + // ignores.other, + + # The number of speculative executions triggered by a slow response from this node (exposed + # as a Counter). + // speculative-executions, + + # The number of errors encountered while trying to establish a connection to this node + # (exposed as a Counter). + # + # Connection errors are not a fatal issue for the driver, failed connections will be retried + # periodically according to the reconnection policy. You can choose whether or not to log + # those errors at WARN level with the connection.warn-on-init-error option. + # + # Authentication errors are not included in this counter, they are tracked separately in + # errors.connection.auth. + // errors.connection.init, + + # The number of authentication errors encountered while trying to establish a connection to + # this node (exposed as a Counter). + # Authentication errors are also logged at WARN level. + // errors.connection.auth, + ] + + // See cql-requests in the `session` section + cql-messages { + highest-latency = 3 seconds + significant-digits = 3 + refresh-interval = 5 minutes + } + } + } + # The address translator to use to convert the addresses sent by Cassandra nodes into ones that # the driver uses to connect. # This is only needed if the nodes are not directly reachable from the driver (for example, the diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 34ab1f9100e..70dfd52a5fb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -25,12 +25,8 @@ import com.datastax.oss.driver.internal.core.channel.MockChannelFactoryHelper; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; -import com.google.common.collect.ImmutableList; -import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.time.Duration; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.junit.Test; @@ -64,7 +60,7 @@ public void should_init_with_first_contact_point_if_reachable() { // Then assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); factoryHelper.verifyNoMoreCalls(); } @@ -106,8 +102,8 @@ public void should_init_with_second_contact_point_if_first_one_fails() { // Then assertThat(initFuture) .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); // each attempt tries all nodes, so there is no reconnection Mockito.verify(reconnectionPolicy, never()).newSchedule(); @@ -131,8 +127,8 @@ public void should_fail_to_init_if_all_contact_points_fail() { // Then assertThat(initFuture).isFailed(); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node2)); // no reconnections at init Mockito.verify(reconnectionPolicy, never()).newSchedule(); @@ -158,7 +154,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When channel1.close(); @@ -171,8 +167,8 @@ public void should_reconnect_if_channel_goes_down() throws Exception { factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); Mockito.verify(metadataManager).refreshNodes(); Mockito.verify(loadBalancingPolicyWrapper).init(); @@ -197,11 +193,11 @@ public void should_reconnect_if_node_becomes_ignored() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When - mockQueryPlan(NODE2); - eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, NODE1)); + mockQueryPlan(node2); + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node1)); waitForPendingAdminTasks(); // Then @@ -210,8 +206,8 @@ public void should_reconnect_if_node_becomes_ignored() { factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); Mockito.verify(metadataManager).refreshNodes(); Mockito.verify(loadBalancingPolicyWrapper).init(); @@ -219,8 +215,16 @@ public void should_reconnect_if_node_becomes_ignored() { } @Test - @UseDataProvider("node1RemovedOrForcedDown") - public void should_reconnect_if_node_is_removed_or_forced_down(NodeStateEvent event) { + public void should_reconnect_if_node_is_removed() { + should_reconnect_if_event(NodeStateEvent.removed(node1)); + } + + @Test + public void should_reconnect_if_node_is_forced_down() { + should_reconnect_if_event(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node1)); + } + + private void should_reconnect_if_event(NodeStateEvent event) { // Given Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); @@ -237,10 +241,10 @@ public void should_reconnect_if_node_is_removed_or_forced_down(NodeStateEvent ev waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When - mockQueryPlan(NODE2); + mockQueryPlan(node2); eventBus.fire(event); waitForPendingAdminTasks(); @@ -250,8 +254,8 @@ public void should_reconnect_if_node_is_removed_or_forced_down(NodeStateEvent ev factoryHelper.waitForCall(ADDRESS2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); Mockito.verify(metadataManager).refreshNodes(); Mockito.verify(loadBalancingPolicyWrapper).init(); @@ -281,20 +285,20 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); - mockQueryPlan(NODE2, NODE1); + mockQueryPlan(node2, node1); // channel1 goes down, triggering a reconnection channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); Mockito.verify(reconnectionSchedule).nextDelay(); // the reconnection to node2 is in progress factoryHelper.waitForCall(ADDRESS2); // When // node2 becomes ignored - eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, NODE2)); + eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); // the reconnection to node2 completes channel2Future.complete(channel2); waitForPendingAdminTasks(); @@ -306,10 +310,17 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( } @Test - @UseDataProvider("node2RemovedOrForcedDown") - public void - should_reconnect_if_node_was_removed_or_forced_down_ignored_during_reconnection_attempt( - NodeStateEvent event) { + public void should_reconnect_if_node_was_removed_during_reconnection_attempt() { + should_reconnect_if_event_during_reconnection_attempt(NodeStateEvent.removed(node2)); + } + + @Test + public void should_reconnect_if_node_was_forced_down_during_reconnection_attempt() { + should_reconnect_if_event_during_reconnection_attempt( + NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); + } + + private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEvent event) { // Given Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); @@ -331,13 +342,13 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); - mockQueryPlan(NODE2, NODE1); + mockQueryPlan(node2, node1); // channel1 goes down, triggering a reconnection channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); Mockito.verify(reconnectionSchedule).nextDelay(); // the reconnection to node2 is in progress factoryHelper.waitForCall(ADDRESS2); @@ -374,12 +385,12 @@ public void should_force_reconnection_if_pending() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled for later channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); Mockito.verify(reconnectionSchedule).nextDelay(); // When @@ -390,7 +401,7 @@ public void should_force_reconnection_if_pending() { // Then assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -412,7 +423,7 @@ public void should_force_reconnection_even_if_connected() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When controlConnection.reconnectNow(); @@ -423,8 +434,8 @@ public void should_force_reconnection_even_if_connected() { waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); Mockito.verify(channel1).forceClose(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE2)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -505,12 +516,12 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCall(ADDRESS1); // channel2 starts initializing (but the future is not completed yet) @@ -526,8 +537,8 @@ public void should_close_channel_if_closed_during_reconnection() { // Then Mockito.verify(channel2).forceClose(); // no event because the control connection never "owned" the channel - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE2)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE2)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node2)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -552,12 +563,12 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThat(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(NODE1)); + Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); Mockito.verify(reconnectionSchedule).nextDelay(); // channel1 starts initializing (but the future is not completed yet) factoryHelper.waitForCall(ADDRESS1); @@ -573,18 +584,4 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { // first failure factoryHelper.verifyNoMoreCalls(); } - - @DataProvider - public static List> node1RemovedOrForcedDown() { - return ImmutableList.of( - ImmutableList.of(NodeStateEvent.removed(NODE1)), - ImmutableList.of(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, NODE1))); - } - - @DataProvider - public static List> node2RemovedOrForcedDown() { - return ImmutableList.of( - ImmutableList.of(NodeStateEvent.removed(NODE2)), - ImmutableList.of(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, NODE2))); - } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 11e8c97b1a0..bcfbc9aface 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultEventLoopGroup; @@ -54,8 +55,6 @@ abstract class ControlConnectionTestBase { protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - protected static final DefaultNode NODE1 = new DefaultNode(ADDRESS1); - protected static final DefaultNode NODE2 = new DefaultNode(ADDRESS2); @Mock protected InternalDriverContext context; @Mock protected ReconnectionPolicy reconnectionPolicy; @@ -67,7 +66,11 @@ abstract class ControlConnectionTestBase { protected Exchanger> channelFactoryFuture; @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @Mock protected MetadataManager metadataManager; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; + protected AddressTranslator addressTranslator; + protected DefaultNode node1; + protected DefaultNode node2; protected ControlConnection controlConnection; @@ -99,7 +102,11 @@ public void setup() { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - mockQueryPlan(NODE1, NODE2); + + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + node1 = new DefaultNode(ADDRESS1, context); + node2 = new DefaultNode(ADDRESS2, context); + mockQueryPlan(node1, node2); Mockito.when(metadataManager.refreshNodes()) .thenReturn(CompletableFuture.completedFuture(null)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 3ee4ca0fa82..f3a86df4111 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -17,7 +17,9 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMost; import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.CoreConsistencyLevel; @@ -27,6 +29,7 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -45,6 +48,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Iterator; import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.Mockito; @@ -115,6 +119,12 @@ public void should_always_rethrow_query_validation_error( .isInstanceOf(InvalidQueryException.class) .hasMessage("mock message"); Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); + + Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.OTHER_ERRORS); + Mockito.verify(nodeMetricUpdater1) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -147,6 +157,14 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getCoordinator()).isEqualTo(node2); assertThat(executionInfo.getErrors()).hasSize(1); assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); + Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.RETRIES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.retryMetric); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -179,6 +197,14 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getCoordinator()).isEqualTo(node1); assertThat(executionInfo.getErrors()).hasSize(1); assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); + Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.RETRIES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.retryMetric); + Mockito.verify(nodeMetricUpdater1, atMost(2)) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -208,6 +234,14 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( ExecutionInfo executionInfo = resultSet.getExecutionInfo(); assertThat(executionInfo.getCoordinator()).isEqualTo(node1); assertThat(executionInfo.getErrors()).hasSize(0); + + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); + Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.IGNORES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.ignoreMetric); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -231,7 +265,15 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( assertThat(resultSetFuture) .isFailed( - error -> assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass)); + error -> { + assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); + + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + }); } } @@ -270,6 +312,12 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re if (!shouldCallRetryPolicy) { Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); } + + Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .updateTimer( + eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -280,9 +328,19 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re */ private abstract static class FailureScenario { private final Class expectedExceptionClass; - - protected FailureScenario(Class expectedExceptionClass) { + final CoreNodeMetric errorMetric; + final CoreNodeMetric retryMetric; + final CoreNodeMetric ignoreMetric; + + protected FailureScenario( + Class expectedExceptionClass, + CoreNodeMetric errorMetric, + CoreNodeMetric retryMetric, + CoreNodeMetric ignoreMetric) { this.expectedExceptionClass = expectedExceptionClass; + this.errorMetric = errorMetric; + this.retryMetric = retryMetric; + this.ignoreMetric = ignoreMetric; } abstract void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node); @@ -293,7 +351,11 @@ protected FailureScenario(Class expectedExceptionClass) { @DataProvider public static Object[][] failure() { return TestDataProviders.fromList( - new FailureScenario(ReadTimeoutException.class) { + new FailureScenario( + ReadTimeoutException.class, + CoreNodeMetric.READ_TIMEOUTS, + CoreNodeMetric.RETRIES_ON_READ_TIMEOUT, + CoreNodeMetric.IGNORES_ON_READ_TIMEOUT) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -316,7 +378,11 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) .thenReturn(decision); } }, - new FailureScenario(WriteTimeoutException.class) { + new FailureScenario( + WriteTimeoutException.class, + CoreNodeMetric.WRITE_TIMEOUTS, + CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT, + CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -343,7 +409,11 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) .thenReturn(decision); } }, - new FailureScenario(UnavailableException.class) { + new FailureScenario( + UnavailableException.class, + CoreNodeMetric.UNAVAILABLES, + CoreNodeMetric.RETRIES_ON_UNAVAILABLE, + CoreNodeMetric.IGNORES_ON_UNAVAILABLE) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -365,7 +435,11 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) .thenReturn(decision); } }, - new FailureScenario(ServerError.class) { + new FailureScenario( + ServerError.class, + CoreNodeMetric.OTHER_ERRORS, + CoreNodeMetric.RETRIES_ON_OTHER_ERROR, + CoreNodeMetric.IGNORES_ON_OTHER_ERROR) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -382,7 +456,11 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) .thenReturn(decision); } }, - new FailureScenario(HeartbeatException.class) { + new FailureScenario( + HeartbeatException.class, + CoreNodeMetric.ABORTED_REQUESTS, + CoreNodeMetric.RETRIES_ON_ABORTED, + CoreNodeMetric.IGNORES_ON_ABORTED) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponseFailure(node, Mockito.mock(HeartbeatException.class)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index c5ea31c7bdc..b6acf194811 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import com.datastax.oss.driver.api.core.AllNodesFailedException; @@ -24,6 +25,7 @@ import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; @@ -59,6 +61,7 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( assertThat(harness.nextScheduledTask()).isNull(); Mockito.verifyNoMoreInteractions(speculativeExecutionPolicy); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); } } @@ -102,7 +105,9 @@ public void should_schedule_speculative_executions( harness.nextScheduledTask(); assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); firstExecutionTask.run(); + Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); @@ -110,7 +115,9 @@ public void should_schedule_speculative_executions( harness.nextScheduledTask(); assertThat(secondExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) .isEqualTo(secondExecutionDelay); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater2); secondExecutionTask.run(); + Mockito.verify(nodeMetricUpdater2).incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); node3Behavior.verifyWrite(); node3Behavior.setWriteSuccess(); @@ -161,6 +168,10 @@ public void should_not_start_execution_if_result_complete( // The speculative execution should have been cancelled assertThat(firstExecutionTask.isCancelled()).isTrue(); + Mockito.verify(nodeMetricUpdater1) + .updateTimer(eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + // Run the task anyway; we're bending reality a bit here since the task is already cancelled // and wouldn't run, but this allows us to test the case where the completion races with the // start of the task. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index a1b3a55095f..d92ac7f659a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -18,7 +18,8 @@ import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -30,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Collections; @@ -38,6 +40,7 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @RunWith(DataProviderRunner.class) @@ -49,14 +52,24 @@ public abstract class CqlRequestHandlerTestBase { SimpleStatement.builder("mock query").withIdempotence(true).build(); protected static final SimpleStatement NON_IDEMPOTENT_STATEMENT = SimpleStatement.builder("mock query").withIdempotence(false).build(); + protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); + protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + protected static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); - @Mock protected Node node1; - @Mock protected Node node2; - @Mock protected Node node3; + @Mock protected DefaultNode node1; + @Mock protected DefaultNode node2; + @Mock protected DefaultNode node3; + @Mock protected NodeMetricUpdater nodeMetricUpdater1; + @Mock protected NodeMetricUpdater nodeMetricUpdater2; + @Mock protected NodeMetricUpdater nodeMetricUpdater3; @Before public void setup() { MockitoAnnotations.initMocks(this); + + Mockito.when(node1.getMetricUpdater()).thenReturn(nodeMetricUpdater1); + Mockito.when(node2.getMetricUpdater()).thenReturn(nodeMetricUpdater2); + Mockito.when(node3.getMetricUpdater()).thenReturn(nodeMetricUpdater3); } protected static Frame defaultFrameOf(Message responseMessage) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 56c353669b6..1aa13ad0146 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -20,13 +20,16 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; import java.util.UUID; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -34,9 +37,16 @@ public class AddNodeRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - private static final DefaultNode node1 = new DefaultNode(ADDRESS1); - @Mock private InternalDriverContext context; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; + + private DefaultNode node1; + + @Before + public void setup() { + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + node1 = new DefaultNode(ADDRESS1, context); + } @Test public void should_add_new_node() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 8fc050c6d6a..1f424576cc4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; @@ -62,6 +63,8 @@ public class DefaultTopologyMonitorTest { @Mock private DriverConfigProfile defaultConfig; @Mock private ControlConnection controlConnection; @Mock private DriverChannel channel; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; + private AddressTranslator addressTranslator; private DefaultNode node1; private DefaultNode node2; @@ -84,8 +87,10 @@ public void setup() { Mockito.when(controlConnection.channel()).thenReturn(channel); Mockito.when(context.controlConnection()).thenReturn(controlConnection); - node1 = new DefaultNode(ADDRESS1); - node2 = new DefaultNode(ADDRESS2); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + + node1 = new DefaultNode(ADDRESS1, context); + node2 = new DefaultNode(ADDRESS2, context); topologyMonitor = new TestTopologyMonitor(context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index bcd0de9bc5f..e66f22eda9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -19,13 +19,16 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.UUID; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -35,11 +38,21 @@ public class FullNodeListRefreshTest { private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); private static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); - private static final DefaultNode node1 = new DefaultNode(ADDRESS1); - private static final DefaultNode node2 = new DefaultNode(ADDRESS2); - private static final DefaultNode node3 = new DefaultNode(ADDRESS3); - @Mock private InternalDriverContext context; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; + + private DefaultNode node1; + private DefaultNode node2; + private DefaultNode node3; + + @Before + public void setup() { + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + + node1 = new DefaultNode(ADDRESS1, context); + node2 = new DefaultNode(ADDRESS2, context); + node3 = new DefaultNode(ADDRESS3, context); + } @Test public void should_add_and_remove_nodes() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 44838cab078..d2623980dcd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -18,11 +18,14 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableSet; import java.net.InetSocketAddress; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -32,6 +35,12 @@ public class InitContactPointsRefreshTest { private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); @Mock private InternalDriverContext context; + @Mock private MetricUpdaterFactory metricUpdaterFactory; + + @Before + public void setup() { + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + } @Test public void should_create_nodes() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index bf8d848f217..f735a3a3ece 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -29,6 +29,7 @@ import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -59,6 +60,7 @@ public class LoadBalancingPolicyWrapperTest { private EventBus eventBus; @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; private LoadBalancingPolicyWrapper wrapper; @@ -66,9 +68,11 @@ public class LoadBalancingPolicyWrapperTest { public void setup() { MockitoAnnotations.initMocks(this); - node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042)); - node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042)); - node3 = new DefaultNode(new InetSocketAddress("127.0.0.3", 9042)); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + + node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); + node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); + node3 = new DefaultNode(new InetSocketAddress("127.0.0.3", 9042), context); contactPointsMap = ImmutableMap.builder() diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 6893a686a6f..3a097b6c67d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -28,6 +28,7 @@ import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.metadata.schema.parsing.SchemaParserFactory; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaQueriesFactory; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Uninterruptibles; @@ -64,6 +65,7 @@ public class MetadataManagerTest { @Mock private EventBus eventBus; @Mock private SchemaQueriesFactory schemaQueriesFactory; @Mock private SchemaParserFactory schemaParserFactory; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; private DefaultEventLoopGroup adminEventLoopGroup; @@ -89,6 +91,8 @@ public void setup() { Mockito.when(context.schemaQueriesFactory()).thenReturn(schemaQueriesFactory); Mockito.when(context.schemaParserFactory()).thenReturn(schemaParserFactory); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + metadataManager = new TestMetadataManager(context); } @@ -150,7 +154,7 @@ public void should_refresh_all_nodes() { @Test public void should_refresh_single_node() { // Given - Node node = new DefaultNode(ADDRESS1); + Node node = new DefaultNode(ADDRESS1, context); NodeInfo info = Mockito.mock(NodeInfo.class); Mockito.when(info.getDatacenter()).thenReturn("dc1"); Mockito.when(topologyMonitor.refreshNode(node)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index f630dac20a9..b85fa6a2fd9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -59,6 +60,7 @@ public class NodeStateManagerTest { @Mock private NettyOptions nettyOptions; @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; private DefaultNode node1, node2; private EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; @@ -82,8 +84,9 @@ public void setup() { Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); - node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042)); - node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042)); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); + node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); ImmutableMap nodes = ImmutableMap.builder() .put(node1.getConnectAddress(), node1) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 543ab74c0c0..c77cc5f3bb6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -18,11 +18,14 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.google.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -31,10 +34,18 @@ public class RemoveNodeRefreshTest { private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - private static final DefaultNode node1 = new DefaultNode(ADDRESS1); - private static final DefaultNode node2 = new DefaultNode(ADDRESS2); - @Mock private InternalDriverContext context; + @Mock protected MetricUpdaterFactory metricUpdaterFactory; + + private DefaultNode node1; + private DefaultNode node2; + + @Before + public void setup() { + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + node1 = new DefaultNode(ADDRESS1, context); + node2 = new DefaultNode(ADDRESS2, context); + } @Test public void should_remove_existing_node() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 94fa2797cd7..63d533cce7a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -51,14 +52,14 @@ public void should_initialize_when_all_channels_succeed() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture) .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); - Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); factoryHelper.verifyNoMoreCalls(); } @@ -75,13 +76,15 @@ public void should_initialize_when_all_channels_fail() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + Mockito.verify(nodeMetricUpdater, times(3)) + .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } @@ -98,11 +101,17 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(pool -> assertThat(pool.isInvalidKeyspace()).isTrue()); + assertThat(poolFuture) + .isSuccess( + pool -> { + assertThat(pool.isInvalidKeyspace()).isTrue(); + Mockito.verify(nodeMetricUpdater, times(3)) + .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + }); } @Test @@ -118,14 +127,16 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro .failure(ADDRESS, error) .build(); - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); + Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + Mockito.verify(nodeMetricUpdater, times(3)) + .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } @@ -150,7 +161,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -158,20 +169,21 @@ public void should_reconnect_when_init_incomplete() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); // A reconnection should have been scheduled Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); channel2Future.complete(channel2); factoryHelper.waitForCalls(ADDRESS, 1); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel2); + Mockito.verify(nodeMetricUpdater).incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index bdefb377f13..2df0d145df4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -44,7 +44,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -86,7 +86,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { .build(); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -96,7 +96,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { // Check that reconnection has kicked in, but do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(ADDRESS, 2); // Switch keyspace, it succeeds immediately since there is no active channel @@ -110,7 +110,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { channel2Future.complete(channel2); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); Mockito.verify(channel1).setKeyspace(newKeyspace); Mockito.verify(channel2).setKeyspace(newKeyspace); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 6ca59661dbc..1844ad70e7c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -57,7 +57,7 @@ public void should_reconnect_when_channel_closes() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -65,22 +65,22 @@ public void should_reconnect_when_channel_closes() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); // Simulate fatal error on channel2 ((ChannelPromise) channel2.closeFuture()) .setFailure(new Exception("mock channel init failure")); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCall(ADDRESS); channel3Future.complete(channel3); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel3); @@ -108,7 +108,7 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); @@ -116,21 +116,21 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); // Simulate graceful shutdown on channel2 ((ChannelPromise) channel2.closeStartedFuture()).setSuccess(); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCall(ADDRESS); channel3Future.complete(channel3); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel3); @@ -159,19 +159,19 @@ public void should_let_current_attempt_complete_when_reconnecting_now() // Initial connection CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 1); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); - inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelOpened(node)); // Kill channel1, reconnection begins and starts initializing channel2, but the initialization // is still pending (channel2Future not completed) ((ChannelPromise) channel1.closeStartedFuture()).setSuccess(); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); Mockito.verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(ADDRESS, 1); @@ -184,8 +184,8 @@ public void should_let_current_attempt_complete_when_reconnecting_now() // Complete the initialization of channel2, reconnection succeeds channel2Future.complete(channel2); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); + Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index d29d9373e74..389547e3395 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -53,7 +53,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); + ChannelPool.init(node, null, NodeDistance.REMOTE, context, "test"); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); @@ -61,12 +61,12 @@ public void should_shrink_outside_of_reconnection() throws Exception { assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); - inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(node)); pool.resize(NodeDistance.LOCAL); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); assertThat(pool.channels).containsOnly(channel3, channel4); @@ -100,19 +100,19 @@ public void should_shrink_during_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.REMOTE, context, "test"); + ChannelPool.init(node, null, NodeDistance.REMOTE, context, "test"); factoryHelper.waitForCalls(ADDRESS, 4); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); // A reconnection should have been scheduled to add the missing channels, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); pool.resize(NodeDistance.LOCAL); @@ -126,9 +126,9 @@ public void should_shrink_during_reconnection() throws Exception { waitForPendingAdminTasks(); // Pool should have shrinked back to 2. We keep the most recent channels so 1 and 2 get closed. - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel3, channel4); factoryHelper.verifyNoMoreCalls(); @@ -157,11 +157,11 @@ public void should_grow_outside_of_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -172,12 +172,12 @@ public void should_grow_outside_of_reconnection() throws Exception { // The resizing should have triggered a reconnection Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -212,11 +212,11 @@ public void should_grow_during_reconnection() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -224,7 +224,7 @@ public void should_grow_during_reconnection() throws Exception { // A reconnection should have been scheduled to add the missing channel, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); pool.resize(NodeDistance.REMOTE); @@ -234,23 +234,23 @@ public void should_grow_during_reconnection() throws Exception { channel2Future.complete(channel2); factoryHelper.waitForCall(ADDRESS); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); assertThat(pool.channels).containsOnly(channel1, channel2); // A second attempt should have been scheduled since we're now still under the target size Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); // Same reconnection is still running, no additional events - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(node)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(node)); // Two more channels get opened, bringing us to the target count factoryHelper.waitForCalls(ADDRESS, 2); channel3Future.complete(channel3); channel4Future.complete(channel4); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -279,11 +279,11 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -296,12 +296,12 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc // It should have triggered a reconnection Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -335,11 +335,11 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -347,7 +347,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti // A reconnection should have been scheduled to add the missing channel, don't complete yet Mockito.verify(reconnectionSchedule).nextDelay(); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); // Simulate a configuration change Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); @@ -358,23 +358,23 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti channel2Future.complete(channel2); factoryHelper.waitForCall(ADDRESS); waitForPendingAdminTasks(); - inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); assertThat(pool.channels).containsOnly(channel1, channel2); // A second attempt should have been scheduled since we're now still under the target size Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); // Same reconnection is still running, no additional events - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(node)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(node)); // Two more channels get opened, bringing us to the target count factoryHelper.waitForCalls(ADDRESS, 2); channel3Future.complete(channel3); channel4Future.complete(channel4); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); @@ -397,11 +397,11 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 2); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 54140ce0fd1..1f9152e6e8d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -57,11 +57,11 @@ public void should_close_all_channels_when_closed() throws Exception { InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); @@ -69,7 +69,7 @@ public void should_close_all_channels_when_closed() throws Exception { // Simulate graceful shutdown on channel3 ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(node)); // Reconnection should have kicked in and started to open channel4, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); @@ -81,7 +81,7 @@ public void should_close_all_channels_when_closed() throws Exception { // The two original channels were closed normally Mockito.verify(channel1).close(); Mockito.verify(channel2).close(); - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); // The closing channel was not closed again Mockito.verify(channel3, never()).close(); @@ -92,8 +92,8 @@ public void should_close_all_channels_when_closed() throws Exception { // It should be force-closed once we find out the pool was closed Mockito.verify(channel4).forceClose(); // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node)); // We don't wait for reconnected channels to close, so the pool only depends on channel 1 to 3 ((ChannelPromise) channel1.closeFuture()).setSuccess(); @@ -128,19 +128,19 @@ public void should_force_close_all_channels_when_force_closed() throws Exception InOrder inOrder = Mockito.inOrder(eventBus); CompletionStage poolFuture = - ChannelPool.init(NODE, null, NodeDistance.LOCAL, context, "test"); + ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(ADDRESS, 3); waitForPendingAdminTasks(); assertThat(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); - inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(NODE)); + inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); // Simulate graceful shutdown on channel3 ((ChannelPromise) channel3.closeStartedFuture()).setSuccess(); waitForPendingAdminTasks(); - inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(node)); // Reconnection should have kicked in and started to open a channel, do not complete it yet Mockito.verify(reconnectionSchedule).nextDelay(); @@ -154,7 +154,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception Mockito.verify(channel2).forceClose(); Mockito.verify(channel3).forceClose(); // Only two events because the one for channel3 was sent earlier - inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); // Complete the reconnecting channel channel4Future.complete(channel4); @@ -163,8 +163,8 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // It should be force-closed once we find out the pool was closed Mockito.verify(channel4).forceClose(); // No events because the channel was never really associated to the pool - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(NODE)); - inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(NODE)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node)); // We don't wait for reconnected channels to close, so the pool only depends on channel 1-3 ((ChannelPromise) channel1.closeFuture()).setSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 9adda1f0ca5..4c01df2d26b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -22,13 +22,13 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.google.common.util.concurrent.Uninterruptibles; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultEventLoopGroup; @@ -48,16 +48,17 @@ abstract class ChannelPoolTestBase { static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); - static final Node NODE = new DefaultNode(ADDRESS); - @Mock InternalDriverContext context; + @Mock protected InternalDriverContext context; @Mock private DriverConfig config; - @Mock DriverConfigProfile defaultProfile; + @Mock protected DriverConfigProfile defaultProfile; @Mock private ReconnectionPolicy reconnectionPolicy; - @Mock ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; + @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; @Mock private NettyOptions nettyOptions; - @Mock ChannelFactory channelFactory; - EventBus eventBus; + @Mock protected ChannelFactory channelFactory; + @Mock protected DefaultNode node; + @Mock protected NodeMetricUpdater nodeMetricUpdater; + protected EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; @Before @@ -79,6 +80,9 @@ public void setup() { // By default, set a large reconnection delay. Tests that care about reconnection will override // it. Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + + Mockito.when(node.getConnectAddress()).thenReturn(ADDRESS); + Mockito.when(node.getMetricUpdater()).thenReturn(nodeMetricUpdater); } @After diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index f7800a1cd41..447843a16ab 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -46,6 +46,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.pool.ChannelPoolFactory; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -88,6 +89,7 @@ public class DefaultSessionPoolsTest { @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; @Mock private AddressTranslator addressTranslator; @Mock private ControlConnection controlConnection; + @Mock private MetricUpdaterFactory metricUpdaterFactory; private DefaultNode node1; private DefaultNode node2; @@ -133,6 +135,8 @@ public void setup() { Mockito.when(context.configLoader()).thenReturn(configLoader); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + // Runtime behavior: Mockito.when(context.sessionName()).thenReturn("test"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index c4906312cfe..48bac3f9dcc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -57,10 +57,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; @Category(ParallelizableTests.class) +@RunWith(MockitoJUnitRunner.class) public class NodeStateIT { public @Rule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); @@ -79,6 +84,8 @@ public class NodeStateIT { .withNodeStateListeners(nodeStateListener) .build(); + private @Captor ArgumentCaptor nodeCaptor; + private InternalDriverContext driverContext; private final BlockingQueue stateEvents = new LinkedBlockingDeque<>(); @@ -475,8 +482,8 @@ public void should_remove_invalid_contact_point() { // The order of the calls is not deterministic because contact points are shuffled, but it // does not matter here since Mockito.verify does not enforce order. - Mockito.verify(localNodeStateListener, timeout(500)) - .onRemove(new DefaultNode(wrongContactPoint)); + Mockito.verify(localNodeStateListener, timeout(500)).onRemove(nodeCaptor.capture()); + assertThat(nodeCaptor.getValue().getConnectAddress()).isEqualTo(wrongContactPoint); Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java new file mode 100644 index 00000000000..4b3a319de8f --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java @@ -0,0 +1,76 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.codahale.metrics.Timer; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import java.util.Map; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class MetricsIT { + + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + + @Test + public void should_expose_metrics() { + try (CqlSession session = + SessionUtils.newSession(ccmRule, "metrics.session.enabled = [ cql-requests ]")) { + for (int i = 0; i < 10; i++) { + session.execute("SELECT release_version FROM system.local"); + } + + // Metric names are prefixed with the session id, which depends on the number of other tests + // run before, so do a linear search to find the metric we're interested in. + Timer requestsTimer = null; + for (Map.Entry entry : session.getMetricRegistry().getTimers().entrySet()) { + if (entry.getKey().endsWith(CoreSessionMetric.CQL_REQUESTS.getPath())) { + requestsTimer = entry.getValue(); + } + } + assertThat(requestsTimer).isNotNull(); + + // No need to be very sophisticated, metrics are already covered individually in unit tests. + assertThat(requestsTimer.getCount()).isEqualTo(10); + } + } + + @Test + public void should_not_expose_metrics_if_disabled() { + try (CqlSession session = + // cql_requests is disabled: + SessionUtils.newSession(ccmRule, "metrics.session.enabled = []")) { + for (int i = 0; i < 10; i++) { + session.execute("SELECT release_version FROM system.local"); + } + + Timer requestsTimer = null; + for (Map.Entry entry : session.getMetricRegistry().getTimers().entrySet()) { + if (entry.getKey().endsWith(CoreSessionMetric.CQL_REQUESTS.name())) { + requestsTimer = entry.getValue(); + } + } + assertThat(requestsTimer).isNull(); + } + } +} diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf index 5519bc99175..7981ecc4947 100644 --- a/integration-tests/src/test/resources/application.conf +++ b/integration-tests/src/test/resources/application.conf @@ -15,4 +15,9 @@ datastax-java-driver { # CcmBridge). local-datacenter = dc1 } + metrics { + // Raise histogram bounds because the tests execute DDL queries with a higher timeout + session.cql_requests.highest_latency = 30 seconds + node.cql_messages.highest_latency = 30 seconds + } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index a90845edf44..1615a9676fc 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,16 @@ jnr-posix 3.0.27 + + io.dropwizard.metrics + metrics-core + 4.0.2 + + + org.hdrhistogram + HdrHistogram + 2.1.10 + junit junit From 6d248ffb55b1efeb22a2462fcd5e856b972972a1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 13:45:57 -0800 Subject: [PATCH 327/742] Bump native-protocol to 1.4.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1615a9676fc..18e8cf3fbb0 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ com.datastax.oss native-protocol - 1.4.2-SNAPSHOT + 1.4.2 io.netty From 82d0d280078cb10ec043c544c9cd7e3d8dea6b02 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 14:08:07 -0800 Subject: [PATCH 328/742] Fix test failing in CI --- .../concurrent/ScheduledTaskCapturingEventLoop.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index a58fa6e0750..2953b487a9d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -26,6 +26,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; @@ -152,7 +153,16 @@ public void run() { } public boolean isCancelled() { - return futureTask.isCancelled(); + // futureTask.isCancelled() can create timing issues in CI environments, so give the + // cancellation a short time to complete instead: + try { + futureTask.get(500, TimeUnit.MILLISECONDS); + } catch (CancellationException e) { + return true; + } catch (Exception e) { + // ignore + } + return false; } public long getInitialDelay(TimeUnit targetUnit) { From fec06894231e49846cc22f53f1c3ea8cbbbbbef3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 14:16:00 -0800 Subject: [PATCH 329/742] Increase timeout for failing test --- .../core/util/concurrent/ScheduledTaskCapturingEventLoop.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index 2953b487a9d..927b5604f31 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -156,7 +156,7 @@ public boolean isCancelled() { // futureTask.isCancelled() can create timing issues in CI environments, so give the // cancellation a short time to complete instead: try { - futureTask.get(500, TimeUnit.MILLISECONDS); + futureTask.get(3, TimeUnit.SECONDS); } catch (CancellationException e) { return true; } catch (Exception e) { From 81dc3b00bd33213411143b6d11c8b930feed4a53 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 11 Feb 2018 14:59:45 -0800 Subject: [PATCH 330/742] Fix javadoc errors --- .../oss/driver/api/core/CqlIdentifier.java | 2 +- .../oss/driver/api/core/DriverException.java | 3 +-- .../driver/api/core/DriverExecutionException.java | 6 ++++-- .../oss/driver/api/core/data/SettableById.java | 2 +- .../oss/driver/api/core/data/SettableByIndex.java | 2 +- .../oss/driver/api/core/data/SettableByName.java | 2 +- .../api/core/metadata/schema/IndexMetadata.java | 2 +- .../api/core/metadata/token/TokenRange.java | 2 +- .../core/servererrors/OverloadedException.java | 8 ++++---- .../core/servererrors/ReadFailureException.java | 8 ++++---- .../driver/api/core/servererrors/ServerError.java | 8 ++++---- .../api/core/servererrors/TruncateException.java | 8 ++++---- .../core/servererrors/WriteFailureException.java | 8 ++++---- .../internal/core/metadata/TopologyEvent.java | 2 +- .../schema/parsing/DataTypeCqlNameParser.java | 3 ++- .../metadata/schema/parsing/DataTypeParser.java | 4 ++-- .../oss/driver/internal/core/util/ArrayUtils.java | 4 ++-- pom.xml | 15 ++++++++++++--- .../api/testinfra/CassandraRequirement.java | 2 +- .../oss/driver/api/testinfra/DseRequirement.java | 2 +- .../api/testinfra/session/SessionUtils.java | 5 ++--- 21 files changed, 54 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 5249fb6ac44..be0adb65e11 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -38,7 +38,7 @@ * * Examples: * - *

        • CQL3 data type Getter name Java type
          + *
          * * * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java index 290327b4965..90a340e25ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java @@ -18,8 +18,7 @@ /** * Base class for all exceptions thrown by the driver. * - *

          Note that, for obvious programming errors (for example, calling {@link Cluster#connect()} on a - * cluster instance that was previously closed), the driver might throw JDK runtime exceptions, such + *

          Note that, for obvious programming errors, the driver might throw JDK runtime exceptions, such * as {@link IllegalArgumentException} or {@link IllegalStateException}. In all other cases, it will * be an instance of this class. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java index d9dc4064b6e..daa4e9ac4a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java @@ -15,9 +15,11 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.Statement; + /** - * Thrown by synchronous wrapper methods (such as {@link Cluster#connect()}, when the underlying - * future was completed with a checked exception. + * Thrown by synchronous wrapper methods (such as {@link CqlSession#execute(Statement)}, when the + * underlying future was completed with a checked exception. * *

          This exception should be rarely thrown (if ever). Most of the time, the driver uses unchecked * exceptions, which will be rethrown directly instead of being wrapped in this class. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index e612d8f2f51..fb64d130a40 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -100,7 +100,7 @@ default T set(CqlIdentifier id, V v, TypeCodec codec) { *

          The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. * *

          This variant is for generic Java types. If the target type is not generic, use {@link - * #set(int, V, Class)} instead, which may perform slightly better. + * #set(int, Object, Class)} instead, which may perform slightly better. * *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java index 98b33ed03bc..8d64bafecd9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -91,7 +91,7 @@ default T set(int i, V v, TypeCodec codec) { *

          The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. * *

          This variant is for generic Java types. If the target type is not generic, use {@link - * #set(int, V, Class)} instead, which may perform slightly better. + * #set(int, Object, Class)} instead, which may perform slightly better. * * @throws IndexOutOfBoundsException if the index is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index 62aca6f437f..d745a5c038f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -99,7 +99,7 @@ default T set(String name, V v, TypeCodec codec) { *

          The {@link #codecRegistry()} will be used to look up a codec to handle the conversion. * *

          This variant is for generic Java types. If the target type is not generic, use {@link - * #set(int, V, Class)} instead, which may perform slightly better. + * #set(int, Object, Class)} instead, which may perform slightly better. * *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java index fcc2a54ed2a..22284a6d38f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/IndexMetadata.java @@ -44,7 +44,7 @@ default String getClassName() { * The options of the index. * *

          This directly reflects the corresponding column of the system table ( {@code - * system.schema_columns.index_options} in Cassandra <= 2.2, or {@code + * system.schema_columns.index_options} in Cassandra <= 2.2, or {@code * system_schema.indexes.options} in later versions). * *

          Note that some of these options might also be exposed as standalone fields in this diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java index 7ca2aea8eab..4ee4bec34c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/TokenRange.java @@ -42,7 +42,7 @@ public interface TokenRange extends Comparable { *

          Splitting an empty range is not permitted. But note that, in edge cases, splitting a range * might produce one or more empty ranges. * - * @throws IllegalArgumentException if the range is empty or if numberOfSplits < 1. + * @throws IllegalArgumentException if the range is empty or if {@code numberOfSplits < 1}. */ List splitEvenly(int numberOfSplits); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java index ad24e6d8d85..58872342639 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java @@ -24,10 +24,10 @@ /** * Thrown when the coordinator reported itself as being overloaded. * - *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, - * which will decide if it is rethrown directly to the client or if the request should be retried. - * If all other tried nodes also fail, this exception will appear in the {@link - * AllNodesFailedException} thrown to the client. + *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, + * CoordinatorException, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. */ public class OverloadedException extends QueryExecutionException { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java index 0c4f99ccced..09cf3f8f962 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java @@ -30,10 +30,10 @@ *

          This happens when some of the replicas that were contacted by the coordinator replied with an * error. * - *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, - * which will decide if it is rethrown directly to the client or if the request should be retried. - * If all other tried nodes also fail, this exception will appear in the {@link - * AllNodesFailedException} thrown to the client. + *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, + * CoordinatorException, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. */ public class ReadFailureException extends QueryConsistencyException { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java index 5732a8e1083..990f0ffb164 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java @@ -26,10 +26,10 @@ * *

          This should be considered as a server bug and reported as such. * - *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, - * which will decide if it is rethrown directly to the client or if the request should be retried. - * If all other tried nodes also fail, this exception will appear in the {@link - * AllNodesFailedException} thrown to the client. + *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, + * CoordinatorException, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. */ public class ServerError extends CoordinatorException { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java index 86a22d4eb2a..8d7ab0009a0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java @@ -24,10 +24,10 @@ /** * An error during a truncation operation. * - *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, - * which will decide if it is rethrown directly to the client or if the request should be retried. - * If all other tried nodes also fail, this exception will appear in the {@link - * AllNodesFailedException} thrown to the client. + *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, + * CoordinatorException, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. */ public class TruncateException extends QueryExecutionException { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java index b364b12fe8a..6db5d483b2c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java @@ -30,10 +30,10 @@ *

          This happens when some of the replicas that were contacted by the coordinator replied with an * error. * - *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, Throwable, int)}, - * which will decide if it is rethrown directly to the client or if the request should be retried. - * If all other tried nodes also fail, this exception will appear in the {@link - * AllNodesFailedException} thrown to the client. + *

          This exception is processed by {@link RetryPolicy#onErrorResponse(Request, + * CoordinatorException, int)}, which will decide if it is rethrown directly to the client or if the + * request should be retried. If all other tried nodes also fail, this exception will appear in the + * {@link AllNodesFailedException} thrown to the client. */ public class WriteFailureException extends QueryConsistencyException { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java index 111e6d058d9..0100d3551bb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java @@ -86,7 +86,7 @@ public static TopologyEvent suggestDown(InetSocketAddress address) { * * * In all cases, the driver will never try to reconnect to the node again. If you decide to - * reconnect to it later, use {@link #forceUp(InetSocketAddress)}. + * reconnect to it later, use {@link #forceUp(InetSocketAddress)}. * *

          This is intended for deployments that use a custom {@link TopologyMonitor} (for example if * you do some kind of maintenance on a live node). This is also used internally by the driver diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java index 4baf6b5fc0e..f94183d206d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java @@ -33,7 +33,8 @@ /** * Parses data types from schema tables, for Cassandra 3.0 and above. * - *

          In these versions, data types appear as string literals, like "ascii" or "tuple". + *

          In these versions, data types appear as string literals, like "ascii" or + * "tuple<int,int>". */ public class DataTypeCqlNameParser implements DataTypeParser { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java index d9c5317c96b..0d22b7d9f5c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java @@ -33,8 +33,8 @@ interface DataTypeParser { * resolve subtypes if the type to parse is complex (such as {@code list}). The only * situation where we don't have them is when we refresh all the UDTs of a keyspace; in that * case, the filed will be {@code null} and any UDT encountered by this method will always be - * re-created from scratch: for Cassandra < 2.2, this means parsing the whole definition; for - * > 3.0, this means materializing it as a {@link ShallowUserDefinedType} that will be + * re-created from scratch: for Cassandra < 2.2, this means parsing the whole definition; + * for > 3.0, this means materializing it as a {@link ShallowUserDefinedType} that will be * resolved in a second pass. */ DataType parse( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 4b28091f424..2dc225bb195 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -29,7 +29,7 @@ public static void swap(T[] elements, int i, int j) { /** * Moves an element towards the beginning of the array, shifting all the intermediary elements to - * the right (no-op if targetIndex >= sourceIndex). + * the right (no-op if {@code targetIndex >= sourceIndex}). */ public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i > targetIndex; i--) { @@ -39,7 +39,7 @@ public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) /** * Moves an element towards the end of the array, shifting all the intermediary elements to the - * left (no-op if targetIndex <= sourceIndex). + * left (no-op if {@code targetIndex <= sourceIndex}). */ public static void bubbleDown(T[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i < targetIndex; i++) { diff --git a/pom.xml b/pom.xml index 18e8cf3fbb0..1658db5864b 100644 --- a/pom.xml +++ b/pom.xml @@ -358,15 +358,24 @@ limitations under the License.]]> maven-javadoc-plugin + + + + + leaks + X + + + attach-javadocs jar - - -Xdoclint:none - check-api-leaks diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java index 2bc80e42d0f..e28757e420f 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraRequirement.java @@ -30,7 +30,7 @@ /** * @return the maximum exclusive version allowed to execute this test, i.e. "2.2" means only tests - * < "2.2" may execute this test. + * < "2.2" may execute this test. */ String max() default ""; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java index 7fbffcc8784..c80c6914282 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/DseRequirement.java @@ -30,7 +30,7 @@ /** * @return the maximum exclusive version allowed to execute this test, i.e. "2.2" means only tests - * < "2.2" may execute this test. + * < "2.2" may execute this test. */ String max() default ""; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 6ef54d07708..5249d780638 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -49,9 +49,8 @@ * } * } * - * The instances returned by {@link #newSession(CassandraResourceRule, NodeStateListener[], - * String...)} are not managed automatically, you need to close them yourself (this is done with a - * try-with-resources block in the example above). + * The instances returned by {@code newSession()} methods are not managed automatically, you need to + * close them yourself (this is done with a try-with-resources block in the example above). * *

          If you can share the same {@code Session} instance between all test methods, {@link * SessionRule} provides a simpler alternative. From e0c6b2fee1e8a1238dcf0fb9bb0ab199b295c6ea Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 11:45:35 -0800 Subject: [PATCH 331/742] Rename CoreProtocolVersion to DefaultProtocolVersion --- ...rsion.java => DefaultProtocolVersion.java} | 4 +- .../oss/driver/api/core/ProtocolVersion.java | 4 +- .../driver/api/core/cql/BatchStatement.java | 4 +- .../driver/api/core/cql/ExecutionInfo.java | 6 +-- .../api/core/cql/PreparedStatement.java | 8 ++-- .../driver/api/core/cql/SimpleStatement.java | 4 +- .../oss/driver/api/core/data/Data.java | 6 +-- .../oss/driver/api/core/session/Request.java | 4 +- .../CassandraProtocolVersionRegistry.java | 18 ++++---- .../core/channel/ProtocolInitHandler.java | 4 +- .../parsing/DataTypeClassNameParser.java | 4 +- .../oss/driver/api/core/uuid/UuidsTest.java | 8 ++-- ...tocolVersionRegistryHighestCommonTest.java | 18 ++++---- .../ChannelFactoryAvailableIdsTest.java | 4 +- .../ChannelFactoryClusterNameTest.java | 6 +-- ...ChannelFactoryProtocolNegotiationTest.java | 44 +++++++++---------- .../core/channel/DriverChannelTest.java | 6 +-- .../core/channel/InFlightHandlerTest.java | 6 +-- .../core/channel/ProtocolInitHandlerTest.java | 30 ++++++------- .../core/cql/CqlPrepareHandlerTest.java | 4 +- .../core/cql/CqlRequestHandlerTestBase.java | 4 +- .../core/cql/DefaultAsyncResultSetTest.java | 8 ++-- .../metadata/token/DefaultTokenMapTest.java | 11 ++--- .../core/session/ReprepareOnUpTest.java | 10 ++--- .../ProtocolVersionInitialNegotiationIT.java | 3 +- .../core/ProtocolVersionMixedClusterIT.java | 6 +-- .../driver/api/testinfra/ccm/BaseCcmRule.java | 6 +-- .../testinfra/simulacron/SimulacronRule.java | 4 +- 28 files changed, 123 insertions(+), 121 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/{CoreProtocolVersion.java => DefaultProtocolVersion.java} (92%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultProtocolVersion.java similarity index 92% rename from core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java rename to core/src/main/java/com/datastax/oss/driver/api/core/DefaultProtocolVersion.java index ae56e403d64..feda0c2afc8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CoreProtocolVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultProtocolVersion.java @@ -22,7 +22,7 @@ * *

          Legacy versions 1 (Cassandra 1.2) and 2 (Cassandra 2.0) are not supported anymore. */ -public enum CoreProtocolVersion implements ProtocolVersion { +public enum DefaultProtocolVersion implements ProtocolVersion { /** Version 3, supported by Cassandra 2.1 and above. */ V3(ProtocolConstants.Version.V3, false), @@ -42,7 +42,7 @@ public enum CoreProtocolVersion implements ProtocolVersion { private final int code; private final boolean beta; - CoreProtocolVersion(int code, boolean beta) { + DefaultProtocolVersion(int code, boolean beta) { this.code = code; this.beta = beta; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java index 1b3c31e8756..1168d3ad487 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java @@ -22,13 +22,13 @@ * *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all - * {@code ProtocolVersion}s are {@link CoreProtocolVersion} instances. + * {@code ProtocolVersion}s are {@link DefaultProtocolVersion} instances. */ public interface ProtocolVersion { /** The default version used for {@link Detachable detached} objects. */ // Implementation note: we can't use the ProtocolVersionRegistry here, this has to be a // compile-time constant. - ProtocolVersion DEFAULT = CoreProtocolVersion.V4; + ProtocolVersion DEFAULT = DefaultProtocolVersion.V4; /** * A numeric code that uniquely identifies the version (this is the code used in network frames). diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 971d7eb992e..98e29b80867 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.cql.DefaultBatchStatement; import com.google.common.collect.ImmutableList; @@ -103,7 +103,7 @@ static BatchStatementBuilder builder(BatchStatement template) { * simple statement in the batch that has a keyspace set (or will be null if no such statement * exists). * - *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + *

          This feature is only available with {@link DefaultProtocolVersion#V5 native protocol v5} or * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime * error. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 7e94320fb37..86fa493aceb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; @@ -82,7 +82,7 @@ public interface ExecutionInfo { /** * The server-side warnings for this query, if any (otherwise the list will be empty). * - *

          This feature is only available with {@link CoreProtocolVersion#V4} or above; with lower + *

          This feature is only available with {@link DefaultProtocolVersion#V4} or above; with lower * versions, this list will always be empty. */ List getWarnings(); @@ -95,7 +95,7 @@ public interface ExecutionInfo { * mutable. If multiple clients will read these values, care should be taken not to corrupt the * data (in particular, preserve the indices by calling {@link ByteBuffer#duplicate()}). * - *

          This feature is only available with {@link CoreProtocolVersion#V4} or above; with lower + *

          This feature is only available with {@link DefaultProtocolVersion#V4} or above; with lower * versions, this map will always be empty. */ Map getIncomingPayload(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index c5037404210..dbc2ce30023 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import java.nio.ByteBuffer; import java.util.List; @@ -74,11 +74,11 @@ public interface PreparedStatement { * A unique identifier for result metadata (essentially a hash of {@link * #getResultSetDefinitions()}). * - *

          This information is mostly for internal use: with protocol {@link CoreProtocolVersion#V5} or - * higher, the driver sends it with every execution of the prepared statement, to validate that + *

          This information is mostly for internal use: with protocol {@link DefaultProtocolVersion#V5} + * or higher, the driver sends it with every execution of the prepared statement, to validate that * its result metadata is still up-to-date. * - *

          Note: this method returns null for protocol {@link CoreProtocolVersion#V4} or lower; + *

          Note: this method returns null for protocol {@link DefaultProtocolVersion#V4} or lower; * otherwise, the returned buffer is read-only. * * @see CASSANDRA-10786 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 5fe4ee8b44c..9fad83a9cd3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; import java.util.Arrays; @@ -155,7 +155,7 @@ static SimpleStatementBuilder builder(SimpleStatement template) { /** * Sets the CQL keyspace to associate with the query. * - *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + *

          This feature is only available with {@link DefaultProtocolVersion#V5 native protocol v5} or * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime * error. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java index 8792d9b41c0..d9ba3ad0461 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/Data.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.data; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.detach.Detachable; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -38,8 +38,8 @@ public interface Data { * Returns the protocol version that is currently used to convert values for this instance. * *

          If you obtained this object from the driver, this will be set automatically. If you created - * it manually, or just deserialized it, it is set to {@link CoreProtocolVersion#DEFAULT}. You can - * reattach this object to an existing driver instance to use its protocol version. + * it manually, or just deserialized it, it is set to {@link DefaultProtocolVersion#DEFAULT}. You + * can reattach this object to an existing driver instance to use its protocol version. * * @see Detachable */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index fc8e07d194f..8488df1f6b8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.session; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -60,7 +60,7 @@ public interface Request { * specify the keyspace without forcing it globally on the session, nor hard-coding it in the * query string. * - *

          This feature is only available with {@link CoreProtocolVersion#V5 native protocol v5} or + *

          This feature is only available with {@link DefaultProtocolVersion#V5 native protocol v5} or * higher. Specifying a per-request keyspace with lower protocol versions will cause a runtime * error. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 1e0180530c5..5c03084bfad 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.metadata.Node; @@ -40,19 +40,19 @@ *

          This can be overridden with a custom implementation by subclassing {@link * DefaultDriverContext}. * - * @see CoreProtocolVersion + * @see DefaultProtocolVersion */ public class CassandraProtocolVersionRegistry implements ProtocolVersionRegistry { private static final Logger LOG = LoggerFactory.getLogger(CassandraProtocolVersionRegistry.class); private static final ImmutableList values = - ImmutableList.builder().add(CoreProtocolVersion.values()).build(); + ImmutableList.builder().add(DefaultProtocolVersion.values()).build(); private final String logPrefix; private final NavigableMap versionsByCode; public CassandraProtocolVersionRegistry(String logPrefix) { - this(logPrefix, CoreProtocolVersion.values()); + this(logPrefix, DefaultProtocolVersion.values()); } protected CassandraProtocolVersionRegistry(String logPrefix, ProtocolVersion[]... versionRanges) { @@ -109,9 +109,9 @@ public ProtocolVersion highestCommon(Collection nodes) { throw new IllegalArgumentException("Expected at least one node"); } - SortedSet candidates = new TreeSet<>(); + SortedSet candidates = new TreeSet<>(); - for (CoreProtocolVersion version : CoreProtocolVersion.values()) { + for (DefaultProtocolVersion version : DefaultProtocolVersion.values()) { // Beta versions always need to be forced, and we only call this method if the version // wasn't forced if (!version.isBeta()) { @@ -139,7 +139,7 @@ public ProtocolVersion highestCommon(Collection nodes) { "Node %s reports Cassandra version %s, " + "but the driver only supports 2.1.0 and above", node.getConnectAddress(), cassandraVersion), - ImmutableList.of(CoreProtocolVersion.V3, CoreProtocolVersion.V4)); + ImmutableList.of(DefaultProtocolVersion.V3, DefaultProtocolVersion.V4)); } LOG.debug( @@ -148,7 +148,7 @@ public ProtocolVersion highestCommon(Collection nodes) { node.getConnectAddress(), cassandraVersion); if (cassandraVersion.compareTo(CassandraVersion.V2_2_0) < 0 - && candidates.remove(CoreProtocolVersion.V4)) { + && candidates.remove(DefaultProtocolVersion.V4)) { LOG.debug("[{}] Excluding protocol V4", logPrefix); } } @@ -161,7 +161,7 @@ public ProtocolVersion highestCommon(Collection nodes) { "Could not determine a common protocol version, " + "enable DEBUG logs for '%s' for more details", LOG.getName()), - ImmutableList.of(CoreProtocolVersion.V3, CoreProtocolVersion.V4)); + ImmutableList.of(DefaultProtocolVersion.V3, DefaultProtocolVersion.V4)); } else { return candidates.last(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 48788aaaead..ce4905f1b5c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -324,6 +324,6 @@ public String toString() { } private String getString(List row, int i) { - return TypeCodecs.TEXT.decode(row.get(i), CoreProtocolVersion.DEFAULT); + return TypeCodecs.TEXT.decode(row.get(i), DefaultProtocolVersion.DEFAULT); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java index adcb36daa7c..5985e962630 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; @@ -261,7 +261,7 @@ private Map getNameAndTypeParameters() { try { name = TypeCodecs.TEXT.decode( - Bytes.fromHexString("0x" + bbHex), CoreProtocolVersion.DEFAULT); + Bytes.fromHexString("0x" + bbHex), DefaultProtocolVersion.DEFAULT); } catch (NumberFormatException e) { throwSyntaxError(e.getMessage()); } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java index 7540584d322..da51e00f366 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/uuid/UuidsTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import java.nio.ByteBuffer; import java.util.HashSet; @@ -111,9 +111,9 @@ public void should_generate_within_bounds_for_given_timestamp() { // Compares using Cassandra's sorting algorithm (not the same as compareTo). private static void assertBetween(UUID uuid, UUID lowerBound, UUID upperBound) { - ByteBuffer uuidBytes = TypeCodecs.UUID.encode(uuid, CoreProtocolVersion.V3); - ByteBuffer lb = TypeCodecs.UUID.encode(lowerBound, CoreProtocolVersion.V3); - ByteBuffer ub = TypeCodecs.UUID.encode(upperBound, CoreProtocolVersion.V3); + ByteBuffer uuidBytes = TypeCodecs.UUID.encode(uuid, DefaultProtocolVersion.V3); + ByteBuffer lb = TypeCodecs.UUID.encode(lowerBound, DefaultProtocolVersion.V3); + ByteBuffer ub = TypeCodecs.UUID.encode(upperBound, DefaultProtocolVersion.V3); assertThat(compareTimestampBytes(lb, uuidBytes)).isLessThanOrEqualTo(0); assertThat(compareTimestampBytes(ub, uuidBytes)).isGreaterThanOrEqualTo(0); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java index b9b0fde7631..27989646760 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.metadata.Node; import com.google.common.collect.ImmutableList; @@ -29,7 +29,7 @@ /** * Covers {@link CassandraProtocolVersionRegistry#highestCommon(Collection)} separately, because it - * relies explicitly on {@link CoreProtocolVersion} as the version implementation. + * relies explicitly on {@link DefaultProtocolVersion} as the version implementation. */ public class CassandraProtocolVersionRegistryHighestCommonTest { @@ -40,7 +40,7 @@ public void should_pick_v3_when_at_least_one_node_is_2_1() { assertThat( registry.highestCommon( ImmutableList.of(mockNode("2.2.1"), mockNode("2.1.0"), mockNode("3.1.9")))) - .isEqualTo(CoreProtocolVersion.V3); + .isEqualTo(DefaultProtocolVersion.V3); } @Test @@ -48,7 +48,7 @@ public void should_pick_v4_when_all_nodes_are_2_2_or_more() { assertThat( registry.highestCommon( ImmutableList.of(mockNode("2.2.0"), mockNode("2.2.1"), mockNode("3.1.9")))) - .isEqualTo(CoreProtocolVersion.V4); + .isEqualTo(DefaultProtocolVersion.V4); } @Test @@ -56,11 +56,11 @@ public void should_treat_rcs_as_next_stable_versions() { assertThat( registry.highestCommon( ImmutableList.of(mockNode("2.2.1"), mockNode("2.1.0-rc1"), mockNode("3.1.9")))) - .isEqualTo(CoreProtocolVersion.V3); + .isEqualTo(DefaultProtocolVersion.V3); assertThat( registry.highestCommon( ImmutableList.of(mockNode("2.2.0-rc2"), mockNode("2.2.1"), mockNode("3.1.9")))) - .isEqualTo(CoreProtocolVersion.V4); + .isEqualTo(DefaultProtocolVersion.V4); } @Test @@ -68,13 +68,13 @@ public void should_skip_nodes_that_report_null_version() { assertThat( registry.highestCommon( ImmutableList.of(mockNode(null), mockNode("2.1.0"), mockNode("3.1.9")))) - .isEqualTo(CoreProtocolVersion.V3); + .isEqualTo(DefaultProtocolVersion.V3); // Edge case: if all do, go with the latest version assertThat( registry.highestCommon( ImmutableList.of(mockNode(null), mockNode(null), mockNode(null)))) - .isEqualTo(CoreProtocolVersion.V4); + .isEqualTo(DefaultProtocolVersion.V4); } @Test @@ -83,7 +83,7 @@ public void should_use_v4_for_future_cassandra_versions() { assertThat( registry.highestCommon( ImmutableList.of(mockNode("3.0.0"), mockNode("12.1.5"), mockNode("98.7.22")))) - .isEqualTo(CoreProtocolVersion.V4); + .isEqualTo(DefaultProtocolVersion.V4); } @Test(expected = IllegalArgumentException.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 937039d99ba..643e68ead3b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -19,7 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.timeout; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; @@ -43,7 +43,7 @@ public void setup() throws InterruptedException { .thenReturn(true); Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(128); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index da6a1f5c605..6fc51260760 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.protocol.internal.response.Ready; @@ -32,7 +32,7 @@ public void should_set_cluster_name_from_first_connection() { // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -52,7 +52,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index 1665b065174..4198b54921b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.internal.core.TestResponses; @@ -41,7 +41,7 @@ public void should_succeed_if_version_specified_and_supported_by_server() { .thenReturn(true); Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -53,7 +53,7 @@ public void should_succeed_if_version_specified_and_supported_by_server() { // Then assertThat(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); - assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); + assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V4); } @Test @@ -64,7 +64,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err .thenReturn(true); Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -72,7 +72,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V4.getCode()); // Server does not support v4 writeInboundFrame( requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); @@ -85,7 +85,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err .isInstanceOf(UnsupportedProtocolVersionException.class) .hasMessageContaining("Host does not support protocol version V4"); assertThat(((UnsupportedProtocolVersionException) e).getAttemptedVersions()) - .containsExactly(CoreProtocolVersion.V4); + .containsExactly(DefaultProtocolVersion.V4); }); } @@ -94,7 +94,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -102,7 +102,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V4.getCode()); writeInboundFrame(requestFrame, new Ready()); requestFrame = readOutboundFrame(); @@ -111,7 +111,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s // Then assertThat(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); - assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V4); + assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V4); } @Test @@ -120,9 +120,9 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); - Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V4)) - .thenReturn(Optional.of(CoreProtocolVersion.V3)); + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) + .thenReturn(Optional.of(DefaultProtocolVersion.V3)); ChannelFactory factory = newChannelFactory(); // When @@ -130,7 +130,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V4.getCode()); // Server does not support v4 writeInboundFrame( requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); @@ -138,14 +138,14 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy // Then // Factory should initialize a new connection, that retries with the lower version requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V3.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V3.getCode()); writeInboundFrame(requestFrame, new Ready()); requestFrame = readOutboundFrame(); writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); assertThat(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); - assertThat(factory.protocolVersion).isEqualTo(CoreProtocolVersion.V3); + assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V3); } @Test @@ -154,10 +154,10 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) // Given Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) .thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(CoreProtocolVersion.V4); - Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V4)) - .thenReturn(Optional.of(CoreProtocolVersion.V3)); - Mockito.when(protocolVersionRegistry.downgrade(CoreProtocolVersion.V3)) + Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) + .thenReturn(Optional.of(DefaultProtocolVersion.V3)); + Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V3)) .thenReturn(Optional.empty()); ChannelFactory factory = newChannelFactory(); @@ -166,14 +166,14 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) factory.connect(SERVER_ADDRESS, DriverChannelOptions.DEFAULT); Frame requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V4.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V4.getCode()); // Server does not support v4 writeInboundFrame( requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); // Client retries with v3 requestFrame = readOutboundFrame(); - assertThat(requestFrame.protocolVersion).isEqualTo(CoreProtocolVersion.V3.getCode()); + assertThat(requestFrame.protocolVersion).isEqualTo(DefaultProtocolVersion.V3.getCode()); // Server does not support v3 writeInboundFrame( requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); @@ -188,7 +188,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) "Protocol negotiation failed: could not find a common version " + "(attempted: [V4, V3])"); assertThat(((UnsupportedProtocolVersionException) e).getAttemptedVersions()) - .containsExactly(CoreProtocolVersion.V4, CoreProtocolVersion.V3); + .containsExactly(DefaultProtocolVersion.V4, DefaultProtocolVersion.V3); }); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index fea55b2ed24..b581ad4efc2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; @@ -52,7 +52,7 @@ public void setup() { .pipeline() .addLast( new InFlightHandler( - CoreProtocolVersion.V3, + DefaultProtocolVersion.V3, streamIds, Integer.MAX_VALUE, SET_KEYSPACE_TIMEOUT_MILLIS, @@ -60,7 +60,7 @@ public void setup() { null, "test")); writeCoalescer = new MockWriteCoalescer(); - driverChannel = new DriverChannel(channel, writeCoalescer, CoreProtocolVersion.V3); + driverChannel = new DriverChannel(channel, writeCoalescer, DefaultProtocolVersion.V3); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 2ed3fe08fe8..9cb8712c45e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -18,8 +18,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; @@ -485,7 +485,7 @@ public void should_notify_callback_of_events() { ProtocolConstants.StatusChangeType.UP, new InetSocketAddress("127.0.0.1", 9042)); Frame eventFrame = Frame.forResponse( - CoreProtocolVersion.V3.getCode(), + DefaultProtocolVersion.V3.getCode(), -1, null, Collections.emptyMap(), @@ -508,7 +508,7 @@ private void addToPipelineWithEventCallback(EventCallback eventCallback) { .pipeline() .addLast( new InFlightHandler( - CoreProtocolVersion.V3, + DefaultProtocolVersion.V3, streamIds, MAX_ORPHAN_IDS, SET_KEYSPACE_TIMEOUT_MILLIS, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index c3bf61eed77..5af4f551a8c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -22,8 +22,8 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.auth.AuthenticationException; @@ -107,7 +107,7 @@ public void setup() { .addLast( "inflight", new InFlightHandler( - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, new StreamIdGenerator(100), Integer.MAX_VALUE, 100, @@ -134,7 +134,7 @@ public void should_initialize() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -169,7 +169,7 @@ public void should_initialize_with_compression() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -193,7 +193,7 @@ public void should_add_heartbeat_handler_to_pipeline_on_success() { ProtocolInitHandler protocolInitHandler = new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler); @@ -237,7 +237,7 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -261,7 +261,7 @@ public void should_initialize_with_authentication() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -325,7 +325,7 @@ public void should_warn_if_auth_configured_but_server_does_not_send_challenge() "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -362,7 +362,7 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -406,7 +406,7 @@ public void should_check_cluster_name_if_provided() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, "expectedClusterName", DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -435,7 +435,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, "expectedClusterName", DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -464,7 +464,7 @@ public void should_initialize_with_keyspace() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, CoreProtocolVersion.V4, null, options, heartbeatHandler)); + internalDriverContext, DefaultProtocolVersion.V4, null, options, heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -491,7 +491,7 @@ public void should_initialize_with_events() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, driverChannelOptions, heartbeatHandler)); @@ -524,7 +524,7 @@ public void should_initialize_with_keyspace_and_events() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, driverChannelOptions, heartbeatHandler)); @@ -557,7 +557,7 @@ public void should_fail_to_initialize_if_keyspace_is_invalid() { "init", new ProtocolInitHandler( internalDriverContext, - CoreProtocolVersion.V4, + DefaultProtocolVersion.V4, null, driverChannelOptions, heartbeatHandler)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index c72441c233a..6429972deb7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -19,7 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; @@ -336,7 +336,7 @@ public void should_fail_if_retry_policy_ignores_error() { private static Frame defaultFrameOf(Message responseMessage) { return Frame.forResponse( - CoreProtocolVersion.V4.getCode(), + DefaultProtocolVersion.V4.getCode(), 0, null, Frame.NO_PAYLOAD, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index d92ac7f659a..160a5ef0a64 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.TestDataProviders; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; @@ -74,7 +74,7 @@ public void setup() { protected static Frame defaultFrameOf(Message responseMessage) { return Frame.forResponse( - CoreProtocolVersion.V4.getCode(), + DefaultProtocolVersion.V4.getCode(), 0, null, Frame.NO_PAYLOAD, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 15e1e80e50c..7ed55970772 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -17,8 +17,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; @@ -56,7 +56,7 @@ public void setup() { Mockito.when(executionInfo.getStatement()).thenReturn((Statement) statement); Mockito.when(context.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.protocolVersion()).thenReturn(CoreProtocolVersion.DEFAULT); + Mockito.when(context.protocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); } @Test(expected = IllegalStateException.class) @@ -139,7 +139,7 @@ public void should_report_not_applied_if_column_present_and_false() { Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); Queue> data = new ArrayDeque<>(); - data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(false, CoreProtocolVersion.DEFAULT))); + data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(false, DefaultProtocolVersion.DEFAULT))); // When DefaultAsyncResultSet resultSet = @@ -160,7 +160,7 @@ public void should_report_not_applied_if_column_present_and_true() { Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); Queue> data = new ArrayDeque<>(); - data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(true, CoreProtocolVersion.DEFAULT))); + data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(true, DefaultProtocolVersion.DEFAULT))); // When DefaultAsyncResultSet resultSet = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java index fe3cd551274..b21507fc8fb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java @@ -17,8 +17,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -65,10 +65,11 @@ public class DefaultTokenMapTest { range(TOKEN_FACTORY.minToken(), TOKEN_FACTORY.minToken()); // Some random routing keys that land in the ranges above (they were generated manually) - private static ByteBuffer ROUTING_KEY12 = TypeCodecs.BIGINT.encode(2L, CoreProtocolVersion.V3); - private static ByteBuffer ROUTING_KEY23 = TypeCodecs.BIGINT.encode(0L, CoreProtocolVersion.V3); - private static ByteBuffer ROUTING_KEY34 = TypeCodecs.BIGINT.encode(1L, CoreProtocolVersion.V3); - private static ByteBuffer ROUTING_KEY41 = TypeCodecs.BIGINT.encode(99L, CoreProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY12 = TypeCodecs.BIGINT.encode(2L, DefaultProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY23 = TypeCodecs.BIGINT.encode(0L, DefaultProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY34 = TypeCodecs.BIGINT.encode(1L, DefaultProtocolVersion.V3); + private static ByteBuffer ROUTING_KEY41 = + TypeCodecs.BIGINT.encode(99L, DefaultProtocolVersion.V3); private static final ImmutableMap REPLICATE_ON_BOTH_DCS = ImmutableMap.of( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index 53ed50d3ace..a046f73269f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -154,7 +154,7 @@ public void should_reprepare_all_if_system_table_empty() { .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); // server knows no ids: adminQuery.resultFuture.complete( - new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + new AdminResult(preparedIdRows(/*none*/ ), null, DefaultProtocolVersion.DEFAULT)); for (char c = 'a'; c <= 'f'; c++) { adminQuery = reprepareOnUp.queries.poll(); @@ -202,7 +202,7 @@ public void should_not_reprepare_already_known_statements() { .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); // server knows d, e and f already: adminQuery.resultFuture.complete( - new AdminResult(preparedIdRows('d', 'e', 'f'), null, CoreProtocolVersion.DEFAULT)); + new AdminResult(preparedIdRows('d', 'e', 'f'), null, DefaultProtocolVersion.DEFAULT)); for (char c = 'a'; c <= 'c'; c++) { adminQuery = reprepareOnUp.queries.poll(); @@ -245,7 +245,7 @@ public void should_limit_number_of_statements_to_reprepare() { .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); // server knows no ids: adminQuery.resultFuture.complete( - new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + new AdminResult(preparedIdRows(/*none*/ ), null, DefaultProtocolVersion.DEFAULT)); for (char c = 'a'; c <= 'c'; c++) { adminQuery = reprepareOnUp.queries.poll(); @@ -274,7 +274,7 @@ public void should_limit_number_of_statements_reprepared_in_parallel() { .isEqualTo("SELECT prepared_id FROM system.prepared_statements"); // server knows no ids => will reprepare all 6: adminQuery.resultFuture.complete( - new AdminResult(preparedIdRows(/*none*/ ), null, CoreProtocolVersion.DEFAULT)); + new AdminResult(preparedIdRows(/*none*/ ), null, DefaultProtocolVersion.DEFAULT)); // 3 statements have enqueued, we've not completed the queries yet so no more should be sent: assertThat(reprepareOnUp.queries.size()).isEqualTo(3); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index f97987c076a..ced3c7ecc1d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -61,7 +61,8 @@ public void should_fail_if_provided_version_isnt_supported() { assertThat(cause).isInstanceOf(UnsupportedProtocolVersionException.class); UnsupportedProtocolVersionException unsupportedException = (UnsupportedProtocolVersionException) cause; - assertThat(unsupportedException.getAttemptedVersions()).containsOnly(CoreProtocolVersion.V4); + assertThat(unsupportedException.getAttemptedVersions()) + .containsOnly(DefaultProtocolVersion.V4); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 55e398950b3..80a67117e5a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -56,7 +56,7 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); - assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V3); + assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V3); // Find out which node became the control node after the reconnection (not necessarily node 0) InetSocketAddress controlAddress = @@ -96,7 +96,7 @@ public void should_keep_current_if_supported_by_all_peers() { .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); - assertThat(context.protocolVersion()).isEqualTo(CoreProtocolVersion.V4); + assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(3); assertThat(protocolQueries(contactPoint, 4)) .containsExactly( @@ -135,7 +135,7 @@ public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { new TestConfigLoader( "protocol.version = V4", "metadata.schema.enabled = false")) .build()) { - assertThat(session.getContext().protocolVersion()).isEqualTo(CoreProtocolVersion.V4); + assertThat(session.getContext().protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(3); assertThat(protocolQueries(contactPoint, 4)) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java index 45f390036d5..237474d7b70 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.testinfra.ccm; import com.datastax.oss.driver.api.core.CassandraVersion; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; @@ -142,9 +142,9 @@ public Optional getDseVersion() { @Override public ProtocolVersion getHighestProtocolVersion() { if (ccmBridge.getCassandraVersion().compareTo(CassandraVersion.V2_2_0) >= 0) { - return CoreProtocolVersion.V4; + return DefaultProtocolVersion.V4; } else { - return CoreProtocolVersion.V3; + return DefaultProtocolVersion.V3; } } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index 7fabeabfdd9..ea0db9251b5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.testinfra.simulacron; -import com.datastax.oss.driver.api.core.CoreProtocolVersion; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -85,6 +85,6 @@ public Set getContactPoints() { @Override public ProtocolVersion getHighestProtocolVersion() { - return CoreProtocolVersion.V4; + return DefaultProtocolVersion.V4; } } From 991b8b010ccea5bafa494d70295315136a42e441 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 11:47:36 -0800 Subject: [PATCH 332/742] Rename CoreConsistencyLevel to DefaultConsistencyLevel --- .../oss/driver/api/core/ConsistencyLevel.java | 2 +- ...cyLevel.java => DefaultConsistencyLevel.java} | 16 ++++++++-------- .../core/DefaultConsistencyLevelRegistry.java | 8 ++++---- .../api/core/retry/DefaultRetryPolicyTest.java | 2 +- .../core/cql/CqlRequestHandlerRetryTest.java | 8 ++++---- .../internal/core/cql/QueryTraceFetcherTest.java | 8 ++++---- .../core/cql/RequestHandlerTestHarness.java | 6 +++--- .../api/core/retry/DefaultRetryPolicyIT.java | 16 ++++++++-------- 8 files changed, 33 insertions(+), 33 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/{CoreConsistencyLevel.java => DefaultConsistencyLevel.java} (76%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java index 672c48a7a33..52f362659a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ConsistencyLevel.java @@ -20,7 +20,7 @@ * *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all - * {@code ConsistencyLevel}s are {@link CoreConsistencyLevel} instances. + * {@code ConsistencyLevel}s are {@link DefaultConsistencyLevel} instances. */ public interface ConsistencyLevel { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultConsistencyLevel.java similarity index 76% rename from core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java rename to core/src/main/java/com/datastax/oss/driver/api/core/DefaultConsistencyLevel.java index 062870d898f..e61e6878a87 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CoreConsistencyLevel.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DefaultConsistencyLevel.java @@ -20,7 +20,7 @@ import java.util.Map; /** A default consistency level supported by the driver out of the box. */ -public enum CoreConsistencyLevel implements ConsistencyLevel { +public enum DefaultConsistencyLevel implements ConsistencyLevel { ANY(ProtocolConstants.ConsistencyLevel.ANY), ONE(ProtocolConstants.ConsistencyLevel.ONE), TWO(ProtocolConstants.ConsistencyLevel.TWO), @@ -37,7 +37,7 @@ public enum CoreConsistencyLevel implements ConsistencyLevel { private final int protocolCode; - CoreConsistencyLevel(int protocolCode) { + DefaultConsistencyLevel(int protocolCode) { this.protocolCode = protocolCode; } @@ -46,19 +46,19 @@ public int getProtocolCode() { return protocolCode; } - public static CoreConsistencyLevel fromCode(int code) { - CoreConsistencyLevel level = BY_CODE.get(code); + public static DefaultConsistencyLevel fromCode(int code) { + DefaultConsistencyLevel level = BY_CODE.get(code); if (level == null) { throw new IllegalArgumentException("Unknown code: " + code); } return level; } - private static Map BY_CODE = mapByCode(values()); + private static Map BY_CODE = mapByCode(values()); - private static Map mapByCode(CoreConsistencyLevel[] levels) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (CoreConsistencyLevel level : levels) { + private static Map mapByCode(DefaultConsistencyLevel[] levels) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (DefaultConsistencyLevel level : levels) { builder.put(level.protocolCode, level); } return builder.build(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java index 086e8ecab9c..7db2b39d6fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java @@ -16,22 +16,22 @@ package com.datastax.oss.driver.internal.core; import com.datastax.oss.driver.api.core.ConsistencyLevel; -import com.datastax.oss.driver.api.core.CoreConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.google.common.collect.ImmutableList; public class DefaultConsistencyLevelRegistry implements ConsistencyLevelRegistry { private static final ImmutableList values = - ImmutableList.builder().add(CoreConsistencyLevel.values()).build(); + ImmutableList.builder().add(DefaultConsistencyLevel.values()).build(); @Override public ConsistencyLevel fromCode(int code) { - return CoreConsistencyLevel.fromCode(code); + return DefaultConsistencyLevel.fromCode(code); } @Override public ConsistencyLevel fromName(String name) { - return CoreConsistencyLevel.valueOf(name); + return DefaultConsistencyLevel.valueOf(name); } @Override diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index b8a4efc9158..924f1eadbd4 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.retry; -import static com.datastax.oss.driver.api.core.CoreConsistencyLevel.QUORUM; +import static com.datastax.oss.driver.api.core.DefaultConsistencyLevel.QUORUM; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index f3a86df4111..e1c83aa9d69 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.atMost; import com.datastax.oss.driver.TestDataProviders; -import com.datastax.oss.driver.api.core.CoreConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -370,7 +370,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onReadTimeout( any(SimpleStatement.class), - eq(CoreConsistencyLevel.LOCAL_ONE), + eq(DefaultConsistencyLevel.LOCAL_ONE), eq(2), eq(1), eq(true), @@ -401,7 +401,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onWriteTimeout( any(SimpleStatement.class), - eq(CoreConsistencyLevel.LOCAL_ONE), + eq(DefaultConsistencyLevel.LOCAL_ONE), eq(CoreWriteType.SIMPLE), eq(2), eq(1), @@ -428,7 +428,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) Mockito.when( policy.onUnavailable( any(SimpleStatement.class), - eq(CoreConsistencyLevel.LOCAL_ONE), + eq(DefaultConsistencyLevel.LOCAL_ONE), eq(2), eq(1), eq(0))) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index b8ca844a395..a43a0255c8e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -20,8 +20,8 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -96,13 +96,13 @@ public void setup() { Mockito.when(config.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL)) .thenReturn(Duration.ZERO); Mockito.when(config.getString(CoreDriverOption.REQUEST_CONSISTENCY)) - .thenReturn(CoreConsistencyLevel.LOCAL_ONE.name()); + .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); Mockito.when(config.getString(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)) - .thenReturn(CoreConsistencyLevel.ONE.name()); + .thenReturn(DefaultConsistencyLevel.ONE.name()); Mockito.when( config.withString( - CoreDriverOption.REQUEST_CONSISTENCY, CoreConsistencyLevel.ONE.name())) + CoreDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) .thenReturn(traceConfig); Mockito.when(context.consistencyLevelRegistry()) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 72bddda47cc..2ba0373b7e3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -19,8 +19,8 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.CoreDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -95,10 +95,10 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_CONSISTENCY)) - .thenReturn(CoreConsistencyLevel.LOCAL_ONE.name()); + .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) - .thenReturn(CoreConsistencyLevel.SERIAL.name()); + .thenReturn(DefaultConsistencyLevel.SERIAL.name()); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index f261e3f091f..42774f4fa20 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -27,8 +27,8 @@ import static org.assertj.core.api.Assertions.fail; import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.CoreConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -114,7 +114,7 @@ public void should_not_retry_on_read_timeout_when_data_present() { fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown - assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(1); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isTrue(); @@ -137,7 +137,7 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown - assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(2); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isFalse(); @@ -160,7 +160,7 @@ public void should_retry_on_read_timeout_when_enough_responses_and_data_not_pres fail("Expected a ReadTimeoutException"); } catch (ReadTimeoutException rte) { // then a read timeout exception is thrown. - assertThat(rte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(rte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(rte.getReceived()).isEqualTo(3); assertThat(rte.getBlockFor()).isEqualTo(3); assertThat(rte.wasDataPresent()).isFalse(); @@ -275,7 +275,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); @@ -314,7 +314,7 @@ public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); } @@ -339,7 +339,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown - assertThat(wte.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(wte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); @@ -389,7 +389,7 @@ public void should_only_retry_once_on_unavailable() { // tried). assertThat(ue.getCoordinator().getConnectAddress()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); - assertThat(ue.getConsistencyLevel()).isEqualTo(CoreConsistencyLevel.LOCAL_QUORUM); + assertThat(ue.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(ue.getRequired()).isEqualTo(3); assertThat(ue.getAlive()).isEqualTo(0); } From 5ae220f90417bc291b46cd9d84bd67c6c2ef0638 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 11:51:39 -0800 Subject: [PATCH 333/742] Rename CoreDriverOption to DefaultDriverOption --- .../api/core/auth/PlainTextAuthProvider.java | 6 +-- ...erOption.java => DefaultDriverOption.java} | 4 +- .../ExponentialReconnectionPolicy.java | 14 +++--- .../driver/api/core/cql/ExecutionInfo.java | 6 +-- .../driver/api/core/metadata/Metadata.java | 8 ++-- .../driver/api/core/metadata/TokenMap.java | 6 +-- .../oss/driver/api/core/session/Request.java | 4 +- .../oss/driver/api/core/session/Session.java | 10 ++--- .../api/core/session/SessionBuilder.java | 13 +++--- .../ConstantSpeculativeExecutionPolicy.java | 6 +-- .../api/core/ssl/DefaultSslEngineFactory.java | 6 +-- .../time/MonotonicTimestampGenerator.java | 12 ++--- .../core/ProtocolVersionRegistry.java | 4 +- .../internal/core/channel/ChannelFactory.java | 14 +++--- .../core/channel/DefaultWriteCoalescer.java | 6 +-- .../core/channel/HeartbeatHandler.java | 12 ++--- .../core/channel/ProtocolInitHandler.java | 6 +-- .../typesafe/DefaultDriverConfigLoader.java | 11 +++-- .../core/context/DefaultDriverContext.java | 42 +++++++++--------- .../core/context/DefaultNettyOptions.java | 18 ++++---- .../driver/internal/core/cql/Conversions.java | 8 ++-- .../core/cql/CqlPrepareHandlerBase.java | 6 +-- .../core/cql/CqlRequestHandlerBase.java | 6 +-- .../internal/core/cql/QueryTraceFetcher.java | 12 ++--- .../DefaultLoadBalancingPolicy.java | 10 ++--- .../core/metadata/DefaultTopologyMonitor.java | 4 +- .../core/metadata/MetadataManager.java | 22 +++++----- .../core/metadata/NodeStateManager.java | 6 +-- .../core/metadata/SchemaAgreementChecker.java | 10 ++--- .../schema/queries/SchemaQueries.java | 10 ++--- .../metrics/DefaultMetricUpdaterFactory.java | 6 +-- .../metrics/DefaultNodeMetricUpdater.java | 8 ++-- .../metrics/DefaultSessionMetricUpdater.java | 8 ++-- .../core/metrics/MetricUpdaterBase.java | 8 ++-- .../internal/core/pool/ChannelPool.java | 8 ++-- .../internal/core/session/DefaultSession.java | 4 +- .../internal/core/session/PoolManager.java | 8 ++-- .../internal/core/session/ReprepareOnUp.java | 12 ++--- ...onstantSpeculativeExecutionPolicyTest.java | 6 +-- .../MonotonicTimestampGeneratorTestBase.java | 10 ++--- .../ChannelFactoryAvailableIdsTest.java | 8 ++-- .../ChannelFactoryClusterNameTest.java | 6 +-- ...ChannelFactoryProtocolNegotiationTest.java | 16 +++---- .../core/channel/ChannelFactoryTestBase.java | 19 ++++---- .../core/channel/ProtocolInitHandlerTest.java | 11 +++-- .../DefaultDriverConfigLoaderTest.java | 4 +- .../typesafe/TypeSafeDriverConfigTest.java | 4 +- .../core/cql/CqlPrepareHandlerTest.java | 4 +- .../core/cql/CqlRequestHandlerTest.java | 4 +- .../core/cql/QueryTraceFetcherTest.java | 14 +++--- .../core/cql/RequestHandlerTestHarness.java | 15 ++++--- .../metadata/DefaultTopologyMonitorTest.java | 4 +- .../core/metadata/MetadataManagerTest.java | 7 +-- .../core/metadata/NodeStateManagerTest.java | 10 ++--- .../metadata/SchemaAgreementCheckerTest.java | 18 +++++--- .../queries/Cassandra21SchemaQueriesTest.java | 4 +- .../queries/Cassandra22SchemaQueriesTest.java | 4 +- .../queries/Cassandra3SchemaQueriesTest.java | 8 ++-- .../schema/queries/SchemaQueriesTest.java | 6 +-- .../core/pool/ChannelPoolInitTest.java | 17 ++++--- .../core/pool/ChannelPoolKeyspaceTest.java | 8 ++-- .../core/pool/ChannelPoolReconnectTest.java | 11 +++-- .../core/pool/ChannelPoolResizeTest.java | 44 ++++++++++++------- .../core/pool/ChannelPoolShutdownTest.java | 8 ++-- .../core/session/DefaultSessionPoolsTest.java | 12 ++--- .../core/session/ReprepareOnUpTest.java | 16 +++---- .../config/DriverConfigProfileReloadIT.java | 8 ++-- manual/core/configuration/README.md | 20 ++++----- .../api/testinfra/session/SessionUtils.java | 4 +- .../testinfra/session/TestConfigLoader.java | 4 +- 70 files changed, 371 insertions(+), 327 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/config/{CoreDriverOption.java => DefaultDriverOption.java} (98%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java index ce6cfbd4bbf..bb5b3193aa5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProvider.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.auth; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Charsets; @@ -52,8 +52,8 @@ public PlainTextAuthProvider(DriverContext context) { @Override public Authenticator newAuthenticator(SocketAddress host, String serverAuthenticator) { - String username = config.getString(CoreDriverOption.AUTH_PROVIDER_USER_NAME); - String password = config.getString(CoreDriverOption.AUTH_PROVIDER_PASSWORD); + String username = config.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME); + String password = config.getString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD); return new PlainTextAuthenticator(username, password); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java similarity index 98% rename from core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java rename to core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index a37297779aa..06c76dfc4ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/CoreDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -20,7 +20,7 @@ * *

          Refer to {@code reference.conf} in the driver codebase for a full description of each option. */ -public enum CoreDriverOption implements DriverOption { +public enum DefaultDriverOption implements DriverOption { CONTACT_POINTS("contact-points", false), PROTOCOL_VERSION("protocol.version", false), @@ -131,7 +131,7 @@ public enum CoreDriverOption implements DriverOption { private final String path; private final boolean required; - CoreDriverOption(String path, boolean required) { + DefaultDriverOption(String path, boolean required) { this.path = path; this.required = required; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java index 8fd943f4738..b1412780b55 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ExponentialReconnectionPolicy.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.connection; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.google.common.base.Preconditions; @@ -35,24 +35,24 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { public ExponentialReconnectionPolicy(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - this.baseDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_BASE_DELAY).toMillis(); - this.maxDelayMs = config.getDuration(CoreDriverOption.RECONNECTION_MAX_DELAY).toMillis(); + this.baseDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY).toMillis(); + this.maxDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY).toMillis(); Preconditions.checkArgument( baseDelayMs > 0, "%s must be strictly positive (got %s)", - CoreDriverOption.RECONNECTION_BASE_DELAY.getPath(), + DefaultDriverOption.RECONNECTION_BASE_DELAY.getPath(), baseDelayMs); Preconditions.checkArgument( maxDelayMs >= 0, "%s must be positive (got %s)", - CoreDriverOption.RECONNECTION_MAX_DELAY.getPath(), + DefaultDriverOption.RECONNECTION_MAX_DELAY.getPath(), maxDelayMs); Preconditions.checkArgument( maxDelayMs >= baseDelayMs, "%s must be bigger than %s (got %s, %s)", - CoreDriverOption.RECONNECTION_MAX_DELAY.getPath(), - CoreDriverOption.RECONNECTION_BASE_DELAY.getPath(), + DefaultDriverOption.RECONNECTION_MAX_DELAY.getPath(), + DefaultDriverOption.RECONNECTION_BASE_DELAY.getPath(), maxDelayMs, baseDelayMs); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 86fa493aceb..393fb9796c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; @@ -113,8 +113,8 @@ public interface ExecutionInfo { *

          Schema agreement is only checked for schema-altering queries. For other query types, this * method will always return {@code true}. * - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT + * @see DefaultDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL + * @see DefaultDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT */ boolean isSchemaInAgreement(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 3280988eef4..4c35cc9bb72 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.session.Session; import java.net.InetSocketAddress; @@ -46,9 +46,9 @@ public interface Metadata { *

          Note that schema metadata can be disabled or restricted to a subset of keyspaces, therefore * this map might be empty or incomplete. * - * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @see DefaultDriverOption#METADATA_SCHEMA_ENABLED * @see Session#setSchemaMetadataEnabled(Boolean) - * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES + * @see DefaultDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ Map getKeyspaces(); @@ -62,7 +62,7 @@ default KeyspaceMetadata getKeyspace(CqlIdentifier keyspaceId) { *

          Note that this property might be absent if token metadata was disabled, or if there was a * runtime error while computing the map (this would generate a warning log). * - * @see CoreDriverOption#METADATA_TOKEN_MAP_ENABLED + * @see DefaultDriverOption#METADATA_TOKEN_MAP_ENABLED */ Optional getTokenMap(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java index 9646b77bd35..24ce18b9867 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.metadata.token.TokenRange; import com.datastax.oss.driver.api.core.session.Session; @@ -33,9 +33,9 @@ * disabled or restricted to a subset of keyspaces; therefore these methods might return empty * results for some or all of the keyspaces. * - * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @see DefaultDriverOption#METADATA_SCHEMA_ENABLED * @see Session#setSchemaMetadataEnabled(Boolean) - * @see CoreDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES + * @see DefaultDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ public interface TokenMap { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 8488df1f6b8..606a3654072 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -128,7 +128,7 @@ public interface Request { * don't retry if there is the slightest chance that the request reached a coordinator). * * @return a boolean value, or {@code null} to use the default value defined in the configuration. - * @see CoreDriverOption#REQUEST_DEFAULT_IDEMPOTENCE + * @see DefaultDriverOption#REQUEST_DEFAULT_IDEMPOTENCE */ Boolean isIdempotent(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index a4827bb241f..4b7ec59130f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; @@ -65,7 +65,7 @@ static String getCoreDriverVersion() { /** * The unique name identifying this client. * - * @see CoreDriverOption#SESSION_NAME + * @see DefaultDriverOption#SESSION_NAME */ String getName(); @@ -102,7 +102,7 @@ static String getCoreDriverVersion() { * * @param newValue a boolean value to enable or disable schema metadata programmatically, or * {@code null} to use the driver's configuration. - * @see CoreDriverOption#METADATA_SCHEMA_ENABLED + * @see DefaultDriverOption#METADATA_SCHEMA_ENABLED * @return if this call triggered a refresh, a future that will complete when that refresh is * complete. Otherwise, a completed future with the current metadata. */ @@ -148,8 +148,8 @@ default Metadata refreshSchema() { * * @return a future that completes with {@code true} if the nodes agree, or {@code false} if the * timeout fired. - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL - * @see CoreDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT + * @see DefaultDriverOption#CONTROL_CONNECTION_AGREEMENT_INTERVAL + * @see DefaultDriverOption#CONTROL_CONNECTION_AGREEMENT_TIMEOUT */ CompletionStage checkSchemaAgreementAsync(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index b5da8fcc7e5..40476802e58 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -75,7 +75,7 @@ public abstract class SessionBuilder { * *

        • the resulting configuration is expected to contain a {@code datastax-java-driver} * section. - *
        • that section is validated against the {@link CoreDriverOption core driver options}. + *
        • that section is validated against the {@link DefaultDriverOption core driver options}. * * * The core driver JAR includes a {@code reference.conf} file with sensible defaults for all @@ -172,15 +172,16 @@ protected final CompletionStage buildDefaultSessionAsync() { DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = - defaultConfig.isDefined(CoreDriverOption.CONTACT_POINTS) - ? defaultConfig.getStringList(CoreDriverOption.CONTACT_POINTS) + defaultConfig.isDefined(DefaultDriverOption.CONTACT_POINTS) + ? defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS) : Collections.emptyList(); Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); - if (keyspace == null && defaultConfig.isDefined(CoreDriverOption.SESSION_KEYSPACE)) { - keyspace = CqlIdentifier.fromCql(defaultConfig.getString(CoreDriverOption.SESSION_KEYSPACE)); + if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) { + keyspace = + CqlIdentifier.fromCql(defaultConfig.getString(DefaultDriverOption.SESSION_KEYSPACE)); } return DefaultSession.init( diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java index 920b9e67eab..bcdd24304db 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.specex; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; @@ -36,12 +36,12 @@ public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionP public ConstantSpeculativeExecutionPolicy(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - this.maxExecutions = config.getInt(CoreDriverOption.SPECULATIVE_EXECUTION_MAX); + this.maxExecutions = config.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX); if (this.maxExecutions < 1) { throw new IllegalArgumentException("Max must be at least 1"); } this.constantDelayMillis = - config.getDuration(CoreDriverOption.SPECULATIVE_EXECUTION_DELAY).toMillis(); + config.getDuration(DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY).toMillis(); if (this.constantDelayMillis < 0) { throw new IllegalArgumentException("Delay must be positive or 0"); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java index c6444ff0ec0..2743e99a702 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactory.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.ssl; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import java.net.InetSocketAddress; @@ -55,8 +55,8 @@ public DefaultSslEngineFactory(DriverContext driverContext) { throw new IllegalStateException("Cannot initialize SSL Context", e); } DriverConfigProfile config = driverContext.config().getDefaultProfile(); - if (config.isDefined(CoreDriverOption.SSL_CIPHER_SUITES)) { - List list = config.getStringList(CoreDriverOption.SSL_CIPHER_SUITES); + if (config.isDefined(DefaultDriverOption.SSL_CIPHER_SUITES)) { + List list = config.getStringList(DefaultDriverOption.SSL_CIPHER_SUITES); String tmp[] = new String[list.size()]; this.cipherSuites = list.toArray(tmp); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java index 8cafeded3ca..bea3dea601c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGenerator.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.time; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.time.Clock; @@ -47,9 +47,9 @@ protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); this.warningThresholdMicros = - (config.isDefined(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + (config.isDefined(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) ? config - .getDuration(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD) + .getDuration(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD) .toNanos() / 1000 : 0; @@ -59,7 +59,7 @@ protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { } else { this.warningIntervalMillis = config - .getDuration(CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL) + .getDuration(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL) .toMillis(); } } @@ -100,8 +100,8 @@ private void maybeLog(long currentTick, long last) { private static Clock buildClock(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); boolean forceJavaClock = - config.isDefined(CoreDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK) - && config.getBoolean(CoreDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); + config.isDefined(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK) + && config.getBoolean(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); return Clock.getInstance(forceJavaClock); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java index 0f9e46d6fe0..2f3c3b9a972 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolVersionRegistry.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import java.util.Collection; @@ -38,7 +38,7 @@ public interface ProtocolVersionRegistry { * forced in the configuration. * * @throws IllegalArgumentException if there is no known version with this name. - * @see CoreDriverOption#PROTOCOL_VERSION + * @see DefaultDriverOption#PROTOCOL_VERSION */ ProtocolVersion fromName(String name); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 86344f341e5..b93b80371e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -56,8 +56,8 @@ public ChannelFactory(InternalDriverContext context) { this.context = context; DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); - if (defaultConfig.isDefined(CoreDriverOption.PROTOCOL_VERSION)) { - String versionName = defaultConfig.getString(CoreDriverOption.PROTOCOL_VERSION); + if (defaultConfig.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) { + String versionName = defaultConfig.getString(DefaultDriverOption.PROTOCOL_VERSION); this.protocolVersion = context.protocolVersionRegistry().fromName(versionName); } // else it will be negotiated with the first opened connection } @@ -175,14 +175,14 @@ protected void initChannel(Channel channel) throws Exception { long setKeyspaceTimeoutMillis = defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) .toMillis(); int maxFrameLength = - (int) defaultConfigProfile.getBytes(CoreDriverOption.PROTOCOL_MAX_FRAME_LENGTH); + (int) defaultConfigProfile.getBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH); int maxRequestsPerConnection = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); int maxOrphanRequests = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); + defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); InFlightHandler inFlightHandler = new InFlightHandler( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index e2f21d50411..995971f6c9e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.channel.Channel; @@ -53,9 +53,9 @@ public class DefaultWriteCoalescer implements WriteCoalescer { public DefaultWriteCoalescer(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - this.maxRunsWithNoWork = config.getInt(CoreDriverOption.COALESCER_MAX_RUNS); + this.maxRunsWithNoWork = config.getInt(DefaultDriverOption.COALESCER_MAX_RUNS); this.rescheduleIntervalNanos = - config.getDuration(CoreDriverOption.COALESCER_INTERVAL).toNanos(); + config.getDuration(DefaultDriverOption.COALESCER_INTERVAL).toNanos(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index 333bcee18de..9e516e4d66a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.protocol.internal.Message; @@ -40,7 +40,7 @@ class HeartbeatHandler extends IdleStateHandler { super( (int) defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL) + .getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL) .getSeconds(), 0, 0); @@ -54,17 +54,17 @@ protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws LOG.warn( "Not sending heartbeat because a previous one is still in progress. " + "Check that {} is not lower than {}.", - CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL.getPath(), - CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT.getPath()); + DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL.getPath(), + DefaultDriverOption.CONNECTION_HEARTBEAT_TIMEOUT.getPath()); } else { LOG.debug( "Connection was inactive for {} seconds, sending heartbeat", defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL) + .getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL) .getSeconds()); long timeoutMillis = defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_TIMEOUT) + .getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_TIMEOUT) .toMillis(); this.request = new HeartbeatRequest(ctx, timeoutMillis); this.request.send(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index ce4905f1b5c..b2311e928e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.auth.Authenticator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ConnectionInitException; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; @@ -80,9 +80,9 @@ class ProtocolInitHandler extends ConnectInitHandler { DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); this.timeoutMillis = - defaultConfig.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); + defaultConfig.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); this.warnIfNoServerAuth = - defaultConfig.getBoolean(CoreDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH); + defaultConfig.getBoolean(DefaultDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH); this.initialProtocolVersion = protocolVersion; this.expectedClusterName = expectedClusterName; this.options = options; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index 23122b1c9ed..a7bce5f9e83 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -58,7 +58,7 @@ public class DefaultDriverConfigLoader implements DriverConfigLoader { * SessionBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. */ public DefaultDriverConfigLoader() { - this(DEFAULT_CONFIG_SUPPLIER, CoreDriverOption.values()); + this(DEFAULT_CONFIG_SUPPLIER, DefaultDriverOption.values()); } /** @@ -105,7 +105,10 @@ private SingleThreaded(InternalDriverContext context) { this.eventBus = context.eventBus(); this.config = context.config().getDefaultProfile(); this.reloadInterval = - context.config().getDefaultProfile().getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); + context + .config() + .getDefaultProfile() + .getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); forceLoadListenerKey = this.eventBus.register( @@ -141,7 +144,7 @@ private void reload() { if (driverConfig.reload(configSupplier.get())) { LOG.info("[{}] Detected a configuration change", logPrefix); eventBus.fire(ConfigChangeEvent.INSTANCE); - Duration newReloadInterval = config.getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL); + Duration newReloadInterval = config.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); if (!newReloadInterval.equals(reloadInterval)) { reloadInterval = newReloadInterval; scheduleReloadTask(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index bc462d1f143..0ef8e3304e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.auth.AuthProvider; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -174,8 +174,8 @@ public DefaultDriverContext(DriverConfigLoader configLoader, List> this.config = configLoader.getInitialConfig(); this.configLoader = configLoader; DriverConfigProfile defaultProfile = config.getDefaultProfile(); - if (defaultProfile.isDefined(CoreDriverOption.SESSION_NAME)) { - this.sessionName = defaultProfile.getString(CoreDriverOption.SESSION_NAME); + if (defaultProfile.isDefined(DefaultDriverOption.SESSION_NAME)) { + this.sessionName = defaultProfile.getString(DefaultDriverOption.SESSION_NAME); } else { this.sessionName = "s" + SESSION_NAME_COUNTER.getAndIncrement(); } @@ -184,68 +184,69 @@ public DefaultDriverContext(DriverConfigLoader configLoader, List> protected LoadBalancingPolicy buildLoadBalancingPolicy() { return Reflection.buildFromConfig( - this, CoreDriverOption.LOAD_BALANCING_POLICY_CLASS, LoadBalancingPolicy.class) + this, DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, LoadBalancingPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing load balancing policy, check your configuration (%s)", - (DriverOption) CoreDriverOption.LOAD_BALANCING_POLICY_CLASS))); + (DriverOption) DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS))); } protected ReconnectionPolicy buildReconnectionPolicy() { return Reflection.buildFromConfig( - this, CoreDriverOption.RECONNECTION_POLICY_CLASS, ReconnectionPolicy.class) + this, DefaultDriverOption.RECONNECTION_POLICY_CLASS, ReconnectionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing reconnection policy, check your configuration (%s)", - CoreDriverOption.RECONNECTION_POLICY_CLASS))); + DefaultDriverOption.RECONNECTION_POLICY_CLASS))); } protected RetryPolicy buildRetryPolicy() { - return Reflection.buildFromConfig(this, CoreDriverOption.RETRY_POLICY_CLASS, RetryPolicy.class) + return Reflection.buildFromConfig( + this, DefaultDriverOption.RETRY_POLICY_CLASS, RetryPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing retry policy, check your configuration (%s)", - CoreDriverOption.RETRY_POLICY_CLASS))); + DefaultDriverOption.RETRY_POLICY_CLASS))); } protected SpeculativeExecutionPolicy buildSpeculativeExecutionPolicy() { return Reflection.buildFromConfig( this, - CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, SpeculativeExecutionPolicy.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing speculative execution policy, check your configuration (%s)", - CoreDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS))); + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS))); } protected AddressTranslator buildAddressTranslator() { return Reflection.buildFromConfig( - this, CoreDriverOption.ADDRESS_TRANSLATOR_CLASS, AddressTranslator.class) + this, DefaultDriverOption.ADDRESS_TRANSLATOR_CLASS, AddressTranslator.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing address translator, check your configuration (%s)", - CoreDriverOption.ADDRESS_TRANSLATOR_CLASS))); + DefaultDriverOption.ADDRESS_TRANSLATOR_CLASS))); } protected Optional buildAuthProvider() { return Reflection.buildFromConfig( - this, CoreDriverOption.AUTH_PROVIDER_CLASS, AuthProvider.class); + this, DefaultDriverOption.AUTH_PROVIDER_CLASS, AuthProvider.class); } protected Optional buildSslEngineFactory() { return Reflection.buildFromConfig( - this, CoreDriverOption.SSL_ENGINE_FACTORY_CLASS, SslEngineFactory.class); + this, DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, SslEngineFactory.class); } protected EventBus buildEventBus() { @@ -256,7 +257,7 @@ protected EventBus buildEventBus() { protected Compressor buildCompressor() { return (Compressor) Reflection.buildFromConfig( - this, CoreDriverOption.PROTOCOL_COMPRESSOR_CLASS, Compressor.class) + this, DefaultDriverOption.PROTOCOL_COMPRESSOR_CLASS, Compressor.class) .orElse(Compressor.none()); } @@ -290,13 +291,14 @@ protected Optional buildSslHandlerFactory() { } protected WriteCoalescer buildWriteCoalescer() { - return Reflection.buildFromConfig(this, CoreDriverOption.COALESCER_CLASS, WriteCoalescer.class) + return Reflection.buildFromConfig( + this, DefaultDriverOption.COALESCER_CLASS, WriteCoalescer.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing write coalescer, check your configuration (%s)", - CoreDriverOption.COALESCER_CLASS))); + DefaultDriverOption.COALESCER_CLASS))); } protected ChannelFactory buildChannelFactory() { @@ -330,13 +332,13 @@ protected CodecRegistry buildCodecRegistry(String logPrefix, List> protected TimestampGenerator buildTimestampGenerator() { return Reflection.buildFromConfig( - this, CoreDriverOption.TIMESTAMP_GENERATOR_CLASS, TimestampGenerator.class) + this, DefaultDriverOption.TIMESTAMP_GENERATOR_CLASS, TimestampGenerator.class) .orElseThrow( () -> new IllegalArgumentException( String.format( "Missing timestamp generator, check your configuration (%s)", - CoreDriverOption.TIMESTAMP_GENERATOR_CLASS))); + DefaultDriverOption.TIMESTAMP_GENERATOR_CLASS))); } protected SchemaQueriesFactory buildSchemaQueriesFactory() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 324b9585828..35333646782 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.context; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -46,17 +46,17 @@ public class DefaultNettyOptions implements NettyOptions { public DefaultNettyOptions(InternalDriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - int ioGroupSize = config.getInt(CoreDriverOption.NETTY_IO_SIZE); - this.ioShutdownQuietPeriod = config.getInt(CoreDriverOption.NETTY_IO_SHUTDOWN_QUIET_PERIOD); - this.ioShutdownTimeout = config.getInt(CoreDriverOption.NETTY_IO_SHUTDOWN_TIMEOUT); + int ioGroupSize = config.getInt(DefaultDriverOption.NETTY_IO_SIZE); + this.ioShutdownQuietPeriod = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_QUIET_PERIOD); + this.ioShutdownTimeout = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_TIMEOUT); this.ioShutdownUnit = - TimeUnit.valueOf(config.getString(CoreDriverOption.NETTY_IO_SHUTDOWN_UNIT)); - int adminGroupSize = config.getInt(CoreDriverOption.NETTY_ADMIN_SIZE); + TimeUnit.valueOf(config.getString(DefaultDriverOption.NETTY_IO_SHUTDOWN_UNIT)); + int adminGroupSize = config.getInt(DefaultDriverOption.NETTY_ADMIN_SIZE); this.adminShutdownQuietPeriod = - config.getInt(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_QUIET_PERIOD); - this.adminShutdownTimeout = config.getInt(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_TIMEOUT); + config.getInt(DefaultDriverOption.NETTY_ADMIN_SHUTDOWN_QUIET_PERIOD); + this.adminShutdownTimeout = config.getInt(DefaultDriverOption.NETTY_ADMIN_SHUTDOWN_TIMEOUT); this.adminShutdownUnit = - TimeUnit.valueOf(config.getString(CoreDriverOption.NETTY_ADMIN_SHUTDOWN_UNIT)); + TimeUnit.valueOf(config.getString(DefaultDriverOption.NETTY_ADMIN_SHUTDOWN_UNIT)); ThreadFactory safeFactory = new BlockingOperation.SafeThreadFactory(); ThreadFactory ioThreadFactory = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 52e9fd031f7..478925535e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BatchStatement; @@ -97,13 +97,13 @@ static Message toMessage( int consistency = context .consistencyLevelRegistry() - .fromName(config.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .getProtocolCode(); - int pageSize = config.getInt(CoreDriverOption.REQUEST_PAGE_SIZE); + int pageSize = config.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE); int serialConsistency = context .consistencyLevelRegistry() - .fromName(config.getString(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) + .fromName(config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) .getProtocolCode(); long timestamp = statement.getTimestamp(); if (timestamp == Long.MIN_VALUE) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index fe7d3354dec..e131acb0c1d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; @@ -143,10 +143,10 @@ protected CqlPrepareHandlerBase( new Prepare(request.getQuery(), (keyspace == null) ? null : keyspace.asInternal()); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeout = configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); - this.prepareOnAllNodes = configProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES); + this.prepareOnAllNodes = configProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); sendRequest(null, 0); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index da6a08f499c..653c86e2e86 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; @@ -148,7 +148,7 @@ protected CqlRequestHandlerBase( } this.isIdempotent = (statement.isIdempotent() == null) - ? configProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) + ? configProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : statement.isIdempotent(); this.result = new CompletableFuture<>(); this.result.exceptionally( @@ -165,7 +165,7 @@ protected CqlRequestHandlerBase( this.message = Conversions.toMessage(statement, configProfile, context); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - this.timeout = configProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); + this.timeout = configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index 95d4fea5c10..95bb69981e8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; @@ -56,20 +56,20 @@ class QueryTraceFetcher { ConsistencyLevel regularConsistency = context .consistencyLevelRegistry() - .fromName(configProfile.getString(CoreDriverOption.REQUEST_CONSISTENCY)); + .fromName(configProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); ConsistencyLevel traceConsistency = context .consistencyLevelRegistry() - .fromName(configProfile.getString(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)); + .fromName(configProfile.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)); this.configProfile = (traceConsistency.equals(regularConsistency)) ? configProfile : configProfile.withString( - CoreDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); + DefaultDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); - this.maxAttempts = configProfile.getInt(CoreDriverOption.REQUEST_TRACE_ATTEMPTS); + this.maxAttempts = configProfile.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS); this.intervalNanos = - configProfile.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); + configProfile.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); this.scheduler = context.nettyOptions().adminEventExecutorGroup().next(); querySession(maxAttempts); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 1ffcb128365..819d8c6f12d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.loadbalancing; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -114,7 +114,7 @@ public void init( } else if (localDc == null) { throw new IllegalStateException( "You provided explicit contact points, the local DC must be specified (see " - + CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER.getPath() + + DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER.getPath() + " in the config)"); } else { ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -271,8 +271,8 @@ public void close() { private static String getLocalDcFromConfig(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); - return (config.isDefined(CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) - ? config.getString(CoreDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) + return (config.isDefined(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) + ? config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) : null; } @@ -280,7 +280,7 @@ private static String getLocalDcFromConfig(DriverContext context) { private static Predicate getFilterFromConfig(DriverContext context) { return (Predicate) Reflection.buildFromConfig( - context, CoreDriverOption.LOAD_BALANCING_FILTER_CLASS, Predicate.class) + context, DefaultDriverOption.LOAD_BALANCING_FILTER_CLASS, Predicate.class) .orElse(INCLUDE_ALL_NODES); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 58a93be3da3..3b9f5b15f98 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; @@ -68,7 +68,7 @@ public DefaultTopologyMonitor(InternalDriverContext context) { this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); DriverConfigProfile config = context.config().getDefaultProfile(); - this.timeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); + this.timeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.closeFuture = new CompletableFuture<>(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 444b0d23911..77afd57dc5c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; @@ -74,12 +74,12 @@ public MetadataManager(InternalDriverContext context) { this.singleThreaded = new SingleThreaded(context, config); this.controlConnection = context.controlConnection(); this.metadata = DefaultMetadata.EMPTY; - this.schemaEnabledInConfig = config.getBoolean(CoreDriverOption.METADATA_SCHEMA_ENABLED); + this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = - config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) : Collections.emptyList(); - this.tokenMapEnabled = config.getBoolean(CoreDriverOption.METADATA_TOKEN_MAP_ENABLED); + this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED); context.eventBus().register(ConfigChangeEvent.class, this::onConfigChanged); } @@ -89,12 +89,12 @@ private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event boolean tokenMapEnabledBefore = tokenMapEnabled; List keyspacesBefore = this.refreshedKeyspaces; - this.schemaEnabledInConfig = config.getBoolean(CoreDriverOption.METADATA_SCHEMA_ENABLED); + this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = - config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) : Collections.emptyList(); - this.tokenMapEnabled = config.getBoolean(CoreDriverOption.METADATA_TOKEN_MAP_ENABLED); + this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED); if ((!schemaEnabledBefore || !keyspacesBefore.equals(refreshedKeyspaces) @@ -261,8 +261,8 @@ private SingleThreaded(InternalDriverContext context, DriverConfigProfile config adminExecutor, this::coalesceSchemaRequests, this::startSchemaRequest, - config.getDuration(CoreDriverOption.METADATA_SCHEMA_WINDOW), - config.getInt(CoreDriverOption.METADATA_SCHEMA_MAX_EVENTS)); + config.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW), + config.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)); this.schemaQueriesFactory = context.schemaQueriesFactory(); this.schemaParserFactory = context.schemaParserFactory(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 29b3dff976b..e97b4f2a9b2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -98,8 +98,8 @@ private SingleThreaded(InternalDriverContext context) { adminExecutor, this::coalesceTopologyEvents, this::flushTopologyEvents, - config.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW), - config.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); + config.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW), + config.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); this.eventBus = context.eventBus(); this.eventBus.register( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java index 11fe8e33583..f1f5acded76 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -74,12 +74,12 @@ class SchemaAgreementChecker { this.port = port; this.logPrefix = logPrefix; DriverConfigProfile config = context.config().getDefaultProfile(); - this.queryTimeout = config.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT); + this.queryTimeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.intervalNs = - config.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL).toNanos(); + config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL).toNanos(); this.timeoutNs = - config.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT).toNanos(); - this.warnOnFailure = config.getBoolean(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN); + config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT).toNanos(); + this.warnOnFailure = config.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN); this.start = System.nanoTime(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java index 7923b8075bb..8b9f1aa1c72 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; @@ -76,12 +76,12 @@ protected SchemaQueries( this.isCassandraV3 = isCassandraV3; this.refreshFuture = refreshFuture; this.logPrefix = logPrefix; - this.timeout = config.getDuration(CoreDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT); - this.pageSize = config.getInt(CoreDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); + this.timeout = config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT); + this.pageSize = config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); List refreshedKeyspaces = - config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) + ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) : Collections.emptyList(); this.whereClause = buildWhereClause(refreshedKeyspaces); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java index f6495fa2409..b81005933c5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metrics; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; @@ -44,9 +44,9 @@ public DefaultMetricUpdaterFactory(InternalDriverContext context) { this.context = context; DriverConfigProfile config = context.config().getDefaultProfile(); this.enabledSessionMetrics = - parseSessionMetricPaths(config.getStringList(CoreDriverOption.METRICS_SESSION_ENABLED)); + parseSessionMetricPaths(config.getStringList(DefaultDriverOption.METRICS_SESSION_ENABLED)); this.enabledNodeMetrics = - parseNodeMetricPaths(config.getStringList(CoreDriverOption.METRICS_NODE_ENABLED)); + parseNodeMetricPaths(config.getStringList(DefaultDriverOption.METRICS_NODE_ENABLED)); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java index ad5cb5bcfc1..2d6ed227663 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metrics; import com.codahale.metrics.Gauge; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; @@ -55,9 +55,9 @@ public DefaultNodeMetricUpdater( initializeHdrTimer( CoreNodeMetric.CQL_MESSAGES, config, - CoreDriverOption.METRICS_NODE_CQL_MESSAGES_HIGHEST, - CoreDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS, - CoreDriverOption.METRICS_NODE_CQL_MESSAGES_INTERVAL); + DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_HIGHEST, + DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS, + DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_INTERVAL); initializeDefaultCounter(CoreNodeMetric.UNSENT_REQUESTS); initializeDefaultCounter(CoreNodeMetric.ABORTED_REQUESTS); initializeDefaultCounter(CoreNodeMetric.WRITE_TIMEOUTS); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java index c7f3a0f4d85..80d7729579c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metrics; import com.codahale.metrics.Gauge; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; import com.datastax.oss.driver.api.core.metrics.SessionMetric; @@ -50,9 +50,9 @@ public DefaultSessionMetricUpdater( initializeHdrTimer( CoreSessionMetric.CQL_REQUESTS, context.config().getDefaultProfile(), - CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST, - CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, - CoreDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); + DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST, + DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, + DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); initializeDefaultCounter(CoreSessionMetric.CQL_CLIENT_TIMEOUTS); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java index 70d6f41c448..68bdd0c6059 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterBase.java @@ -17,7 +17,7 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import java.time.Duration; import java.util.Set; @@ -77,9 +77,9 @@ protected void initializeDefaultCounter(MetricT metric) { protected void initializeHdrTimer( MetricT metric, DriverConfigProfile config, - CoreDriverOption highestLatencyOption, - CoreDriverOption significantDigitsOption, - CoreDriverOption intervalOption) { + DefaultDriverOption highestLatencyOption, + DefaultDriverOption significantDigitsOption, + DefaultDriverOption intervalOption) { if (enabledMetrics.contains(metric)) { String fullName = buildFullName(metric); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 2f287f4cda4..3e2f1ec0434 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.auth.AuthenticationException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; @@ -308,7 +308,7 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { } else { if (config .getDefaultProfile() - .getBoolean(CoreDriverOption.CONNECTION_WARN_INIT_ERROR)) { + .getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) { Loggers.warnWithException( LOG, "[{}] Error while opening new channel", logPrefix, error); } else { @@ -548,8 +548,8 @@ private int getConfiguredSize(NodeDistance distance) { .getDefaultProfile() .getInt( (distance == NodeDistance.LOCAL) - ? CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE - : CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE); + ? DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE + : DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index de88337fa8a..77344dedfa4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -302,7 +302,7 @@ private void init(CqlIdentifier keyspace) { private void afterInitialNodeListRefresh(CqlIdentifier keyspace) { try { boolean protocolWasForced = - context.config().getDefaultProfile().isDefined(CoreDriverOption.PROTOCOL_VERSION); + context.config().getDefaultProfile().isDefined(DefaultDriverOption.PROTOCOL_VERSION); boolean needSchemaRefresh = true; if (!protocolWasForced) { ProtocolVersion currentVersion = context.protocolVersion(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java index 84197598029..acb84ecec0f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; @@ -108,7 +108,7 @@ public CompletionStage setKeyspace(CqlIdentifier newKeyspace) { if (Objects.equals(oldKeyspace, newKeyspace)) { return CompletableFuture.completedFuture(null); } - if (config.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { + if (config.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) { LOG.warn( "[{}] Detected a keyspace change at runtime ({} => {}). " + "This is an anti-pattern that should be avoided in production " @@ -116,7 +116,7 @@ public CompletionStage setKeyspace(CqlIdentifier newKeyspace) { logPrefix, (oldKeyspace == null) ? "" : oldKeyspace.asInternal(), newKeyspace.asInternal(), - CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); + DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE.getPath()); } this.keyspace = newKeyspace; CompletableFuture result = new CompletableFuture<>(); @@ -383,7 +383,7 @@ private void onPoolInitialized(ChannelPool pool) { private void reprepareStatements(ChannelPool pool) { assert adminExecutor.inEventLoop(); - if (config.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) { + if (config.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) { new ReprepareOnUp( logPrefix + "|" + pool.getNode().getConnectAddress(), pool, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index e18046a41e6..7f7653204ff 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.session; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -91,12 +91,12 @@ class ReprepareOnUp { DriverConfig config = context.config(); this.checkSystemTable = - config.getDefaultProfile().getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); - this.timeout = config.getDefaultProfile().getDuration(CoreDriverOption.REPREPARE_TIMEOUT); + config.getDefaultProfile().getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); + this.timeout = config.getDefaultProfile().getDuration(DefaultDriverOption.REPREPARE_TIMEOUT); this.maxStatements = - config.getDefaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS); + config.getDefaultProfile().getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS); this.maxParallelism = - config.getDefaultProfile().getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM); + config.getDefaultProfile().getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM); } void start() { @@ -123,7 +123,7 @@ void start() { LOG.debug( "[{}] {} is disabled, repreparing directly", logPrefix, - CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); + DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE.getPath()); RunOrSchedule.on( channel.eventLoop(), () -> { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index aba6a944800..608ff82341f 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -44,9 +44,9 @@ public void setup() { } private void mockOptions(int maxExecutions, long constantDelayMillis) { - Mockito.when(defaultProfile.getInt(CoreDriverOption.SPECULATIVE_EXECUTION_MAX)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX)) .thenReturn(maxExecutions); - Mockito.when(defaultProfile.getDuration(CoreDriverOption.SPECULATIVE_EXECUTION_DELAY)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY)) .thenReturn(Duration.ofMillis(constantDelayMillis)); } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java index a4b886a8ef7..67dc32e8af6 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/time/MonotonicTimestampGeneratorTestBase.java @@ -21,7 +21,7 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -60,16 +60,16 @@ public void setup() { // Disable warnings by default Mockito.when( defaultConfigProfile.isDefined( - CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) .thenReturn(true); Mockito.when( defaultConfigProfile.getDuration( - CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) .thenReturn(Duration.ofNanos(0)); // Actual value doesn't really matter since we only test the first warning Mockito.when( defaultConfigProfile.getDuration( - CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) .thenReturn(Duration.ofSeconds(10)); logger = (Logger) LoggerFactory.getLogger(MonotonicTimestampGenerator.class); @@ -113,7 +113,7 @@ public void should_increment_if_clock_does_not_increase() { public void should_warn_if_timestamps_drift() { Mockito.when( defaultConfigProfile.getDuration( - CoreDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) .thenReturn(Duration.ofNanos(2 * 1000)); Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 643e68ead3b..e10d97348a4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.timeout; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.Void; @@ -39,13 +39,13 @@ public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Override public void setup() throws InterruptedException { super.setup(); - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(128); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 6fc51260760..509510720f3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.protocol.internal.response.Ready; import java.util.concurrent.CompletionStage; @@ -30,7 +30,7 @@ public class ChannelFactoryClusterNameTest extends ChannelFactoryTestBase { @Test public void should_set_cluster_name_from_first_connection() { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -50,7 +50,7 @@ public void should_set_cluster_name_from_first_connection() { @Test public void should_check_cluster_name_for_next_connections() throws Throwable { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index 4198b54921b..a52221f4cec 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -37,9 +37,9 @@ public class ChannelFactoryProtocolNegotiationTest extends ChannelFactoryTestBas @Test public void should_succeed_if_version_specified_and_supported_by_server() { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -60,9 +60,9 @@ public void should_succeed_if_version_specified_and_supported_by_server() { @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_version_specified_and_not_supported_by_server(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -92,7 +92,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err @Test public void should_succeed_if_version_not_specified_and_server_supports_latest_supported() { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -118,7 +118,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s @UseDataProvider("unsupportedProtocolCodes") public void should_negotiate_if_version_not_specified_and_server_supports_legacy(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) @@ -152,7 +152,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 4970fee23f3..6369aa22b39 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.fail; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; @@ -112,15 +112,18 @@ public void setup() throws InterruptedException { Mockito.when(context.config()).thenReturn(driverConfig); Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.AUTH_PROVIDER_CLASS)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)) .thenReturn(false); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + Mockito.when( + defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) + Mockito.when( + defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(1); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) + Mockito.when( + defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); @@ -235,10 +238,10 @@ protected void initChannel(Channel channel) throws Exception { long setKeyspaceTimeoutMillis = defaultConfigProfile - .getDuration(CoreDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) + .getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) .toMillis(); int maxRequestsPerConnection = - defaultConfigProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); + defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); InFlightHandler inFlightHandler = new InFlightHandler( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 5af4f551a8c..9aabc237727 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.auth.AuthenticationException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; @@ -90,12 +90,15 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + Mockito.when( + defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) + Mockito.when( + defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); Mockito.when( - defaultConfigProfile.getBoolean(CoreDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH)) + defaultConfigProfile.getBoolean( + DefaultDriverOption.AUTH_PROVIDER_WARN_IF_NO_SERVER_AUTH)) .thenReturn(true); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index 6723752282d..e3cca381aad 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -69,7 +69,7 @@ public void setup() { // it. Mockito.when(context.config()).thenReturn(config); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.CONFIG_RELOAD_INTERVAL)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) .thenReturn(Duration.ofSeconds(12)); configSource = new AtomicReference<>("required_int = 42"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 96908e4e70b..37a74e55122 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -70,7 +70,7 @@ public void should_override_option_in_profile() { public void should_load_default_driver_config() { // No assertions here, but this validates that `reference.conf` is well-formed. new TypeSafeDriverConfig( - ConfigFactory.load().getConfig("datastax-java-driver"), CoreDriverOption.values()); + ConfigFactory.load().getConfig("datastax-java-driver"), DefaultDriverOption.values()); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 6429972deb7..f61795c0488 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -20,7 +20,7 @@ import static org.mockito.ArgumentMatchers.eq; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -116,7 +116,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); - Mockito.when(config.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); + Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = new CqlPrepareAsyncHandler( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index c7bae13455f..a1c1961a7ff 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; @@ -124,7 +124,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { .getContext() .config() .getDefaultProfile() - .getDuration(CoreDriverOption.REQUEST_TIMEOUT); + .getDuration(DefaultDriverOption.REQUEST_TIMEOUT); assertThat(scheduledTask.getInitialDelay(TimeUnit.NANOSECONDS)) .isEqualTo(configuredTimeout.toNanos()); scheduledTask.run(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index a43a0255c8e..6282245589f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -91,18 +91,18 @@ public void setup() { return null; }); - Mockito.when(config.getInt(CoreDriverOption.REQUEST_TRACE_ATTEMPTS)).thenReturn(3); + Mockito.when(config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS)).thenReturn(3); // Doesn't really matter since we mock the scheduler - Mockito.when(config.getDuration(CoreDriverOption.REQUEST_TRACE_INTERVAL)) + Mockito.when(config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL)) .thenReturn(Duration.ZERO); - Mockito.when(config.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + Mockito.when(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); - Mockito.when(config.getString(CoreDriverOption.REQUEST_TRACE_CONSISTENCY)) + Mockito.when(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.ONE.name()); Mockito.when( config.withString( - CoreDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) + DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) .thenReturn(traceConfig); Mockito.when(context.consistencyLevelRegistry()) @@ -157,7 +157,7 @@ public void should_succeed_when_both_queries_succeed_immediately() { /** * This should not happen with a sane configuration, but we need to handle it in case {@link - * CoreDriverOption#REQUEST_PAGE_SIZE} is set ridiculously low. + * DefaultDriverOption#REQUEST_PAGE_SIZE} is set ridiculously low. */ @Test public void should_succeed_when_events_query_is_paged() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 2ba0373b7e3..43b8fb4aec6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; @@ -92,16 +92,17 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); // TODO make configurable in the test, also handle profiles - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_CONSISTENCY)) + Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); - Mockito.when(defaultConfigProfile.getString(CoreDriverOption.REQUEST_SERIAL_CONSISTENCY)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE)) + .thenReturn(5000); + Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.SERIAL.name()); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.PREPARE_ON_ALL_NODES)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)) .thenReturn(true); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 1f424576cc4..50747df5565 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -75,7 +75,7 @@ public class DefaultTopologyMonitorTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT)) + Mockito.when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 3a097b6c67d..6c5ed71bf76 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.timeout; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; @@ -81,9 +81,10 @@ public void setup() { Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); - Mockito.when(defaultProfile.getDuration(CoreDriverOption.METADATA_SCHEMA_WINDOW)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW)) .thenReturn(Duration.ZERO); - Mockito.when(defaultProfile.getInt(CoreDriverOption.METADATA_SCHEMA_MAX_EVENTS)).thenReturn(1); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)) + .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(context.config()).thenReturn(config); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index b85fa6a2fd9..6de01d69b22 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -70,9 +70,9 @@ public void setup() { MockitoAnnotations.initMocks(this); // Disable debouncing by default, tests that need it will override - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofSeconds(0)); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); @@ -382,9 +382,9 @@ public void should_ignore_removal_of_nonexistent_node() { @Test public void should_coalesce_topology_events() { // Given - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofDays(1)); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(5); new NodeStateManager(context); node1.state = NodeState.FORCED_DOWN; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index 760bb96dead..976356cb58f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.addresstranslation.PassThroughAddressTranslator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -74,13 +74,15 @@ public class SchemaAgreementCheckerTest { @Before public void setup() { - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_TIMEOUT)) + Mockito.when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) + Mockito.when( + defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) .thenReturn(Duration.ofMillis(200)); - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + Mockito.when( + defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ofSeconds(10)); - Mockito.when(defaultConfig.getBoolean(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) + Mockito.when(defaultConfig.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) .thenReturn(true); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); Mockito.when(context.config()).thenReturn(config); @@ -108,7 +110,8 @@ public void setup() { @Test public void should_skip_if_timeout_is_zero() { // Given - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + Mockito.when( + defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ZERO); TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); @@ -252,7 +255,8 @@ public void should_reschedule_if_versions_do_not_match_on_first_try() { @Test public void should_fail_if_versions_do_not_match_after_timeout() { // Given - Mockito.when(defaultConfig.getDuration(CoreDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + Mockito.when( + defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ofNanos(10)); TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); checker.stubQueries( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index e9a9d2f43a7..1f0be9f04ce 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -35,7 +35,7 @@ public class Cassandra21SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) .thenReturn(false); SchemaQueriesWithMockedChannel queries = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index 0480f8944e2..af7232c8657 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -35,7 +35,7 @@ public class Cassandra22SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) .thenReturn(false); SchemaQueriesWithMockedChannel queries = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index 99f47c34f0e..6c000d57968 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -17,7 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -39,7 +39,7 @@ public void setup() { super.setup(); // By default, no keyspace filter - Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) .thenReturn(false); } @@ -50,9 +50,9 @@ public void should_query_without_keyspace_filter() { @Test public void should_query_with_keyspace_filter() { - Mockito.when(config.isDefined(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) .thenReturn(true); - Mockito.when(config.getStringList(CoreDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when(config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) .thenReturn(ImmutableList.of("ks1", "ks2")); should_query_with_where_clause(" WHERE keyspace_name in ('ks1','ks2')"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java index e7c1cecd9bd..52f44faf98b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -50,9 +50,9 @@ public abstract class SchemaQueriesTest { @Before public void setup() { // Whatever, not actually used because the requests are mocked - Mockito.when(config.getDuration(CoreDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT)) + Mockito.when(config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT)) .thenReturn(Duration.ZERO); - Mockito.when(config.getInt(CoreDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE)) + Mockito.when(config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE)) .thenReturn(5000); channel = new EmbeddedChannel(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 63d533cce7a..0c0c20d087f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.times; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -39,7 +39,8 @@ public class ChannelPoolInitTest extends ChannelPoolTestBase { @Test public void should_initialize_when_all_channels_succeed() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -66,7 +67,8 @@ public void should_initialize_when_all_channels_succeed() throws Exception { @Test public void should_initialize_when_all_channels_fail() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -91,7 +93,8 @@ public void should_initialize_when_all_channels_fail() throws Exception { @Test public void should_indicate_when_keyspace_failed_on_all_channels() { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -116,7 +119,8 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { @Test public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); ClusterNameMismatchException error = new ClusterNameMismatchException(ADDRESS, "actual", "expected"); @@ -145,7 +149,8 @@ public void should_reconnect_when_init_incomplete() throws Exception { // Short delay so we don't have to wait in the test Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index 2df0d145df4..b65ed11f752 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -33,7 +33,8 @@ public class ChannelPoolKeyspaceTest extends ChannelPoolTestBase { @Test public void should_switch_keyspace_on_existing_channels() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -69,7 +70,8 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { public void should_switch_keyspace_on_pending_channels() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); CompletableFuture channel1Future = new CompletableFuture<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 1844ad70e7c..dd92c90474f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -40,7 +40,8 @@ public class ChannelPoolReconnectTest extends ChannelPoolTestBase { public void should_reconnect_when_channel_closes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -91,7 +92,8 @@ public void should_reconnect_when_channel_closes() throws Exception { public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -142,7 +144,8 @@ public void should_let_current_attempt_complete_when_reconnecting_now() throws ExecutionException, InterruptedException { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(1); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(1); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index 389547e3395..04aa316f56f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -36,8 +36,10 @@ public class ChannelPoolResizeTest extends ChannelPoolTestBase { @Test public void should_shrink_outside_of_reconnection() throws Exception { - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) + .thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -77,8 +79,10 @@ public void should_shrink_outside_of_reconnection() throws Exception { public void should_shrink_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) + .thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -138,8 +142,10 @@ public void should_shrink_during_reconnection() throws Exception { public void should_grow_outside_of_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) + .thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -188,8 +194,10 @@ public void should_grow_outside_of_reconnection() throws Exception { public void should_grow_during_reconnection() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) + .thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -261,7 +269,8 @@ public void should_grow_during_reconnection() throws Exception { public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -290,7 +299,8 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc assertThat(pool.channels).containsOnly(channel1, channel2); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); @@ -312,7 +322,8 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc public void should_resize_during_reconnection_if_config_changes() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -350,7 +361,8 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); @@ -385,7 +397,8 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti public void should_ignore_config_change_if_not_relevant() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -408,7 +421,8 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); // Config changes, but not for our distance - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(1); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) + .thenReturn(1); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 1f9152e6e8d..2d0448655f8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -38,7 +38,8 @@ public class ChannelPoolShutdownTest extends ChannelPoolTestBase { public void should_close_all_channels_when_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -109,7 +110,8 @@ public void should_close_all_channels_when_closed() throws Exception { public void should_force_close_all_channels_when_force_closed() throws Exception { Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(CoreDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) + .thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index 447843a16ab..9dea2b37172 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -106,15 +106,15 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); // Config: - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_ENABLED)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) .thenReturn(false); - Mockito.when(defaultConfigProfile.isDefined(CoreDriverOption.PROTOCOL_VERSION)) + Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ZERO); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(context.config()).thenReturn(config); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index a046f73269f..ad2f36bfdfc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -75,13 +75,13 @@ public void setup() { Mockito.when(eventLoop.inEventLoop()).thenReturn(true); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getDuration(CoreDriverOption.REPREPARE_TIMEOUT)) + Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.REPREPARE_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)) .thenReturn(0); - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(100); Mockito.when(context.config()).thenReturn(config); @@ -168,7 +168,7 @@ public void should_reprepare_all_if_system_table_empty() { @Test public void should_reprepare_all_if_system_query_disabled() { - Mockito.when(defaultConfigProfile.getBoolean(CoreDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(false); MockReprepareOnUp reprepareOnUp = @@ -230,7 +230,7 @@ public void should_proceed_if_schema_agreement_fails() { @Test public void should_limit_number_of_statements_to_reprepare() { - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_STATEMENTS)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)) .thenReturn(3); MockReprepareOnUp reprepareOnUp = @@ -259,7 +259,7 @@ public void should_limit_number_of_statements_to_reprepare() { @Test public void should_limit_number_of_statements_reprepared_in_parallel() { - Mockito.when(defaultConfigProfile.getInt(CoreDriverOption.REPREPARE_MAX_PARALLELISM)) + Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(3); MockReprepareOnUp reprepareOnUp = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java index 43b0261976a..9c2a0ebcda0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java @@ -54,7 +54,7 @@ public void should_periodically_reload_configuration() throws Exception { () -> ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), - CoreDriverOption.values()); + DefaultDriverOption.values()); try (CqlSession session = CqlSession.builder() .withConfigLoader(loader) @@ -89,7 +89,7 @@ public void should_reload_configuration_when_event_fired() throws Exception { () -> ConfigFactory.parseString("config-reload-interval = 0\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), - CoreDriverOption.values()); + DefaultDriverOption.values()); try (CqlSession session = CqlSession.builder() .withConfigLoader(loader) @@ -127,7 +127,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { () -> ConfigFactory.parseString("config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), - CoreDriverOption.values()); + DefaultDriverOption.values()); try (CqlSession session = CqlSession.builder() .withConfigLoader(loader) @@ -167,7 +167,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio "profiles.slow.request.consistency = ONE\nconfig-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get()), - CoreDriverOption.values()); + DefaultDriverOption.values()); try (CqlSession session = CqlSession.builder() .withConfigLoader(loader) diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 37d6cc82a35..10b53b253b9 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -137,12 +137,12 @@ config.getNamedProfiles().forEach((name, profile) -> ...); [DriverConfigProfile] has typed option getters: ```java -Duration requestTimeout = defaultProfile.getDuration(CoreDriverOption.REQUEST_TIMEOUT); -int maxRequestsPerConnection = defaultProfile.getInt(CoreDriverOption.CONNECTION_MAX_REQUESTS); +Duration requestTimeout = defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); +int maxRequestsPerConnection = defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); ``` -Note that we use the [CoreDriverOption] enum to access built-in options, but the method takes a more -generic [DriverOption] interface. This is intended to allow custom options, see the +Note that we use the [DefaultDriverOption] enum to access built-in options, but the method takes a +more generic [DriverOption] interface. This is intended to allow custom options, see the [Advanced topics](#custom-options) section. #### Derived profiles @@ -158,7 +158,7 @@ that overrides a subset of options: DriverConfigProfile defaultProfile = session.getContext().config().getDefaultProfile(); DriverConfigProfile dynamicProfile = defaultProfile.withConsistencyLevel( - CoreDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.EACH_QUORUM); + DefaultDriverOption.REQUEST_CONSISTENCY, ConsistencyLevel.EACH_QUORUM); SimpleStatement s = SimpleStatement.builder("SELECT name FROM user WHERE id = 1") .withConfigProfile(dynamicProfile) @@ -229,13 +229,13 @@ implementation to the rest of the driver. Here we use the built-in class, but te TypeSafe Config object with the previous method: ```java -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; DriverConfigLoader session1ConfigLoader = new DefaultDriverConfigLoader( - () -> loadConfig("session1"), CoreDriverOption.values()); + () -> loadConfig("session1"), DefaultDriverOption.values()); ``` Finally, pass the config loader when building the driver: @@ -265,7 +265,7 @@ DriverConfigLoader loader = Config application = ConfigFactory.parseString(configSource); return application.withFallback(reference); }, - CoreDriverOption.values()); + DefaultDriverOption.values()); CqlSession session = CqlSession.builder().withConfigLoader(loader).build(); ``` @@ -408,7 +408,7 @@ Pass the options to the config loader: CqlSession session = CqlSession.builder() .withConfigLoader(new DefaultDriverConfigLoader( DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER, - CoreDriverOption.values(), // don't forget to keep the core options + DefaultDriverOption.values(), // don't forget to keep the core options MyCustomOption.values())) .build(); ``` @@ -436,7 +436,7 @@ config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); [DriverConfig]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html [DriverConfigProfile]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigProfile.html [DriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html -[CoreDriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/CoreDriverOption.html +[DefaultDriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html [DriverConfigLoader]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html [TypeSafe Config]: https://github.com/typesafehub/config diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 5249d780638..5092eeeb718 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -187,6 +187,6 @@ public static DriverConfigProfile slowProfile(Session session) { .getContext() .config() .getDefaultProfile() - .withString(CoreDriverOption.REQUEST_TIMEOUT, "30s"); + .withString(DefaultDriverOption.REQUEST_TIMEOUT, "30s"); } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java index a7af47000e7..49320c25dc5 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.testinfra.session; -import com.datastax.oss.driver.api.core.config.CoreDriverOption; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.typesafe.config.Config; @@ -24,7 +24,7 @@ public class TestConfigLoader extends DefaultDriverConfigLoader { public TestConfigLoader(String... customOptions) { - super(() -> buildConfig(customOptions), CoreDriverOption.values()); + super(() -> buildConfig(customOptions), DefaultDriverOption.values()); } private static Config buildConfig(String... customOptions) { From 678f9eeebf33d639ea48b72611fe6654481debe3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 11:53:12 -0800 Subject: [PATCH 334/742] Rename CoreNodeMetric to DefaultNodeMetric --- ...NodeMetric.java => DefaultNodeMetric.java} | 16 ++--- .../driver/api/core/metrics/NodeMetric.java | 4 +- .../core/cql/CqlRequestHandlerBase.java | 52 +++++++-------- .../metrics/DefaultMetricUpdaterFactory.java | 6 +- .../metrics/DefaultNodeMetricUpdater.java | 56 ++++++++-------- .../internal/core/pool/ChannelPool.java | 6 +- .../core/cql/CqlRequestHandlerRetryTest.java | 64 +++++++++---------- ...equestHandlerSpeculativeExecutionTest.java | 8 +-- .../core/pool/ChannelPoolInitTest.java | 10 +-- 9 files changed, 112 insertions(+), 110 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/metrics/{CoreNodeMetric.java => DefaultNodeMetric.java} (82%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultNodeMetric.java similarity index 82% rename from core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java rename to core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultNodeMetric.java index 8afef5601cb..de547a7b5da 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreNodeMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultNodeMetric.java @@ -19,7 +19,7 @@ import java.util.Map; /** See {@code reference.conf} for a description of each metric. */ -public enum CoreNodeMetric implements NodeMetric { +public enum DefaultNodeMetric implements NodeMetric { OPEN_CONNECTIONS("pool.open-connections"), AVAILABLE_STREAMS("pool.available-streams"), IN_FLIGHT("pool.in-flight"), @@ -48,11 +48,11 @@ public enum CoreNodeMetric implements NodeMetric { AUTHENTICATION_ERRORS("errors.connection.auth"), ; - private static final Map BY_PATH = sortByPath(); + private static final Map BY_PATH = sortByPath(); private final String path; - CoreNodeMetric(String path) { + DefaultNodeMetric(String path) { this.path = path; } @@ -61,17 +61,17 @@ public String getPath() { return path; } - public static CoreNodeMetric fromPath(String path) { - CoreNodeMetric metric = BY_PATH.get(path); + public static DefaultNodeMetric fromPath(String path) { + DefaultNodeMetric metric = BY_PATH.get(path); if (metric == null) { throw new IllegalArgumentException("Unknown node metric path " + path); } return metric; } - private static Map sortByPath() { - ImmutableMap.Builder result = ImmutableMap.builder(); - for (CoreNodeMetric value : values()) { + private static Map sortByPath() { + ImmutableMap.Builder result = ImmutableMap.builder(); + for (DefaultNodeMetric value : values()) { result.put(value.getPath(), value); } return result.build(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java index 869f6df3b91..2a1d6596a6d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/NodeMetric.java @@ -36,8 +36,8 @@ * Meter retriesMeter = session.getMetricRegistry().meter("s0.nodes.0:0:0:0:0:0:0:1_9042.retries.total"); * * - *

          All metrics exposed out of the box by the driver are instances of {@link CoreNodeMetric} (this - * interface only exists to allow custom metrics in driver extensions). + *

          All metrics exposed out of the box by the driver are instances of {@link DefaultNodeMetric} + * (this interface only exists to allow custom metrics in driver extensions). * * @see SessionMetric */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 653c86e2e86..cef52a7d951 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -26,8 +26,8 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -362,7 +362,9 @@ public void operationComplete(Future future) throws Exception { channel, error); recordError(node, error); - ((DefaultNode) node).getMetricUpdater().incrementCounter(CoreNodeMetric.UNSENT_REQUESTS); + ((DefaultNode) node) + .getMetricUpdater() + .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS); sendRequest(null, execution, retryCount, scheduleNextExecution); // try next node } } else { @@ -399,7 +401,7 @@ public void operationComplete(Future future) throws Exception { startedSpeculativeExecutionsCount.incrementAndGet(); ((DefaultNode) node) .getMetricUpdater() - .incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); + .incrementCounter(DefaultNodeMetric.SPECULATIVE_EXECUTIONS); sendRequest(null, nextExecution, 0, true); } }, @@ -421,7 +423,7 @@ public void onResponse(Frame responseFrame) { ((DefaultNode) node) .getMetricUpdater() .updateTimer( - CoreNodeMetric.CQL_MESSAGES, System.nanoTime() - start, TimeUnit.NANOSECONDS); + DefaultNodeMetric.CQL_MESSAGES, System.nanoTime() - start, TimeUnit.NANOSECONDS); inFlightCallbacks.remove(this); if (result.isDone()) { return; @@ -519,7 +521,7 @@ private void processErrorResponse(Error errorMessage) { || error instanceof FunctionFailureException || error instanceof ProtocolError) { LOG.debug("[{}] Unrecoverable error, rethrowing", logPrefix); - metricUpdater.incrementCounter(CoreNodeMetric.OTHER_ERRORS); + metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS); setFinalError(error); } else { RetryDecision decision; @@ -536,9 +538,9 @@ private void processErrorResponse(Error errorMessage) { updateErrorMetrics( metricUpdater, decision, - CoreNodeMetric.READ_TIMEOUTS, - CoreNodeMetric.RETRIES_ON_READ_TIMEOUT, - CoreNodeMetric.IGNORES_ON_READ_TIMEOUT); + DefaultNodeMetric.READ_TIMEOUTS, + DefaultNodeMetric.RETRIES_ON_READ_TIMEOUT, + DefaultNodeMetric.IGNORES_ON_READ_TIMEOUT); } else if (error instanceof WriteTimeoutException) { WriteTimeoutException writeTimeout = (WriteTimeoutException) error; decision = @@ -554,9 +556,9 @@ private void processErrorResponse(Error errorMessage) { updateErrorMetrics( metricUpdater, decision, - CoreNodeMetric.WRITE_TIMEOUTS, - CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT, - CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT); + DefaultNodeMetric.WRITE_TIMEOUTS, + DefaultNodeMetric.RETRIES_ON_WRITE_TIMEOUT, + DefaultNodeMetric.IGNORES_ON_WRITE_TIMEOUT); } else if (error instanceof UnavailableException) { UnavailableException unavailable = (UnavailableException) error; decision = @@ -569,9 +571,9 @@ private void processErrorResponse(Error errorMessage) { updateErrorMetrics( metricUpdater, decision, - CoreNodeMetric.UNAVAILABLES, - CoreNodeMetric.RETRIES_ON_UNAVAILABLE, - CoreNodeMetric.IGNORES_ON_UNAVAILABLE); + DefaultNodeMetric.UNAVAILABLES, + DefaultNodeMetric.RETRIES_ON_UNAVAILABLE, + DefaultNodeMetric.IGNORES_ON_UNAVAILABLE); } else { decision = isIdempotent @@ -580,9 +582,9 @@ private void processErrorResponse(Error errorMessage) { updateErrorMetrics( metricUpdater, decision, - CoreNodeMetric.OTHER_ERRORS, - CoreNodeMetric.RETRIES_ON_OTHER_ERROR, - CoreNodeMetric.IGNORES_ON_OTHER_ERROR); + DefaultNodeMetric.OTHER_ERRORS, + DefaultNodeMetric.RETRIES_ON_OTHER_ERROR, + DefaultNodeMetric.IGNORES_ON_OTHER_ERROR); } processRetryDecision(decision, error); } @@ -611,18 +613,18 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { private void updateErrorMetrics( NodeMetricUpdater metricUpdater, RetryDecision decision, - CoreNodeMetric error, - CoreNodeMetric retriesOnError, - CoreNodeMetric ignoresOnError) { + DefaultNodeMetric error, + DefaultNodeMetric retriesOnError, + DefaultNodeMetric ignoresOnError) { metricUpdater.incrementCounter(error); switch (decision) { case RETRY_SAME: case RETRY_NEXT: - metricUpdater.incrementCounter(CoreNodeMetric.RETRIES); + metricUpdater.incrementCounter(DefaultNodeMetric.RETRIES); metricUpdater.incrementCounter(retriesOnError); break; case IGNORE: - metricUpdater.incrementCounter(CoreNodeMetric.IGNORES); + metricUpdater.incrementCounter(DefaultNodeMetric.IGNORES); metricUpdater.incrementCounter(ignoresOnError); break; case RETHROW: @@ -647,9 +649,9 @@ public void onFailure(Throwable error) { updateErrorMetrics( ((DefaultNode) node).getMetricUpdater(), decision, - CoreNodeMetric.ABORTED_REQUESTS, - CoreNodeMetric.RETRIES_ON_ABORTED, - CoreNodeMetric.IGNORES_ON_ABORTED); + DefaultNodeMetric.ABORTED_REQUESTS, + DefaultNodeMetric.RETRIES_ON_ABORTED, + DefaultNodeMetric.IGNORES_ON_ABORTED); } public void cancel() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java index b81005933c5..8915219f860 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.NodeMetric; import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -72,10 +72,10 @@ private Set parseSessionMetricPaths(List paths) { } private Set parseNodeMetricPaths(List paths) { - EnumSet result = EnumSet.noneOf(CoreNodeMetric.class); + EnumSet result = EnumSet.noneOf(DefaultNodeMetric.class); for (String path : paths) { try { - result.add(CoreNodeMetric.fromPath(path)); + result.add(DefaultNodeMetric.fromPath(path)); } catch (IllegalArgumentException e) { LOG.warn("[{}] Unknown node metric {}, skipping", logPrefix, path); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java index 2d6ed227663..757a5ca543e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultNodeMetricUpdater.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.NodeMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.pool.ChannelPool; @@ -42,43 +42,43 @@ public DefaultNodeMetricUpdater( DriverConfigProfile config = context.config().getDefaultProfile(); - if (enabledMetrics.contains(CoreNodeMetric.OPEN_CONNECTIONS)) { + if (enabledMetrics.contains(DefaultNodeMetric.OPEN_CONNECTIONS)) { metricRegistry.register( - buildFullName(CoreNodeMetric.OPEN_CONNECTIONS), + buildFullName(DefaultNodeMetric.OPEN_CONNECTIONS), (Gauge) node::getOpenConnections); } initializePoolGauge( - CoreNodeMetric.AVAILABLE_STREAMS, node, ChannelPool::getAvailableIds, context); - initializePoolGauge(CoreNodeMetric.IN_FLIGHT, node, ChannelPool::getInFlight, context); + DefaultNodeMetric.AVAILABLE_STREAMS, node, ChannelPool::getAvailableIds, context); + initializePoolGauge(DefaultNodeMetric.IN_FLIGHT, node, ChannelPool::getInFlight, context); initializePoolGauge( - CoreNodeMetric.ORPHANED_STREAMS, node, ChannelPool::getOrphanedIds, context); + DefaultNodeMetric.ORPHANED_STREAMS, node, ChannelPool::getOrphanedIds, context); initializeHdrTimer( - CoreNodeMetric.CQL_MESSAGES, + DefaultNodeMetric.CQL_MESSAGES, config, DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_HIGHEST, DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_DIGITS, DefaultDriverOption.METRICS_NODE_CQL_MESSAGES_INTERVAL); - initializeDefaultCounter(CoreNodeMetric.UNSENT_REQUESTS); - initializeDefaultCounter(CoreNodeMetric.ABORTED_REQUESTS); - initializeDefaultCounter(CoreNodeMetric.WRITE_TIMEOUTS); - initializeDefaultCounter(CoreNodeMetric.READ_TIMEOUTS); - initializeDefaultCounter(CoreNodeMetric.UNAVAILABLES); - initializeDefaultCounter(CoreNodeMetric.OTHER_ERRORS); - initializeDefaultCounter(CoreNodeMetric.RETRIES); - initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_ABORTED); - initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_READ_TIMEOUT); - initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT); - initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_UNAVAILABLE); - initializeDefaultCounter(CoreNodeMetric.RETRIES_ON_OTHER_ERROR); - initializeDefaultCounter(CoreNodeMetric.IGNORES); - initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_ABORTED); - initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_READ_TIMEOUT); - initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT); - initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_UNAVAILABLE); - initializeDefaultCounter(CoreNodeMetric.IGNORES_ON_OTHER_ERROR); - initializeDefaultCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); - initializeDefaultCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); - initializeDefaultCounter(CoreNodeMetric.AUTHENTICATION_ERRORS); + initializeDefaultCounter(DefaultNodeMetric.UNSENT_REQUESTS); + initializeDefaultCounter(DefaultNodeMetric.ABORTED_REQUESTS); + initializeDefaultCounter(DefaultNodeMetric.WRITE_TIMEOUTS); + initializeDefaultCounter(DefaultNodeMetric.READ_TIMEOUTS); + initializeDefaultCounter(DefaultNodeMetric.UNAVAILABLES); + initializeDefaultCounter(DefaultNodeMetric.OTHER_ERRORS); + initializeDefaultCounter(DefaultNodeMetric.RETRIES); + initializeDefaultCounter(DefaultNodeMetric.RETRIES_ON_ABORTED); + initializeDefaultCounter(DefaultNodeMetric.RETRIES_ON_READ_TIMEOUT); + initializeDefaultCounter(DefaultNodeMetric.RETRIES_ON_WRITE_TIMEOUT); + initializeDefaultCounter(DefaultNodeMetric.RETRIES_ON_UNAVAILABLE); + initializeDefaultCounter(DefaultNodeMetric.RETRIES_ON_OTHER_ERROR); + initializeDefaultCounter(DefaultNodeMetric.IGNORES); + initializeDefaultCounter(DefaultNodeMetric.IGNORES_ON_ABORTED); + initializeDefaultCounter(DefaultNodeMetric.IGNORES_ON_READ_TIMEOUT); + initializeDefaultCounter(DefaultNodeMetric.IGNORES_ON_WRITE_TIMEOUT); + initializeDefaultCounter(DefaultNodeMetric.IGNORES_ON_UNAVAILABLE); + initializeDefaultCounter(DefaultNodeMetric.IGNORES_ON_OTHER_ERROR); + initializeDefaultCounter(DefaultNodeMetric.SPECULATIVE_EXECUTIONS); + initializeDefaultCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS); + initializeDefaultCounter(DefaultNodeMetric.AUTHENTICATION_ERRORS); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 3e2f1ec0434..8b62fc6266f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; @@ -293,8 +293,8 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { .getMetricUpdater() .incrementCounter( error instanceof AuthenticationException - ? CoreNodeMetric.AUTHENTICATION_ERRORS - : CoreNodeMetric.CONNECTION_INIT_ERRORS); + ? DefaultNodeMetric.AUTHENTICATION_ERRORS + : DefaultNodeMetric.CONNECTION_INIT_ERRORS); if (error instanceof ClusterNameMismatchException || error instanceof UnsupportedProtocolVersionException) { // This will likely be thrown by all channels, but finish the loop cleanly diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index e1c83aa9d69..6727963da12 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -29,7 +29,7 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -120,10 +120,10 @@ public void should_always_rethrow_query_validation_error( .hasMessage("mock message"); Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy()); - Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.OTHER_ERRORS); + Mockito.verify(nodeMetricUpdater1).incrementCounter(DefaultNodeMetric.OTHER_ERRORS); Mockito.verify(nodeMetricUpdater1) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -159,11 +159,11 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); - Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.RETRIES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(DefaultNodeMetric.RETRIES); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.retryMetric); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -199,11 +199,11 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); - Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.RETRIES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(DefaultNodeMetric.RETRIES); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.retryMetric); Mockito.verify(nodeMetricUpdater1, atMost(2)) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -236,11 +236,11 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getErrors()).hasSize(0); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); - Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.IGNORES); + Mockito.verify(nodeMetricUpdater1).incrementCounter(DefaultNodeMetric.IGNORES); Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.ignoreMetric); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -271,7 +271,7 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -316,7 +316,7 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re Mockito.verify(nodeMetricUpdater1).incrementCounter(failureScenario.errorMetric); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( - eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); }); } @@ -328,15 +328,15 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re */ private abstract static class FailureScenario { private final Class expectedExceptionClass; - final CoreNodeMetric errorMetric; - final CoreNodeMetric retryMetric; - final CoreNodeMetric ignoreMetric; + final DefaultNodeMetric errorMetric; + final DefaultNodeMetric retryMetric; + final DefaultNodeMetric ignoreMetric; protected FailureScenario( Class expectedExceptionClass, - CoreNodeMetric errorMetric, - CoreNodeMetric retryMetric, - CoreNodeMetric ignoreMetric) { + DefaultNodeMetric errorMetric, + DefaultNodeMetric retryMetric, + DefaultNodeMetric ignoreMetric) { this.expectedExceptionClass = expectedExceptionClass; this.errorMetric = errorMetric; this.retryMetric = retryMetric; @@ -353,9 +353,9 @@ public static Object[][] failure() { return TestDataProviders.fromList( new FailureScenario( ReadTimeoutException.class, - CoreNodeMetric.READ_TIMEOUTS, - CoreNodeMetric.RETRIES_ON_READ_TIMEOUT, - CoreNodeMetric.IGNORES_ON_READ_TIMEOUT) { + DefaultNodeMetric.READ_TIMEOUTS, + DefaultNodeMetric.RETRIES_ON_READ_TIMEOUT, + DefaultNodeMetric.IGNORES_ON_READ_TIMEOUT) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -380,9 +380,9 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) }, new FailureScenario( WriteTimeoutException.class, - CoreNodeMetric.WRITE_TIMEOUTS, - CoreNodeMetric.RETRIES_ON_WRITE_TIMEOUT, - CoreNodeMetric.IGNORES_ON_WRITE_TIMEOUT) { + DefaultNodeMetric.WRITE_TIMEOUTS, + DefaultNodeMetric.RETRIES_ON_WRITE_TIMEOUT, + DefaultNodeMetric.IGNORES_ON_WRITE_TIMEOUT) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -411,9 +411,9 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) }, new FailureScenario( UnavailableException.class, - CoreNodeMetric.UNAVAILABLES, - CoreNodeMetric.RETRIES_ON_UNAVAILABLE, - CoreNodeMetric.IGNORES_ON_UNAVAILABLE) { + DefaultNodeMetric.UNAVAILABLES, + DefaultNodeMetric.RETRIES_ON_UNAVAILABLE, + DefaultNodeMetric.IGNORES_ON_UNAVAILABLE) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -437,9 +437,9 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) }, new FailureScenario( ServerError.class, - CoreNodeMetric.OTHER_ERRORS, - CoreNodeMetric.RETRIES_ON_OTHER_ERROR, - CoreNodeMetric.IGNORES_ON_OTHER_ERROR) { + DefaultNodeMetric.OTHER_ERRORS, + DefaultNodeMetric.RETRIES_ON_OTHER_ERROR, + DefaultNodeMetric.IGNORES_ON_OTHER_ERROR) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponse( @@ -458,9 +458,9 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) }, new FailureScenario( HeartbeatException.class, - CoreNodeMetric.ABORTED_REQUESTS, - CoreNodeMetric.RETRIES_ON_ABORTED, - CoreNodeMetric.IGNORES_ON_ABORTED) { + DefaultNodeMetric.ABORTED_REQUESTS, + DefaultNodeMetric.RETRIES_ON_ABORTED, + DefaultNodeMetric.IGNORES_ON_ABORTED) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { builder.withResponseFailure(node, Mockito.mock(HeartbeatException.class)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index b6acf194811..ce940c96627 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -25,7 +25,7 @@ import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; @@ -107,7 +107,7 @@ public void should_schedule_speculative_executions( .isEqualTo(firstExecutionDelay); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); firstExecutionTask.run(); - Mockito.verify(nodeMetricUpdater1).incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); + Mockito.verify(nodeMetricUpdater1).incrementCounter(DefaultNodeMetric.SPECULATIVE_EXECUTIONS); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); @@ -117,7 +117,7 @@ public void should_schedule_speculative_executions( .isEqualTo(secondExecutionDelay); Mockito.verifyNoMoreInteractions(nodeMetricUpdater2); secondExecutionTask.run(); - Mockito.verify(nodeMetricUpdater2).incrementCounter(CoreNodeMetric.SPECULATIVE_EXECUTIONS); + Mockito.verify(nodeMetricUpdater2).incrementCounter(DefaultNodeMetric.SPECULATIVE_EXECUTIONS); node3Behavior.verifyWrite(); node3Behavior.setWriteSuccess(); @@ -169,7 +169,7 @@ public void should_not_start_execution_if_result_complete( assertThat(firstExecutionTask.isCancelled()).isTrue(); Mockito.verify(nodeMetricUpdater1) - .updateTimer(eq(CoreNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); + .updateTimer(eq(DefaultNodeMetric.CQL_MESSAGES), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); // Run the task anyway; we're bending reality a bit here since the task is already cancelled diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 0c0c20d087f..9d3f7a2f7a8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.api.core.metrics.CoreNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.ClusterNameMismatchException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -86,7 +86,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); Mockito.verify(nodeMetricUpdater, times(3)) - .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } @@ -113,7 +113,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { pool -> { assertThat(pool.isInvalidKeyspace()).isTrue(); Mockito.verify(nodeMetricUpdater, times(3)) - .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS); }); } @@ -140,7 +140,7 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); Mockito.verify(nodeMetricUpdater, times(3)) - .incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } @@ -188,7 +188,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); - Mockito.verify(nodeMetricUpdater).incrementCounter(CoreNodeMetric.CONNECTION_INIT_ERRORS); + Mockito.verify(nodeMetricUpdater).incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS); factoryHelper.verifyNoMoreCalls(); } } From 51a09ac60625d5d1ca33cce6bb3af0c560f3f8be Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 11:55:04 -0800 Subject: [PATCH 335/742] Rename CoreSessionMetric to DefaultSessionMetric --- ...sionMetric.java => DefaultSessionMetric.java} | 16 ++++++++-------- .../driver/api/core/metrics/SessionMetric.java | 2 +- .../internal/core/cql/CqlRequestHandlerBase.java | 6 +++--- .../metrics/DefaultMetricUpdaterFactory.java | 6 +++--- .../metrics/DefaultSessionMetricUpdater.java | 10 +++++----- .../oss/driver/api/core/metrics/MetricsIT.java | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/metrics/{CoreSessionMetric.java => DefaultSessionMetric.java} (71%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java similarity index 71% rename from core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java rename to core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java index 380b58f5603..981ac949411 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/CoreSessionMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java @@ -19,17 +19,17 @@ import java.util.Map; /** See {@code reference.conf} for a description of each metric. */ -public enum CoreSessionMetric implements SessionMetric { +public enum DefaultSessionMetric implements SessionMetric { CONNECTED_NODES("connected-nodes"), CQL_REQUESTS("cql-requests"), CQL_CLIENT_TIMEOUTS("cql-client-timeouts"), ; - private static final Map BY_PATH = sortByPath(); + private static final Map BY_PATH = sortByPath(); private final String path; - CoreSessionMetric(String path) { + DefaultSessionMetric(String path) { this.path = path; } @@ -38,17 +38,17 @@ public String getPath() { return path; } - public static CoreSessionMetric fromPath(String path) { - CoreSessionMetric metric = BY_PATH.get(path); + public static DefaultSessionMetric fromPath(String path) { + DefaultSessionMetric metric = BY_PATH.get(path); if (metric == null) { throw new IllegalArgumentException("Unknown session metric path " + path); } return metric; } - private static Map sortByPath() { - ImmutableMap.Builder result = ImmutableMap.builder(); - for (CoreSessionMetric value : values()) { + private static Map sortByPath() { + ImmutableMap.Builder result = ImmutableMap.builder(); + for (DefaultSessionMetric value : values()) { result.put(value.getPath(), value); } return result.build(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java index 74289609ec7..ba406934b68 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/SessionMetric.java @@ -28,7 +28,7 @@ * Timer requestsTimer = session.getMetricRegistry().timer("s0.cql_requests"); * * - *

          All metrics exposed out of the box by the driver are instances of {@link CoreSessionMetric} + *

          All metrics exposed out of the box by the driver are instances of {@link DefaultSessionMetric} * (this interface only exists to allow custom metrics in driver extensions). * * @see NodeMetric diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index cef52a7d951..13ec35b7aee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -26,8 +26,8 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -273,7 +273,7 @@ private void setFinalResult( session .getMetricUpdater() .updateTimer( - CoreSessionMetric.CQL_REQUESTS, + DefaultSessionMetric.CQL_REQUESTS, System.nanoTime() - startTimeNanos, TimeUnit.NANOSECONDS); } @@ -307,7 +307,7 @@ private void setFinalError(Throwable error) { if (result.completeExceptionally(error)) { cancelScheduledTasks(); if (error instanceof DriverTimeoutException) { - session.getMetricUpdater().incrementCounter(CoreSessionMetric.CQL_CLIENT_TIMEOUTS); + session.getMetricUpdater().incrementCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java index 8915219f860..a21ee3f21ee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java @@ -18,8 +18,8 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.metrics.NodeMetric; import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -60,10 +60,10 @@ public NodeMetricUpdater newNodeUpdater(Node node) { } private Set parseSessionMetricPaths(List paths) { - EnumSet result = EnumSet.noneOf(CoreSessionMetric.class); + EnumSet result = EnumSet.noneOf(DefaultSessionMetric.class); for (String path : paths) { try { - result.add(CoreSessionMetric.fromPath(path)); + result.add(DefaultSessionMetric.fromPath(path)); } catch (IllegalArgumentException e) { LOG.warn("[{}] Unknown session metric {}, skipping", logPrefix, path); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java index 80d7729579c..bafccbe4531 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java @@ -18,7 +18,7 @@ import com.codahale.metrics.Gauge; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metrics.CoreSessionMetric; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import java.util.Set; @@ -33,9 +33,9 @@ public DefaultSessionMetricUpdater( super(enabledMetrics, context.metricRegistry()); this.metricNamePrefix = context.sessionName() + "."; - if (enabledMetrics.contains(CoreSessionMetric.CONNECTED_NODES)) { + if (enabledMetrics.contains(DefaultSessionMetric.CONNECTED_NODES)) { metricRegistry.register( - buildFullName(CoreSessionMetric.CONNECTED_NODES), + buildFullName(DefaultSessionMetric.CONNECTED_NODES), (Gauge) () -> { int count = 0; @@ -48,12 +48,12 @@ public DefaultSessionMetricUpdater( }); } initializeHdrTimer( - CoreSessionMetric.CQL_REQUESTS, + DefaultSessionMetric.CQL_REQUESTS, context.config().getDefaultProfile(), DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); - initializeDefaultCounter(CoreSessionMetric.CQL_CLIENT_TIMEOUTS); + initializeDefaultCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java index 4b3a319de8f..1e8c66f85ae 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java @@ -44,7 +44,7 @@ public void should_expose_metrics() { // run before, so do a linear search to find the metric we're interested in. Timer requestsTimer = null; for (Map.Entry entry : session.getMetricRegistry().getTimers().entrySet()) { - if (entry.getKey().endsWith(CoreSessionMetric.CQL_REQUESTS.getPath())) { + if (entry.getKey().endsWith(DefaultSessionMetric.CQL_REQUESTS.getPath())) { requestsTimer = entry.getValue(); } } @@ -66,7 +66,7 @@ public void should_not_expose_metrics_if_disabled() { Timer requestsTimer = null; for (Map.Entry entry : session.getMetricRegistry().getTimers().entrySet()) { - if (entry.getKey().endsWith(CoreSessionMetric.CQL_REQUESTS.name())) { + if (entry.getKey().endsWith(DefaultSessionMetric.CQL_REQUESTS.name())) { requestsTimer = entry.getValue(); } } From ff395d0c5e66eb98cdaca18aae1ee4cafa1f5bf6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 12:52:24 -0800 Subject: [PATCH 336/742] Rename CoreWriteType to DefaultWriteType --- .../oss/driver/api/core/retry/DefaultRetryPolicy.java | 4 ++-- .../{CoreWriteType.java => DefaultWriteType.java} | 2 +- .../oss/driver/api/core/servererrors/WriteType.java | 2 +- .../core/servererrors/DefaultWriteTypeRegistry.java | 6 +++--- .../oss/driver/api/core/retry/DefaultRetryPolicyTest.java | 4 ++-- .../internal/core/cql/CqlRequestHandlerRetryTest.java | 4 ++-- .../oss/driver/api/core/retry/DefaultRetryPolicyIT.java | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/servererrors/{CoreWriteType.java => DefaultWriteType.java} (97%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java index 0b3a30b7982..ee2eb795819 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicy.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; -import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; +import com.datastax.oss.driver.api.core.servererrors.DefaultWriteType; import com.datastax.oss.driver.api.core.servererrors.ReadFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteType; @@ -84,7 +84,7 @@ public RetryDecision onWriteTimeout( int received, int retryCount) { - return (retryCount == 0 && writeType == CoreWriteType.BATCH_LOG) + return (retryCount == 0 && writeType == DefaultWriteType.BATCH_LOG) ? RetryDecision.RETRY_SAME : RetryDecision.RETHROW; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/DefaultWriteType.java similarity index 97% rename from core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/servererrors/DefaultWriteType.java index 7e2637e590a..b7fe225ff61 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoreWriteType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/DefaultWriteType.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.servererrors; /** A default write type supported by the driver out of the box. */ -public enum CoreWriteType implements WriteType { +public enum DefaultWriteType implements WriteType { /** A write to a single partition key. Such writes are guaranteed to be atomic and isolated. */ SIMPLE, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java index 84e038d8247..ae895658f25 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteType.java @@ -23,7 +23,7 @@ * *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all - * {@code WriteType}s are {@link CoreWriteType} instances. + * {@code WriteType}s are {@link DefaultWriteType} instances. */ public interface WriteType { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java index 88bb06947dd..3ba9174c091 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/servererrors/DefaultWriteTypeRegistry.java @@ -15,18 +15,18 @@ */ package com.datastax.oss.driver.internal.core.servererrors; -import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; +import com.datastax.oss.driver.api.core.servererrors.DefaultWriteType; import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.google.common.collect.ImmutableList; public class DefaultWriteTypeRegistry implements WriteTypeRegistry { private static final ImmutableList values = - ImmutableList.builder().add(CoreWriteType.values()).build(); + ImmutableList.builder().add(DefaultWriteType.values()).build(); @Override public WriteType fromName(String name) { - return CoreWriteType.valueOf(name); + return DefaultWriteType.valueOf(name); } @Override diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java index 924f1eadbd4..0311a544ab1 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyTest.java @@ -19,8 +19,8 @@ import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETHROW; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_NEXT; import static com.datastax.oss.driver.api.core.retry.RetryDecision.RETRY_SAME; -import static com.datastax.oss.driver.api.core.servererrors.CoreWriteType.BATCH_LOG; -import static com.datastax.oss.driver.api.core.servererrors.CoreWriteType.SIMPLE; +import static com.datastax.oss.driver.api.core.servererrors.DefaultWriteType.BATCH_LOG; +import static com.datastax.oss.driver.api.core.servererrors.DefaultWriteType.SIMPLE; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 6727963da12..655271d0dac 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -33,7 +33,7 @@ import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; -import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; +import com.datastax.oss.driver.api.core.servererrors.DefaultWriteType; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; import com.datastax.oss.driver.api.core.servererrors.ServerError; @@ -402,7 +402,7 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) policy.onWriteTimeout( any(SimpleStatement.class), eq(DefaultConsistencyLevel.LOCAL_ONE), - eq(CoreWriteType.SIMPLE), + eq(DefaultWriteType.SIMPLE), eq(2), eq(1), eq(0))) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java index 42774f4fa20..ffafd1e69da 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/DefaultRetryPolicyIT.java @@ -33,7 +33,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.servererrors.CoreWriteType; +import com.datastax.oss.driver.api.core.servererrors.DefaultWriteType; import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; @@ -278,7 +278,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { assertThat(wte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); - assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); + assertThat(wte.getWriteType()).isEqualTo(DefaultWriteType.BATCH_LOG); } // there should have been a retry, and it should have been executed on the same host. @@ -342,7 +342,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id assertThat(wte.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(wte.getReceived()).isEqualTo(1); assertThat(wte.getBlockFor()).isEqualTo(3); - assertThat(wte.getWriteType()).isEqualTo(CoreWriteType.BATCH_LOG); + assertThat(wte.getWriteType()).isEqualTo(DefaultWriteType.BATCH_LOG); } // should not have been retried. From f261e77e7094c6e44cf4245e978dd9fce5c44b55 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Feb 2018 12:55:04 -0800 Subject: [PATCH 337/742] Rename CoreBatchType to DefaultBatchType --- .../oss/driver/api/core/cql/BatchType.java | 2 +- ...reBatchType.java => DefaultBatchType.java} | 4 ++-- .../core/config/DriverConfigProfileIT.java | 4 ++-- .../driver/api/core/cql/AsyncResultSetIT.java | 4 ++-- .../driver/api/core/cql/BatchStatementIT.java | 22 +++++++++---------- .../api/core/cql/PerRequestKeyspaceIT.java | 10 +++++---- 6 files changed, 24 insertions(+), 22 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/cql/{CoreBatchType.java => DefaultBatchType.java} (94%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java index fe4f1536efa..de823fcec0b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchType.java @@ -20,7 +20,7 @@ * *

          The only reason to model this as an interface (as opposed to an enum type) is to accommodate * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all - * {@code BatchType}s are {@link CoreBatchType} instances. + * {@code BatchType}s are {@link DefaultBatchType} instances. */ public interface BatchType { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/DefaultBatchType.java similarity index 94% rename from core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java rename to core/src/main/java/com/datastax/oss/driver/api/core/cql/DefaultBatchType.java index 88b4c4f3f1b..f941d48906d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/CoreBatchType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/DefaultBatchType.java @@ -18,7 +18,7 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; /** A default batch type supported by the driver out of the box. */ -public enum CoreBatchType implements BatchType { +public enum DefaultBatchType implements BatchType { /** * A logged batch: Cassandra will first write the batch to its distributed batch log to ensure the * atomicity of the batch (atomicity meaning that if any statement in the batch succeeds, all will @@ -41,7 +41,7 @@ public enum CoreBatchType implements BatchType { private final byte code; - CoreBatchType(byte code) { + DefaultBatchType(byte code) { this.code = code; } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java index ae832c8c93a..5fa00bfc157 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java @@ -27,7 +27,7 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; -import com.datastax.oss.driver.api.core.cql.CoreBatchType; +import com.datastax.oss.driver.api.core.cql.DefaultBatchType; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -186,7 +186,7 @@ public void should_use_profile_page_size() { .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(CoreBatchType.UNLOGGED).withConfigProfile(slowProfile); + BatchStatement.builder(DefaultBatchType.UNLOGGED).withConfigProfile(slowProfile); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 15a236e5a01..163e2f6e1d8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -58,8 +58,8 @@ public static void setupSchema() { PreparedStatement prepared = sessionRule.session().prepare("INSERT INTO test (k0, k1, v) VALUES (?, ?, ?)"); - BatchStatementBuilder batchPart1 = BatchStatement.builder(CoreBatchType.UNLOGGED); - BatchStatementBuilder batchPart2 = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder batchPart1 = BatchStatement.builder(DefaultBatchType.UNLOGGED); + BatchStatementBuilder batchPart2 = BatchStatement.builder(DefaultBatchType.UNLOGGED); for (int i = 0; i < ROWS_PER_PARTITION; i++) { batchPart1.addStatement(prepared.bind(PARTITION_KEY1, i, i)); batchPart2.addStatement( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 2fa42051e52..e36d0bfd732 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -64,7 +64,7 @@ public void createTable() { @Test public void should_execute_batch_of_simple_statements_with_variables() { // Build a batch of batchCount simple statements, each with their own positional variables. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); for (int i = 0; i < batchCount; i++) { SimpleStatement insert = SimpleStatement.builder( @@ -85,7 +85,7 @@ public void should_execute_batch_of_simple_statements_with_variables() { public void should_execute_batch_of_bound_statements_with_variables() { // Build a batch of batchCount statements with bound statements, each with their own positional // variables. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -108,7 +108,7 @@ public void should_execute_batch_of_bound_statements_with_variables() { public void should_execute_batch_of_bound_statements_with_unset_values() { // Build a batch of batchCount statements with bound statements, each with their own positional // variables. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -125,7 +125,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { verifyBatchInsert(); - BatchStatementBuilder builder2 = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder2 = BatchStatement.builder(DefaultBatchType.UNLOGGED); for (int i = 0; i < batchCount; i++) { BoundStatement boundStatement = preparedStatement.bind(i, i + 2); // unset v every 20 statements. @@ -163,7 +163,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { public void should_execute_batch_of_bound_statements_with_named_variables() { // Build a batch of batchCount statements with bound statements, each with their own named // variable values. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); PreparedStatement preparedStatement = sessionRule.session().prepare("INSERT INTO test (k0, k1, v) values (:k0, :k1, :v)"); @@ -186,7 +186,7 @@ public void should_execute_batch_of_bound_statements_with_named_variables() { @Test public void should_execute_batch_of_bound_and_simple_statements_with_variables() { // Build a batch of batchCount statements with simple and bound statements alternating. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -217,7 +217,7 @@ public void should_execute_batch_of_bound_and_simple_statements_with_variables() @Test public void should_execute_cas_batch() { // Build a batch with CAS operations on the same partition. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.UNLOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.UNLOGGED); SimpleStatement insert = SimpleStatement.builder( String.format( @@ -244,7 +244,7 @@ public void should_execute_cas_batch() { @Test public void should_execute_counter_batch() { // should be able to do counter increments in a counter batch. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.COUNTER); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.COUNTER); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -277,7 +277,7 @@ public void should_execute_counter_batch() { @Test(expected = InvalidQueryException.class) public void should_fail_logged_batch_with_counter_increment() { // should not be able to do counter inserts in a unlogged batch. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.LOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.LOGGED); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -296,7 +296,7 @@ public void should_fail_logged_batch_with_counter_increment() { @Test(expected = InvalidQueryException.class) public void should_fail_counter_batch_with_non_counter_increment() { // should not be able to do a counter batch if it contains a non-counter increment statement. - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.COUNTER); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.COUNTER); for (int i = 1; i <= 3; i++) { SimpleStatement insert = @@ -328,7 +328,7 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { PreparedStatement prepared = v3Session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); - BatchStatementBuilder builder = BatchStatement.builder(CoreBatchType.LOGGED); + BatchStatementBuilder builder = BatchStatement.builder(DefaultBatchType.LOGGED); builder.addStatements( // All set => OK prepared.bind(name.getMethodName(), 1, 1), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 909272daa64..3303be34fb6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -73,7 +73,7 @@ public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1); should_reject_statement_with_keyspace_in_protocol_v4( - BatchStatement.builder(CoreBatchType.LOGGED) + BatchStatement.builder(DefaultBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatement(statementWithoutKeyspace) .build()); @@ -87,7 +87,9 @@ public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1) .setKeyspace(sessionRule.keyspace()); should_reject_statement_with_keyspace_in_protocol_v4( - BatchStatement.builder(CoreBatchType.LOGGED).addStatement(statementWithKeyspace).build()); + BatchStatement.builder(DefaultBatchType.LOGGED) + .addStatement(statementWithKeyspace) + .build()); } private void should_reject_statement_with_keyspace_in_protocol_v4(Statement statement) { @@ -121,7 +123,7 @@ public void should_execute_simple_statement_with_keyspace() { public void should_execute_batch_with_explicit_keyspace() { CqlSession session = sessionRule.session(); session.execute( - BatchStatement.builder(CoreBatchType.LOGGED) + BatchStatement.builder(DefaultBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( @@ -145,7 +147,7 @@ public void should_execute_batch_with_explicit_keyspace() { public void should_execute_batch_with_inferred_keyspace() { CqlSession session = sessionRule.session(); session.execute( - BatchStatement.builder(CoreBatchType.LOGGED) + BatchStatement.builder(DefaultBatchType.LOGGED) .withKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( From a2cc9569b68d66ecf94f02351b6672b8379d2230 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 12 Feb 2018 16:54:04 +0100 Subject: [PATCH 338/742] Expose channel configuration --- .../oss/driver/internal/core/channel/DriverChannel.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 3496c9d34ca..abf167a3a6d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoop; import io.netty.util.AttributeKey; @@ -162,6 +163,11 @@ public SocketAddress localAddress() { return channel.localAddress(); } + /** @return The {@link ChannelConfig configuration} of this channel. */ + public ChannelConfig config() { + return channel.config(); + } + /** * Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will * be allowed to complete before the underlying channel is closed. From 1f9731d986d30ee58c6ee5ae8c0678069cf8d293 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 12 Feb 2018 11:13:41 +0100 Subject: [PATCH 339/742] Change signature of AsyncResultSet.fetchNextPage() Motivation: Its return type should reflect that AsyncResultSet is an interface, so what is actually returned is a future of some concrete implementation, hence the correct return type should be CompletionStage. --- .../driver/api/core/cql/AsyncResultSet.java | 2 +- .../internal/core/cql/ResultSetsTest.java | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 24181ae608f..0afa4a892a5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -66,7 +66,7 @@ default Row one() { * @throws IllegalStateException if there are no more pages. Use {@link #hasMorePages()} to check * if you can call this method. */ - CompletionStage fetchNextPage() throws IllegalStateException; + CompletionStage fetchNextPage() throws IllegalStateException; /** * If the query that produced this result was a conditional update, indicate whether it was diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index 4f63ea7b0f5..e6f1dfe3d61 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import org.junit.Test; import org.mockito.Mockito; @@ -69,8 +70,8 @@ public void should_create_result_set_from_multiple_pages() { AsyncResultSet page2 = mockPage(true, 3, 4, 5); AsyncResultSet page3 = mockPage(false, 6, 7, 8); - ((CompletableFuture) page1.fetchNextPage()).complete(page2); - ((CompletableFuture) page2.fetchNextPage()).complete(page3); + complete(page1.fetchNextPage(), page2); + complete(page2.fetchNextPage(), page3); // When ResultSet resultSet = ResultSets.newInstance(page1); @@ -137,8 +138,8 @@ public void should_fetch_multiple_pages_in_advance() { AsyncResultSet page2 = mockPage(true, 3, 4, 5); AsyncResultSet page3 = mockPage(false, 6, 7, 8); - ((CompletableFuture) page1.fetchNextPage()).complete(page2); - ((CompletableFuture) page2.fetchNextPage()).complete(page3); + complete(page1.fetchNextPage(), page2); + complete(page2.fetchNextPage(), page3); // When ResultSet resultSet = ResultSets.newInstance(page1); @@ -201,8 +202,7 @@ private AsyncResultSet mockPage(boolean nextPage, Integer... data) { if (nextPage) { Mockito.when(page.hasMorePages()).thenReturn(true); - CompletableFuture nextPageFuture = Mockito.spy(new CompletableFuture<>()); - Mockito.when(page.fetchNextPage()).thenReturn(nextPageFuture); + Mockito.when(page.fetchNextPage()).thenReturn(Mockito.spy(new CompletableFuture<>())); } else { Mockito.when(page.hasMorePages()).thenReturn(false); Mockito.when(page.fetchNextPage()).thenThrow(new IllegalStateException()); @@ -230,4 +230,11 @@ private Row mockRow(int index) { Mockito.when(row.getInt(0)).thenReturn(index); return row; } + + private static void complete( + CompletionStage stage, AsyncResultSet result) { + @SuppressWarnings("unchecked") + CompletableFuture future = (CompletableFuture) stage; + future.complete(result); + } } From 072014aac1a830259757432b9352ac398383602f Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 12 Feb 2018 16:59:16 +0100 Subject: [PATCH 340/742] Increase visibility of members of Conversions class --- .../driver/internal/core/cql/Conversions.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 478925535e7..e947495f03d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -90,9 +90,9 @@ * *

          The main goal of this class is to move this code out of the request handlers. */ -class Conversions { +public class Conversions { - static Message toMessage( + public static Message toMessage( Statement statement, DriverConfigProfile config, InternalDriverContext context) { int consistency = context @@ -174,7 +174,7 @@ static Message toMessage( } List queriesOrIds = new ArrayList<>(batchStatement.size()); List> values = new ArrayList<>(batchStatement.size()); - for (BatchableStatement child : batchStatement) { + for (BatchableStatement child : batchStatement) { if (child instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) child; if (simpleStatement.getNamedValues().size() > 0) { @@ -209,7 +209,7 @@ static Message toMessage( } } - private static List encode( + public static List encode( List values, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { if (values.isEmpty()) { return Collections.emptyList(); @@ -222,7 +222,7 @@ private static List encode( } } - private static Map encode( + public static Map encode( Map values, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { if (values.isEmpty()) { return Collections.emptyMap(); @@ -235,7 +235,7 @@ private static Map encode( } } - private static ByteBuffer encode( + public static ByteBuffer encode( Object value, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { if (value instanceof Token) { if (value instanceof Murmur3Token) { @@ -252,7 +252,7 @@ private static ByteBuffer encode( } } - private static void ensureAllSet(BoundStatement boundStatement) { + public static void ensureAllSet(BoundStatement boundStatement) { for (int i = 0; i < boundStatement.size(); i++) { if (!boundStatement.isSet(i)) { throw new IllegalStateException( @@ -264,7 +264,7 @@ private static void ensureAllSet(BoundStatement boundStatement) { } } - private static void ensureAllSet(BatchStatement batchStatement) { + public static void ensureAllSet(BatchStatement batchStatement) { for (BatchableStatement batchableStatement : batchStatement) { if (batchableStatement instanceof BoundStatement) { ensureAllSet(((BoundStatement) batchableStatement)); @@ -272,7 +272,7 @@ private static void ensureAllSet(BatchStatement batchStatement) { } } - static AsyncResultSet toResultSet( + public static AsyncResultSet toResultSet( Result result, ExecutionInfo executionInfo, CqlSession session, @@ -292,7 +292,7 @@ static AsyncResultSet toResultSet( } } - private static ColumnDefinitions getResultDefinitions( + public static ColumnDefinitions getResultDefinitions( Rows rows, Statement statement, InternalDriverContext context) { RowsMetadata rowsMetadata = rows.getMetadata(); if (rowsMetadata.columnSpecs.isEmpty()) { @@ -315,7 +315,7 @@ private static ColumnDefinitions getResultDefinitions( } } - static DefaultPreparedStatement toPreparedStatement( + public static DefaultPreparedStatement toPreparedStatement( Prepared response, PrepareRequest request, InternalDriverContext context) { return new DefaultPreparedStatement( ByteBuffer.wrap(response.preparedQueryId).asReadOnlyBuffer(), @@ -336,7 +336,7 @@ static DefaultPreparedStatement toPreparedStatement( ImmutableMap.copyOf(request.getCustomPayload())); } - private static ColumnDefinitions toColumnDefinitions( + public static ColumnDefinitions toColumnDefinitions( RowsMetadata metadata, InternalDriverContext context) { ImmutableList.Builder definitions = ImmutableList.builder(); for (ColumnSpec columnSpec : metadata.columnSpecs) { @@ -345,7 +345,7 @@ private static ColumnDefinitions toColumnDefinitions( return DefaultColumnDefinitions.valueOf(definitions.build()); } - private static List asList(int[] pkIndices) { + public static List asList(int[] pkIndices) { if (pkIndices == null || pkIndices.length == 0) { return Collections.emptyList(); } else { @@ -357,7 +357,7 @@ private static List asList(int[] pkIndices) { } } - static CoordinatorException toThrowable( + public static CoordinatorException toThrowable( Node node, Error errorMessage, InternalDriverContext context) { switch (errorMessage.code) { case ProtocolConstants.ErrorCode.UNPREPARED: From 4daf59b65c379ceb5c2f3a37da30b64be150b7cb Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 11 Feb 2018 14:39:37 -0800 Subject: [PATCH 341/742] Document query tracing --- manual/core/tracing/README.md | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 manual/core/tracing/README.md diff --git a/manual/core/tracing/README.md b/manual/core/tracing/README.md new file mode 100644 index 00000000000..21fa2ea7168 --- /dev/null +++ b/manual/core/tracing/README.md @@ -0,0 +1,104 @@ +## Query tracing + +To help troubleshooting performance, Cassandra offers the ability to *trace* a query, in other words +capture detailed information about the the internal operations performed by all nodes in the cluster +in order to build the response. + +The driver provides a way to enable tracing on a particular statement, and an API to examine the +results. + +### Enabling tracing + +Set the tracing flag on the `Statement` instance. There are various ways depending on how you build +it (see [statements](../statements/) for more details): + +```java +// Setter-based: +Statement statement = + SimpleStatement.newInstance("SELECT * FROM users WHERE id = 1234").setTracing(true); + +// Builder-based: +Statement statement = + SimpleStatement.builder("SELECT * FROM users WHERE id = 1234").withTracing().build(); +``` + +Tracing is supposed to be run on a small percentage of requests only. Do not enable it on every +request, you would risk overwhelming your cluster. + +### Retrieving tracing data + +Once you've executed a statement with tracing enabled, tracing data is available through the +[ExecutionInfo]: + +```java +ResultSet rs = session.execute(statement); +ExecutionInfo executionInfo = rs.getExecutionInfo(); +``` + +#### Tracing id + +Cassandra assigns a unique identifier to each query trace. It is returned with the query results, +and therefore available immediately: + +```java +UUID tracingId = executionInfo.getTracingId(); +``` + +This is the primary key in the `system_traces.sessions` and `system_traces.events` tables where +Cassandra stores tracing data (you don't need to query those tables manually, see the next section). + +If you call `getTracingId()` for a statement that didn't have tracing enabled, the resulting id will +be `null`. + +#### Tracing information + +To get to the details of the trace, retrieve the [QueryTrace] instance: + +```java +QueryTrace trace = executionInfo.getQueryTrace(); + +// Or asynchronous equivalent: +CompletionStage traceFuture = executionInfo.getQueryTraceAsync(); +``` + +This triggers background queries to fetch the information from the `system_traces.sessions` and +`system_traces.events` tables. Because Cassandra writes that information asynchronously, it might +not be immediately available, therefore the driver will retry a few times if necessary. You can +control this behavior through the configuration: + +``` +# These options can be changed at runtime, the new values will be used for requests issued after +# the change. They can be overridden in a profile. +datastax-java-driver.request.trace { + # How many times the driver will attempt to fetch the query if it is not ready yet. + attempts = 5 + + # The interval between each attempt. + interval = 3 milliseconds + + # The consistency level to use for trace queries. + # Note that the default replication strategy for the system_traces keyspace is SimpleStrategy + # with RF=2, therefore LOCAL_ONE might not work if the local DC has no replicas for a given + # trace id. + consistency = ONE +} +``` + +Once you have the `QueryTrace` object, access its properties for relevant information, for example: + +```java +System.out.printf( + "'%s' to %s took %dμs%n", + trace.getRequestType(), trace.getCoordinator(), trace.getDurationMicros()); +for (TraceEvent event : trace.getEvents()) { + System.out.printf( + " %d - %s - %s%n", + event.getSourceElapsedMicros(), event.getSource(), event.getActivity()); +} +``` + +If you call `getQueryTrace()` for a statement that didn't have tracing enabled, an exception is +thrown. + +[ExecutionInfo]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/ExecutionInfo.html +[QueryTrace]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/QueryTrace.html From 9fe0475203c036fa4a0bdffe1d8de9ea0a3556cc Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 11 Feb 2018 17:46:49 -0800 Subject: [PATCH 342/742] Document metrics --- manual/core/metrics/README.md | 126 ++++++++++++++++++++++++++++++++++ upgrade_guide/README.md | 7 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 manual/core/metrics/README.md diff --git a/manual/core/metrics/README.md b/manual/core/metrics/README.md new file mode 100644 index 00000000000..b1ccf8be138 --- /dev/null +++ b/manual/core/metrics/README.md @@ -0,0 +1,126 @@ +## Metrics + +The driver exposes measurements of its internal behavior through the popular [Dropwizard Metrics] +library. Application developers can select which metrics are enabled, and export them to a +monitoring tool. + +### Structure + +There are two categories of metrics: + +* session-level: the measured data is global to a `Session` instance. For example, `connected-nodes` + measures the number of nodes to which we have connections. +* node-level: the data is specific to a node (and therefore there is one metric instance per node). + For example, `pool.open-connections` measures the number of connections open to this particular + node. + +Metric names are path-like, dot-separated strings. The driver prefixes them with the name of the +session (see `session-name` in the configuration), and in the case of node-level metrics, `nodes` +followed by a textual representation of the node's address. For example: + +``` +s0.connected-nodes => 2 +s0.nodes.127_0_0_1_9042.pool.open-connections => 2 +s0.nodes.127_0_0_2_9042.pool.open-connections => 1 +``` + +### Configuration + +By default, all metrics are disabled. You can turn them on individually in the configuration, by +adding their name to these lists: + +``` +datastax-java-driver.metrics { + session.enabled = [ connected-nodes, cql-requests ] + node.enabled = [ pool.open-connections, pool.in-flight ] +} +``` + +To find out which metrics are available, see to the [reference.conf] file corresponding to your +version of the driver (it can be found in the driver's JAR or in the sources). It contains a +commented-out line for each metric, with detailed explanations on its intended usage. + +If you specify a metric that doesn't exist, it will be ignored and a warning will be logged. + +The `metrics` section may also contain additional configuration for some specific metrics; again, +refer to `reference.conf` for more details. + +### Export + +The Dropwizard `MetricRegistry` is exposed via `session.getMetricRegistry()`. You can retrieve it +and configure a `Reporter` to send the metrics to a monitoring tool. + +#### JMX + +Unlike previous driver versions, JMX support is not included out of the box. + +Add the following dependency to your application (make sure the version matches the `metrics-core` +dependency of the driver): + +``` + + io.dropwizard.metrics + metrics-jmx + 4.0.2 + +``` + +Then create a JMX reporter for the registry: + +``` +JmxReporter reporter = + JmxReporter.forRegistry(session.getMetricRegistry()) + .inDomain("com.datastax.oss.driver") + .build(); +``` + +Note: by default, the JMX reporter exposes all metrics in a flat structure (for example, +`pool.open-connections` and `pool.in-flight` appear as root elements). If you prefer a hierarchical +structure (`open-connections` and `in-flight` nested into a `pool` sub-domain), use a custom object +factory: + +```java +import com.codahale.metrics.jmx.JmxReporter; +import com.codahale.metrics.jmx.ObjectNameFactory; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +ObjectNameFactory objectNameFactory = (type, domain, name) -> { + StringBuilder objectName = new StringBuilder(domain).append(':'); + List nameParts = Splitter.on('.').splitToList(name); + int i = 0; + for (String namePart : nameParts) { + boolean isLast = (i == nameParts.size() - 1); + String key = + isLast ? "name" : Strings.padStart(Integer.toString(i), 2, '0'); + objectName.append(key).append('=').append(namePart); + if (!isLast) { + objectName.append(','); + } + i += 1; + } + try { + return new ObjectName(objectName.toString()); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } +}; + +JmxReporter reporter = + JmxReporter.forRegistry(session.getMetricRegistry()) + .inDomain("com.datastax.oss.driver") + .createsObjectNamesWith(objectNameFactory) + .build(); +``` + +#### Other protocols + +Dropwizard Metrics has built-in reporters for other output formats: JSON (via a servlet), stdout, +CSV files, SLF4J logs and Graphite. Refer to their [manual][Dropwizard manual] for more details. + + +[Dropwizard Metrics]: http://metrics.dropwizard.io/4.0.0/manual/index.html +[Dropwizard Manual]: http://metrics.dropwizard.io/4.0.0/getting-started.html#reporting-via-http +[reference.conf]: https://github.com/datastax/java-driver/blob/4.x/core/src/main/resources/reference.conf \ No newline at end of file diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 4b4297ec006..f958a6356fc 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -140,4 +140,9 @@ See the [manual](../manual/core/metadata/) for all the details. You no longer need to force the protocol version in a mixed cluster: upon connecting to the first node, the driver will read the release version of all the nodes in the cluster and infer the best -protocol version that works with all of them. \ No newline at end of file +protocol version that works with all of them. + +#### Improved metrics + +[Metrics](../manual/core/metrics/) can now be enabled selectively. In addition, they are exposed per +node when that is relevant. \ No newline at end of file From 58a3076cb16a3b814a6d022a6f518731f0f03275 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 12 Feb 2018 11:04:27 -0800 Subject: [PATCH 343/742] Document per-query keyspace --- manual/core/statements/README.md | 1 + .../statements/per_query_keyspace/README.md | 120 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 manual/core/statements/per_query_keyspace/README.md diff --git a/manual/core/statements/README.md b/manual/core/statements/README.md index f5922735ece..3e9faa3cf8d 100644 --- a/manual/core/statements/README.md +++ b/manual/core/statements/README.md @@ -23,6 +23,7 @@ either setters or a builder: * tracing flag. * query timestamp. * paging state. +* [per-query keyspace](per_query_keyspace/) (Cassandra 4 or above). When setting these options, keep in mind that statements are immutable, and every method returns a different instance: diff --git a/manual/core/statements/per_query_keyspace/README.md b/manual/core/statements/per_query_keyspace/README.md new file mode 100644 index 00000000000..c880d317415 --- /dev/null +++ b/manual/core/statements/per_query_keyspace/README.md @@ -0,0 +1,120 @@ +## Per-query keyspace + +Sometimes it is convenient to send the keyspace separately from the query string, and without +switching the whole session to that keyspace either. For example, you might have a multi-tenant +setup where identical requests are executed against different keyspaces. + +**This feature is only available with Cassandra 4.0 or above** ([CASSANDRA-10145]). Make sure you +are using native protocol v5 or above to connect. + +If you try against an older version, you will get an error: + +``` +Exception in thread "main" java.lang.IllegalArgumentException: Can't use per-request keyspace with protocol V4 +``` + +*Note: at the time of writing, Cassandra 4 is not released yet. If you want to test those examples +against the development version, keep in mind that native protocol v5 is still in beta, so you'll +need to force it in the configuration: `datastax-java-driver.protocol.version = V5`*. + +### Basic usage + +To use a per-query keyspace, set it on your statement instance: + +```java +CqlSession session = CqlSession.builder().build(); +CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); +SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM foo WHERE k = 1").setKeyspace(keyspace); +session.execute(statement); +``` + +You can do this on [simple](../simple/), [prepared](../prepared) or [batch](../batch/) statements. + +If the session is connected to another keyspace, the per-query keyspace takes precedence: + +```java +CqlIdentifier keyspace1 = CqlIdentifier.fromCql("test1"); +CqlIdentifier keyspace2 = CqlIdentifier.fromCql("test2"); + +CqlSession session = CqlSession.builder().withKeyspace(keyspace1).build(); + +// Will query test2.foo: +SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM foo WHERE k = 1").setKeyspace(keyspace2); +session.execute(statement); +``` + +On the other hand, if a keyspace is hard-coded in the query, it takes precedence over the per-query +keyspace: + +```java +// Will query test1.foo: +SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM test1.foo WHERE k = 1").setKeyspace(keyspace2); +``` + +### Bound statements + +Bound statements can't have a per-query keyspace; they only inherit the one that was set on the +prepared statement: + +```java +CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); +PreparedStatement pst = + session.prepare( + SimpleStatement.newInstance("SELECT * FROM foo WHERE k = ?").setKeyspace(keyspace)); + +// Will query test.foo: +BoundStatement bs = pst.bind(1); +``` + +The rationale is that prepared statements hold metadata about the target table; if Cassandra allowed +execution against different keyspaces, it would be under the assumption that all tables have the +same exact schema, which could create issues if this turned out not to be true at runtime. + +Therefore you'll have to prepare against every target keyspace. A good strategy is to do this lazily +with a cache. Here is a simple example using Guava: + +```java +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +LoadingCache cache = + CacheBuilder.newBuilder() + .build( + new CacheLoader() { + @Override + public PreparedStatement load(CqlIdentifier keyspace) throws Exception { + return session.prepare( + SimpleStatement.newInstance("SELECT * FROM foo WHERE k = ?") + .setKeyspace(keyspace)); + } + }); +CqlIdentifier keyspace = CqlIdentifier.fromCql("test"); +BoundStatement bs = cache.get(keyspace).bind(1); +``` + +### Relation to the routing keyspace + +Statements have another keyspace-related method: `Statement.setRoutingKeyspace()`. However, the +routing keyspace is only used for [token-aware routing], as a hint to help the driver send requests +to the best replica. It does not affect the query string itself. + +If you are using a per-query keyspace, the routing keyspace becomes obsolete: the driver will use +the per-query keyspace as the routing keyspace. + +```java +SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM foo WHERE k = 1") + .setKeyspace(keyspace) + .setRoutingKeyspace(keyspace); // NOT NEEDED: will be ignored +``` + +At some point in the future, when Cassandra 4 becomes prevalent and using a per-query keyspace is +the norm, we'll probably deprecate `setRoutingKeyspace()`. + +[token-aware routing]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Request.html#getRoutingKey-- + +[CASSANDRA-10145]: https://issues.apache.org/jira/browse/CASSANDRA-10145 \ No newline at end of file From 3eb827c0671995e64dc94eb2f2ccf9dadb5f7fc4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 12 Feb 2018 11:25:15 -0800 Subject: [PATCH 344/742] Update prepared statement manual for metadata_changed flag --- manual/core/statements/prepared/README.md | 38 ++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md index f03098c569d..3ba56db6ab9 100644 --- a/manual/core/statements/prepared/README.md +++ b/manual/core/statements/prepared/README.md @@ -238,20 +238,30 @@ You can customize these strategies through the [configuration](../../configurati Read the `reference.conf` file provided with the driver for a detailed description of each of those options. -### Avoid preparing 'SELECT *' queries - -Both the driver and Cassandra maintain a mapping of `PreparedStatement` queries to their metadata. -When a change is made to a table, such as a column being added or dropped, there is currently no -mechanism for Cassandra to invalidate the existing metadata. Because of this, the driver is not able -to properly react to these changes and will improperly read rows after a schema change is made. - -Therefore it is currently recommended to not create prepared statements for 'SELECT *' queries if -you plan on making schema changes involving adding or dropping columns. Instead, you should list all -columns of interest in your statement, i.e.: `SELECT a, b, c FROM tbl`. - -This will be addressed in a future release of both Cassandra and the driver. Follow -[CASSANDRA-10786] and [JAVA-1196] for more information. +### Avoid preparing 'SELECT *' queries (Cassandra 3 and below) + +With Cassandra 3 and below, the driver does not handle schema changes that would affect the results +of a prepared statement. Therefore `SELECT *` queries can create issues, for example: + +* table `foo` contains columns `b` and `c`. +* the driver prepares `SELECT * FROM foo`. It gets a reply indicating that executing this statement + will return columns `b` and `c`, and caches that metadata locally (for performance reasons: this + avoids sending it with each response later). +* someone alters table `foo` to add a new column `a`. +* the next time the driver executes the prepared statement, it gets a response that now contains + columns `a`, `b` and `c`. However, it's still using its stale copy of the metadata, so it decodes + `a` thinking it's `b`. In the best case scenario, `a` and `b` have different types and decoding + fails; in the worst case, they have compatible types and the client gets corrupt data. + +To avoid this, do not create prepared statements for `SELECT *` queries if you plan on making schema +changes involving adding or dropping columns. Instead, always list all columns of interest in your +statement, i.e.: `SELECT b, c FROM foo`. + +With Cassandra 4 and native protocol v5, this issue is fixed ([CASSANDRA-10786]): the server detects +that the driver is operating on stale metadata and sends the new version with the response; the +driver updates its local cache transparently, and the client can observe the new columns in the +result set. [BoundStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BoundStatement.html + [CASSANDRA-10786]: https://issues.apache.org/jira/browse/CASSANDRA-10786 -[JAVA-1196]: https://datastax-oss.atlassian.net/browse/JAVA-1196 \ No newline at end of file From fa6b1ce0cd50a7accf0455652b77e10f55c3fca3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 20 Feb 2018 09:53:18 -0800 Subject: [PATCH 345/742] Fix skipping of integration tests during release --- pom.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 1658db5864b..1e788e773f5 100644 --- a/pom.xml +++ b/pom.xml @@ -410,8 +410,8 @@ limitations under the License.]]> false release deploy - - -DskipITs + + -DskipParallelizableITs -DskipSerialITs -DskipIsolatedITs @@ -435,10 +435,6 @@ limitations under the License.]]> - - - true - From 30175741abb3eee9301bad22ccaf9d52d343cdde Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 20 Feb 2018 10:15:41 -0800 Subject: [PATCH 346/742] [maven-release-plugin] prepare release 4.0.0-alpha3 --- core/pom.xml | 6 ++---- integration-tests/pom.xml | 2 +- pom.xml | 8 +++----- test-infra/pom.xml | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 32007abf24d..276f5e3ffc1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-alpha3-SNAPSHOT + 4.0.0-alpha3 java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index d5b8a299255..e8cfca73ee6 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3-SNAPSHOT + 4.0.0-alpha3 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 1e788e773f5..2e59602d655 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,12 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-alpha3-SNAPSHOT + 4.0.0-alpha3 pom DataStax Java driver for Apache Cassandra® @@ -455,7 +453,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-alpha3 diff --git a/test-infra/pom.xml b/test-infra/pom.xml index fa16bc135e4..506038c653d 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3-SNAPSHOT + 4.0.0-alpha3 java-driver-test-infra From 1a5e60ae23ec473066c3331f23758cbd2f9e018d Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 20 Feb 2018 10:16:41 -0800 Subject: [PATCH 347/742] [maven-release-plugin] prepare for next development iteration --- core/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- test-infra/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 276f5e3ffc1..c996d639407 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3 + 4.0.0-alpha4-SNAPSHOT java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index e8cfca73ee6..eb591c1ce3e 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3 + 4.0.0-alpha4-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 2e59602d655..e63019d020c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3 + 4.0.0-alpha4-SNAPSHOT pom DataStax Java driver for Apache Cassandra® @@ -453,7 +453,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-alpha3 + HEAD diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 506038c653d..c329c0fe70b 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha3 + 4.0.0-alpha4-SNAPSHOT java-driver-test-infra From da48cc3467db925615fd7e8e957f97a3595ac45d Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 20 Feb 2018 10:44:19 -0800 Subject: [PATCH 348/742] Update docs for next development iteration --- README.md | 2 +- changelog/README.md | 4 +++- manual/core/README.md | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5b60d97183..9886c980161 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-alpha2](https://github.com/datastax/java-driver/tree/4.0.0-alpha2).* +[4.0.0-alpha3](https://github.com/datastax/java-driver/tree/4.0.0-alpha3).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and [DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query diff --git a/changelog/README.md b/changelog/README.md index deeac5cd259..74e38dd7be7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,9 @@ -### 4.0.0-alpha3 (in progress) +### 4.0.0-alpha4 (in progress) + +### 4.0.0-alpha3 - [new feature] JAVA-1518: Expose metrics - [improvement] JAVA-1739: Add host_id and schema_version to node metadata diff --git a/manual/core/README.md b/manual/core/README.md index 5d9899a0da6..27640f9680f 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-alpha2 + 4.0.0-alpha3 ``` From 7bf731430d7f27102c8a27a64166d120684ab92f Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 19 Feb 2018 16:19:51 +0100 Subject: [PATCH 349/742] JAVA-1756: Propagate custom payload when preparing a statement --- changelog/README.md | 2 + .../core/cql/CqlPrepareHandlerBase.java | 8 +-- .../core/cql/CqlPrepareHandlerTest.java | 70 +++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 74e38dd7be7..be336aeebab 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0-alpha4 (in progress) +- [bug] JAVA-1756: Propagate custom payload when preparing a statement + ### 4.0.0-alpha3 - [new feature] JAVA-1518: Expose metrics diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index e131acb0c1d..1c6c3de5ebb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -191,7 +191,7 @@ private void sendRequest(Node node, int retryCount) { InitialPrepareCallback initialPrepareCallback = new InitialPrepareCallback(node, channel, retryCount); channel - .write(message, false, Frame.NO_PAYLOAD, initialPrepareCallback) + .write(message, false, request.getCustomPayload(), initialPrepareCallback) .addListener(initialPrepareCallback); } } @@ -212,7 +212,7 @@ private void recordError(Node node, Throwable error) { private void setFinalResult(Prepared prepared) { DefaultPreparedStatement newStatement = - Conversions.toPreparedStatement(prepared, (PrepareRequest) request, context); + Conversions.toPreparedStatement(prepared, request, context); DefaultPreparedStatement cachedStatement = cache(newStatement); @@ -285,7 +285,7 @@ private CompletionStage prepareOnOtherNode(Node node) { AdminRequestHandler handler = new AdminRequestHandler(channel, message, timeout, logPrefix, message.toString()); return handler - .start() + .start(request.getCustomPayload()) .handle( (result, error) -> { if (error == null) { @@ -321,7 +321,7 @@ private InitialPrepareCallback(Node node, DriverChannel channel, int retryCount) // this gets invoked once the write completes. @Override - public void operationComplete(Future future) throws Exception { + public void operationComplete(Future future) { if (!future.isSuccess()) { LOG.debug( "[{}] Failed to send request on {}, trying next node (cause: {})", diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index f61795c0488..fafa1a40561 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -24,12 +25,15 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.servererrors.OverloadedException; +import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.result.ColumnSpec; import com.datastax.oss.protocol.internal.response.result.Prepared; @@ -37,8 +41,10 @@ import com.datastax.oss.protocol.internal.response.result.RowsMetadata; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.nio.ByteBuffer; import java.util.Collections; +import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -59,6 +65,8 @@ public class CqlPrepareHandlerTest { private ConcurrentMap preparedStatementsCache = new ConcurrentHashMap<>(); + private final Map payload = + ImmutableMap.of("key1", ByteBuffer.wrap(new byte[] {1, 2, 3, 4})); @Before public void setup() { @@ -334,6 +342,68 @@ public void should_fail_if_retry_policy_ignores_error() { } } + @Test + public void should_propagate_custom_payload_on_single_node() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + DefaultPrepareRequest prepareRequest = + new DefaultPrepareRequest( + SimpleStatement.newInstance("irrelevant").setCustomPayload(payload)); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); + Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); + CompletionStage prepareFuture = + new CqlPrepareAsyncHandler( + prepareRequest, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); + Mockito.verify(node1Behavior.channel) + .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); + node2Behavior.verifyNoWrite(); + node3Behavior.verifyNoWrite(); + assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + } + } + + @Test + public void should_propagate_custom_payload_on_all_nodes() { + RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); + DefaultPrepareRequest prepareRequest = + new DefaultPrepareRequest( + SimpleStatement.newInstance("irrelevant").setCustomPayload(payload)); + PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); + PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); + PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); + node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + node2Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); + Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); + CompletionStage prepareFuture = + new CqlPrepareAsyncHandler( + prepareRequest, + preparedStatementsCache, + harness.getSession(), + harness.getContext(), + "test") + .handle(); + Mockito.verify(node1Behavior.channel) + .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); + Mockito.verify(node2Behavior.channel) + .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); + Mockito.verify(node3Behavior.channel) + .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); + assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + } + } + private static Frame defaultFrameOf(Message responseMessage) { return Frame.forResponse( DefaultProtocolVersion.V4.getCode(), From b90f79f1b93a6d0330e4179c6f9f69b65d65bda7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 20 Feb 2018 14:11:44 -0800 Subject: [PATCH 350/742] Improve javadoc of DefaultPrepareRequest --- .../core/cql/DefaultPrepareRequest.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index 4cbda0156fb..c447b5a4d40 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -24,11 +25,32 @@ import java.util.Map; /** - * For simplicity, this default implementation makes opinionated choices about {@code - * *ForBoundStatements} methods: these should be appropriate for most cases, but not all use cases - * are supported (for example, preparing with one profile and having the bound statements use - * another profile is not possible). For exotic use cases, subclass or write your own - * implementation. + * Default implementation of a prepare request, which is built internally to handle calls such as + * {@link CqlSession#prepare(String)} and {@link CqlSession#prepare(SimpleStatement)}. + * + *

          When a {@link SimpleStatement} gets prepared, some of its fields are propagated automatically. + * For simplicity, this implementation makes the following opinionated choices: + * + *

            + *
          • the prepare request: + *
              + *
            • will use the same configuration profile (or configuration profile name) as the {@code + * SimpleStatement}; + *
            • will use the same custom payload as the {@code SimpleStatement}; + *
            + *
          • any bound statement created from the prepared statement: + *
              + *
            • will use the same configuration profile (or configuration profile name) as the {@code + * SimpleStatement}; + *
            • will use the same custom payload as the {@code SimpleStatement}; + *
            • will be idempotent if and only if the {@code SimpleStatement} was idempotent. + *
            + *
          + * + *

          This should be appropriate for most use cases; however if you need something more exotic (for + * example, preparing with one profile, but executing bound statements with another one), you can + * either write your own {@code PrepareRequest} implementation, or set the options manually on every + * bound statement. */ public class DefaultPrepareRequest implements PrepareRequest { From ef6ce31a943796b86462b802b500d62c98d12ec5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 21 Feb 2018 12:39:32 +0100 Subject: [PATCH 351/742] JAVA-1537: Add remaining socket options (#951) --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 7 ++ .../internal/core/channel/ChannelFactory.java | 33 +++++++- core/src/main/resources/reference.conf | 45 +++++++++++ .../connection/ChannelSocketOptionsIT.java | 81 +++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java diff --git a/changelog/README.md b/changelog/README.md index be336aeebab..72b12690519 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -8,6 +8,7 @@ ### 4.0.0-alpha3 +- [new feature] JAVA-1537: Add remaining socket options - [new feature] JAVA-1518: Expose metrics - [improvement] JAVA-1739: Add host_id and schema_version to node metadata - [improvement] JAVA-1738: Convert enums to allow extensibility diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 06c76dfc4ca..8c6bdcbffd3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -41,6 +41,13 @@ public enum DefaultDriverOption implements DriverOption { CONNECTION_POOL_LOCAL_SIZE("connection.pool.local.size", true), CONNECTION_POOL_REMOTE_SIZE("connection.pool.remote.size", true), + CONNECTION_SOCKET_TCP_NODELAY("connection.socket.tcpNoDelay", true), + CONNECTION_SOCKET_KEEP_ALIVE("connection.socket.keepAlive", false), + CONNECTION_SOCKET_REUSE_ADDRESS("connection.socket.reuseAddress", false), + CONNECTION_SOCKET_LINGER_INTERVAL("connection.socket.lingerInterval", false), + CONNECTION_SOCKET_RECEIVE_BUFFER_SIZE("connection.socket.receiveBufferSize", false), + CONNECTION_SOCKET_SEND_BUFFER_SIZE("connection.socket.sendBufferSize", false), + REQUEST_TIMEOUT("request.timeout", true), REQUEST_CONSISTENCY("request.consistency", true), REQUEST_PAGE_SIZE("request.page-size", true), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index b93b80371e4..2fdb2438e17 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -31,6 +31,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.FixedRecvByteBufAllocator; import java.net.SocketAddress; import java.util.List; import java.util.Optional; @@ -117,6 +118,36 @@ private void connect( .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) .handler(initializer(address, currentVersion, options, resultFuture)); + DriverConfigProfile config = context.config().getDefaultProfile(); + + boolean tcpNoDelay = config.getBoolean(DefaultDriverOption.CONNECTION_SOCKET_TCP_NODELAY); + bootstrap = bootstrap.option(ChannelOption.TCP_NODELAY, tcpNoDelay); + if (config.isDefined(DefaultDriverOption.CONNECTION_SOCKET_KEEP_ALIVE)) { + boolean keepAlive = config.getBoolean(DefaultDriverOption.CONNECTION_SOCKET_KEEP_ALIVE); + bootstrap = bootstrap.option(ChannelOption.SO_KEEPALIVE, keepAlive); + } + if (config.isDefined(DefaultDriverOption.CONNECTION_SOCKET_REUSE_ADDRESS)) { + boolean reuseAddress = config.getBoolean(DefaultDriverOption.CONNECTION_SOCKET_REUSE_ADDRESS); + bootstrap = bootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress); + } + if (config.isDefined(DefaultDriverOption.CONNECTION_SOCKET_LINGER_INTERVAL)) { + int lingerInterval = config.getInt(DefaultDriverOption.CONNECTION_SOCKET_LINGER_INTERVAL); + bootstrap = bootstrap.option(ChannelOption.SO_LINGER, lingerInterval); + } + if (config.isDefined(DefaultDriverOption.CONNECTION_SOCKET_RECEIVE_BUFFER_SIZE)) { + int receiveBufferSize = + config.getInt(DefaultDriverOption.CONNECTION_SOCKET_RECEIVE_BUFFER_SIZE); + bootstrap = + bootstrap + .option(ChannelOption.SO_RCVBUF, receiveBufferSize) + .option( + ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(receiveBufferSize)); + } + if (config.isDefined(DefaultDriverOption.CONNECTION_SOCKET_SEND_BUFFER_SIZE)) { + int sendBufferSize = config.getInt(DefaultDriverOption.CONNECTION_SOCKET_SEND_BUFFER_SIZE); + bootstrap = bootstrap.option(ChannelOption.SO_SNDBUF, sendBufferSize); + } + nettyOptions.afterBootstrapInitialized(bootstrap); ChannelFuture connectFuture = bootstrap.connect(address); @@ -169,7 +200,7 @@ ChannelInitializer initializer( CompletableFuture resultFuture) { return new ChannelInitializer() { @Override - protected void initChannel(Channel channel) throws Exception { + protected void initChannel(Channel channel) { try { DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index fc6e0bbcada..25fa29f3043 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -297,6 +297,51 @@ datastax-java-driver { size = 1 } } + + socket { + + # Whether or not to disable the Nagle algorithm. + # + # By default, this option is set to true (Nagle disabled), because the driver has its own + # internal message coalescing algorithm. + # + # See java.net.StandardSocketOptions.TCP_NODELAY. + tcpNoDelay = true + + # All other socket options are unset by default. The actual value depends on the underlying + # Netty transport: + # - NIO uses the defaults from java.net.Socket (refer to the javadocs of + # java.net.StandardSocketOptions for each option). + # - Epoll delegates to the underlying file descriptor, which uses the O/S defaults. + + # Whether or not to enable TCP keep-alive probes. + # + # See java.net.StandardSocketOptions.SO_KEEPALIVE. + //keepAlive = false + + # Whether or not to allow address reuse. + # + # See java.net.StandardSocketOptions.SO_REUSEADDR. + //reuseAddress = true + + # Sets the linger interval. + # + # If the value is zero or greater, then it represents a timeout value, in seconds; + # if the value is negative, it means that this option is disabled. + # + # See java.net.StandardSocketOptions.SO_LINGER. + //lingerInterval = 0 + + # Sets a hint to the size of the underlying buffers for incoming network I/O. + # + # See java.net.StandardSocketOptions.SO_RCVBUF. + //receiveBufferSize = 65535 + + # Sets a hint to the size of the underlying buffers for outgoing network I/O. + # + # See java.net.StandardSocketOptions.SO_SNDBUF. + //sendBufferSize = 65535 + } } request { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java new file mode 100644 index 00000000000..e0416320153 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -0,0 +1,81 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.connection; + +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_KEEP_ALIVE; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_LINGER_INTERVAL; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_RECEIVE_BUFFER_SIZE; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_REUSE_ADDRESS; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_SEND_BUFFER_SIZE; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONNECTION_SOCKET_TCP_NODELAY; +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import io.netty.channel.FixedRecvByteBufAllocator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.socket.SocketChannelConfig; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class ChannelSocketOptionsIT { + + public static @ClassRule SimulacronRule simulacron = + new SimulacronRule(ClusterSpec.builder().withNodes(1)); + + @ClassRule + public static SessionRule sessionRule = + new SessionRule<>( + simulacron, + "connection.socket.tcpNoDelay = true", + "connection.socket.keepAlive = false", + "connection.socket.reuseAddress = false", + "connection.socket.lingerInterval = 10", + "connection.socket.receiveBufferSize = 123456", + "connection.socket.sendBufferSize = 123456"); + + @Test + public void should_report_socket_options() { + DefaultSession session = sessionRule.session(); + DriverConfigProfile config = session.getContext().config().getDefaultProfile(); + assertThat(config.getBoolean(CONNECTION_SOCKET_TCP_NODELAY)).isTrue(); + assertThat(config.getBoolean(CONNECTION_SOCKET_KEEP_ALIVE)).isFalse(); + assertThat(config.getBoolean(CONNECTION_SOCKET_REUSE_ADDRESS)).isFalse(); + assertThat(config.getInt(CONNECTION_SOCKET_LINGER_INTERVAL)).isEqualTo(10); + assertThat(config.getInt(CONNECTION_SOCKET_RECEIVE_BUFFER_SIZE)).isEqualTo(123456); + assertThat(config.getInt(CONNECTION_SOCKET_SEND_BUFFER_SIZE)).isEqualTo(123456); + Node node = session.getMetadata().getNodes().values().iterator().next(); + DriverChannel channel = session.getChannel(node, null); + assertThat(channel.config()).isInstanceOf(SocketChannelConfig.class); + SocketChannelConfig socketConfig = (SocketChannelConfig) channel.config(); + assertThat(socketConfig.isTcpNoDelay()).isTrue(); + assertThat(socketConfig.isKeepAlive()).isFalse(); + assertThat(socketConfig.isReuseAddress()).isFalse(); + assertThat(socketConfig.getSoLinger()).isEqualTo(10); + RecvByteBufAllocator allocator = socketConfig.getRecvByteBufAllocator(); + assertThat(allocator).isInstanceOf(FixedRecvByteBufAllocator.class); + assertThat(allocator.newHandle().guess()).isEqualTo(123456); + // cannot assert around SO_RCVBUF and SO_SNDBUF, such values are just hints + } +} From e6bfdd31cf3b65edda7578e1c3023889cb83bb2f Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 22 Feb 2018 14:58:01 -0800 Subject: [PATCH 352/742] Fix changelog for JAVA-1537 Amends datastax/java-driver@ef6ce31a9 --- changelog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 72b12690519..e04015feb09 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,11 +4,11 @@ ### 4.0.0-alpha4 (in progress) +- [new feature] JAVA-1537: Add remaining socket options - [bug] JAVA-1756: Propagate custom payload when preparing a statement ### 4.0.0-alpha3 -- [new feature] JAVA-1537: Add remaining socket options - [new feature] JAVA-1518: Expose metrics - [improvement] JAVA-1739: Add host_id and schema_version to node metadata - [improvement] JAVA-1738: Convert enums to allow extensibility From 3c046e28eca82fcf22aaa4522dec6a6bb2493a35 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 28 Feb 2018 15:02:49 +0100 Subject: [PATCH 353/742] Create a test-jar for the core module (#957) This commit also introduces minor enhancements to the following classes: * RequestHandlerTestHarness * CompletionStageAssert * CompletableFutures --- core/pom.xml | 12 ++++++ .../util/concurrent/CompletableFutures.java | 6 ++- .../internal/core/CompletionStageAssert.java | 40 ++++++++++++++++++- .../internal/core/cql/PoolBehavior.java | 21 +++++++++- .../core/cql/RequestHandlerTestHarness.java | 25 +++++++++++- 5 files changed, 99 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index c996d639407..8e08118a57d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -128,6 +128,17 @@ + + maven-jar-plugin + + + test-jar + + test-jar + + + + maven-shade-plugin @@ -147,6 +158,7 @@ com.datastax.oss.driver.shaded.guava + true diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java index 8a1d9c89c0e..c37c56c9213 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/CompletableFutures.java @@ -75,7 +75,8 @@ public static void whenAllDone( } /** Get the result now, when we know for sure that the future is complete. */ - public static T getCompleted(CompletableFuture future) { + public static T getCompleted(CompletionStage stage) { + CompletableFuture future = stage.toCompletableFuture(); Preconditions.checkArgument(future.isDone() && !future.isCompletedExceptionally()); try { return future.get(); @@ -86,7 +87,8 @@ public static T getCompleted(CompletableFuture future) { } /** Get the error now, when we know for sure that the future is failed. */ - public static Throwable getFailed(CompletableFuture future) { + public static Throwable getFailed(CompletionStage stage) { + CompletableFuture future = stage.toCompletableFuture(); Preconditions.checkArgument(future.isCompletedExceptionally()); try { future.get(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java index aaf8049e909..0484386cd8f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -66,8 +67,45 @@ public CompletionStageAssert isFailed() { return isFailed(f -> {}); } + public CompletionStageAssert isCancelled() { + boolean cancelled = false; + try { + actual.toCompletableFuture().get(2, TimeUnit.SECONDS); + } catch (CancellationException e) { + cancelled = true; + } catch (Exception ignored) { + } + if (!cancelled) { + fail("Expected completion stage to be cancelled"); + } + return this; + } + + public CompletionStageAssert isNotCancelled() { + boolean cancelled = false; + try { + actual.toCompletableFuture().get(2, TimeUnit.SECONDS); + } catch (CancellationException e) { + cancelled = true; + } catch (Exception ignored) { + } + if (cancelled) { + fail("Expected completion stage not to be cancelled"); + } + return this; + } + + public CompletionStageAssert isDone() { + assertThat(actual.toCompletableFuture().isDone()) + .overridingErrorMessage("Expected completion stage to be done") + .isTrue(); + return this; + } + public CompletionStageAssert isNotDone() { - assertThat(actual.toCompletableFuture().isDone()).isFalse(); + assertThat(actual.toCompletableFuture().isDone()) + .overridingErrorMessage("Expected completion stage not to be done") + .isFalse(); return this; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 9cf17f0c9ad..32a628028ab 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -25,7 +25,10 @@ import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; +import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoop; +import io.netty.channel.socket.DefaultSocketChannelConfig; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.Promise; import java.util.concurrent.CompletableFuture; @@ -52,17 +55,25 @@ public PoolBehavior(Node node, boolean createChannel) { this.writePromise = null; } else { this.channel = Mockito.mock(DriverChannel.class); + EventLoop eventLoop = Mockito.mock(EventLoop.class); + ChannelConfig config = Mockito.mock(DefaultSocketChannelConfig.class); this.writePromise = GlobalEventExecutor.INSTANCE.newPromise(); Mockito.when( channel.write( any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class))) .thenAnswer( invocation -> { - callbackFuture.complete(invocation.getArgument(3)); + ResponseCallback callback = invocation.getArgument(3); + if (callback.holdStreamId()) { + callback.onStreamIdAssigned(1); + } + callbackFuture.complete(callback); return writePromise; }); ChannelFuture closeFuture = Mockito.mock(ChannelFuture.class); Mockito.when(channel.closeFuture()).thenReturn(closeFuture); + Mockito.when(channel.eventLoop()).thenReturn(eventLoop); + Mockito.when(channel.config()).thenReturn(config); } } @@ -92,6 +103,14 @@ public void setResponseFailure(Throwable cause) { callbackFuture.thenAccept(callback -> callback.onFailure(cause)); } + public Node getNode() { + return node; + } + + public DriverChannel getChannel() { + return channel; + } + /** Mocks a follow-up request on the same channel. */ public void mockFollowupRequest(Class expectedMessage, Frame responseFrame) { Promise writePromise2 = GlobalEventExecutor.INSTANCE.newPromise(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 43b8fb4aec6..9b6c89aa9f4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -37,7 +37,9 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultMetadata; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.servererrors.DefaultWriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.DefaultSession; @@ -70,6 +72,7 @@ public static Builder builder() { } private final ScheduledTaskCapturingEventLoop schedulingEventLoop; + private final Map pools; @Mock private InternalDriverContext context; @Mock private DefaultSession session; @@ -82,6 +85,7 @@ public static Builder builder() { @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; @Mock private TimestampGenerator timestampGenerator; @Mock private ProtocolVersionRegistry protocolVersionRegistry; + @Mock private SessionMetricUpdater sessionMetricUpdater; private RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); @@ -126,7 +130,7 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(timestampGenerator.next()).thenReturn(Long.MIN_VALUE); Mockito.when(context.timestampGenerator()).thenReturn(timestampGenerator); - Map pools = builder.buildMockPools(); + pools = builder.buildMockPools(); Mockito.when(session.getChannel(any(Node.class), anyString())) .thenAnswer( invocation -> { @@ -138,12 +142,20 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(session.setKeyspace(any(CqlIdentifier.class))) .thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(session.getMetricUpdater()).thenReturn(sessionMetricUpdater); + + Mockito.when(session.getMetadata()).thenReturn(DefaultMetadata.EMPTY); + Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); Mockito.when( protocolVersionRegistry.supports( any(ProtocolVersion.class), any(ProtocolFeature.class))) .thenReturn(true); + if (builder.protocolVersion != null) { + Mockito.when(context.protocolVersion()).thenReturn(builder.protocolVersion); + } + Mockito.when(context.consistencyLevelRegistry()) .thenReturn(new DefaultConsistencyLevelRegistry()); @@ -158,6 +170,11 @@ public InternalDriverContext getContext() { return context; } + public DriverChannel getChannel(Node node) { + ChannelPool pool = pools.get(node); + return pool.next(); + } + /** * Returns the next task that was scheduled on the request handler's admin executor. The test must * run it manually. @@ -174,6 +191,7 @@ public void close() { public static class Builder { private final List poolBehaviors = new ArrayList<>(); private boolean defaultIdempotence; + private ProtocolVersion protocolVersion; /** * Sets the given node as the next one in the query plan; an empty pool will be simulated when @@ -224,6 +242,11 @@ public Builder withDefaultIdempotence(boolean defaultIdempotence) { return this; } + public Builder withProtocolVersion(ProtocolVersion protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + /** * Sets the given node as the next one in the query plan; the test code is responsible of * calling the methods on the returned object to complete the write and the query. From 8257234d8cdf83b7489d68219ffc15b1defda016 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 28 Feb 2018 17:11:06 -0800 Subject: [PATCH 354/742] JAVA-1772: Revisit multi-response callbacks Motivation: In the previous version, multi-response ResponseCallback implementations had to explicitly indicate when to release the stream id. It's simpler to do it directly in InFlightHandler, provided that we have a simple way to test when a Frame is the last that the server will send (and we should ensure that this is always the case for future multi-response requests). Modifications: Replace ResponseCallback.holdStreamId() by isLastResponse(Frame). Remove DriverChannel.release(int). Keep track of orphaned ResponseCallbacks in InFlightHandler, and release them (and the stream id) when their last response is received. Result: ResponseCallbacks now only need to indicate how to identify the last frame, InFlightHandler handles the rest. Cancelled multi-response callbacks will be properly released. --- changelog/README.md | 1 + .../internal/core/channel/DriverChannel.java | 18 ---- .../core/channel/InFlightHandler.java | 88 ++++++++++++------- .../core/channel/ResponseCallback.java | 36 ++++---- .../ChannelFactoryAvailableIdsTest.java | 2 + .../core/channel/InFlightHandlerTest.java | 85 ++++++++++++++++-- .../core/channel/MockResponseCallback.java | 13 +-- 7 files changed, 163 insertions(+), 80 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index e04015feb09..3a55c2d897a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1772: Revisit multi-response callbacks - [new feature] JAVA-1537: Add remaining socket options - [bug] JAVA-1756: Propagate custom payload when preparing a statement diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index abf167a3a6d..3416f561560 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -87,16 +87,6 @@ public void cancel(ResponseCallback responseCallback) { writeCoalescer.writeAndFlush(channel, responseCallback).addListener(UncaughtExceptions::log); } - /** - * Releases a stream id if the client was holding onto it, and has now determined that it can be - * safely reused. - * - * @see ResponseCallback#holdStreamId() - */ - public void release(int streamId) { - channel.pipeline().fireUserEventTriggered(new ReleaseEvent(streamId)); - } - /** * Switches the underlying Cassandra connection to a new keyspace (as if a {@code USE ...} * statement was issued). @@ -244,14 +234,6 @@ static class RequestMessage { } } - static class ReleaseEvent { - final int streamId; - - ReleaseEvent(int streamId) { - this.streamId = streamId; - } - } - static class SetKeyspaceEvent { final CqlIdentifier keyspaceName; final Promise promise; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index be5f1e442bf..83d6f30b0cc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.connection.BusyConnectionException; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.connection.HeartbeatException; -import com.datastax.oss.driver.internal.core.channel.DriverChannel.ReleaseEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel.RequestMessage; import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; @@ -30,6 +29,7 @@ import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; +import com.google.common.base.MoreObjects; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import io.netty.channel.ChannelDuplexHandler; @@ -38,6 +38,8 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.Promise; +import java.util.HashMap; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,13 +52,14 @@ public class InFlightHandler extends ChannelDuplexHandler { final ChannelPromise closeStartedFuture; private final String ownerLogPrefix; private final BiMap inFlight; + private final Map orphaned; + private volatile int orphanedSize; // thread-safe view for metrics private final long setKeyspaceTimeoutMillis; private final EventCallback eventCallback; private final int maxOrphanStreamIds; private boolean closingGracefully; private SetKeyspaceRequest setKeyspaceRequest; private String logPrefix; - private volatile int orphanStreamIds; // volatile only for metrics InFlightHandler( ProtocolVersion protocolVersion, @@ -73,6 +76,7 @@ public class InFlightHandler extends ChannelDuplexHandler { this.ownerLogPrefix = ownerLogPrefix; this.logPrefix = ownerLogPrefix + "|connecting..."; this.inFlight = HashBiMap.create(streamIds.getMaxAvailableIds()); + this.orphaned = new HashMap<>(maxOrphanStreamIds); this.setKeyspaceTimeoutMillis = setKeyspaceTimeoutMillis; this.eventCallback = eventCallback; } @@ -138,22 +142,19 @@ private void write(ChannelHandlerContext ctx, RequestMessage message, ChannelPro writeFuture.addListener( future -> { if (future.isSuccess()) { - if (message.responseCallback.holdStreamId()) { - message.responseCallback.onStreamIdAssigned(streamId); - } + message.responseCallback.onStreamIdAssigned(streamId); } else { release(streamId, ctx); } }); } - @SuppressWarnings("NonAtomicVolatileUpdate") private void cancel( ChannelHandlerContext ctx, ResponseCallback responseCallback, ChannelPromise promise) { Integer streamId = inFlight.inverse().remove(responseCallback); if (streamId == null) { LOG.debug( - "[{}] Received cancellation request for unknown callback {}, skipping", + "[{}] Received cancellation for unknown or already cancelled callback {}, skipping", logPrefix, responseCallback); } else { @@ -164,15 +165,17 @@ private void cancel( ctx.channel().close(); } else { // We can't release the stream id, because a response might still come back from the server. - // Keep track of how many of those ids are held, because we want to replace the channel if - // it becomes too high. - orphanStreamIds += 1; // safe because the method is confined to the I/O thread - if (orphanStreamIds > maxOrphanStreamIds) { + // Keep track of those "orphaned" ids, to release them later if we get a response and the + // callback says it's the last one. + orphaned.put(streamId, responseCallback); + if (orphaned.size() > maxOrphanStreamIds) { LOG.debug( "[{}] Orphan stream ids exceeded the configured threshold ({}), closing gracefully", logPrefix, maxOrphanStreamIds); startGracefulShutdown(ctx); + } else { + orphanedSize = orphaned.size(); } } } @@ -215,25 +218,44 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } } } else { - ResponseCallback responseCallback = inFlight.get(streamId); - if (responseCallback == null) { - LOG.debug("[{}] Got response on orphan stream id {}, releasing", logPrefix, streamId); - release(streamId, ctx); - orphanStreamIds -= 1; // safe because the method is confined to the I/O thread - } else { - LOG.debug( - "[{}] Got response on stream id {}, completing {}", - logPrefix, - streamId, - responseCallback); - if (!responseCallback.holdStreamId()) { + boolean wasInFlight = true; + ResponseCallback callback = inFlight.get(streamId); + if (callback == null) { + wasInFlight = false; + callback = orphaned.get(streamId); + if (callback == null) { + LOG.warn("[{}] Got response on unknown stream id {}, skipping", streamId); + return; + } + } + try { + if (callback.isLastResponse(responseFrame)) { + LOG.debug( + "[{}] Got last response on {} stream id {}, completing and releasing", + wasInFlight ? "in-flight" : "orphaned", + streamId); release(streamId, ctx); + } else { + LOG.debug( + "[{}] Got non-last response on {} stream id {}, still holding", + wasInFlight ? "in-flight" : "orphaned", + streamId); } - try { - responseCallback.onResponse(responseFrame); - } catch (Throwable t) { + if (wasInFlight) { + callback.onResponse(responseFrame); + } + } catch (Throwable t) { + if (wasInFlight) { + callback.onFailure( + new IllegalArgumentException("Unexpected error while invoking response handler", t)); + } else { + // Assume the callback is already completed, so it's better to log Loggers.warnWithException( - LOG, "[{}] Unexpected error while invoking response handler", logPrefix, t); + LOG, + "[{}] Unexpected error while invoking response handler on stream id {}", + logPrefix, + t, + streamId); } } } @@ -273,11 +295,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable exception) thro @Override public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception { - if (event instanceof ReleaseEvent) { - int streamId = ((ReleaseEvent) event).streamId; - LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); - release(streamId, ctx); - } else if (event instanceof SetKeyspaceEvent) { + if (event instanceof SetKeyspaceEvent) { SetKeyspaceEvent setKeyspaceEvent = (SetKeyspaceEvent) event; if (this.setKeyspaceRequest != null) { setKeyspaceEvent.promise.setFailure( @@ -305,7 +323,9 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { LOG.debug("[{}] Releasing stream id {}", logPrefix, streamId); - ResponseCallback responseCallback = inFlight.remove(streamId); + ResponseCallback responseCallback = + MoreObjects.firstNonNull(inFlight.remove(streamId), orphaned.remove(streamId)); + orphanedSize = orphaned.size(); streamIds.release(streamId); // If we're in the middle of an orderly close and this was the last request, actually close // the channel now @@ -344,7 +364,7 @@ int getInFlight() { } int getOrphanIds() { - return orphanStreamIds; + return orphanedSize; } private class SetKeyspaceRequest extends ChannelHandlerRequest { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java index 3cfc961e416..e8fcd87247f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ResponseCallback.java @@ -47,31 +47,33 @@ public interface ResponseCallback { void onFailure(Throwable error); /** - * Whether to hold the stream id beyond the first response. + * Reports the stream id used for the request on the current connection. * - *

          By default, this is false, and the channel will release the stream id (and make it available - * for other requests) as soon as {@link #onResponse(Frame)} or {@link #onFailure(Throwable)} gets - * invoked. + *

          This is called every time the request is written successfully to a connection (and therefore + * might multiple times in case of retries). It is guaranteed to be invoked before any response to + * the request on that connection is processed. * - *

          If this is true, the channel will keep the stream id assigned to this request, and {@code - * onResponse} might be invoked multiple times. {@link #onStreamIdAssigned(int)} will be called to - * notify the caller of the stream id, and it is the caller's responsibility to determine when the - * request is over, and then call {@link DriverChannel#release(int)} to release the stream id. + *

          The default implementation does nothing. This only needs to be overridden for specialized + * requests that hold the stream id across multiple responses. * - *

          This is intended to allow streaming requests, that would send multiple chunks of data in - * response to a single request (this feature does not exist yet in Cassandra but might be - * implemented in the future). + * @see #isLastResponse(Frame) */ - default boolean holdStreamId() { - return false; + default void onStreamIdAssigned(int streamId) { + // nothing to do } /** - * Reports the stream id to the caller if {@link #holdStreamId()} is true. + * Whether the given frame is the last response to this request. + * + *

          This is invoked for each response received by this callback; if it returns {@code true}, the + * driver assumes that the server is no longer using this stream id, and that it can be safely + * reused to send another request. * - *

          By default, this will never get called. + *

          The default implementation always returns {@code true}: regular CQL requests only have one + * response, and we can reuse the stream id as soon as we've received it. This only needs to be + * overridden for specialized requests that hold the stream id across multiple responses. */ - default void onStreamIdAssigned(int streamId) { - // nothing to do by default + default boolean isLastResponse(Frame responseFrame) { + return true; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index e10d97348a4..ec0e15285cf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -47,6 +47,8 @@ public void setup() throws InterruptedException { Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(128); + + Mockito.when(responseCallback.isLastResponse(any(Frame.class))).thenReturn(true); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 9cb8712c45e..45cc98bc510 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -26,6 +26,7 @@ import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.event.StatusChangeEvent; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.response.result.Void; @@ -133,6 +134,28 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl Mockito.verify(streamIds).release(42); } + @Test + public void should_release_stream_id_when_orphaned_callback_receives_response() { + // Given + addToPipeline(); + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = new MockResponseCallback(); + channel.writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); + Frame requestFrame = readOutboundFrame(); + + // When + channel.writeAndFlush(responseCallback); // means cancellation (see DriverChannel#cancel) + Frame responseFrame = buildInboundFrame(requestFrame, Void.INSTANCE); + writeInboundFrame(responseFrame); + + // Then + Mockito.verify(streamIds).release(42); + // The response is not propagated, because we assume a callback that cancelled managed its own + // termination + assertThat(responseCallback.getLastResponse()).isNull(); + } + @Test public void should_delay_graceful_close_and_complete_when_last_pending_completes() { // Given @@ -398,11 +421,12 @@ public void should_fail_all_pending_if_connection_lost() { } @Test - public void should_hold_stream_id_if_required() { + public void should_hold_stream_id_for_multi_response_callback() { // Given addToPipeline(); Mockito.when(streamIds.acquire()).thenReturn(42); - MockResponseCallback responseCallback = new MockResponseCallback(true); + MockResponseCallback responseCallback = + new MockResponseCallback(frame -> frame.message instanceof Error); // When channel @@ -428,16 +452,67 @@ public void should_hold_stream_id_if_required() { } // When - // the client releases the stream id - channel.pipeline().fireUserEventTriggered(new DriverChannel.ReleaseEvent(42)); + // a terminal response comes in + Frame responseFrame = buildInboundFrame(requestFrame, new Error(0, "test")); + writeInboundFrame(responseFrame); // Then Mockito.verify(streamIds).release(42); + assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); + + // When + // more responses come in writeInboundFrame(requestFrame, Void.INSTANCE); - // if more responses use this stream id, the handler does not get them anymore + + // Then + // the callback does not get them anymore (this could only be responses to a new request that + // reused the id) assertThat(responseCallback.getLastResponse()).isNull(); } + @Test + public void + should_release_stream_id_when_orphaned_multi_response_callback_receives_last_response() { + // Given + addToPipeline(); + Mockito.when(streamIds.acquire()).thenReturn(42); + MockResponseCallback responseCallback = + new MockResponseCallback(frame -> frame.message instanceof Error); + + channel + .writeAndFlush( + new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)) + .awaitUninterruptibly(); + + Frame requestFrame = readOutboundFrame(); + for (int i = 0; i < 5; i++) { + Frame responseFrame = buildInboundFrame(requestFrame, Void.INSTANCE); + writeInboundFrame(responseFrame); + assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); + Mockito.verify(streamIds, never()).release(42); + } + + // When + // cancelled mid-flight + channel.writeAndFlush(responseCallback); + + // Then + // subsequent non-final responses are not propagated (we assume the callback completed itself + // already), but do not release the stream id + writeInboundFrame(requestFrame, Void.INSTANCE); + assertThat(responseCallback.getLastResponse()).isNull(); + Mockito.verify(streamIds, never()).release(42); + + // When + // the terminal response arrives + writeInboundFrame(requestFrame, new Error(0, "test")); + + // Then + // still not propagated but the id is released + assertThat(responseCallback.getLastResponse()).isNull(); + Mockito.verify(streamIds).release(42); + } + @Test public void should_set_keyspace() { // Given diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java index d5360d63120..d8f5604ac72 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockResponseCallback.java @@ -18,19 +18,20 @@ import com.datastax.oss.protocol.internal.Frame; import java.util.ArrayDeque; import java.util.Queue; +import java.util.function.Predicate; class MockResponseCallback implements ResponseCallback { - private final boolean holdStreamId; private final Queue responses = new ArrayDeque<>(); + private final Predicate isLastResponse; volatile int streamId = -1; MockResponseCallback() { - this(false); + this(f -> true); } - MockResponseCallback(boolean holdStreamId) { - this.holdStreamId = holdStreamId; + MockResponseCallback(Predicate isLastResponse) { + this.isLastResponse = isLastResponse; } @Override @@ -44,8 +45,8 @@ public void onFailure(Throwable error) { } @Override - public boolean holdStreamId() { - return holdStreamId; + public boolean isLastResponse(Frame responseFrame) { + return isLastResponse.test(responseFrame); } @Override From e999158954507c67d40e36b1489a309ed8e07484 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Mar 2018 13:22:06 -0800 Subject: [PATCH 355/742] Fix compile error from previous commit --- .../datastax/oss/driver/internal/core/cql/PoolBehavior.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 32a628028ab..351ab29b93c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -64,9 +64,7 @@ public PoolBehavior(Node node, boolean createChannel) { .thenAnswer( invocation -> { ResponseCallback callback = invocation.getArgument(3); - if (callback.holdStreamId()) { - callback.onStreamIdAssigned(1); - } + callback.onStreamIdAssigned(1); callbackFuture.complete(callback); return writePromise; }); From 61fd210547437a1b1d14fe267a1d98dd76761fca Mon Sep 17 00:00:00 2001 From: GregBestland Date: Tue, 27 Feb 2018 17:07:55 -0600 Subject: [PATCH 356/742] Add map support to DriverConfigProfile --- .../api/core/config/DriverConfigProfile.java | 5 +++ .../typesafe/TypesafeDriverConfigProfile.java | 31 +++++++++++++++++++ .../core/config/typesafe/MockOptions.java | 3 +- .../typesafe/TypeSafeDriverConfigTest.java | 29 +++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 844db27898a..0e8107fca34 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -17,6 +17,7 @@ import java.time.Duration; import java.util.List; +import java.util.Map; /** * A profile in the driver's configuration. @@ -52,6 +53,10 @@ public interface DriverConfigProfile { DriverConfigProfile withStringList(DriverOption option, List value); + Map getStringMap(DriverOption option); + + DriverConfigProfile withStringMap(DriverOption option, Map value); + /** Returns a size in bytes. */ long getBytes(DriverOption option); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index be6f346a512..3defd93e46a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -17,13 +17,17 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapMaker; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueFactory; +import com.typesafe.config.ConfigValueType; import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -97,6 +101,33 @@ public DriverConfigProfile withStringList(DriverOption option, List valu return with(option, value); } + @Override + public Map getStringMap(DriverOption option) { + Config subConfig = getCached(option.getPath(), getEffectiveOptions()::getConfig); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map.Entry entry : subConfig.entrySet()) { + if (entry.getValue().valueType().equals(ConfigValueType.STRING)) { + builder.put(entry.getKey(), (String) entry.getValue().unwrapped()); + } + } + return builder.build(); + } + + @Override + public DriverConfigProfile withStringMap(DriverOption option, Map map) { + Base base = getBaseProfile(); + // Add the new option to any already derived options + Config newAdded = getAddedOptions(); + for (String key : map.keySet()) { + newAdded = + newAdded.withValue( + option.getPath() + "." + key, ConfigValueFactory.fromAnyRef(map.get(key))); + } + Derived derived = new Derived(base, newAdded); + base.register(derived); + return derived; + } + @Override public long getBytes(DriverOption option) { return getCached(option.getPath(), getEffectiveOptions()::getBytes); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java index 423f7cf2879..35626e61e51 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/MockOptions.java @@ -19,7 +19,8 @@ enum MockOptions implements DriverOption { REQUIRED_INT("required_int", true), - OPTIONAL_INT("optional_int", false); + OPTIONAL_INT("optional_int", false), + OPTIONAL_AUTH("auth_provider", false); private final String path; private final boolean required; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java index 37a74e55122..ba09ed4af82 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import java.util.HashMap; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -94,6 +96,33 @@ public void should_create_derived_profile_overriding_option() { assertThat(derived.getInt(MockOptions.REQUIRED_INT)).isEqualTo(43); } + @Test + public void should_fetch_string_map() { + TypeSafeDriverConfig config = + parse( + "required_int = 42 \n auth_provider { auth_thing_one= one \n auth_thing_two = two \n auth_thing_three = three}"); + DriverConfigProfile base = config.getDefaultProfile(); + base.getStringMap(MockOptions.OPTIONAL_AUTH); + Map map = base.getStringMap(MockOptions.OPTIONAL_AUTH); + assertThat(map.entrySet().size()).isEqualTo(3); + assertThat(map.get("auth_thing_one")).isEqualTo("one"); + assertThat(map.get("auth_thing_two")).isEqualTo("two"); + assertThat(map.get("auth_thing_three")).isEqualTo("three"); + } + + @Test + public void should_create_derived_profile_with_string_map() { + TypeSafeDriverConfig config = parse("required_int = 42"); + Map authThingMap = new HashMap<>(); + authThingMap.put("auth_thing_one", "one"); + authThingMap.put("auth_thing_two", "two"); + authThingMap.put("auth_thing_three", "three"); + DriverConfigProfile base = config.getDefaultProfile(); + DriverConfigProfile mapBase = base.withStringMap(MockOptions.OPTIONAL_AUTH, authThingMap); + Map fetchedMap = mapBase.getStringMap(MockOptions.OPTIONAL_AUTH); + assertThat(fetchedMap).isEqualTo(authThingMap); + } + @Test public void should_reload() { TypeSafeDriverConfig config = From 9f7f0791adcc4912533bf03bf3bde1af0343be40 Mon Sep 17 00:00:00 2001 From: GregBestland Date: Thu, 1 Mar 2018 16:43:16 -0600 Subject: [PATCH 357/742] Add catch all for failures in authentication initialization --- .../oss/driver/internal/core/channel/ProtocolInitHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index b2311e928e8..7b6179ed21b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -294,6 +294,8 @@ void onResponse(Message response) { } } catch (AuthenticationException e) { fail(e); + } catch (Throwable t) { + fail("Unexpected exception at step " + step, t); } } From 020bc534c4baa86f9f0a04d88d7a7d3a7fa84d93 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 8 Mar 2018 15:17:18 -0800 Subject: [PATCH 358/742] Use consistent capitalization of Typesafe --- .../typesafe/DefaultDriverConfigLoader.java | 8 +++--- ...rConfig.java => TypesafeDriverConfig.java} | 6 ++-- ...est.java => TypesafeDriverConfigTest.java} | 28 +++++++++---------- manual/core/configuration/README.md | 18 ++++++------ upgrade_guide/README.md | 4 +-- 5 files changed, 32 insertions(+), 32 deletions(-) rename core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/{TypeSafeDriverConfig.java => TypesafeDriverConfig.java} (97%) rename core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/{TypeSafeDriverConfigTest.java => TypesafeDriverConfigTest.java} (89%) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index a7bce5f9e83..0d02bc5b427 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -37,7 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** The default loader; it is based on TypeSafe Config and reloads at a configurable interval. */ +/** The default loader; it is based on Typesafe Config and reloads at a configurable interval. */ public class DefaultDriverConfigLoader implements DriverConfigLoader { private static final Logger LOG = LoggerFactory.getLogger(DefaultDriverConfigLoader.class); @@ -49,12 +49,12 @@ public class DefaultDriverConfigLoader implements DriverConfigLoader { }; private final Supplier configSupplier; - private final TypeSafeDriverConfig driverConfig; + private final TypesafeDriverConfig driverConfig; private volatile SingleThreaded singleThreaded; /** - * Builds a new instance with the default TypeSafe config loading rules (documented in {@link + * Builds a new instance with the default Typesafe config loading rules (documented in {@link * SessionBuilder#withConfigLoader(DriverConfigLoader)}) and the core driver options. */ public DefaultDriverConfigLoader() { @@ -67,7 +67,7 @@ public DefaultDriverConfigLoader() { */ public DefaultDriverConfigLoader(Supplier configSupplier, DriverOption[]... options) { this.configSupplier = configSupplier; - this.driverConfig = new TypeSafeDriverConfig(configSupplier.get(), options); + this.driverConfig = new TypesafeDriverConfig(configSupplier.get(), options); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java similarity index 97% rename from core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java index 13d31e662a9..3c4cf504253 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java @@ -33,9 +33,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class TypeSafeDriverConfig implements DriverConfig { +public class TypesafeDriverConfig implements DriverConfig { - private static final Logger LOG = LoggerFactory.getLogger(TypeSafeDriverConfig.class); + private static final Logger LOG = LoggerFactory.getLogger(TypesafeDriverConfig.class); private static final String DEFAULT_PROFILE_KEY = "__default_internal__"; private final Collection options; @@ -44,7 +44,7 @@ public class TypeSafeDriverConfig implements DriverConfig { // Only used to detect if reload saw any change private volatile Config lastLoadedConfig; - public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) { + public TypesafeDriverConfig(Config config, DriverOption[]... optionArrays) { this.lastLoadedConfig = config; this.options = merge(optionArrays); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java similarity index 89% rename from core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java rename to core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java index ba09ed4af82..82a55466a60 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypeSafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java @@ -27,19 +27,19 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class TypeSafeDriverConfigTest { +public class TypesafeDriverConfigTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void should_load_minimal_config_with_required_options_and_no_profiles() { - TypeSafeDriverConfig config = parse("required_int = 42"); + TypesafeDriverConfig config = parse("required_int = 42"); assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); } @Test public void should_load_config_with_no_profiles_and_optional_values() { - TypeSafeDriverConfig config = parse("required_int = 42\n optional_int = 43"); + TypesafeDriverConfig config = parse("required_int = 42\n optional_int = 43"); assertThat(config).hasIntOption(MockOptions.REQUIRED_INT, 42); assertThat(config).hasIntOption(MockOptions.OPTIONAL_INT, 43); } @@ -53,7 +53,7 @@ public void should_fail_if_required_option_is_missing() { @Test public void should_inherit_option_in_profile() { - TypeSafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { } }"); + TypesafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { } }"); assertThat(config) .hasIntOption(MockOptions.REQUIRED_INT, 42) .hasIntOption("profile1", MockOptions.REQUIRED_INT, 42); @@ -61,7 +61,7 @@ public void should_inherit_option_in_profile() { @Test public void should_override_option_in_profile() { - TypeSafeDriverConfig config = + TypesafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); assertThat(config) .hasIntOption(MockOptions.REQUIRED_INT, 42) @@ -71,13 +71,13 @@ public void should_override_option_in_profile() { @Test public void should_load_default_driver_config() { // No assertions here, but this validates that `reference.conf` is well-formed. - new TypeSafeDriverConfig( + new TypesafeDriverConfig( ConfigFactory.load().getConfig("datastax-java-driver"), DefaultDriverOption.values()); } @Test public void should_create_derived_profile_with_new_option() { - TypeSafeDriverConfig config = parse("required_int = 42"); + TypesafeDriverConfig config = parse("required_int = 42"); DriverConfigProfile base = config.getDefaultProfile(); DriverConfigProfile derived = base.withInt(MockOptions.OPTIONAL_INT, 43); @@ -88,7 +88,7 @@ public void should_create_derived_profile_with_new_option() { @Test public void should_create_derived_profile_overriding_option() { - TypeSafeDriverConfig config = parse("required_int = 42"); + TypesafeDriverConfig config = parse("required_int = 42"); DriverConfigProfile base = config.getDefaultProfile(); DriverConfigProfile derived = base.withInt(MockOptions.REQUIRED_INT, 43); @@ -98,7 +98,7 @@ public void should_create_derived_profile_overriding_option() { @Test public void should_fetch_string_map() { - TypeSafeDriverConfig config = + TypesafeDriverConfig config = parse( "required_int = 42 \n auth_provider { auth_thing_one= one \n auth_thing_two = two \n auth_thing_three = three}"); DriverConfigProfile base = config.getDefaultProfile(); @@ -112,7 +112,7 @@ public void should_fetch_string_map() { @Test public void should_create_derived_profile_with_string_map() { - TypeSafeDriverConfig config = parse("required_int = 42"); + TypesafeDriverConfig config = parse("required_int = 42"); Map authThingMap = new HashMap<>(); authThingMap.put("auth_thing_one", "one"); authThingMap.put("auth_thing_two", "two"); @@ -125,7 +125,7 @@ public void should_create_derived_profile_with_string_map() { @Test public void should_reload() { - TypeSafeDriverConfig config = + TypesafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); config.reload( @@ -138,7 +138,7 @@ public void should_reload() { @Test public void should_update_derived_profiles_after_reloading() { - TypeSafeDriverConfig config = + TypesafeDriverConfig config = parse("required_int = 42\n profiles { profile1 { required_int = 43 } }"); DriverConfigProfile derivedFromDefault = @@ -157,8 +157,8 @@ public void should_update_derived_profiles_after_reloading() { assertThat(derivedFromProfile1.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(51); } - private TypeSafeDriverConfig parse(String configString) { + private TypesafeDriverConfig parse(String configString) { Config config = ConfigFactory.parseString(configString); - return new TypeSafeDriverConfig(config, MockOptions.values()); + return new TypesafeDriverConfig(config, MockOptions.values()); } } diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 10b53b253b9..d6f4c18dea2 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -3,7 +3,7 @@ The driver's configuration is composed of options, organized in a hierarchical manner. Optionally, it can define *profiles* that customize a set of options for a particular kind of request. -The default implementation is based on the TypeSafe Config framework. It can be completely +The default implementation is based on the Typesafe Config framework. It can be completely overridden if needed. For a complete list of built-in options, see [reference.conf] in the driver sources. @@ -54,9 +54,9 @@ arbitrary number of named profiles. They inherit from the default profile, so yo override the options that have a different value. -### Default implementation: TypeSafe Config +### Default implementation: Typesafe Config -Out of the box, the driver uses [TypeSafe Config]. +Out of the box, the driver uses [Typesafe Config]. It looks at the following locations, according to the [standard behavior][config standard behavior] of that library: @@ -226,7 +226,7 @@ private static Config loadConfig(String prefix) { Next, create a `DriverConfigLoader`. This is the component that abstracts the configuration implementation to the rest of the driver. Here we use the built-in class, but tell it to load the -TypeSafe Config object with the previous method: +Typesafe Config object with the previous method: ```java import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -249,8 +249,8 @@ CqlSession session1 = #### Loading from a different source -If you don't want to use a config file, you can write custom code to create the TypeSafe `Config` -object (refer to the [documentation][TypeSafe Config] for more details). +If you don't want to use a config file, you can write custom code to create the Typesafe `Config` +object (refer to the [documentation][Typesafe Config] for more details). Then reuse the examples from the previous section to merge it with the driver's reference file, and pass it to the driver. Here's a contrived example that loads the configuration from a string: @@ -270,9 +270,9 @@ DriverConfigLoader loader = CqlSession session = CqlSession.builder().withConfigLoader(loader).build(); ``` -#### Bypassing TypeSafe Config +#### Bypassing Typesafe Config -If TypeSafe Config doesn't work for you, it is possible to get rid of it entirely. +If Typesafe Config doesn't work for you, it is possible to get rid of it entirely. You will need to provide your own implementations of [DriverConfig] and [DriverConfigProfile]. Then write a [DriverConfigLoader] and pass it to the session at initialization, as shown in the previous @@ -439,7 +439,7 @@ config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); [DefaultDriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html [DriverConfigLoader]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html -[TypeSafe Config]: https://github.com/typesafehub/config +[Typesafe Config]: https://github.com/typesafehub/config [config standard behavior]: https://github.com/typesafehub/config#standard-behavior [reference.conf]: https://github.com/datastax/java-driver/blob/4.x/core/src/main/resources/reference.conf [HOCON]: https://github.com/typesafehub/config/blob/master/HOCON.md diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index f958a6356fc..097a763f749 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -23,14 +23,14 @@ IDE to find out the new locations. #### New configuration API The configuration has been completely revamped. Instead of ad-hoc configuration classes, the default -configuration mechanism is now file-based, using the [TypeSafe Config] library. This is a better +configuration mechanism is now file-based, using the [Typesafe Config] library. This is a better choice for most deployments, since it allows configuration changes without recompiling the client application. This is fully customizable, including loading from different sources, or completely overriding the default implementation. For more details, refer to the [manual](../manual/core/configuration). -[TypeSafe Config]: https://github.com/typesafehub/config +[Typesafe Config]: https://github.com/typesafehub/config #### Expose interfaces, not classes From 57e3223a62b0f5d9ba0d83d636f122bd63524cb3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Mar 2018 09:53:12 -0700 Subject: [PATCH 359/742] Add missing reporter.start() calls in metrics manual examples --- manual/core/metrics/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manual/core/metrics/README.md b/manual/core/metrics/README.md index b1ccf8be138..13831ad0aba 100644 --- a/manual/core/metrics/README.md +++ b/manual/core/metrics/README.md @@ -67,11 +67,12 @@ dependency of the driver): Then create a JMX reporter for the registry: -``` +```java JmxReporter reporter = JmxReporter.forRegistry(session.getMetricRegistry()) .inDomain("com.datastax.oss.driver") .build(); +reporter.start(); ``` Note: by default, the JMX reporter exposes all metrics in a flat structure (for example, @@ -113,6 +114,7 @@ JmxReporter reporter = .inDomain("com.datastax.oss.driver") .createsObjectNamesWith(objectNameFactory) .build(); +reporter.start(); ``` #### Other protocols From c6bdbb430a36ebf5b03c9fcd25ff2062b554240f Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Mar 2018 10:29:54 -0700 Subject: [PATCH 360/742] Add string overloads for API methods that take CqlIdentifier arguments This is friendly to users who follow the good practice of using only case-insensitive identifiers. --- .../driver/api/core/cql/BatchStatement.java | 8 +++ .../driver/api/core/cql/SimpleStatement.java | 8 +++ .../oss/driver/api/core/cql/Statement.java | 8 +++ .../driver/api/core/cql/StatementBuilder.java | 8 +++ .../driver/api/core/metadata/Metadata.java | 8 +++ .../driver/api/core/metadata/TokenMap.java | 32 ++++++++++++ .../metadata/schema/FunctionSignature.java | 16 ++++++ .../metadata/schema/KeyspaceMetadata.java | 50 +++++++++++++++++++ .../metadata/schema/RelationMetadata.java | 11 +++- .../api/core/session/SessionBuilder.java | 8 +++ 10 files changed, 155 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 98e29b80867..3ebe3b3ae28 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -111,6 +111,14 @@ static BatchStatementBuilder builder(BatchStatement template) { */ BatchStatement setKeyspace(CqlIdentifier newKeyspace); + /** + * Shortcut for {@link #setKeyspace(CqlIdentifier) + * setKeyspace(CqlIdentifier.fromCql(newKeyspaceName))}. + */ + default BatchStatement setKeyspace(String newKeyspaceName) { + return setKeyspace(CqlIdentifier.fromCql(newKeyspaceName)); + } + /** * Adds a new statement to the batch. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 9fad83a9cd3..d33e0ecfb4d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -163,6 +163,14 @@ static SimpleStatementBuilder builder(SimpleStatement template) { */ SimpleStatement setKeyspace(CqlIdentifier newKeyspace); + /** + * Shortcut for {@link #setKeyspace(CqlIdentifier) + * setKeyspace(CqlIdentifier.fromCql(newKeyspaceName))}. + */ + default SimpleStatement setKeyspace(String newKeyspaceName) { + return setKeyspace(CqlIdentifier.fromCql(newKeyspaceName)); + } + List getPositionalValues(); /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 802cc44f26d..e90505e9992 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -81,6 +81,14 @@ public interface Statement> extends Request { */ T setRoutingKeyspace(CqlIdentifier newRoutingKeyspace); + /** + * Shortcut for {@link #setRoutingKeyspace(CqlIdentifier) + * setRoutingKeyspace(CqlIdentifier.fromCql(newRoutingKeyspaceName))}. + */ + default T setRoutingKeyspace(String newRoutingKeyspaceName) { + return setRoutingKeyspace(CqlIdentifier.fromCql(newRoutingKeyspaceName)); + } + /** * Sets the key to use for token-aware routing. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 63d828434e9..8c73ffb7286 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -80,6 +80,14 @@ public T withRoutingKeyspace(CqlIdentifier routingKeyspace) { return self; } + /** + * Shortcut for {@link #withRoutingKeyspace(CqlIdentifier) + * withRoutingKeyspace(CqlIdentifier.fromCql(routingKeyspaceName))}. + */ + public T withRoutingKeyspace(String routingKeyspaceName) { + return withRoutingKeyspace(CqlIdentifier.fromCql(routingKeyspaceName)); + } + /** @see Statement#setRoutingKey(ByteBuffer) */ public T withRoutingKey(ByteBuffer routingKey) { this.routingKey = routingKey; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 4c35cc9bb72..6c495135c1b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -56,6 +56,14 @@ default KeyspaceMetadata getKeyspace(CqlIdentifier keyspaceId) { return getKeyspaces().get(keyspaceId); } + /** + * Shortcut for {@link #getKeyspace(CqlIdentifier) + * getKeyspace(CqlIdentifier.fromCql(keyspaceName))}. + */ + default KeyspaceMetadata getKeyspace(String keyspaceName) { + return getKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + /** * The token map for this cluster. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java index 24ce18b9867..c78f3f6b4ab 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java @@ -78,12 +78,36 @@ default Set getTokens(Node node) { /** The token ranges that are replicated on the given node, for the given keyspace. */ Set getTokenRanges(CqlIdentifier keyspace, Node replica); + /** + * Shortcut for {@link #getTokenRanges(CqlIdentifier, Node) + * getTokenRanges(CqlIdentifier.fromCql(keyspaceName), replica)}. + */ + default Set getTokenRanges(String keyspaceName, Node replica) { + return getTokenRanges(CqlIdentifier.fromCql(keyspaceName), replica); + } + /** The replicas for a given partition key in the given keyspace. */ Set getReplicas(CqlIdentifier keyspace, ByteBuffer partitionKey); + /** + * Shortcut for {@link #getReplicas(CqlIdentifier, ByteBuffer) + * getReplicas(CqlIdentifier.fromCql(keyspaceName), partitionKey)}. + */ + default Set getReplicas(String keyspaceName, ByteBuffer partitionKey) { + return getReplicas(CqlIdentifier.fromCql(keyspaceName), partitionKey); + } + /** The replicas for a given token in the given keyspace. */ Set getReplicas(CqlIdentifier keyspace, Token token); + /** + * Shortcut for {@link #getReplicas(CqlIdentifier, Token) + * getReplicas(CqlIdentifier.fromCql(keyspaceName), token)}. + */ + default Set getReplicas(String keyspaceName, Token token) { + return getReplicas(CqlIdentifier.fromCql(keyspaceName), token); + } + /** * The replicas for a given range in the given keyspace. * @@ -95,4 +119,12 @@ default Set getTokens(Node node) { default Set getReplicas(CqlIdentifier keyspace, TokenRange range) { return getReplicas(keyspace, range.getEnd()); } + + /** + * Shortcut for {@link #getReplicas(CqlIdentifier, TokenRange) + * getReplicas(CqlIdentifier.fromCql(keyspaceName), range)}. + */ + default Set getReplicas(String keyspaceName, TokenRange range) { + return getReplicas(CqlIdentifier.fromCql(keyspaceName), range); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java index 7f5782505c7..628f345b40b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java @@ -40,6 +40,22 @@ public FunctionSignature(CqlIdentifier name, DataType... parameterTypes) { this(name, ImmutableList.builder().add(parameterTypes).build()); } + /** + * Shortcut for {@link #FunctionSignature(CqlIdentifier, Iterable) new + * FunctionSignature(CqlIdentifier.fromCql(name), parameterTypes)}. + */ + public FunctionSignature(String name, Iterable parameterTypes) { + this(CqlIdentifier.fromCql(name), parameterTypes); + } + + /** + * Shortcut for {@link #FunctionSignature(CqlIdentifier, DataType...)} new + * FunctionSignature(CqlIdentifier.fromCql(name), parameterTypes)}. + */ + public FunctionSignature(String name, DataType... parameterTypes) { + this(CqlIdentifier.fromCql(name), parameterTypes); + } + public CqlIdentifier getName() { return name; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java index fae5b1c43d6..656d71af932 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java @@ -39,6 +39,11 @@ default TableMetadata getTable(CqlIdentifier tableId) { return getTables().get(tableId); } + /** Shortcut for {@link #getTable(CqlIdentifier) getTable(CqlIdentifier.fromCql(tableName))}. */ + default TableMetadata getTable(String tableName) { + return getTable(CqlIdentifier.fromCql(tableName)); + } + Map getViews(); /** Gets the views based on a given table. */ @@ -56,12 +61,25 @@ default ViewMetadata getView(CqlIdentifier viewId) { return getViews().get(viewId); } + /** Shortcut for {@link #getView(CqlIdentifier) getView(CqlIdentifier.fromCql(viewName))}. */ + default ViewMetadata getView(String viewName) { + return getView(CqlIdentifier.fromCql(viewName)); + } + Map getUserDefinedTypes(); default UserDefinedType getUserDefinedType(CqlIdentifier typeId) { return getUserDefinedTypes().get(typeId); } + /** + * Shortcut for {@link #getUserDefinedType(CqlIdentifier) + * getUserDefinedType(CqlIdentifier.fromCql(typeName))}. + */ + default UserDefinedType getUserDefinedType(String typeName) { + return getUserDefinedType(CqlIdentifier.fromCql(typeName)); + } + Map getFunctions(); default FunctionMetadata getFunction(FunctionSignature functionSignature) { @@ -73,10 +91,26 @@ default FunctionMetadata getFunction( return getFunctions().get(new FunctionSignature(functionId, parameterTypes)); } + /** + * Shortcut for {@link #getFunction(CqlIdentifier, Iterable) + * getFunction(CqlIdentifier.fromCql(functionName), parameterTypes)}. + */ + default FunctionMetadata getFunction(String functionName, Iterable parameterTypes) { + return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); + } + default FunctionMetadata getFunction(CqlIdentifier functionId, DataType... parameterTypes) { return getFunctions().get(new FunctionSignature(functionId, parameterTypes)); } + /** + * Shortcut for {@link #getFunction(CqlIdentifier, DataType...) + * getFunction(CqlIdentifier.fromCql(functionName), parameterTypes)}. + */ + default FunctionMetadata getFunction(String functionName, DataType... parameterTypes) { + return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); + } + Map getAggregates(); default AggregateMetadata getAggregate(FunctionSignature aggregateSignature) { @@ -88,10 +122,26 @@ default AggregateMetadata getAggregate( return getAggregates().get(new FunctionSignature(aggregateId, parameterTypes)); } + /** + * Shortcut for {@link #getAggregate(CqlIdentifier, Iterable) + * getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes)}. + */ + default AggregateMetadata getAggregate(String aggregateName, Iterable parameterTypes) { + return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); + } + default AggregateMetadata getAggregate(CqlIdentifier aggregateId, DataType... parameterTypes) { return getAggregates().get(new FunctionSignature(aggregateId, parameterTypes)); } + /** + * Shortcut for {@link #getAggregate(CqlIdentifier, DataType...)} + * getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes)}. + */ + default AggregateMetadata getAggregate(String aggregateName, DataType... parameterTypes) { + return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); + } + @Override default String describe(boolean pretty) { ScriptBuilder builder = diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java index 3823ea58e31..70cd8971c4a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java @@ -36,8 +36,15 @@ public interface RelationMetadata extends Describable { Map getColumns(); - default ColumnMetadata getColumn(CqlIdentifier columnName) { - return getColumns().get(columnName); + default ColumnMetadata getColumn(CqlIdentifier columnId) { + return getColumns().get(columnId); + } + + /** + * Shortcut for {@link #getColumn(CqlIdentifier) getColumn(CqlIdentifier.fromCql(columnName))}. + */ + default ColumnMetadata getColumn(String columnName) { + return getColumn(CqlIdentifier.fromCql(columnName)); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 40476802e58..ce8bf6c0e01 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -146,6 +146,14 @@ public SelfT withKeyspace(CqlIdentifier keyspace) { return self; } + /** + * Shortcut for {@link #withKeyspace(CqlIdentifier) + * withKeyspace(CqlIdentifier.fromCql(keyspaceName))} + */ + public SelfT withKeyspace(String keyspaceName) { + return withKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + /** * Creates the session with the options set by this builder. * From 7057301b5869e1f07af6da14743733b5e3823a3a Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 16 Feb 2018 10:27:06 -0800 Subject: [PATCH 361/742] Pass AdminRequestHandler's custom payload at construction time This allows having a single start() method, which will simplify throttling those requests. --- .../core/adminrequest/AdminRequestHandler.java | 17 +++++++++++------ .../core/cql/CqlPrepareHandlerBase.java | 6 ++++-- .../core/cql/CqlRequestHandlerBase.java | 3 ++- .../internal/core/session/ReprepareOnUp.java | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 3e32a8310f0..caecf8a7483 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -62,7 +62,8 @@ public static AdminRequestHandler query( if (!parameters.isEmpty()) { debugString += " with parameters " + parameters; } - return new AdminRequestHandler(channel, message, timeout, logPrefix, debugString); + return new AdminRequestHandler( + channel, message, Frame.NO_PAYLOAD, timeout, logPrefix, debugString); } public static AdminRequestHandler query( @@ -72,6 +73,7 @@ public static AdminRequestHandler query( private final DriverChannel channel; private final Message message; + private final Map customPayload; private final Duration timeout; private final String logPrefix; private final String debugString; @@ -83,21 +85,19 @@ public static AdminRequestHandler query( public AdminRequestHandler( DriverChannel channel, Message message, + Map customPayload, Duration timeout, String logPrefix, String debugString) { this.channel = channel; this.message = message; + this.customPayload = customPayload; this.timeout = timeout; this.logPrefix = logPrefix; this.debugString = debugString; } public CompletionStage start() { - return start(Frame.NO_PAYLOAD); - } - - public CompletionStage start(Map customPayload) { LOG.debug("[{}] Executing {}", logPrefix, this); channel.write(message, false, customPayload, this).addListener(this::onWriteComplete); return result; @@ -158,7 +158,12 @@ private AdminRequestHandler copy(ByteBuffer pagingState) { QueryOptions newOptions = buildQueryOptions(currentOptions.pageSize, currentOptions.namedValues, pagingState); return new AdminRequestHandler( - channel, new Query(current.query, newOptions), timeout, logPrefix, debugString); + channel, + new Query(current.query, newOptions), + customPayload, + timeout, + logPrefix, + debugString); } private static QueryOptions buildQueryOptions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 1c6c3de5ebb..d47d157534d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -283,9 +283,11 @@ private CompletionStage prepareOnOtherNode(Node node) { return CompletableFuture.completedFuture(null); } else { AdminRequestHandler handler = - new AdminRequestHandler(channel, message, timeout, logPrefix, message.toString()); + new AdminRequestHandler( + channel, message, request.getCustomPayload(), timeout, logPrefix, message.toString()); + return handler - .start(request.getCustomPayload()) + .start() .handle( (result, error) -> { if (error == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 13ec35b7aee..d05d2137c3e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -476,11 +476,12 @@ private void processErrorResponse(Error errorMessage) { new AdminRequestHandler( channel, reprepareMessage, + repreparePayload.customPayload, timeout, logPrefix, "Reprepare " + reprepareMessage.toString()); reprepareHandler - .start(repreparePayload.customPayload) + .start() .handle( (result, exception) -> { if (exception != null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 7f7653204ff..89bd9bc81b9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -232,7 +232,7 @@ private void startWorker() { protected CompletionStage queryAsync( Message message, Map customPayload, String debugString) { AdminRequestHandler reprepareHandler = - new AdminRequestHandler(channel, message, timeout, logPrefix, debugString); - return reprepareHandler.start(customPayload); + new AdminRequestHandler(channel, message, customPayload, timeout, logPrefix, debugString); + return reprepareHandler.start(); } } From f1527f8e7d9d5d24195dfb5caef6a16b1f8bedac Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 14 Feb 2018 16:10:29 -0800 Subject: [PATCH 362/742] JAVA-1536: Add request throttling --- changelog/README.md | 1 + .../api/core/RequestThrottlingException.java | 35 ++ .../api/core/config/DefaultDriverOption.java | 8 + .../core/metrics/DefaultSessionMetric.java | 3 + .../adminrequest/AdminRequestHandler.java | 22 +- .../ThrottledAdminRequestHandler.java | 98 ++++++ .../core/context/DefaultDriverContext.java | 19 ++ .../core/context/InternalDriverContext.java | 3 + .../core/cql/CqlPrepareHandlerBase.java | 55 ++- .../core/cql/CqlRequestHandlerBase.java | 43 ++- .../metrics/DefaultMetricUpdaterFactory.java | 9 +- .../metrics/DefaultSessionMetricUpdater.java | 34 ++ .../core/metrics/MetricUpdaterFactory.java | 3 +- .../internal/core/session/DefaultSession.java | 2 +- .../internal/core/session/ReprepareOnUp.java | 24 +- .../ConcurrencyLimitingRequestThrottler.java | 180 ++++++++++ .../core/session/throttling/NanoClock.java | 21 ++ .../PassThroughRequestThrottler.java | 56 ++++ .../RateLimitingRequestThrottler.java | 242 +++++++++++++ .../session/throttling/RequestThrottler.java | 49 +++ .../core/session/throttling/Throttled.java | 40 +++ core/src/main/resources/reference.conf | 75 +++++ .../core/cql/RequestHandlerTestHarness.java | 3 + .../core/session/ReprepareOnUpTest.java | 7 + ...ncurrencyLimitingRequestThrottlerTest.java | 239 +++++++++++++ .../session/throttling/MockThrottled.java | 35 ++ .../RateLimitingRequestThrottlerTest.java | 317 ++++++++++++++++++ .../session/throttling/SettableNanoClock.java | 32 ++ .../api/core/throttling/ThrottlingIT.java | 69 ++++ manual/core/throttling/README.md | 143 ++++++++ 30 files changed, 1837 insertions(+), 30 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/ThrottledAdminRequestHandler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/NanoClock.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RequestThrottler.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/Throttled.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/MockThrottled.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/SettableNanoClock.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java create mode 100644 manual/core/throttling/README.md diff --git a/changelog/README.md b/changelog/README.md index 3a55c2d897a..a97d03faf68 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [new feature] JAVA-1536: Add request throttling - [improvement] JAVA-1772: Revisit multi-response callbacks - [new feature] JAVA-1537: Add remaining socket options - [bug] JAVA-1756: Propagate custom payload when preparing a statement diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java b/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java new file mode 100644 index 00000000000..c38cea3b7b2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java @@ -0,0 +1,35 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +/** + * Thrown if the session uses a request throttler, and it didn't allow the current request to + * execute. + * + *

          This can happen either when the session is overloaded, or at shutdown for requests that had + * been enqueued. + */ +public class RequestThrottlingException extends DriverException { + + public RequestThrottlingException(String message) { + super(message, null, true); + } + + @Override + public DriverException copy() { + return new RequestThrottlingException(getMessage()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 8c6bdcbffd3..28c78581fb3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -61,6 +61,11 @@ public enum DefaultDriverOption implements DriverOption { REQUEST_TRACE_ATTEMPTS("request.trace.attempts", true), REQUEST_TRACE_INTERVAL("request.trace.interval", true), REQUEST_TRACE_CONSISTENCY("request.trace.consistency", true), + REQUEST_THROTTLER_CLASS("request.throttler.class", true), + REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS("request.throttler.max-concurrent-requests", false), + REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND("request.throttler.max-requests-per-second", false), + REQUEST_THROTTLER_MAX_QUEUE_SIZE("request.throttler.max-queue-size", false), + REQUEST_THROTTLER_DRAIN_INTERVAL("request.throttler.drain-interval", false), CONTROL_CONNECTION_TIMEOUT("connection.control-connection.timeout", true), CONTROL_CONNECTION_AGREEMENT_INTERVAL( @@ -121,6 +126,9 @@ public enum DefaultDriverOption implements DriverOption { METRICS_SESSION_CQL_REQUESTS_HIGHEST("metrics.session.cql-requests.highest-latency", false), METRICS_SESSION_CQL_REQUESTS_DIGITS("metrics.session.cql-requests.significant-digits", false), METRICS_SESSION_CQL_REQUESTS_INTERVAL("metrics.session.cql-requests.refresh-interval", false), + METRICS_SESSION_THROTTLING_HIGHEST("metrics.session.throttling.delay.highest-latency", false), + METRICS_SESSION_THROTTLING_DIGITS("metrics.session.throttling.delay.significant-digits", false), + METRICS_SESSION_THROTTLING_INTERVAL("metrics.session.throttling.delay.refresh-interval", false), METRICS_NODE_CQL_MESSAGES_HIGHEST("metrics.node.cql-messages.highest-latency", false), METRICS_NODE_CQL_MESSAGES_DIGITS("metrics.node.cql-messages.significant-digits", false), METRICS_NODE_CQL_MESSAGES_INTERVAL("metrics.node.cql-messages.refresh-interval", false), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java index 981ac949411..04f0830a2c2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java @@ -23,6 +23,9 @@ public enum DefaultSessionMetric implements SessionMetric { CONNECTED_NODES("connected-nodes"), CQL_REQUESTS("cql-requests"), CQL_CLIENT_TIMEOUTS("cql-client-timeouts"), + THROTTLING_DELAY("throttling.delay"), + THROTTLING_QUEUE_SIZE("throttling.queue-size"), + THROTTLING_ERRORS("throttling.errors"), ; private static final Map BY_PATH = sortByPath(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index caecf8a7483..b733da204d0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -77,7 +77,7 @@ public static AdminRequestHandler query( private final Duration timeout; private final String logPrefix; private final String debugString; - private final CompletableFuture result = new CompletableFuture<>(); + protected final CompletableFuture result = new CompletableFuture<>(); // This is only ever accessed on the channel's event loop, so it doesn't need to be volatile private ScheduledFuture timeoutFuture; @@ -110,12 +110,12 @@ private void onWriteComplete(Future future) { channel.eventLoop().schedule(this::fireTimeout, timeout.toNanos(), TimeUnit.NANOSECONDS); timeoutFuture.addListener(UncaughtExceptions::log); } else { - result.completeExceptionally(future.cause()); + setFinalError(future.cause()); } } private void fireTimeout() { - result.completeExceptionally( + setFinalError( new DriverTimeoutException(String.format("%s timed out after %s", debugString, timeout))); if (!channel.closeFuture().isDone()) { channel.cancel(this); @@ -127,7 +127,7 @@ public void onFailure(Throwable error) { if (timeoutFuture != null) { timeoutFuture.cancel(true); } - result.completeExceptionally(error); + setFinalError(error); } @Override @@ -141,16 +141,24 @@ public void onResponse(Frame responseFrame) { Rows rows = (Rows) message; ByteBuffer pagingState = rows.getMetadata().pagingState; AdminRequestHandler nextHandler = (pagingState == null) ? null : this.copy(pagingState); - result.complete(new AdminResult(rows, nextHandler, channel.protocolVersion())); + setFinalResult(new AdminResult(rows, nextHandler, channel.protocolVersion())); } else if (message instanceof Prepared) { // Internal prepares are only "reprepare on up" types of queries, where we only care about // success, not the actual result, so this is good enough: - result.complete(null); + setFinalResult(null); } else { - result.completeExceptionally(new UnexpectedResponseException(debugString, message)); + setFinalError(new UnexpectedResponseException(debugString, message)); } } + protected boolean setFinalResult(AdminResult result) { + return this.result.complete(result); + } + + protected boolean setFinalError(Throwable error) { + return result.completeExceptionally(error); + } + private AdminRequestHandler copy(ByteBuffer pagingState) { assert message instanceof Query; Query current = (Query) this.message; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/ThrottledAdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/ThrottledAdminRequestHandler.java new file mode 100644 index 00000000000..109ff408b9e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/ThrottledAdminRequestHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.adminrequest; + +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; +import com.datastax.oss.driver.internal.core.session.throttling.Throttled; +import com.datastax.oss.protocol.internal.Message; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +public class ThrottledAdminRequestHandler extends AdminRequestHandler implements Throttled { + + private final long startTimeNanos; + private final RequestThrottler throttler; + private final SessionMetricUpdater metricUpdater; + + public ThrottledAdminRequestHandler( + DriverChannel channel, + Message message, + Map customPayload, + Duration timeout, + RequestThrottler throttler, + SessionMetricUpdater metricUpdater, + String logPrefix, + String debugString) { + super(channel, message, customPayload, timeout, logPrefix, debugString); + this.startTimeNanos = System.nanoTime(); + this.throttler = throttler; + this.metricUpdater = metricUpdater; + } + + @Override + public CompletionStage start() { + // Don't write request yet, wait for green light from throttler + throttler.register(this); + return result; + } + + @Override + public void onThrottleReady(boolean wasDelayed) { + if (wasDelayed) { + metricUpdater.updateTimer( + DefaultSessionMetric.THROTTLING_DELAY, + System.nanoTime() - startTimeNanos, + TimeUnit.NANOSECONDS); + } + super.start(); + } + + @Override + public void onThrottleFailure(RequestThrottlingException error) { + metricUpdater.incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS); + setFinalError(error); + } + + @Override + protected boolean setFinalResult(AdminResult result) { + boolean wasSet = super.setFinalResult(result); + if (wasSet) { + throttler.signalSuccess(this); + } + return wasSet; + } + + @Override + protected boolean setFinalError(Throwable error) { + boolean wasSet = super.setFinalError(error); + if (wasSet) { + if (error instanceof DriverTimeoutException) { + throttler.signalTimeout(this); + } else if (!(error instanceof RequestThrottlingException)) { + throttler.signalError(this, error); + } + } + return wasSet; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 0ef8e3304e7..3f86b925ad4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -59,6 +59,7 @@ import com.datastax.oss.driver.internal.core.servererrors.WriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; @@ -163,6 +164,8 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("metricRegistry", this::buildMetricRegistry, cycleDetector); private final LazyReference metricUpdaterFactoryRef = new LazyReference<>("metricUpdaterFactory", this::buildMetricUpdaterFactory, cycleDetector); + private final LazyReference requestThrottlerRef = + new LazyReference<>("requestThrottler", this::buildRequestThrottler, cycleDetector); private final DriverConfig config; private final DriverConfigLoader configLoader; @@ -369,6 +372,17 @@ protected MetricUpdaterFactory buildMetricUpdaterFactory() { return new DefaultMetricUpdaterFactory(this); } + protected RequestThrottler buildRequestThrottler() { + return Reflection.buildFromConfig( + this, DefaultDriverOption.REQUEST_THROTTLER_CLASS, RequestThrottler.class) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Missing request throttler, check your configuration (%s)", + DefaultDriverOption.REQUEST_THROTTLER_CLASS))); + } + @Override public String sessionName() { return sessionName; @@ -539,6 +553,11 @@ public MetricUpdaterFactory metricUpdaterFactory() { return metricUpdaterFactoryRef.get(); } + @Override + public RequestThrottler requestThrottler() { + return requestThrottlerRef.get(); + } + @Override public CodecRegistry codecRegistry() { return codecRegistry; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 051be55a758..e64a04f8be6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -34,6 +34,7 @@ import com.datastax.oss.driver.internal.core.servererrors.WriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; @@ -88,4 +89,6 @@ public interface InternalDriverContext extends DriverContext { PoolManager poolManager(); MetricUpdaterFactory metricUpdaterFactory(); + + RequestThrottler requestThrottler(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index d47d157534d..df64cf21b0c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -19,12 +19,14 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; @@ -34,11 +36,13 @@ import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; import com.datastax.oss.driver.internal.core.ProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; +import com.datastax.oss.driver.internal.core.session.throttling.Throttled; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Frame; @@ -68,10 +72,11 @@ import org.slf4j.LoggerFactory; /** Handles the lifecycle of the preparation of a CQL statement. */ -public abstract class CqlPrepareHandlerBase { +public abstract class CqlPrepareHandlerBase implements Throttled { private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandlerBase.class); + private final long startTimeNanos; private final String logPrefix; private final PrepareRequest request; private final ConcurrentMap preparedStatementsCache; @@ -84,6 +89,7 @@ public abstract class CqlPrepareHandlerBase { private final Duration timeout; private final ScheduledFuture timeoutFuture; private final RetryPolicy retryPolicy; + private final RequestThrottler throttler; private final Boolean prepareOnAllNodes; private volatile InitialPrepareCallback initialCallback; @@ -98,6 +104,7 @@ protected CqlPrepareHandlerBase( InternalDriverContext context, String sessionLogPrefix) { + this.startTimeNanos = System.nanoTime(); this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); LOG.debug("[{}] Creating new handler for prepare request {}", logPrefix, request); @@ -147,6 +154,21 @@ protected CqlPrepareHandlerBase( this.timeoutFuture = scheduleTimeout(timeout); this.retryPolicy = context.retryPolicy(); this.prepareOnAllNodes = configProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); + + this.throttler = context.requestThrottler(); + this.throttler.register(this); + } + + @Override + public void onThrottleReady(boolean wasDelayed) { + if (wasDelayed) { + session + .getMetricUpdater() + .updateTimer( + DefaultSessionMetric.THROTTLING_DELAY, + System.nanoTime() - startTimeNanos, + TimeUnit.NANOSECONDS); + } sendRequest(null, 0); } @@ -211,6 +233,10 @@ private void recordError(Node node, Throwable error) { } private void setFinalResult(Prepared prepared) { + + // Whatever happens below, we're done with this stream id + throttler.signalSuccess(this); + DefaultPreparedStatement newStatement = Conversions.toPreparedStatement(prepared, request, context); @@ -282,10 +308,16 @@ private CompletionStage prepareOnOtherNode(Node node) { LOG.debug("[{}] Could not get a channel to reprepare on {}, skipping", logPrefix, node); return CompletableFuture.completedFuture(null); } else { - AdminRequestHandler handler = - new AdminRequestHandler( - channel, message, request.getCustomPayload(), timeout, logPrefix, message.toString()); - + ThrottledAdminRequestHandler handler = + new ThrottledAdminRequestHandler( + channel, + message, + request.getCustomPayload(), + timeout, + throttler, + session.getMetricUpdater(), + logPrefix, + message.toString()); return handler .start() .handle( @@ -301,9 +333,20 @@ private CompletionStage prepareOnOtherNode(Node node) { } } + @Override + public void onThrottleFailure(RequestThrottlingException error) { + session.getMetricUpdater().incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS); + setFinalError(error); + } + private void setFinalError(Throwable error) { if (result.completeExceptionally(error)) { cancelTimeout(); + if (error instanceof DriverTimeoutException) { + throttler.signalTimeout(this); + } else if (!(error instanceof RequestThrottlingException)) { + throttler.signalError(this, error); + } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index d05d2137c3e..cf8e2055b48 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -39,7 +40,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; @@ -48,6 +49,8 @@ import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; +import com.datastax.oss.driver.internal.core.session.throttling.Throttled; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; @@ -80,7 +83,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class CqlRequestHandlerBase { +public abstract class CqlRequestHandlerBase implements Throttled { private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandlerBase.class); @@ -115,6 +118,7 @@ public abstract class CqlRequestHandlerBase { private final List inFlightCallbacks; private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; + private final RequestThrottler throttler; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -174,7 +178,21 @@ protected CqlRequestHandlerBase( this.startedSpeculativeExecutionsCount = new AtomicInteger(0); this.scheduledExecutions = isIdempotent ? new CopyOnWriteArrayList<>() : null; this.inFlightCallbacks = new CopyOnWriteArrayList<>(); - // Start the initial execution + + this.throttler = context.requestThrottler(); + this.throttler.register(this); + } + + @Override + public void onThrottleReady(boolean wasDelayed) { + if (wasDelayed) { + session + .getMetricUpdater() + .updateTimer( + DefaultSessionMetric.THROTTLING_DELAY, + System.nanoTime() - startTimeNanos, + TimeUnit.NANOSECONDS); + } sendRequest(null, 0, 0, true); } @@ -270,6 +288,7 @@ private void setFinalResult( Conversions.toResultSet(resultMessage, executionInfo, session, context); if (result.complete(resultSet)) { cancelScheduledTasks(); + throttler.signalSuccess(this); session .getMetricUpdater() .updateTimer( @@ -303,11 +322,20 @@ private ExecutionInfo buildExecutionInfo( configProfile); } + @Override + public void onThrottleFailure(RequestThrottlingException error) { + session.getMetricUpdater().incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS); + setFinalError(error); + } + private void setFinalError(Throwable error) { if (result.completeExceptionally(error)) { cancelScheduledTasks(); if (error instanceof DriverTimeoutException) { + throttler.signalTimeout(this); session.getMetricUpdater().incrementCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS); + } else if (!(error instanceof RequestThrottlingException)) { + throttler.signalError(this, error); } } } @@ -472,12 +500,14 @@ private void processErrorResponse(Error errorMessage) { Bytes.toHexString(id))); } Prepare reprepareMessage = new Prepare(repreparePayload.query); - AdminRequestHandler reprepareHandler = - new AdminRequestHandler( + ThrottledAdminRequestHandler reprepareHandler = + new ThrottledAdminRequestHandler( channel, reprepareMessage, repreparePayload.customPayload, timeout, + throttler, + session.getMetricUpdater(), logPrefix, "Reprepare " + reprepareMessage.toString()); reprepareHandler @@ -500,6 +530,9 @@ private void processErrorResponse(Error errorMessage) { return null; } } + } else if (exception instanceof RequestThrottlingException) { + setFinalError(exception); + return null; } recordError(node, exception); LOG.debug("[{}] Reprepare failed, trying next node", logPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java index a21ee3f21ee..4c3d51ddad9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultMetricUpdaterFactory.java @@ -36,22 +36,23 @@ public class DefaultMetricUpdaterFactory implements MetricUpdaterFactory { private final String logPrefix; private final InternalDriverContext context; - private final Set enabledSessionMetrics; private final Set enabledNodeMetrics; + private final SessionMetricUpdater sessionMetricUpdater; public DefaultMetricUpdaterFactory(InternalDriverContext context) { this.logPrefix = context.sessionName(); this.context = context; DriverConfigProfile config = context.config().getDefaultProfile(); - this.enabledSessionMetrics = + Set enabledSessionMetrics = parseSessionMetricPaths(config.getStringList(DefaultDriverOption.METRICS_SESSION_ENABLED)); + this.sessionMetricUpdater = new DefaultSessionMetricUpdater(enabledSessionMetrics, context); this.enabledNodeMetrics = parseNodeMetricPaths(config.getStringList(DefaultDriverOption.METRICS_NODE_ENABLED)); } @Override - public SessionMetricUpdater newSessionUpdater() { - return new DefaultSessionMetricUpdater(enabledSessionMetrics, context); + public SessionMetricUpdater getSessionUpdater() { + return sessionMetricUpdater; } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java index bafccbe4531..1ac63002a23 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DefaultSessionMetricUpdater.java @@ -21,11 +21,18 @@ import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler; +import com.datastax.oss.driver.internal.core.session.throttling.RateLimitingRequestThrottler; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DefaultSessionMetricUpdater extends MetricUpdaterBase implements SessionMetricUpdater { + private static final Logger LOG = LoggerFactory.getLogger(DefaultSessionMetricUpdater.class); + private final String metricNamePrefix; public DefaultSessionMetricUpdater( @@ -47,6 +54,11 @@ public DefaultSessionMetricUpdater( return count; }); } + if (enabledMetrics.contains(DefaultSessionMetric.THROTTLING_QUEUE_SIZE)) { + metricRegistry.register( + buildFullName(DefaultSessionMetric.THROTTLING_QUEUE_SIZE), + buildQueueGauge(context.requestThrottler(), context.sessionName())); + } initializeHdrTimer( DefaultSessionMetric.CQL_REQUESTS, context.config().getDefaultProfile(), @@ -54,10 +66,32 @@ public DefaultSessionMetricUpdater( DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); initializeDefaultCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS); + initializeHdrTimer( + DefaultSessionMetric.THROTTLING_DELAY, + context.config().getDefaultProfile(), + DefaultDriverOption.METRICS_SESSION_THROTTLING_HIGHEST, + DefaultDriverOption.METRICS_SESSION_THROTTLING_DIGITS, + DefaultDriverOption.METRICS_SESSION_THROTTLING_INTERVAL); + initializeDefaultCounter(DefaultSessionMetric.THROTTLING_ERRORS); } @Override protected String buildFullName(SessionMetric metric) { return metricNamePrefix + metric.getPath(); } + + private Gauge buildQueueGauge(RequestThrottler requestThrottler, String logPrefix) { + if (requestThrottler instanceof ConcurrencyLimitingRequestThrottler) { + return ((ConcurrencyLimitingRequestThrottler) requestThrottler)::getQueueSize; + } else if (requestThrottler instanceof RateLimitingRequestThrottler) { + return ((RateLimitingRequestThrottler) requestThrottler)::getQueueSize; + } else { + LOG.warn( + "[{}] Metric {} does not support {}, it will always return 0", + logPrefix, + DefaultSessionMetric.THROTTLING_QUEUE_SIZE.getPath(), + requestThrottler.getClass().getName()); + return () -> 0; + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java index 10fcbc0a76a..6751a98e302 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricUpdaterFactory.java @@ -19,7 +19,8 @@ public interface MetricUpdaterFactory { - SessionMetricUpdater newSessionUpdater(); + /** @return the unique instance for this session (this must return the same object every time). */ + SessionMetricUpdater getSessionUpdater(); NodeMetricUpdater newNodeUpdater(Node node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 77344dedfa4..ab2d80bee6c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -104,7 +104,7 @@ private DefaultSession( this.processorRegistry = context.requestProcessorRegistry(); this.poolManager = context.poolManager(); this.logPrefix = context.sessionName(); - this.metricUpdater = context.metricUpdaterFactory().newSessionUpdater(); + this.metricUpdater = context.metricUpdaterFactory().getSessionUpdater(); } private CompletionStage init(CqlIdentifier keyspace) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 89bd9bc81b9..111fb44bf73 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -17,14 +17,15 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; -import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.pool.ChannelPool; +import com.datastax.oss.driver.internal.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Prepare; @@ -63,12 +64,13 @@ class ReprepareOnUp { private final String logPrefix; private final DriverChannel channel; private final Map repreparePayloads; - private final TopologyMonitor topologyMonitor; private final Runnable whenPrepared; private final boolean checkSystemTable; private final int maxStatements; private final int maxParallelism; private final Duration timeout; + private final RequestThrottler throttler; + private final SessionMetricUpdater metricUpdater; // After the constructor, everything happens on the channel's event loop, so these fields do not // need any synchronization. @@ -86,8 +88,8 @@ class ReprepareOnUp { this.logPrefix = logPrefix; this.channel = pool.next(); this.repreparePayloads = repreparePayloads; - this.topologyMonitor = context.topologyMonitor(); this.whenPrepared = whenPrepared; + this.throttler = context.requestThrottler(); DriverConfig config = context.config(); this.checkSystemTable = @@ -97,6 +99,8 @@ class ReprepareOnUp { config.getDefaultProfile().getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS); this.maxParallelism = config.getDefaultProfile().getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM); + + this.metricUpdater = context.metricUpdaterFactory().getSessionUpdater(); } void start() { @@ -231,8 +235,16 @@ private void startWorker() { @VisibleForTesting protected CompletionStage queryAsync( Message message, Map customPayload, String debugString) { - AdminRequestHandler reprepareHandler = - new AdminRequestHandler(channel, message, customPayload, timeout, logPrefix, debugString); + ThrottledAdminRequestHandler reprepareHandler = + new ThrottledAdminRequestHandler( + channel, + message, + customPayload, + timeout, + throttler, + metricUpdater, + logPrefix, + debugString); return reprepareHandler.start(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java new file mode 100644 index 00000000000..53a47f85398 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -0,0 +1,180 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.google.common.annotations.VisibleForTesting; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.locks.ReentrantLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A request throttler that limits the number of concurrent requests. */ +public class ConcurrencyLimitingRequestThrottler implements RequestThrottler { + + private static final Logger LOG = + LoggerFactory.getLogger(ConcurrencyLimitingRequestThrottler.class); + + private final String logPrefix; + private final int maxConcurrentRequests; + private final int maxQueueSize; + + private final ReentrantLock lock = new ReentrantLock(); + + // guarded by lock + private int concurrentRequests; + // guarded by lock + private Deque queue = new ArrayDeque<>(); + // guarded by lock + private boolean closed; + + public ConcurrencyLimitingRequestThrottler(DriverContext context) { + this.logPrefix = context.sessionName(); + DriverConfigProfile config = context.config().getDefaultProfile(); + this.maxConcurrentRequests = + config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS); + this.maxQueueSize = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE); + LOG.debug( + "[{}] Initializing with maxConcurrentRequests = {}, maxQueueSize = {}", + logPrefix, + maxConcurrentRequests, + maxQueueSize); + } + + @Override + public void register(Throttled request) { + lock.lock(); + try { + if (closed) { + LOG.trace("[{}] Rejecting request after shutdown", logPrefix); + fail(request, "The session is shutting down"); + } else if (queue.isEmpty() && concurrentRequests < maxConcurrentRequests) { + // We have capacity for one more concurrent request + LOG.trace("[{}] Starting newly registered request", logPrefix); + concurrentRequests += 1; + request.onThrottleReady(false); + } else if (queue.size() < maxQueueSize) { + LOG.trace("[{}] Enqueuing request", logPrefix); + queue.add(request); + } else { + LOG.trace("[{}] Rejecting request because of full queue", logPrefix); + fail( + request, + String.format( + "The session has reached its maximum capacity " + + "(concurrent requests: %d, queue size: %d)", + maxConcurrentRequests, maxQueueSize)); + } + } finally { + lock.unlock(); + } + } + + @Override + public void signalSuccess(Throttled request) { + lock.lock(); + try { + onRequestDone(); + } finally { + lock.unlock(); + } + } + + @Override + public void signalError(Throttled request, Throwable error) { + signalSuccess(request); // not treated differently + } + + @Override + public void signalTimeout(Throttled request) { + lock.lock(); + try { + if (!closed) { + if (queue.remove(request)) { // The request timed out before it was active + LOG.trace("[{}] Removing timed out request from the queue", logPrefix); + } else { + onRequestDone(); + } + } + } finally { + lock.unlock(); + } + } + + private void onRequestDone() { + assert lock.isHeldByCurrentThread(); + if (!closed) { + if (queue.isEmpty()) { + concurrentRequests -= 1; + } else { + LOG.trace("[{}] Starting dequeued request", logPrefix); + queue.poll().onThrottleReady(true); + // don't touch concurrentRequests since we finished one but started another + } + } + } + + @Override + public void close() { + lock.lock(); + try { + closed = true; + LOG.trace("[{}] Rejecting {} queued requests after shutdown", logPrefix, queue.size()); + for (Throttled request : queue) { + fail(request, "The session is shutting down"); + } + } finally { + lock.unlock(); + } + } + + public int getQueueSize() { + lock.lock(); + try { + return queue.size(); + } finally { + lock.unlock(); + } + } + + @VisibleForTesting + int getConcurrentRequests() { + lock.lock(); + try { + return concurrentRequests; + } finally { + lock.unlock(); + } + } + + @VisibleForTesting + Deque getQueue() { + lock.lock(); + try { + return queue; + } finally { + lock.unlock(); + } + } + + private static void fail(Throttled request, String message) { + request.onThrottleFailure(new RequestThrottlingException(message)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/NanoClock.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/NanoClock.java new file mode 100644 index 00000000000..65587e7c3a0 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/NanoClock.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +/** A thin wrapper around {@link System#nanoTime()}, to simplify testing. */ +interface NanoClock { + long nanoTime(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java new file mode 100644 index 00000000000..26066c3bc5f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java @@ -0,0 +1,56 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import java.io.IOException; + +/** + * A request throttler that does not enforce any kind of limitation: requests are always executed + * immediately. + */ +public class PassThroughRequestThrottler implements RequestThrottler { + + @SuppressWarnings("unused") + public PassThroughRequestThrottler(DriverContext context) { + // nothing to do + } + + @Override + public void register(Throttled request) { + request.onThrottleReady(false); + } + + @Override + public void signalSuccess(Throttled request) { + // nothing to do + } + + @Override + public void signalError(Throttled request, Throwable error) { + // nothing to do + } + + @Override + public void signalTimeout(Throttled request) { + // nothing to do + } + + @Override + public void close() throws IOException { + // nothing to do + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java new file mode 100644 index 00000000000..bf027299d81 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java @@ -0,0 +1,242 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.google.common.annotations.VisibleForTesting; +import io.netty.util.concurrent.EventExecutor; +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A request throttler that limits the rate of requests per second. */ +public class RateLimitingRequestThrottler implements RequestThrottler { + + private static final Logger LOG = LoggerFactory.getLogger(RateLimitingRequestThrottler.class); + + private final String logPrefix; + private final NanoClock clock; + private final int maxRequestsPerSecond; + private final int maxQueueSize; + private final long drainIntervalNanos; + private final EventExecutor scheduler; + + private final ReentrantLock lock = new ReentrantLock(); + + // guarded by lock + private long lastUpdateNanos; + // guarded by lock + private int storedPermits; + // guarded by lock + private final Deque queue = new ArrayDeque<>(); + // guarded by lock + private boolean closed; + + @SuppressWarnings("unused") + public RateLimitingRequestThrottler(DriverContext context) { + this(context, System::nanoTime); + } + + @VisibleForTesting + RateLimitingRequestThrottler(DriverContext context, NanoClock clock) { + this.logPrefix = context.sessionName(); + this.clock = clock; + + DriverConfigProfile config = context.config().getDefaultProfile(); + + this.maxRequestsPerSecond = + config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND); + this.maxQueueSize = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE); + Duration drainInterval = + config.getDuration(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL); + this.drainIntervalNanos = drainInterval.toNanos(); + + this.lastUpdateNanos = clock.nanoTime(); + // Start with one second worth of permits to avoid delaying initial requests + this.storedPermits = maxRequestsPerSecond; + + this.scheduler = + ((InternalDriverContext) context).nettyOptions().adminEventExecutorGroup().next(); + + LOG.debug( + "[{}] Initializing with maxRequestsPerSecond = {}, maxQueueSize = {}, drainInterval = {}", + logPrefix, + maxRequestsPerSecond, + maxQueueSize, + drainInterval); + } + + @Override + public void register(Throttled request) { + long now = clock.nanoTime(); + lock.lock(); + try { + if (closed) { + LOG.trace("[{}] Rejecting request after shutdown", logPrefix); + fail(request, "The session is shutting down"); + } else if (queue.isEmpty() && acquire(now, 1) == 1) { + LOG.trace("[{}] Starting newly registered request", logPrefix); + request.onThrottleReady(false); + } else if (queue.size() < maxQueueSize) { + LOG.trace("[{}] Enqueuing request", logPrefix); + if (queue.isEmpty()) { + scheduler.schedule(this::drain, drainIntervalNanos, TimeUnit.NANOSECONDS); + } + queue.add(request); + } else { + LOG.trace("[{}] Rejecting request because of full queue", logPrefix); + fail( + request, + String.format( + "The session has reached its maximum capacity " + + "(requests/s: %d, queue size: %d)", + maxRequestsPerSecond, maxQueueSize)); + } + } finally { + lock.unlock(); + } + } + + // Runs periodically when the queue is not empty. It tries to dequeue as much as possible while + // staying under the target rate. If it does not completely drain the queue, it reschedules + // itself. + private void drain() { + assert scheduler.inEventLoop(); + long now = clock.nanoTime(); + lock.lock(); + try { + if (closed || queue.isEmpty()) { + return; + } + int toDequeue = acquire(now, queue.size()); + LOG.trace("[{}] Dequeuing {}/{} elements", logPrefix, toDequeue, queue.size()); + for (int i = 0; i < toDequeue; i++) { + LOG.trace("[{}] Starting dequeued request", logPrefix); + queue.poll().onThrottleReady(true); + } + if (!queue.isEmpty()) { + LOG.trace( + "[{}] {} elements remaining in queue, rescheduling drain task", + logPrefix, + queue.size()); + scheduler.schedule(this::drain, drainIntervalNanos, TimeUnit.NANOSECONDS); + } + } finally { + lock.unlock(); + } + } + + @Override + public void signalSuccess(Throttled request) { + // nothing to do + } + + @Override + public void signalError(Throttled request, Throwable error) { + // nothing to do + } + + @Override + public void signalTimeout(Throttled request) { + lock.lock(); + try { + if (!closed && queue.remove(request)) { // The request timed out before it was active + LOG.trace("[{}] Removing timed out request from the queue", logPrefix); + } + } finally { + lock.unlock(); + } + } + + @Override + public void close() { + lock.lock(); + try { + closed = true; + LOG.trace("[{}] Rejecting {} queued requests after shutdown", logPrefix, queue.size()); + for (Throttled request : queue) { + fail(request, "The session is shutting down"); + } + } finally { + lock.unlock(); + } + } + + private int acquire(long currentTimeNanos, int wantedPermits) { + assert lock.isHeldByCurrentThread() && !closed; + + long elapsedNanos = currentTimeNanos - lastUpdateNanos; + + if (elapsedNanos >= 1_000_000_000) { + // created more than the max, so whatever was stored, the sum will be capped to the max + storedPermits = maxRequestsPerSecond; + lastUpdateNanos = currentTimeNanos; + } else if (elapsedNanos > 0) { + int createdPermits = (int) (elapsedNanos * maxRequestsPerSecond / 1_000_000_000); + if (createdPermits > 0) { + // Only reset interval if we've generated permits, otherwise we might continually reset + // before we get the chance to generate anything. + lastUpdateNanos = currentTimeNanos; + } + storedPermits = Math.min(storedPermits + createdPermits, maxRequestsPerSecond); + } + + int returned = (storedPermits >= wantedPermits) ? wantedPermits : storedPermits; + storedPermits = Math.max(storedPermits - wantedPermits, 0); + return returned; + } + + public int getQueueSize() { + lock.lock(); + try { + return queue.size(); + } finally { + lock.unlock(); + } + } + + @VisibleForTesting + int getStoredPermits() { + lock.lock(); + try { + return storedPermits; + } finally { + lock.unlock(); + } + } + + @VisibleForTesting + Deque getQueue() { + lock.lock(); + try { + return queue; + } finally { + lock.unlock(); + } + } + + private static void fail(Throttled request, String message) { + request.onThrottleFailure(new RequestThrottlingException(message)); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RequestThrottler.java new file mode 100644 index 00000000000..b3a564a0c7f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RequestThrottler.java @@ -0,0 +1,49 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import java.io.Closeable; + +/** Limits the number of concurrent requests executed by the driver. */ +public interface RequestThrottler extends Closeable { + + /** + * Registers a new request to be throttled. The throttler will invoke {@link + * Throttled#onThrottleReady()} when the request is allowed to proceed. + */ + void register(Throttled request); + + /** + * Signals that a request has succeeded. This indicates to the throttler that another request + * might be started. + */ + void signalSuccess(Throttled request); + + /** + * Signals that a request has failed. This indicates to the throttler that another request might + * be started. + */ + void signalError(Throttled request, Throwable error); + + /** + * Signals that a request has timed out. This indicates to the throttler that this request has + * stopped (if it was running already), or that it doesn't need to be started in the future. + * + *

          Note: requests are responsible for handling their own timeout. The throttler does not + * perform time-based eviction on pending requests. + */ + void signalTimeout(Throttled request); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/Throttled.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/Throttled.java new file mode 100644 index 00000000000..50ea45a5b2f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/Throttled.java @@ -0,0 +1,40 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; + +/** A request that may be subjected to throttling by a {@link RequestThrottler}. */ +public interface Throttled { + + /** + * Invoked by the throttler to indicate that the request can now start. The request must wait for + * this call until it does any "actual" work (typically, writing to a connection). + * + * @param wasDelayed indicates whether the throttler delayed at all; this is so that requests + * don't have to rely on measuring time to determine it (this is useful for metrics). + */ + void onThrottleReady(boolean wasDelayed); + + /** + * Invoked by the throttler to indicate that the request cannot be fulfilled. Typically, this + * means we've reached maximum capacity, and the request can't even be enqueued. This error must + * be rethrown to the client. + * + * @param error the error that the request should be completed (exceptionally) with. + */ + void onThrottleFailure(RequestThrottlingException error); +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 25fa29f3043..a8a6b6cdba1 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -475,6 +475,58 @@ datastax-java-driver { # trace id. consistency = ONE } + + # A session-wide component that controls the rate at which requests are executed. + # + # Implementations vary, but throttlers generally track a metric that represents the level of + # utilization of the session, and prevent new requests from starting when that metric exceeds a + # threshold. Pending requests may be enqueued and retried later. + # + # From the public API's point of view, this process is mostly transparent: any time that the + # request is throttled is included in the session.execute() or session.executeAsync() call. + # Similarly, the request timeout encompasses throttling: the timeout starts ticking before the + # throttler has started processing the request; a request may time out while it is still in the + # throttler's queue, before the driver has even tried to send it to a node. + # + # The only visible effect is that a request may fail with a RequestThrottlingException, if the + # throttler has determined that it can neither allow the request to proceed now, nor enqueue it; + # this indicates that your session is overloaded. + throttler { + # The following implementations are provided out of the box (all located in the package + # com.datastax.oss.driver.internal.core.session.throttling): + # + # - PassThroughRequestThrottler: does not perform any kind of throttling, all requests are + # allowed to proceed immediately. Required options: none. + # + # - ConcurrencyLimitingRequestThrottler: limits the number of requests that can be executed + # in parallel. Required options: max-concurrent-requests, max-queue-size. + # + # - RateLimitingRequestThrottler: limits the request rate per second. Required options: + # max-requests-per-second, max-queue-size, drain-interval. + class = com.datastax.oss.driver.internal.core.session.throttling.PassThroughRequestThrottler + + # The maximum number of requests that can be enqueued when the throttling threshold is + # exceeded. Beyond that size, requests will fail with a RequestThrottlingException. + // max-queue-size = 10000 + + # The maximum number of requests that are allowed to execute in parallel. + # Only used by ConcurrencyLimitingRequestThrottler. + // max-concurrent-requests = 10000 + + # The maximum allowed request rate. + # Only used by RateLimitingRequestThrottler. + // max-requests-per-second = 10000 + + # How often the throttler attempts to dequeue requests. This is the only way for rate-based + # throttling, because the completion of an active request does not necessarily free a "slot" + # for a queued one (the rate might still be too high). + # + # You want to set this high enough that each attempt will process multiple entries in the + # queue, but not delay requests too much. A few milliseconds is probably a happy medium. + # + # Only used by RateLimitingRequestThrottler. + // drain-interval = 10 milliseconds + } } prepared-statements { @@ -628,6 +680,23 @@ datastax-java-driver { # The number of CQL requests that timed out -- that is, the session.execute() call failed # with a DriverTimeoutException (exposed as a Counter). // cql-client-timeouts, + + # How long requests are being throttled (exposed as a Timer). + # + # This is the time between the start of the session.execute() call, and the moment when the + # throttler allows the request to proceed. + // throttling.delay, + + # The size of the throttling queue (exposed as a Gauge). + # + # This is the number of requests that the throttler is currently delaying in order to + # preserve its SLA. This metric only works with the built-in concurrency- and rate-based + # throttlers; in other cases, it will always be 0. + // throttling.queue-size, + + # The number of times a request was rejected with a RequestThrottlingException (exposed as a + # Counter) + // throttling.errors, ] # Extra configuration (for the metrics that need it) @@ -666,6 +735,12 @@ datastax-java-driver { # time). refresh-interval = 5 minutes } + + throttling.delay { + highest-latency = 3 seconds + significant-digits = 3 + refresh-interval = 5 minutes + } } # The node-level metrics (all disabled by default). node { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 9b6c89aa9f4..f2dbb65810b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -43,6 +43,7 @@ import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.servererrors.DefaultWriteTypeRegistry; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.throttling.PassThroughRequestThrottler; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; import com.datastax.oss.protocol.internal.Frame; @@ -160,6 +161,8 @@ private RequestHandlerTestHarness(Builder builder) { .thenReturn(new DefaultConsistencyLevelRegistry()); Mockito.when(context.writeTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); + + Mockito.when(context.requestThrottler()).thenReturn(new PassThroughRequestThrottler(context)); } public DefaultSession getSession() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index ad2f36bfdfc..823cccd1e64 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -25,6 +25,8 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; +import com.datastax.oss.driver.internal.core.metrics.MetricUpdaterFactory; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Message; @@ -63,6 +65,8 @@ public class ReprepareOnUpTest { @Mock private DriverConfig config; @Mock private DriverConfigProfile defaultConfigProfile; @Mock private TopologyMonitor topologyMonitor; + @Mock private MetricUpdaterFactory metricUpdaterFactory; + @Mock private SessionMetricUpdater metricUpdater; private Runnable whenPrepared; private CompletionStage done; @@ -85,6 +89,9 @@ public void setup() { .thenReturn(100); Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.metricUpdaterFactory()).thenReturn(metricUpdaterFactory); + Mockito.when(metricUpdaterFactory.getSessionUpdater()).thenReturn(metricUpdater); + done = new CompletableFuture<>(); whenPrepared = () -> ((CompletableFuture) done).complete(null); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java new file mode 100644 index 00000000000..0089749a77c --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java @@ -0,0 +1,239 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import static com.datastax.oss.driver.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.google.common.collect.Lists; +import java.util.List; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ConcurrencyLimitingRequestThrottlerTest { + + @Mock private DriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultProfile; + + private ConcurrencyLimitingRequestThrottler throttler; + + @Before + public void setup() { + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + + Mockito.when( + defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS)) + .thenReturn(5); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) + .thenReturn(10); + + throttler = new ConcurrencyLimitingRequestThrottler(context); + } + + @Test + public void should_start_immediately_when_under_capacity() { + // Given + MockThrottled request = new MockThrottled(); + + // When + throttler.register(request); + + // Then + assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThat(throttler.getConcurrentRequests()).isEqualTo(1); + assertThat(throttler.getQueue()).isEmpty(); + } + + @Test + public void should_allow_new_request_when_active_one_succeeds() { + should_allow_new_request_when_active_one_completes(throttler::signalSuccess); + } + + @Test + public void should_allow_new_request_when_active_one_fails() { + should_allow_new_request_when_active_one_completes( + request -> throttler.signalError(request, new RuntimeException("mock error"))); + } + + @Test + public void should_allow_new_request_when_active_one_times_out() { + should_allow_new_request_when_active_one_completes(throttler::signalTimeout); + } + + private void should_allow_new_request_when_active_one_completes( + Consumer completeCallback) { + // Given + MockThrottled first = new MockThrottled(); + throttler.register(first); + assertThat(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + for (int i = 0; i < 4; i++) { // fill to capacity + throttler.register(new MockThrottled()); + } + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).isEmpty(); + + // When + completeCallback.accept(first); + assertThat(throttler.getConcurrentRequests()).isEqualTo(4); + assertThat(throttler.getQueue()).isEmpty(); + MockThrottled incoming = new MockThrottled(); + throttler.register(incoming); + + // Then + assertThat(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).isEmpty(); + } + + @Test + public void should_enqueue_when_over_capacity() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).isEmpty(); + + // When + MockThrottled incoming = new MockThrottled(); + throttler.register(incoming); + + // Then + assertThat(incoming.started).isNotDone(); + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).containsExactly(incoming); + } + + @Test + public void should_dequeue_when_active_succeeds() { + should_dequeue_when_active_completes(throttler::signalSuccess); + } + + @Test + public void should_dequeue_when_active_fails() { + should_dequeue_when_active_completes( + request -> throttler.signalError(request, new RuntimeException("mock error"))); + } + + @Test + public void should_dequeue_when_active_times_out() { + should_dequeue_when_active_completes(throttler::signalTimeout); + } + + private void should_dequeue_when_active_completes(Consumer completeCallback) { + // Given + MockThrottled first = new MockThrottled(); + throttler.register(first); + assertThat(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + for (int i = 0; i < 4; i++) { + throttler.register(new MockThrottled()); + } + + MockThrottled incoming = new MockThrottled(); + throttler.register(incoming); + assertThat(incoming.started).isNotDone(); + + // When + completeCallback.accept(first); + + // Then + assertThat(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).isEmpty(); + } + + @Test + public void should_reject_when_queue_is_full() { + // Given + for (int i = 0; i < 15; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).hasSize(10); + + // When + MockThrottled incoming = new MockThrottled(); + throttler.register(incoming); + + // Then + assertThat(incoming.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } + + @Test + public void should_remove_timed_out_request_from_queue() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + MockThrottled queued1 = new MockThrottled(); + throttler.register(queued1); + MockThrottled queued2 = new MockThrottled(); + throttler.register(queued2); + + // When + throttler.signalTimeout(queued1); + + // Then + assertThat(queued2.started).isNotDone(); + assertThat(throttler.getConcurrentRequests()).isEqualTo(5); + assertThat(throttler.getQueue()).hasSize(1); + } + + @Test + public void should_reject_enqueued_when_closing() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + List enqueued = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + MockThrottled request = new MockThrottled(); + throttler.register(request); + assertThat(request.started).isNotDone(); + enqueued.add(request); + } + + // When + throttler.close(); + + // Then + for (MockThrottled request : enqueued) { + assertThat(request.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } + + // When + MockThrottled request = new MockThrottled(); + throttler.register(request); + + // Then + assertThat(request.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/MockThrottled.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/MockThrottled.java new file mode 100644 index 00000000000..17fab7651cb --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/MockThrottled.java @@ -0,0 +1,35 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +class MockThrottled implements Throttled { + + final CompletionStage started = new CompletableFuture<>(); + + @Override + public void onThrottleReady(boolean wasDelayed) { + started.toCompletableFuture().complete(wasDelayed); + } + + @Override + public void onThrottleFailure(RequestThrottlingException error) { + started.toCompletableFuture().completeExceptionally(error); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java new file mode 100644 index 00000000000..8c8df76dd3f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java @@ -0,0 +1,317 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +import static com.datastax.oss.driver.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.google.common.collect.Lists; +import io.netty.channel.EventLoopGroup; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class RateLimitingRequestThrottlerTest { + + private static final long ONE_HUNDRED_MILLISECONDS = + TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS); + private static final long TWO_HUNDRED_MILLISECONDS = + TimeUnit.NANOSECONDS.convert(200, TimeUnit.MILLISECONDS); + private static final long TWO_SECONDS = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS); + + // Note: we trigger scheduled task manually, so this is for verification purposes only, it doesn't + // need to be consistent with the actual throttling rate. + private static final Duration DRAIN_INTERVAL = Duration.ofMillis(10); + + @Mock private InternalDriverContext context; + @Mock private DriverConfig config; + @Mock private DriverConfigProfile defaultProfile; + @Mock private NettyOptions nettyOptions; + @Mock private EventLoopGroup adminGroup; + + private ScheduledTaskCapturingEventLoop adminExecutor; + private SettableNanoClock clock = new SettableNanoClock(); + + private RateLimitingRequestThrottler throttler; + + @Before + public void setup() { + Mockito.when(context.config()).thenReturn(config); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + + Mockito.when( + defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND)) + .thenReturn(5); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) + .thenReturn(10); + + // Set to match the time to reissue one permit. Although it does not matter in practice, since + // the executor is mocked and we trigger tasks manually. + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL)) + .thenReturn(DRAIN_INTERVAL); + + Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminGroup); + adminExecutor = new ScheduledTaskCapturingEventLoop(adminGroup); + Mockito.when(adminGroup.next()).thenReturn(adminExecutor); + + throttler = new RateLimitingRequestThrottler(context, clock); + } + + /** Note: the throttler starts with 1 second worth of permits, so at t=0 we have 5 available. */ + @Test + public void should_start_immediately_when_under_capacity() { + // Given + MockThrottled request = new MockThrottled(); + + // When + throttler.register(request); + + // Then + assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThat(throttler.getStoredPermits()).isEqualTo(4); + assertThat(throttler.getQueue()).isEmpty(); + } + + @Test + public void should_allow_new_request_when_under_rate() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getStoredPermits()).isEqualTo(0); + + // When + clock.add(TWO_HUNDRED_MILLISECONDS); + MockThrottled request = new MockThrottled(); + throttler.register(request); + + // Then + assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).isEmpty(); + } + + @Test + public void should_enqueue_when_over_rate() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getStoredPermits()).isEqualTo(0); + + // When + // (do not advance time) + MockThrottled request = new MockThrottled(); + throttler.register(request); + + // Then + assertThat(request.started).isNotDone(); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).containsExactly(request); + + ScheduledTaskCapturingEventLoop.CapturedTask task = adminExecutor.nextTask(); + assertThat(task).isNotNull(); + assertThat(task.getInitialDelay(TimeUnit.NANOSECONDS)).isEqualTo(DRAIN_INTERVAL.toNanos()); + } + + @Test + public void should_reject_when_queue_is_full() { + // Given + for (int i = 0; i < 15; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).hasSize(10); + + // When + clock.add(TWO_HUNDRED_MILLISECONDS); // even if time has passed, queued items have priority + MockThrottled request = new MockThrottled(); + throttler.register(request); + + // Then + assertThat(request.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } + + @Test + public void should_remove_timed_out_request_from_queue() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + MockThrottled queued1 = new MockThrottled(); + throttler.register(queued1); + MockThrottled queued2 = new MockThrottled(); + throttler.register(queued2); + + // When + throttler.signalTimeout(queued1); + + // Then + assertThat(queued2.started).isNotDone(); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).containsExactly(queued2); + } + + @Test + public void should_dequeue_when_draining_task_runs() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + + MockThrottled queued1 = new MockThrottled(); + throttler.register(queued1); + assertThat(queued1.started).isNotDone(); + MockThrottled queued2 = new MockThrottled(); + throttler.register(queued2); + assertThat(queued2.started).isNotDone(); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).hasSize(2); + + ScheduledTaskCapturingEventLoop.CapturedTask task = adminExecutor.nextTask(); + assertThat(task).isNotNull(); + assertThat(task.getInitialDelay(TimeUnit.NANOSECONDS)).isEqualTo(DRAIN_INTERVAL.toNanos()); + + // When + // (do not advance clock => no new permits) + task.run(); + + // Then + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).containsExactly(queued1, queued2); + // task reschedules itself since it did not empty the queue + task = adminExecutor.nextTask(); + assertThat(task).isNotNull(); + assertThat(task.getInitialDelay(TimeUnit.NANOSECONDS)).isEqualTo(DRAIN_INTERVAL.toNanos()); + + // When + clock.add(TWO_HUNDRED_MILLISECONDS); // 1 extra permit issued + task.run(); + + // Then + assertThat(queued1.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThat(queued2.started).isNotDone(); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).containsExactly(queued2); + // task reschedules itself since it did not empty the queue + task = adminExecutor.nextTask(); + assertThat(task).isNotNull(); + assertThat(task.getInitialDelay(TimeUnit.NANOSECONDS)).isEqualTo(DRAIN_INTERVAL.toNanos()); + + // When + clock.add(TWO_HUNDRED_MILLISECONDS); + task.run(); + + // Then + assertThat(queued2.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThat(throttler.getStoredPermits()).isEqualTo(0); + assertThat(throttler.getQueue()).isEmpty(); + assertThat(adminExecutor.nextTask()).isNull(); + } + + @Test + public void should_store_new_permits_up_to_threshold() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getStoredPermits()).isEqualTo(0); + + // When + clock.add(TWO_SECONDS); // should store at most 1 second worth of permits + + // Then + // acquire to trigger the throttler to update its permits + throttler.register(new MockThrottled()); + assertThat(throttler.getStoredPermits()).isEqualTo(4); + } + + /** + * Ensure that permits are still created if we try to acquire faster than the minimal interval to + * create one permit. In an early version of the code there was a bug where we would reset the + * elapsed time on each acquisition attempt, and never regenerate permits. + */ + @Test + public void should_keep_accumulating_time_if_no_permits_created() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + assertThat(throttler.getStoredPermits()).isEqualTo(0); + + // When + clock.add(ONE_HUNDRED_MILLISECONDS); + + // Then + MockThrottled queued = new MockThrottled(); + throttler.register(queued); + assertThat(queued.started).isNotDone(); + + // When + clock.add(ONE_HUNDRED_MILLISECONDS); + adminExecutor.nextTask().run(); + + // Then + assertThat(queued.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + } + + @Test + public void should_reject_enqueued_when_closing() { + // Given + for (int i = 0; i < 5; i++) { + throttler.register(new MockThrottled()); + } + List enqueued = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + MockThrottled request = new MockThrottled(); + throttler.register(request); + assertThat(request.started).isNotDone(); + enqueued.add(request); + } + + // When + throttler.close(); + + // Then + for (MockThrottled request : enqueued) { + assertThat(request.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } + + // When + MockThrottled request = new MockThrottled(); + throttler.register(request); + + // Then + assertThat(request.started) + .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/SettableNanoClock.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/SettableNanoClock.java new file mode 100644 index 00000000000..b12fbf35582 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/SettableNanoClock.java @@ -0,0 +1,32 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.session.throttling; + +class SettableNanoClock implements NanoClock { + + private volatile long nanoTime; + + @Override + public long nanoTime() { + return nanoTime; + } + + // This is racy, but in our tests it's never read concurrently + @SuppressWarnings("NonAtomicVolatileUpdate") + void add(long increment) { + nanoTime += increment; + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java new file mode 100644 index 00000000000..81e45e5345a --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java @@ -0,0 +1,69 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.throttling; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; +import java.util.concurrent.TimeUnit; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ThrottlingIT { + + private static final String QUERY = "select * from foo"; + + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_reject_request_when_throttling_by_concurrency() { + + // Add a delay so that requests don't complete during the test + simulacron + .cluster() + .prime(PrimeDsl.when(QUERY).then(PrimeDsl.noRows()).delay(5, TimeUnit.SECONDS)); + + int maxConcurrentRequests = 10; + int maxQueueSize = 10; + + try (CqlSession session = + SessionUtils.newSession( + simulacron, + "request.throttler.class = " + ConcurrencyLimitingRequestThrottler.class.getName(), + "request.throttler.max-concurrent-requests = " + maxConcurrentRequests, + "request.throttler.max-queue-size = " + maxQueueSize)) { + + // Saturate the session and fill the queue + for (int i = 0; i < maxConcurrentRequests + maxQueueSize; i++) { + session.executeAsync(QUERY); + } + + // The next query should be rejected + thrown.expect(RequestThrottlingException.class); + thrown.expectMessage( + "The session has reached its maximum capacity " + + "(concurrent requests: 10, queue size: 10)"); + + session.execute(QUERY); + } + } +} diff --git a/manual/core/throttling/README.md b/manual/core/throttling/README.md new file mode 100644 index 00000000000..66aff140eff --- /dev/null +++ b/manual/core/throttling/README.md @@ -0,0 +1,143 @@ +## Request throttling + +Throttling allows you to limit how many requests a session can execute concurrently. This is +useful if you have multiple applications connecting to the same Cassandra cluster, and want to +enforce some kind of SLA to ensure fair resource allocation. + +The request throttler tracks the level of utilization of the session, and lets requests proceed as +long as it is under a predefined threshold. When that threshold is exceeded, requests are enqueued +and will be allowed to proceed when utilization goes back to normal. + +From a user's perspective, this process is mostly transparent: any time spent in the queue is +included in the `session.execute()` or `session.executeAsync()` call. Similarly, the request timeout +encompasses throttling: it starts ticking before the request is passed to the throttler; in other +words, a request may time out while it is still in the throttler's queue, before the driver has even +tried to send it to a node. + +The only visible effect is that a request may fail with a [RequestThrottlingException], if the +throttler has determined that it can neither allow the request to proceed now, nor enqueue it; +this indicates that your session is overloaded. How you react to that is specific to your +application; typically, you could display an error asking the end user to retry later. + +Note that the following requests are also affected by throttling: + +* preparing a statement (either directly, or indirectly when the driver reprepares on other nodes, + or when a node comes back up -- see + [how the driver prepares](../statements/prepared/#how-the-driver-prepares)); +* fetching the next page of a result set (which happens in the background when you iterate the + synchronous variant `ResultSet`). +* fetching a [query trace](../tracing/). + +### Configuration + +Request throttling is parameterized in the [configuration](../configuration/) under +`request.throttler`. There are various implementations, detailed in the following sections: + +#### Pass through + +``` +datastax-java-driver { + request.throttler { + class = com.datastax.oss.driver.internal.core.session.throttling.PassThroughRequestThrottler + } +} +``` + +This is a no-op implementation: requests are simply allowed to proceed all the time, never enqueued. + +Note that you will still hit a limit if all your connections run out of stream ids. In that case, +requests will fail with an [AllNodesFailedException], with the `getErrors()` method returning a +[BusyConnectionException] for each node. + + + +#### Concurrency-based + +``` +datastax-java-driver { + request.throttler { + class = com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler + + # Note: the values below are for illustration purposes only, not prescriptive + max-concurrent-requests = 10000 + max-queue-size = 100000 + } +} +``` + +This implementation limits the number of requests that are allowed to execute simultaneously. +Additional requests get enqueued up to the configured limit. Every time an active request completes +(either by succeeding, failing or timing out), the oldest enqueued request is allowed to proceed. + +Make sure you pick a threshold that is consistent with your pooling settings; the driver should +never run out of stream ids before reaching the maximum concurrency, otherwise requests will fail +with [BusyConnectionException] instead of being throttled. The total number of stream ids is a +function of the number of connected nodes and the `connection.pool.*.size` and +`connection.max-requests-per-connection` configuration options. Keep in mind that aggressive +speculative executions and timeout options can inflate stream id consumption, so keep a safety +margin. One good way to get this right is to track the `pool.available-streams` [metric](../metrics) +on every node, and make sure it never reaches 0. + + + +#### Rate-based + +``` +datastax-java-driver { + request.throttler { + class = com.datastax.oss.driver.internal.core.session.throttling.RateLimitingRequestThrottler + + # Note: the values below are for illustration purposes only, not prescriptive + max-requests-per-second = 5000 + max-queue-size = 50000 + drain-interval = 1 millisecond + } +} +``` + +This implementation tracks the rate at which requests start, and enqueues when it exceeds the +configured threshold. + +With this approach, we can't dequeue when requests complete, because having less active requests +does not necessarily mean that the rate is back to normal. So instead the throttler re-checks the +rate periodically and dequeues when possible, this is controlled by the `drain-interval` option. +Picking the right interval is a matter of balance: too low might consume too many resources and only +dequeue a few requests at a time, but too high will delay your requests too much; start with a few +milliseconds and use the `cql-requests` [metric](../metrics/) to check the impact on your latencies. + +Like with the concurrency-based throttler, you should make sure that your target rate is in line +with the pooling options; see the recommendations in the previous section. + +### Monitoring + +Enable the following [metrics](../metrics/) to monitor how the throttler is performing: + +``` +datastax-java-driver { + metrics.session.enabled = [ + # How long requests are being throttled (exposed as a Timer). + # + # This is the time between the start of the session.execute() call, and the moment when the + # throttler allows the request to proceed. + throttling.delay, + + # The size of the throttling queue (exposed as a Gauge). + # + # This is the number of requests that the throttler is currently delaying in order to + # preserve its SLA. This metric only works with the built-in concurrency- and rate-based + # throttlers; in other cases, it will always be 0. + throttling.queue-size, + + # The number of times a request was rejected with a RequestThrottlingException (exposed as a + # Counter) + throttling.errors, + ] +} +``` + +If you enable `throttling.delay`, make sure to also check the associated extra options to correctly +size the underlying histograms (`metrics.session.throttling.delay.*`). + +[RequestThrottlingException]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/RequestThrottlingException.html +[AllNodesFailedException]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/AllNodesFailedException.html +[BusyConnectionException]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/connection/BusyConnectionException.html \ No newline at end of file From acbec227d66f1efe7404ea84ade47a7364655e70 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 15 Mar 2018 16:25:25 -0700 Subject: [PATCH 363/742] Add string overloads to UserDefinedTypeBuilder --- .../driver/internal/core/type/UserDefinedTypeBuilder.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java index 06466ff10c7..23fcf4dce47 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/UserDefinedTypeBuilder.java @@ -43,6 +43,10 @@ public UserDefinedTypeBuilder(CqlIdentifier keyspaceName, CqlIdentifier typeName this.fieldTypes = ImmutableList.builder(); } + public UserDefinedTypeBuilder(String keyspaceName, String typeName) { + this(CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(typeName)); + } + /** * Adds a new field. The fields in the resulting type will be in the order of the calls to this * method. @@ -53,6 +57,10 @@ public UserDefinedTypeBuilder withField(CqlIdentifier name, DataType type) { return this; } + public UserDefinedTypeBuilder withField(String name, DataType type) { + return withField(CqlIdentifier.fromCql(name), type); + } + /** Makes the type frozen (by default, it is not). */ public UserDefinedTypeBuilder frozen() { this.frozen = true; From 242bb2bcb47b303545fdc435842ea6dd1fc0ac1e Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Mar 2018 13:39:58 -0700 Subject: [PATCH 364/742] JAVA-1780: Add manual section about case sensitivity --- changelog/README.md | 1 + manual/.nav | 3 + manual/README.md | 9 +- manual/case_sensitivity/README.md | 133 ++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 manual/.nav create mode 100644 manual/case_sensitivity/README.md diff --git a/changelog/README.md b/changelog/README.md index a97d03faf68..29110492354 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [documentation] JAVA-1780: Add manual section about case sensitivity - [new feature] JAVA-1536: Add request throttling - [improvement] JAVA-1772: Revisit multi-response callbacks - [new feature] JAVA-1537: Add remaining socket options diff --git a/manual/.nav b/manual/.nav new file mode 100644 index 00000000000..274a0da2a60 --- /dev/null +++ b/manual/.nav @@ -0,0 +1,3 @@ +core +api_conventions +case_sensitivity \ No newline at end of file diff --git a/manual/README.md b/manual/README.md index 46ce849ab35..8b754dae026 100644 --- a/manual/README.md +++ b/manual/README.md @@ -1,3 +1,10 @@ ## Manual -* [Core driver](core/) \ No newline at end of file +Driver modules: + +* [Core](core/) + +Common topics: + +* [API conventions](api_conventions/) +* [Case sensitivity](case_sensitivity/) \ No newline at end of file diff --git a/manual/case_sensitivity/README.md b/manual/case_sensitivity/README.md new file mode 100644 index 00000000000..3d6542f6097 --- /dev/null +++ b/manual/case_sensitivity/README.md @@ -0,0 +1,133 @@ +## Case sensitivity + +### In Cassandra + +Cassandra identifiers, such as keyspace, table and column names, are case-insensitive by default. +For example, if you create the following table: + +``` +cqlsh> CREATE TABLE test.FooBar(k int PRIMARY KEY); +``` + +Cassandra actually stores the table name as lower-case: + +``` +cqlsh> SELECT table_name FROM system_schema.tables WHERE keyspace_name = 'test'; + + table_name +------------ + foobar +``` + +And you can use whatever case you want in your queries: + +``` +cqlsh> SELECT * FROM test.FooBar; +cqlsh> SELECT * FROM test.foobar; +cqlsh> SELECT * FROM test.FoObAr; +``` + +However, if you enclose an identifier in double quotes, it becomes case-sensitive: + +``` +cqlsh> CREATE TABLE test."FooBar"(k int PRIMARY KEY); +cqlsh> SELECT table_name FROM system_schema.tables WHERE keyspace_name = 'test'; + + table_name +------------ + FooBar +``` + +You now have to use the exact, quoted form in your queries: + +``` +cqlsh> SELECT * FROM test."FooBar"; +``` + +If you forget to quote, or use the wrong case, you'll get an error: + +``` +cqlsh> SELECT * FROM test.Foobar; +InvalidRequest: Error from server: code=2200 [Invalid query] message="table foobar does not exist" + +cqlsh> SELECT * FROM test."FOOBAR"; +InvalidRequest: Error from server: code=2200 [Invalid query] message="table FOOBAR does not exist" +``` + +### In the driver + +When we deal with identifiers, we use the following definitions: + +* **CQL form**: how you would type it in a CQL query. In other words, case-sensitive if it's quoted, + case-insensitive otherwise; +* **internal form**: how it is stored in system tables. In other words, never quoted and always in + its exact case. + +In previous driver versions, identifiers were represented as raw strings. The problem is that this +does not capture the form; when a method processed an identifier, it always had to know where it +came from and what form it was in, and possibly convert it. This led a lot of internal complexity, +and recurring bugs. + +To address this issue, driver 4+ uses a wrapper: [CqlIdentifier]. Its API methods are always +explicit about the form: + +```java +CqlIdentifier caseInsensitiveId = CqlIdentifier.fromCql("FooBar"); +System.out.println(caseInsensitiveId.asInternal()); // foobar +System.out.println(caseInsensitiveId.asCql(/*pretty=*/ false)); // "foobar" +System.out.println(caseInsensitiveId.asCql(true)); // foobar + +// Double-quotes need to be escaped inside Java strings +CqlIdentifier caseSensitiveId = CqlIdentifier.fromCql("\"FooBar\""); +System.out.println(caseSensitiveId.asInternal()); // FooBar +System.out.println(caseSensitiveId.asCql(true)); // "FooBar" +System.out.println(caseSensitiveId.asCql(false)); // "FooBar" + +CqlIdentifier caseSensitiveId2 = CqlIdentifier.fromInternal("FooBar"); +assert caseSensitiveId.equals(caseSensitiveId2); +``` + +*Side note: as shown above, `asCql` has a pretty-printing option that omits the quotes if they are +not necessary. This looks nicer, but is slightly more expensive because it requires parsing the +string.* + +The driver API uses `CqlIdentifier` whenever it produces or consumes an identifier. For example: + +* getting the keyspace from a table's metadata: `CqlIdentifier keyspaceId = + tableMetadata.getKeyspace()`; +* setting the keyspace when building a session: `CqlSession.builder().withKeyspace(keyspaceId)`. + +For "consuming" methods, string overloads are also provided for convenience, for example +`SessionBuilder.withKeyspace(String)`. + +* getters and setters of "data container" types [Row], [UdtValue], and [BoundStatement] follow + special rules described [here][AccessibleByName] (these methods are treated apart because they are + generally invoked very often, and therefore avoid to create `CqlIdentifier` instances internally); +* in other cases, the string is always assumed to be in CQL form, and converted on the fly with + `CqlIdentifier.fromCql`. + +[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html +[Row]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Row.html +[UdtValue]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/UdtValue.html +[BoundStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BoundStatement.html +[AccessibleByName]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html + +### Good practices + +As should be clear by now, case sensitivity introduces a lot of extra (and arguably unnecessary) +complexity. + +The Java driver team's recommendation is: + +> **Always use case-insensitive identifiers in your data model.** + +You'll never have to create `CqlIdentifier` instances in your application code, nor think about +CQL/internal forms. When you pass an identifier to the driver, use the string-based methods. When +the driver returns an identifier and you need to convert it into a string, use `asInternal()`. + +If you worry about readability, use snake case (`shopping_cart`), or simply stick to camel case +(`ShoppingCart`) and ignore the fact that Cassandra lower-cases everything internally. + +The only reason to use case sensitivity should be if you don't control the data model. In that +case, either pass quoted strings to the driver, or use `CqlIdentifier` instances (stored as +constants to avoid creating them over and over). \ No newline at end of file From 7eb61813c728ec82d4a8ad6001a51a18ce34de75 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 21 Mar 2018 12:42:24 +0100 Subject: [PATCH 365/742] Improve Javadocs generation (#953) This commit alters the javadoc plugin configuration to generate javadocs quietly, and applies the Xdoclint option "all,-missing", i.e., validate everything except missing tags. --- .../oss/driver/internal/core/metadata/DefaultMetadata.java | 6 +++++- pom.xml | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index f424dea990e..32c8be1db37 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -76,11 +76,15 @@ public Optional getTokenMap() { } /** - * @param tokenMapEnabled + * Refreshes the current metadata with the given list of nodes. + * + * @param tokenMapEnabled whether to rebuild the token map or not; if this is {@code false} the + * current token map will be copied into the new metadata without being recomputed. * @param tokensChanged whether we observed a change of tokens for at least one node. This will * require a full rebuild of the token map. * @param tokenFactory only needed for the initial refresh, afterwards the existing one in the * token map is used. + * @return the new metadata. */ public DefaultMetadata withNodes( Map newNodes, diff --git a/pom.xml b/pom.xml index e63019d020c..d0fb8e6bbde 100644 --- a/pom.xml +++ b/pom.xml @@ -186,7 +186,7 @@ maven-javadoc-plugin - 3.0.0-M1 + 3.0.0 maven-jar-plugin @@ -357,6 +357,9 @@ limitations under the License.]]> maven-javadoc-plugin + false + true + all,-missing - + 4.0.0 com.datastax.oss @@ -56,6 +58,12 @@ 4.1.9.Final + com.datastax.oss + java-driver-shaded-guava + 24.1-jre + + + com.google.guava guava 24.1-jre From 1b91481a568e0f571229a61a720300f9a2788230 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 3 Apr 2018 15:51:45 -0700 Subject: [PATCH 370/742] Add string overloads for per-query keyspace in statement builders --- .../oss/driver/api/core/cql/BatchStatementBuilder.java | 8 ++++++++ .../oss/driver/api/core/cql/SimpleStatementBuilder.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index 951deef39c1..877c50713d8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -46,6 +46,14 @@ public BatchStatementBuilder withKeyspace(CqlIdentifier keyspace) { return this; } + /** + * Shortcut for {@link #withKeyspace(CqlIdentifier) + * withKeyspace(CqlIdentifier.fromCql(keyspaceName))}. + */ + public BatchStatementBuilder withKeyspace(String keyspaceName) { + return withKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + /** @see BatchStatement#add(BatchableStatement) */ public BatchStatementBuilder addStatement(BatchableStatement statement) { if (statementsCount >= 0xFFFF) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 2b0c1f1f2f0..db296b32493 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -65,6 +65,14 @@ public SimpleStatementBuilder withKeyspace(CqlIdentifier keyspace) { return this; } + /** + * Shortcut for {@link #withKeyspace(CqlIdentifier) + * withKeyspace(CqlIdentifier.fromCql(keyspaceName))}. + */ + public SimpleStatementBuilder withKeyspace(String keyspaceName) { + return withKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + /** @see SimpleStatement#setPositionalValues(List) */ public SimpleStatementBuilder addPositionalValue(Object value) { if (namedValuesBuilder != null) { From 47db4c75a7003128e7cdd6b9afe7a3b92ca9477e Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 3 Apr 2018 16:01:48 -0700 Subject: [PATCH 371/742] Fix BatchStatement routing methods --- .../core/cql/DefaultBatchStatement.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 05e709073d0..b68e805090e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -277,7 +277,17 @@ public CqlIdentifier getKeyspace() { @Override public CqlIdentifier getRoutingKeyspace() { - return routingKeyspace; + if (routingKeyspace != null) { + return routingKeyspace; + } else { + for (BatchableStatement statement : statements) { + CqlIdentifier ks = statement.getRoutingKeyspace(); + if (ks != null) { + return ks; + } + } + } + return null; } @Override @@ -300,7 +310,17 @@ public BatchStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { @Override public ByteBuffer getRoutingKey() { - return routingKey; + if (routingKey != null) { + return routingKey; + } else { + for (BatchableStatement statement : statements) { + ByteBuffer key = statement.getRoutingKey(); + if (key != null) { + return key; + } + } + } + return null; } @Override @@ -323,7 +343,17 @@ public BatchStatement setRoutingKey(ByteBuffer newRoutingKey) { @Override public Token getRoutingToken() { - return routingToken; + if (routingToken != null) { + return routingToken; + } else { + for (BatchableStatement statement : statements) { + Token token = statement.getRoutingToken(); + if (token != null) { + return token; + } + } + } + return null; } @Override From 82e883e708d2d7c63acf85e2fcc30e3cf1bf1d34 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 9 Apr 2018 09:51:01 -0700 Subject: [PATCH 372/742] Add doc tool config file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ec7be4c3a93..8b0c30cb0cc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .java-version +.documenter_local_last_run /docs target/ dependency-reduced-pom.xml From 44e4b7e3b4a0fe2bb806782016482ec119c6679e Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 30 Mar 2018 14:33:28 -0700 Subject: [PATCH 373/742] JAVA-1773: Make DriverConfigProfile enumerable --- changelog/README.md | 1 + .../api/core/config/DriverConfigProfile.java | 13 ++++++++++ .../typesafe/TypesafeDriverConfigProfile.java | 14 +++++++++++ .../typesafe/TypesafeDriverConfigTest.java | 24 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 348f82807c5..62012c44759 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1773: Make DriverConfigProfile enumerable - [improvement] JAVA-1787: Use standalone shaded Guava artifact - [improvement] JAVA-1769: Allocate exact buffer size for outgoing requests - [documentation] JAVA-1780: Add manual section about case sensitivity diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 0e8107fca34..c3a6476cd0d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -18,6 +18,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.SortedSet; /** * A profile in the driver's configuration. @@ -65,4 +66,16 @@ public interface DriverConfigProfile { Duration getDuration(DriverOption option); DriverConfigProfile withDuration(DriverOption option, Duration value); + + /** + * Enumerates all the entries in this profile, including those that were inherited from another + * profile. + * + *

          The keys are raw strings that match {@link DriverOption#getPath()}. + * + *

          The values are implementation-dependent. With the driver's default implementation, the + * possible types are {@code String}, {@code Number}, {@code Boolean}, {@code Map}, + * {@code List}, or {@code null}. + */ + SortedSet> entrySet(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java index 9799a54dd17..0ac7f63d2fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSortedSet; import com.datastax.oss.driver.shaded.guava.common.collect.MapMaker; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -25,10 +26,13 @@ import com.typesafe.config.ConfigValueFactory; import com.typesafe.config.ConfigValueType; import java.time.Duration; +import java.util.AbstractMap; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -138,6 +142,16 @@ public DriverConfigProfile withBytes(DriverOption option, long value) { return with(option, value); } + @Override + public SortedSet> entrySet() { + ImmutableSortedSet.Builder> builder = + ImmutableSortedSet.orderedBy(Comparator.comparing(Map.Entry::getKey)); + for (Map.Entry entry : getEffectiveOptions().entrySet()) { + builder.add(new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().unwrapped())); + } + return builder.build(); + } + private T getCached(String path, Function compute) { // compute's signature guarantees we get a T, and this is the only place where we mutate the // entry diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java index 82a55466a60..2e363a965eb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.config.typesafe; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -157,6 +158,29 @@ public void should_update_derived_profiles_after_reloading() { assertThat(derivedFromProfile1.getInt(MockOptions.OPTIONAL_INT)).isEqualTo(51); } + @Test + public void should_enumerate_options() { + TypesafeDriverConfig config = + parse( + "required_int = 42 \n" + + "auth_provider { auth_thing_one= one \n auth_thing_two = two \n auth_thing_three = three}\n" + + "profiles { profile1 { required_int = 45 } }"); + + assertThat(config.getDefaultProfile().entrySet()) + .containsExactly( + entry("auth_provider.auth_thing_one", "one"), + entry("auth_provider.auth_thing_three", "three"), + entry("auth_provider.auth_thing_two", "two"), + entry("required_int", 42)); + + assertThat(config.getNamedProfile("profile1").entrySet()) + .containsExactly( + entry("auth_provider.auth_thing_one", "one"), + entry("auth_provider.auth_thing_three", "three"), + entry("auth_provider.auth_thing_two", "two"), + entry("required_int", 45)); + } + private TypesafeDriverConfig parse(String configString) { Config config = ConfigFactory.parseString(configString); return new TypesafeDriverConfig(config, MockOptions.values()); From fd5e75da1a126382a1dae83d5917c2dba6ae1775 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 11 Apr 2018 09:08:52 -0700 Subject: [PATCH 374/742] Update contribution guidelines --- CONTRIBUTING.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31c40c0f884..843f5584fff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -283,13 +283,17 @@ the changes if the first attempt failed and you fixed the tests. ## Commits Keep your changes **focused**. Each commit should have a single, clear purpose expressed in its -message. (Note: these rules can be somewhat relaxed during the initial developement phase, when -adding a feature sometimes requires other semi-related features). +message. -Resist the urge to "fix" cosmetic issues (add/remove blank lines, etc.) in existing code. This adds -cognitive load for reviewers, who have to figure out which changes are relevant to the actual -issue. If you see legitimate issues, like typos, address them in a separate commit (it's fine to -group multiple typo fixes in a single commit). +Resist the urge to "fix" cosmetic issues (add/remove blank lines, move methods, etc.) in existing +code. This adds cognitive load for reviewers, who have to figure out which changes are relevant to +the actual issue. If you see legitimate issues, like typos, address them in a separate commit (it's +fine to group multiple typo fixes in a single commit). + +Isolate trivial refactorings into separate commits. For example, a method rename that affects dozens +of call sites can be reviewed in a few seconds, but if it's part of a larger diff it gets mixed up +with more complex changes (that might affect the same lines), and reviewers have to check every +line. Commit message subjects start with a capital letter, use the imperative form and do **not** end with a period: From 0b47c2ba9e3403e77f211b1b8df13329d6036222 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Mar 2018 10:55:31 -0800 Subject: [PATCH 375/742] JAVA-1515: Add query builder Co-authored-by: Andrew Tolbert Co-authored-by: GregBestland --- changelog/README.md | 1 + .../oss/driver/api/core/session/Request.java | 4 +- .../driver/api/core/type/UserDefinedType.java | 9 +- manual/README.md | 1 + manual/core/idempotence/README.md | 56 ++ manual/query_builder/.nav | 7 + manual/query_builder/README.md | 188 +++++ manual/query_builder/condition/README.md | 135 ++++ manual/query_builder/delete/README.md | 145 ++++ manual/query_builder/idempotence/README.md | 174 ++++ manual/query_builder/insert/README.md | 90 +++ manual/query_builder/relation/README.md | 205 +++++ manual/query_builder/schema/.nav | 7 + manual/query_builder/schema/README.md | 47 ++ .../query_builder/schema/aggregate/README.md | 79 ++ .../query_builder/schema/function/README.md | 95 +++ manual/query_builder/schema/index/README.md | 102 +++ .../query_builder/schema/keyspace/README.md | 88 ++ .../schema/materialized_view/README.md | 89 +++ manual/query_builder/schema/table/README.md | 112 +++ manual/query_builder/schema/type/README.md | 91 +++ manual/query_builder/select/README.md | 395 +++++++++ manual/query_builder/term/README.md | 109 +++ manual/query_builder/update/README.md | 226 ++++++ pom.xml | 1 + query-builder/pom.xml | 84 ++ .../driver/api/querybuilder/BindMarker.java | 33 + .../api/querybuilder/BuildableQuery.java | 75 ++ .../driver/api/querybuilder/CqlSnippet.java | 21 + .../oss/driver/api/querybuilder/Literal.java | 29 + .../api/querybuilder/QueryBuilderDsl.java | 388 +++++++++ .../oss/driver/api/querybuilder/Raw.java | 39 + .../api/querybuilder/SchemaBuilderDsl.java | 583 ++++++++++++++ .../api/querybuilder/condition/Condition.java | 92 +++ .../condition/ConditionBuilder.java | 22 + .../condition/ConditionalStatement.java | 151 ++++ .../api/querybuilder/delete/Delete.java | 24 + .../querybuilder/delete/DeleteSelection.java | 162 ++++ .../api/querybuilder/insert/Insert.java | 42 + .../api/querybuilder/insert/InsertInto.java | 31 + .../api/querybuilder/insert/JsonInsert.java | 34 + .../querybuilder/insert/OngoingValues.java | 38 + .../querybuilder/insert/RegularInsert.java | 19 + .../relation/ArithmeticRelationBuilder.java | 77 ++ .../ColumnComponentRelationBuilder.java | 19 + .../relation/ColumnRelationBuilder.java | 42 + .../relation/InRelationBuilder.java | 47 ++ .../relation/MultiColumnRelationBuilder.java | 19 + .../relation/OngoingWhereClause.java | 226 ++++++ .../api/querybuilder/relation/Relation.java | 159 ++++ .../relation/TokenRelationBuilder.java | 18 + .../querybuilder/schema/AlterKeyspace.java | 23 + .../schema/AlterKeyspaceStart.java | 19 + .../schema/AlterMaterializedView.java | 21 + .../schema/AlterMaterializedViewStart.java | 18 + .../schema/AlterTableAddColumn.java | 55 ++ .../schema/AlterTableAddColumnEnd.java | 20 + .../schema/AlterTableDropColumn.java | 50 ++ .../schema/AlterTableDropColumnEnd.java | 20 + .../schema/AlterTableRenameColumn.java | 35 + .../schema/AlterTableRenameColumnEnd.java | 20 + .../querybuilder/schema/AlterTableStart.java | 48 ++ .../schema/AlterTableWithOptions.java | 18 + .../schema/AlterTableWithOptionsEnd.java | 21 + .../schema/AlterTypeRenameField.java | 35 + .../schema/AlterTypeRenameFieldEnd.java | 20 + .../querybuilder/schema/AlterTypeStart.java | 57 ++ .../schema/CreateAggregateEnd.java | 43 + .../schema/CreateAggregateStart.java | 53 ++ .../schema/CreateAggregateStateFunc.java | 33 + .../schema/CreateFunctionEnd.java | 20 + .../schema/CreateFunctionStart.java | 66 ++ .../schema/CreateFunctionWithLanguage.java | 44 + .../schema/CreateFunctionWithNullOption.java | 32 + .../schema/CreateFunctionWithType.java | 40 + .../api/querybuilder/schema/CreateIndex.java | 31 + .../schema/CreateIndexOnTable.java | 110 +++ .../querybuilder/schema/CreateIndexStart.java | 68 ++ .../querybuilder/schema/CreateKeyspace.java | 20 + .../schema/CreateKeyspaceStart.java | 24 + .../schema/CreateMaterializedView.java | 21 + .../CreateMaterializedViewPrimaryKey.java | 39 + ...CreateMaterializedViewPrimaryKeyStart.java | 36 + .../CreateMaterializedViewSelection.java | 60 ++ ...eMaterializedViewSelectionWithColumns.java | 19 + .../schema/CreateMaterializedViewStart.java | 51 ++ .../schema/CreateMaterializedViewWhere.java | 19 + .../CreateMaterializedViewWhereStart.java | 21 + .../api/querybuilder/schema/CreateTable.java | 81 ++ .../querybuilder/schema/CreateTableStart.java | 25 + .../schema/CreateTableWithOptions.java | 25 + .../api/querybuilder/schema/CreateType.java | 20 + .../querybuilder/schema/CreateTypeStart.java | 25 + .../driver/api/querybuilder/schema/Drop.java | 24 + .../querybuilder/schema/KeyspaceOptions.java | 28 + .../schema/KeyspaceReplicationOptions.java | 65 ++ .../schema/OngoingCreateType.java | 42 + .../schema/OngoingPartitionKey.java | 45 ++ .../querybuilder/schema/OptionProvider.java | 28 + .../querybuilder/schema/RelationOptions.java | 291 +++++++ .../schema/RelationStructure.java | 66 ++ .../schema/compaction/CompactionStrategy.java | 38 + .../compaction/LeveledCompactionStrategy.java | 24 + .../SizeTieredCompactionStrategy.java | 49 ++ .../TimeWindowCompactionStrategy.java | 44 + .../querybuilder/select/OngoingSelection.java | 749 ++++++++++++++++++ .../api/querybuilder/select/Select.java | 180 +++++ .../api/querybuilder/select/SelectFrom.java | 32 + .../api/querybuilder/select/Selector.java | 495 ++++++++++++ .../driver/api/querybuilder/term/Term.java | 54 ++ .../api/querybuilder/update/Assignment.java | 350 ++++++++ .../update/OngoingAssignment.java | 488 ++++++++++++ .../api/querybuilder/update/Update.java | 27 + .../api/querybuilder/update/UpdateStart.java | 41 + .../update/UpdateWithAssignments.java | 24 + .../querybuilder/ArithmeticOperator.java | 48 ++ .../internal/querybuilder/CqlHelper.java | 104 +++ .../internal/querybuilder/DefaultLiteral.java | 76 ++ .../internal/querybuilder/DefaultRaw.java | 83 ++ .../querybuilder/ImmutableCollections.java | 81 ++ .../condition/DefaultCondition.java | 54 ++ .../condition/DefaultConditionBuilder.java | 53 ++ .../querybuilder/delete/DefaultDelete.java | 209 +++++ .../querybuilder/insert/DefaultInsert.java | 226 ++++++ .../lhs/ColumnComponentLeftOperand.java | 37 + .../querybuilder/lhs/ColumnLeftOperand.java | 36 + .../querybuilder/lhs/FieldLeftOperand.java | 42 + .../querybuilder/lhs/LeftOperand.java | 32 + .../querybuilder/lhs/TokenLeftOperand.java | 37 + .../querybuilder/lhs/TupleLeftOperand.java | 37 + .../relation/CustomIndexRelation.java | 51 ++ ...DefaultColumnComponentRelationBuilder.java | 58 ++ .../DefaultColumnRelationBuilder.java | 56 ++ .../DefaultMultiColumnRelationBuilder.java | 58 ++ .../relation/DefaultRelation.java | 62 ++ .../relation/DefaultTokenRelationBuilder.java | 54 ++ .../schema/DefaultAlterKeyspace.java | 68 ++ .../schema/DefaultAlterMaterializedView.java | 80 ++ .../schema/DefaultAlterTable.java | 339 ++++++++ .../querybuilder/schema/DefaultAlterType.java | 166 ++++ .../schema/DefaultCreateAggregate.java | 205 +++++ .../schema/DefaultCreateFunction.java | 292 +++++++ .../schema/DefaultCreateIndex.java | 206 +++++ .../schema/DefaultCreateKeyspace.java | 89 +++ .../schema/DefaultCreateMaterializedView.java | 411 ++++++++++ .../schema/DefaultCreateTable.java | 369 +++++++++ .../schema/DefaultCreateType.java | 121 +++ .../querybuilder/schema/DefaultDrop.java | 84 ++ .../schema/DefaultDropKeyspace.java | 65 ++ .../querybuilder/schema/OptionsUtils.java | 64 ++ .../internal/querybuilder/schema/Utils.java | 30 + .../compaction/DefaultCompactionStrategy.java | 43 + .../DefaultLeveledCompactionStrategy.java | 39 + .../DefaultSizeTieredCompactionStrategy.java | 39 + .../DefaultTimeWindowCompactionStrategy.java | 38 + .../querybuilder/select/AllSelector.java | 38 + .../select/ArithmeticSelector.java | 48 ++ .../select/BinaryArithmeticSelector.java | 91 +++ .../querybuilder/select/CastSelector.java | 89 +++ .../select/CollectionSelector.java | 97 +++ .../querybuilder/select/ColumnSelector.java | 76 ++ .../querybuilder/select/CountAllSelector.java | 68 ++ .../select/DefaultBindMarker.java | 50 ++ .../querybuilder/select/DefaultSelect.java | 466 +++++++++++ .../querybuilder/select/ElementSelector.java | 89 +++ .../querybuilder/select/FieldSelector.java | 86 ++ .../querybuilder/select/FunctionSelector.java | 66 ++ .../querybuilder/select/ListSelector.java | 35 + .../querybuilder/select/MapSelector.java | 144 ++++ .../querybuilder/select/OppositeSelector.java | 79 ++ .../querybuilder/select/RangeSelector.java | 103 +++ .../querybuilder/select/SetSelector.java | 35 + .../querybuilder/select/TupleSelector.java | 35 + .../querybuilder/select/TypeHintSelector.java | 87 ++ .../querybuilder/term/ArithmeticTerm.java | 48 ++ .../term/BinaryArithmeticTerm.java | 74 ++ .../querybuilder/term/FunctionTerm.java | 63 ++ .../querybuilder/term/OppositeTerm.java | 63 ++ .../internal/querybuilder/term/TupleTerm.java | 47 ++ .../querybuilder/term/TypeHintTerm.java | 49 ++ .../querybuilder/update/AppendAssignment.java | 32 + .../update/AppendListElementAssignment.java | 31 + .../update/AppendMapEntryAssignment.java | 26 + .../update/AppendSetElementAssignment.java | 26 + .../update/CollectionElementAssignment.java | 97 +++ .../update/CounterAssignment.java | 31 + .../update/DefaultAssignment.java | 59 ++ .../querybuilder/update/DefaultUpdate.java | 211 +++++ .../update/PrependAssignment.java | 53 ++ .../update/PrependListElementAssignment.java | 31 + .../update/PrependMapEntryAssignment.java | 26 + .../update/PrependSetElementAssignment.java | 26 + .../update/RemoveListElementAssignment.java | 31 + .../update/RemoveMapEntryAssignment.java | 26 + .../update/RemoveSetElementAssignment.java | 26 + .../driver/api/querybuilder/Assertions.java | 27 + .../querybuilder/BuildableQueryAssert.java | 42 + .../driver/api/querybuilder/CharsetCodec.java | 65 ++ .../api/querybuilder/CqlSnippetAssert.java | 34 + .../querybuilder/condition/ConditionTest.java | 65 ++ .../delete/DeleteFluentConditionTest.java | 100 +++ .../delete/DeleteFluentRelationTest.java | 39 + .../delete/DeleteIdempotenceTest.java | 63 ++ .../delete/DeleteSelectorTest.java | 47 ++ .../delete/DeleteTimestampTest.java | 60 ++ .../insert/InsertIdempotenceTest.java | 66 ++ .../querybuilder/insert/JsonInsertTest.java | 50 ++ .../insert/RegularInsertTest.java | 77 ++ .../querybuilder/relation/RelationTest.java | 139 ++++ .../api/querybuilder/relation/TermTest.java | 119 +++ .../schema/AlterKeyspaceTest.java | 42 + .../schema/AlterMaterializedViewTest.java | 43 + .../querybuilder/schema/AlterTableTest.java | 103 +++ .../querybuilder/schema/AlterTypeTest.java | 54 ++ .../schema/CreateAggregateTest.java | 152 ++++ .../schema/CreateFunctionTest.java | 188 +++++ .../querybuilder/schema/CreateIndexTest.java | 145 ++++ .../schema/CreateKeyspaceTest.java | 71 ++ .../schema/CreateMaterializedViewTest.java | 139 ++++ .../querybuilder/schema/CreateTableTest.java | 308 +++++++ .../querybuilder/schema/CreateTypeTest.java | 84 ++ .../schema/DropAggregateTest.java | 38 + .../querybuilder/schema/DropFunctionTest.java | 39 + .../querybuilder/schema/DropIndexTest.java | 39 + .../querybuilder/schema/DropKeyspaceTest.java | 33 + .../schema/DropMaterializedViewTest.java | 40 + .../querybuilder/schema/DropTableTest.java | 39 + .../api/querybuilder/schema/DropTypeTest.java | 39 + .../select/SelectAllowFilteringTest.java | 35 + .../select/SelectFluentRelationTest.java | 150 ++++ .../select/SelectGroupByTest.java | 54 ++ .../querybuilder/select/SelectLimitTest.java | 50 ++ .../select/SelectOrderingTest.java | 75 ++ .../select/SelectSelectorTest.java | 255 ++++++ .../update/UpdateFluentAssignmentTest.java | 193 +++++ .../update/UpdateFluentConditionTest.java | 111 +++ .../update/UpdateFluentRelationTest.java | 39 + .../update/UpdateIdempotenceTest.java | 151 ++++ .../update/UpdateTimestampTest.java | 56 ++ 239 files changed, 20474 insertions(+), 4 deletions(-) create mode 100644 manual/core/idempotence/README.md create mode 100644 manual/query_builder/.nav create mode 100644 manual/query_builder/README.md create mode 100644 manual/query_builder/condition/README.md create mode 100644 manual/query_builder/delete/README.md create mode 100644 manual/query_builder/idempotence/README.md create mode 100644 manual/query_builder/insert/README.md create mode 100644 manual/query_builder/relation/README.md create mode 100644 manual/query_builder/schema/.nav create mode 100644 manual/query_builder/schema/README.md create mode 100644 manual/query_builder/schema/aggregate/README.md create mode 100644 manual/query_builder/schema/function/README.md create mode 100644 manual/query_builder/schema/index/README.md create mode 100644 manual/query_builder/schema/keyspace/README.md create mode 100644 manual/query_builder/schema/materialized_view/README.md create mode 100644 manual/query_builder/schema/table/README.md create mode 100644 manual/query_builder/schema/type/README.md create mode 100644 manual/query_builder/select/README.md create mode 100644 manual/query_builder/term/README.md create mode 100644 manual/query_builder/update/README.md create mode 100644 query-builder/pom.xml create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BindMarker.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Literal.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/Condition.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/Delete.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsert.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/OngoingValues.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsert.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnComponentRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/MultiColumnRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/TokenRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspace.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedView.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumnEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumn.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumnEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumn.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumnEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptionsEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameField.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameFieldEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionEnd.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithLanguage.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithType.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndex.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexOnTable.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspace.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedView.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKey.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKeyStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelection.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelectionWithColumns.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhere.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhereStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateType.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/Drop.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceReplicationOptions.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/SelectFrom.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Update.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateWithAssignments.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ArithmeticOperator.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultRaw.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ImmutableCollections.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultCondition.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultConditionBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnComponentLeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnLeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/FieldLeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/LeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TokenLeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TupleLeftOperand.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/CustomIndexRelation.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnComponentRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultMultiColumnRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultRelation.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultTokenRelationBuilder.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterKeyspace.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterMaterializedView.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterTable.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterType.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateAggregate.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateFunction.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateIndex.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateKeyspace.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateMaterializedView.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateTable.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateType.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDrop.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDropKeyspace.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/OptionsUtils.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/Utils.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultLeveledCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultSizeTieredCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultTimeWindowCompactionStrategy.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/AllSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ArithmeticSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/BinaryArithmeticSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CastSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ColumnSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CountAllSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultBindMarker.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ElementSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FieldSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FunctionSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ListSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/OppositeSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/RangeSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/SetSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TupleSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TypeHintSelector.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/ArithmeticTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/BinaryArithmeticTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/FunctionTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/OppositeTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TupleTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TypeHintTerm.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendListElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendMapEntryAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendSetElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CollectionElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CounterAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependListElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependMapEntryAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependSetElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveListElementAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveMapEntryAssignment.java create mode 100644 query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveSetElementAssignment.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/Assertions.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/BuildableQueryAssert.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CharsetCodec.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CqlSnippetAssert.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentConditionTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentRelationTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteIdempotenceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelectorTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteTimestampTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/InsertIdempotenceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsertTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropAggregateTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropFunctionTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropIndexTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropKeyspaceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropMaterializedViewTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTableTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTypeTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectAllowFilteringTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectFluentRelationTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectGroupByTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectLimitTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentAssignmentTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentConditionTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentRelationTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateIdempotenceTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java diff --git a/changelog/README.md b/changelog/README.md index 62012c44759..f9010dbf84d 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [new feature] JAVA-1515: Add query builder - [improvement] JAVA-1773: Make DriverConfigProfile enumerable - [improvement] JAVA-1787: Use standalone shaded Guava artifact - [improvement] JAVA-1769: Allocate exact buffer size for outgoing requests diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 606a3654072..478552b112f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -120,8 +120,8 @@ public interface Request { Map getCustomPayload(); /** - * Whether the request is idempotent; that is, whether applying the request twice yields the same - * result. + * Whether the request is idempotent; that is, whether applying the request twice leaves the + * database in the same state. * *

          This is used internally for retries and speculative executions: if a request is not * idempotent, the driver will take extra care to ensure that it is not sent twice (for example, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java index 63f8c802ad7..e0090906be3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/UserDefinedType.java @@ -54,8 +54,13 @@ default boolean contains(String name) { @Override default String asCql(boolean includeFrozen, boolean pretty) { - String template = (isFrozen() && includeFrozen) ? "frozen<%s.%s>" : "%s.%s"; - return String.format(template, getKeyspace().asCql(pretty), getName().asCql(pretty)); + if (getKeyspace() != null) { + String template = (isFrozen() && includeFrozen) ? "frozen<%s.%s>" : "%s.%s"; + return String.format(template, getKeyspace().asCql(pretty), getName().asCql(pretty)); + } else { + String template = (isFrozen() && includeFrozen) ? "frozen<%s>" : "%s"; + return String.format(template, getName().asCql(pretty)); + } } @Override diff --git a/manual/README.md b/manual/README.md index 8b754dae026..8d321acbc87 100644 --- a/manual/README.md +++ b/manual/README.md @@ -3,6 +3,7 @@ Driver modules: * [Core](core/) +* [Query builder](query_builder/) Common topics: diff --git a/manual/core/idempotence/README.md b/manual/core/idempotence/README.md new file mode 100644 index 00000000000..717690e01c0 --- /dev/null +++ b/manual/core/idempotence/README.md @@ -0,0 +1,56 @@ +## Query idempotence + +A request is *idempotent* if executing it multiple times leaves the database in the same state as +executing it only once. + +For example: + +* `update my_table set list_col = [1] where pk = 1` is idempotent: no matter how many times it gets + executed, `list_col` will always end up with the value `[1]`; +* `update my_table set list_col = [1] + list_col where pk = 1` is not idempotent: if `list_col` was + initially empty, it will contain `[1]` after the first execution, `[1, 1]` after the second, etc. + +Idempotence matters because the driver sometimes re-runs requests automatically: + +* **retries**: if we're waiting for a response from a node and the connection gets dropped, the + default retry policy automatically retries on another node. But we can't know what went wrong with + the first node: maybe it went down, or maybe it was just a network issue; in any case, it might + have applied the changes already. Therefore non-idempotent requests are never retried. + + + +* **speculative executions**: if they are enabled and a node takes too long to respond, the driver + queries another node to get the response faster. But maybe both nodes will eventually apply the + changes. Therefore non-idempotent requests are never speculatively executed. + + + +In most cases, you need to flag your statements manually: + +```java +SimpleStatement statement = + SimpleStatement.newInstance("SELECT first_name FROM user WHERE id=1") + .setIdempotent(true); + +// Or with a builder: +SimpleStatement statement = + SimpleStatement.builder("SELECT first_name FROM user WHERE id=1") + .withIdempotence(true) + .build(); +``` + +If you don't, they default to the value defined in the [configuration](../configuration/) by the +`request.default-idempotence` option; out of the box, it is set to `false`. + +When you prepare a statement, its idempotence carries over to bound statements: + +```java +PreparedStatement pst = session.prepare( + SimpleStatement.newInstance("SELECT first_name FROM user WHERE id=?") + .setIdempotent(true)); +BoundStatement bs = pst.bind(1); +assert bs.isIdempotent(); +``` + +The query builder tries to infer idempotence automatically; refer to +[its manual](../../query_builder/idempotence/) for more details. diff --git a/manual/query_builder/.nav b/manual/query_builder/.nav new file mode 100644 index 00000000000..a9d30b09a85 --- /dev/null +++ b/manual/query_builder/.nav @@ -0,0 +1,7 @@ +select +insert +update +batch +delete +relation +term \ No newline at end of file diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md new file mode 100644 index 00000000000..d817c944951 --- /dev/null +++ b/manual/query_builder/README.md @@ -0,0 +1,188 @@ +## Query builder + +The query builder is a utility to **generate CQL queries programmatically**. For example, it could +be used to: + +* given a set of optional search parameters, build a search query dynamically depending on which + parameters are provided; +* given a Java class, generate the CRUD queries that map instances of that class to a Cassandra + table. + +To use it in your application, add the following dependency: + +```xml + + com.datastax.oss + java-driver-query-builder + 4.0.0-alpha3 + +``` + +Here is our canonical example rewritten with the query builder: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +try (CqlSession session = CqlSession.builder().build()) { + + Select query = selectFrom("system", "local").column("release_version"); // SELECT release_version FROM system.local + SimpleStatement statement = query.build(); + + ResultSet rs = session.execute(statement); + Row row = rs.one(); + System.out.println(row.getString("release_version")); +} +``` + +### General concepts + +#### Fluent API + +All the starting methods are centralized in the [QueryBuilderDsl] class. To get started, add the +following import: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +``` + +Choose the method matching your desired statement, for example `selectFrom`. Then use your IDE's +completion and the javadocs to add query parts: + +```java +Select select = + selectFrom("ks", "user") + .column("first_name") + .column("last_name") + .whereColumn("id").isEqualTo(bindMarker()); +// SELECT first_name,last_name FROM ks.user WHERE id=? +``` + +When your query is complete, you can either extract a raw query string, or turn it into a +[simple statement](../core/statements/simple) (or its builder): + +```java +String cql = select.asCql(); +SimpleStatement statement = select.build(); +SimpleStatementBuilder builder = select.builder(); +``` + +#### Immutability + +All types in the fluent API are immutable. This means that every step creates a new object: + +```java +SelectFrom selectFrom = selectFrom("ks", "user"); + +Select select1 = selectFrom.column("first_name"); // SELECT first_name FROM ks.user +Select select2 = selectFrom.column("last_name"); // SELECT last_name FROM ks.user + +assert select1 != select2; +``` + +Immutability has great benefits: + +* **thread safety**: you can share built queries across threads, without any race condition or + badly published state. +* **zero sharing**: when you build multiple queries from a shared "base" (as in the example above), + all the queries are totally independent, changes to one query will never "pollute" another. + +On the downside, immutability means that the query builder creates lots of short-lived objects. +Modern garbage collectors are good at handling that, but still we recommend that you **avoid using +the query builder in your hot path**: + +* favor [bound statements](../core/statements/prepared) for queries that are used often. You can + still use the query builder and prepare the result: + + ```java + // During application initialization: + Select selectUser = selectFrom("user").all().whereColumn("id").isEqualTo(bindMarker()); + // SELECT * FROM user WHERE id=? + PreparedStatement preparedSelectUser = session.prepare(selectUser.build()); + + // At runtime: + session.execute(preparedSelectUser.bind(userId)); + ``` +* for queries that never change, build them when your application initializes, and store them in a + field or constant for later. +* for queries that are built dynamically, consider using a cache. + +#### Identifiers + +All fluent API methods use [CqlIdentifier] for schema element names (keyspaces, tables, columns...). +But, for convenience, there are also `String` overloads if you use case-insensitive identifiers (as +we recommend, see [Case sensitivity](../case_sensitivity) for more explanations). + +For conciseness, we'll use the string-based versions for the examples in this manual. + +### Non-goals + +The query builder is **NOT**: + +#### A crutch to learn CQL + +While the fluent API guides you, it does not encode every rule of the CQL grammar. Also, it supports +a wide range of Cassandra versions, some of which may be more recent than your production target, or +not even released yet. It's still possible to generate invalid CQL syntax if you don't know what +you're doing. + +You should always start with a clear idea of the CQL query, and write the builder code that produces +it, not the other way around. + +#### A better way to write static queries + +The primary use case of the query builder is dynamic generation. You will get the most value out of +it when you do things like: + +```java +// The columns to select are only known at runtime: +for (String columnName : columnNames) { + select = select.column(columnName) +} + +// If a search parameter is present, add the corresponding WHERE clause: +if (name != null) { + select = select.whereColumn("name").isEqualTo(name); +} +``` + +If all of your queries could also be written as compile-time string constants, ask yourself what the +query builder is really buying you: + +```java +// Built version: +private static final Statement SELECT_USERS = + selectFrom("user").all().limit(10).build(); + +// String version: +private static final Statement SELECT_USERS = + SimpleStatement.newInstance("SELECT * FROM user LIMIT 10"); +``` + +The built version: + +* is slightly more expensive to build (admittedly, that is not really an issue for constants); +* is not more readable; +* is not necessarily less error-prone (see the previous section). + +It eventually boils down to personal taste, but for simple cases you should consider raw strings as +a better alternative. + +### Building queries + +For a complete tour of the API, browse the child pages in this manual: + +* statement types: + * [SELECT](select/) + * [INSERT](insert/) + * [UPDATE](update/) + * [DELETE](delete/) + * [Schema builder](schema/) (for DDL statements such as CREATE TABLE, etc.) +* common topics: + * [Relations](relation/) + * [Conditions](condition/) + * [Terms](term/) + * [Idempotence](idempotence/) + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/SchemaBuilderDsl.html +[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html diff --git a/manual/query_builder/condition/README.md b/manual/query_builder/condition/README.md new file mode 100644 index 00000000000..a14b1b4bd66 --- /dev/null +++ b/manual/query_builder/condition/README.md @@ -0,0 +1,135 @@ +## Conditions + +A condition is a clause that appears after the IF keyword in a conditional [UPDATE](../update/) or +[DELETE](../delete/) statement. + +The easiest way to add a condition is with an `ifXxx` method in the fluent API: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .ifColumn("v1").isEqualTo(literal(1)) + .ifColumn("v2").isEqualTo(literal(2)); +// DELETE FROM user WHERE k=? IF v1=1 AND v2=2 +``` + +You can also create it manually with one of the factory methods in [Condition], and then pass it to +`if_()`: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +Condition vCondition = Condition.column("v").isEqualTo(literal(1)); +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .if_(vCondition); +// DELETE FROM user WHERE k=? IF v=1 +``` + +If you call `if_()` multiple times, the clauses will be joined with the AND keyword. You can also +add multiple conditions in a single call. This is a bit more efficient since it creates less +temporary objects: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .if_( + Condition.column("v1").isEqualTo(literal(1)), + Condition.column("v2").isEqualTo(literal(2))); +// DELETE FROM user WHERE k=? IF v1=1 AND v2=2 +``` + +Conditions are composed of a left operand, an operator, and a right-hand-side +[term](../term/). + +### Simple columns + +`ifColumn` operates on a single column. It supports basic arithmetic comparison operators: + +| Comparison operator | Method name | +|---------------------|--------------------------| +| `=` | `isEqualTo` | +| `<` | `isLessThan` | +| `<=` | `isLessThanOrEqualTo` | +| `>` | `isGreaterThan` | +| `>=` | `isGreaterThanOrEqualTo` | +| `!=` | `isNotEqualTo` | + +*Note: we support `!=` because it is present in the CQL grammar but, as of Cassandra 4, it is not +implemented yet.* + +In addition, `in()` can test for equality with various alternatives. You can either provide each +alternative as a term: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .ifColumn("v").in(bindMarker(), bindMarker(), bindMarker()); +// DELETE FROM user WHERE k=? IF v IN (?,?,?) +``` + +Or bind the whole list of alternatives as a single variable: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .ifColumn("v").in(bindMarker()); +// DELETE FROM user WHERE k=? IF v IN ? +``` + +### UDT fields + +`ifField` tests a field in a top-level UDT (nested UDTs are not allowed): + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .ifField("address", "zip").isEqualTo(literal(94040)); +// DELETE FROM user WHERE k=? IF address.zip=94040 +``` + +It supports the same set of operators as simple columns. + +### Collection elements + +`ifElement` tests an element in a top-level collection (nested collections are not allowed): + +```java +deleteFrom("product") + .whereColumn("sku").isEqualTo(bindMarker()) + .ifElement("features", literal("color")).in(literal("red"), literal("blue")); +// DELETE FROM product WHERE sku=? IF features['color'] IN ('red','blue') +``` + +It supports the same set of operators as simple columns. + +### Raw snippets + +You can also provide a condition as a raw CQL snippet, that will get appended to the query as-is, +without any syntax checking or escaping: + +```java +deleteFrom("product") + .whereColumn("sku").isEqualTo(bindMarker()) + .ifRaw("features['color'] IN ('red', 'blue') /*some random comment*/"); +// DELETE FROM product WHERE sku=? IF features['color'] IN ('red', 'blue') /*some random comment*/ +``` + +This should be used with caution, as it's possible to generate invalid CQL that will fail at +execution time; on the other hand, it can be used as a workaround to handle new CQL features that +are not yet covered by the query builder. + +### IF EXISTS + +Finally, you can specify an IF EXISTS clause: + +```java +deleteFrom("product").whereColumn("sku").isEqualTo(bindMarker()).ifExists(); +// DELETE FROM product WHERE sku=? IF EXISTS +``` + +It is mutually exclusive with column conditions: if you previously specified column conditions on +the statement, they will be ignored; conversely, adding a column condition cancels a previous IF +EXISTS clause. + +[Condition]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/condition/Condition.html diff --git a/manual/query_builder/delete/README.md b/manual/query_builder/delete/README.md new file mode 100644 index 00000000000..19359af40bd --- /dev/null +++ b/manual/query_builder/delete/README.md @@ -0,0 +1,145 @@ +## DELETE + +To start a DELETE query, use one of the `deleteFrom` methods in [QueryBuilderDsl]. There are several +variants depending on whether your table name is qualified, and whether you use case-sensitive +identifiers or case-insensitive strings: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +DeleteSelection delete = deleteFrom("user"); +``` + +Note that, at this stage, the query can't be built yet. You need at least one +[relation](#relations). + +### Selectors + +A selector is something that appears after the `DELETE` keyword, and will be removed from the +affected row(s). + +Selectors are optional; if you don't provide any, the whole row will be deleted. + +The easiest way to add a selector is with a fluent API method: + +```java +deleteFrom("user").column("v1").column("v2"); +// DELETE v1,v2 FROM user... +``` + +You can also create it manually with one of the factory methods in [Selector], and then pass it to +`selector()`: + +```java +deleteFrom("user").selector(Selector.getColumn("v")) +// DELETE v FROM user ... +``` + +If you have multiple selectors, you can also use `selectors()` to add them all in a single call. +This is a bit more efficient since it creates less temporary objects: + +```java +deleteFrom("user").selectors(getColumn("v1"), getColumn("v2")); +// DELETE v1,v2 FROM user... +``` + +Only 3 types of selectors can be used in DELETE statements: + +* simple columns (as illustrated in the previous examples); +* fields in non-nested UDT columns: + + ```java + deleteFrom("user").field("address", "street"); + // DELETE address.street FROM user ... + ``` + +* elements in non-nested collection columns: + + ```java + deleteFrom("product").element("features", literal("color")); + // DELETE features['color'] FROM product ... + ``` + +You can also pass a raw CQL snippet, that will get appended to the query as-is, without any syntax +checking or escaping: + +```java +deleteFrom("user").raw("v /*some random comment*/") +// DELETE v /*some random comment*/ FROM user ... +``` + +This should be used with caution, as it's possible to generate invalid CQL that will fail at +execution time; on the other hand, it can be used as a workaround to handle new CQL features that +are not yet covered by the query builder. + +### Timestamp + +The USING TIMESTAMP clause specifies the timestamp at which the mutation will be applied. You can +pass either a literal value: + +```java +deleteFrom("user").column("v").usingTimestamp(1234) +// DELETE v FROM user USING TIMESTAMP 1234 +``` + +Or a bind marker: + +```java +deleteFrom("user").column("v").usingTimestamp(bindMarker()) +// DELETE v FROM user USING TIMESTAMP ? +``` + +If you call the method multiple times, the last value will be used. + +### Relations + +Relations get added with the fluent `whereXxx()` methods: + +```java +deleteFrom("user").whereColumn("k").isEqualTo(bindMarker()); +// DELETE FROM user WHERE k=? +``` + +Or you can build and add them manually: + +```java +deleteFrom("user").where( + Relation.column("k").isEqualTo(bindMarker())); +// DELETE FROM user WHERE k=? +``` + +Once there is at least one relation, the statement can be built: + +```java +SimpleStatement statement = deleteFrom("user").whereColumn("k").isEqualTo(bindMarker()).build(); +``` + +Relations are a common feature used by many types of statements, so they have a +[dedicated page](../relation) in this manual. + +### Conditions + +Conditions get added with the fluent `ifXxx()` methods: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .ifColumn("v").isEqualTo(literal(1)); +// DELETE FROM user WHERE k=? IF v=1 +``` + +Or you can build and add them manually: + +```java +deleteFrom("user") + .whereColumn("k").isEqualTo(bindMarker()) + .if_( + Condition.Column("v").isEqualTo(literal(1))); +// DELETE FROM user WHERE k=? IF v=1 +``` + +Conditions are a common feature used by UPDATE and DELETE, so they have a +[dedicated page](../condition) in this manual. + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html diff --git a/manual/query_builder/idempotence/README.md b/manual/query_builder/idempotence/README.md new file mode 100644 index 00000000000..b064c81cdd6 --- /dev/null +++ b/manual/query_builder/idempotence/README.md @@ -0,0 +1,174 @@ +## Idempotence in the query builder + +When you generate a statement (or a statement builder) from the query builder, it automatically +infers the [isIdempotent](../../core/idempotence/) flag: + +```java +SimpleStatement statement = + selectFrom("user").all() + .whereColumn("id").isEqualTo(literal(1)) + .build(); +// SELECT * FROM user WHERE id=1 +assert statement.isIdempotent(); +``` + +This can't always be determined accurately; when in doubt, the builder is pessimistic and marks the +statement as not idempotent. If you know otherwise, you can fix it manually: + +```java +Delete delete = + deleteFrom("product") + .element("features", literal("color")) + .whereColumn("sku").isEqualTo(bindMarker()); +assert !delete.build().isIdempotent(); // see below for why +SimpleStatement statement = delete.builder() + .withIdempotence(true) + .build(); +``` + +The remaining sections describe the rules that are applied to compute the flag. + +### SELECT statements + +SELECT statements don't modify the contents of the database. They're always considered idempotent, +regardless of the other rules below. + +### Unsafe terms + +If you use the result of a user-defined function in an INSERT or UPDATE statement, there is no way +of knowing if that function is idempotent: + +```java +Statement statement = insertInto("foo").value("k", function("generate_id")).build(); +// INSERT INTO foo (k) VALUES (generate_id()) +assert !statement.isIdempotent(); +``` + +This extends to arithmetic operations using such terms: + +```java +Statement statement = + insertInto("foo").value("k", add(function("generate_id"), literal(1))).build(); +// INSERT INTO foo (k) VALUES (generate_id()+1) +assert !statement.isIdempotent(); +``` + +Raw terms could be anything, so they are also considered unsafe by default: + +```java +Statement statement = + insertInto("foo").value("k", raw("generate_id()+1")).build(); +// INSERT INTO foo (k) VALUES (generate_id()+1) +assert !statement.isIdempotent(); +``` + +### Unsafe WHERE clauses + +If a WHERE clause in an UPDATE or DELETE statement uses a comparison with an unsafe term, it could +potentially apply to different rows for each execution: + +```java +Statement statement = + update("foo") + .setColumn("v", bindMarker()) + .whereColumn("k").isEqualTo(function("non_idempotent_func")) + .build(); +// UPDATE foo SET v=? WHERE k=non_idempotent_func() +assert !statement.isIdempotent(); +``` + +### Unsafe updates + +Counter updates are never idempotent: + +```java +Statement statement = + update("foo") + .increment("c") + .whereColumn("k").isEqualTo(bindMarker()) + .build(); +// UPDATE foo SET c+=1 WHERE k=? +assert !statement.isIdempotent(); +``` + +Nor is appending or prepending an element to a list: + +```java +Statement statement = + update("foo") + .appendListElement("l", literal(1)) + .whereColumn("k").isEqualTo(bindMarker()) + .build(); +// UPDATE foo SET l+=[1] WHERE k=? +assert !statement.isIdempotent(); +``` + +The generic `append` and `prepend` methods apply to any kind of collection, so we have to consider +them unsafe by default too: + +```java +Statement statement = + update("foo") + .prepend("l", literal(Arrays.asList(1, 2, 3))) + .whereColumn("k").isEqualTo(bindMarker()) + .build(); +// UPDATE foo SET l=[1,2,3]+l WHERE k=? +assert !statement.isIdempotent(); +``` + +### Unsafe deletions + +Deleting from a list is not idempotent: + +```java +Statement statement = + deleteFrom("foo") + .element("l", literal(0)) + .whereColumn("k").isEqualTo(bindMarker()) + .build(); +// DELETE l[0] FROM foo WHERE k=? +assert !statement.isIdempotent(); +``` + +### Conditional statements + +All conditional statements are considered non-idempotent: + +* INSERT with IF NOT EXISTS; +* UPDATE and DELETE with IF EXISTS or IF conditions on columns. + +This might seem counter-intuitive, as these queries can sometimes be safe to execute multiple times. +For example, consider the following query: + +```java +update("foo") + .setColumn("v", literal(4)) + .whereColumn("k").isEqualTo(literal(1)) + .ifColumn("v").isEqualTo(literal(1)); +// UPDATE foo SET v=4 WHERE k=1 IF v=1 +``` + +If we execute it twice, the IF condition will fail the second time, so the second execution will do +nothing and `v` will still have the value 4. + +However, the problem appears when we consider multiple clients executing the query with retries: + +1. `v` has the value 1; +2. client 1 executes the query above, performing a a CAS (compare and set) from 1 to 4; +3. client 1's connection drops, but the query completes successfully. `v` now has the value 4; +4. client 2 executes a CAS from 4 to 2; +5. client 2's transaction succeeds. `v` now has the value 2; +6. since client 1 lost its connection, it considers the query as failed, and transparently retries + the CAS from 1 to 4. But since the column now has value 2, it receives a "not applied" response. + +One important aspect of lightweight transactions is [linearizability]: given a set of concurrent +operations on a column from different clients, there must be a way to reorder them to yield a +sequential history that is correct. From our clients' point of view, there were two operations: + +* client 1 executed a CAS from 1 to 4, that was not applied; +* client 2 executed a CAS from 4 to 2, that was applied. + +But overall the column changed from 1 to 2. There is no ordering of the two operations that can +explain that change. We broke linearizability by doing a transparent retry at step 6. + +[linearizability]: https://en.wikipedia.org/wiki/Linearizability#Definition_of_linearizability \ No newline at end of file diff --git a/manual/query_builder/insert/README.md b/manual/query_builder/insert/README.md new file mode 100644 index 00000000000..5606580bbd1 --- /dev/null +++ b/manual/query_builder/insert/README.md @@ -0,0 +1,90 @@ +## INSERT + +To start an INSERT query, use one of the `insertInto` methods in [QueryBuilderDsl]. There are +several variants depending on whether your table name is qualified, and whether you use +case-sensitive identifiers or case-insensitive strings: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +InsertInto insert = insertInto("user"); +``` + +Note that, at this stage, the query can't be built yet. You need to set at least one value. + +### Setting values + +#### Regular insert + +A regular insert (as opposed to a JSON insert, covered in the next section) specifies values for a +set of columns. In the Query Builder DSL, this is expressed with the `value` method: + +```java +insertInto("user") + .value("id", bindMarker()) + .value("first_name", literal("John")) + .value("last_name", literal("Doe")); +// INSERT INTO user (id,first_name,last_name) VALUES (?,'John','Doe') +``` + +The column names can only be simple identifiers. The values are [terms](../term). + +#### JSON insert + +To start a JSON insert, use the `json` method instead. It takes the payload as a raw string, that +will get inlined as a CQL literal: + +```java +insertInto("user").json("{\"id\":1, \"first_name\":\"John\", \"last_name\":\"Doe\"}"); +// INSERT INTO user JSON '{"id":1, "first_name":"John", "last_name":"Doe"}' +``` + +In a real application, you'll probably obtain the string from a JSON library such as Jackson. + +You can also bind it as a value: + +```java +insertInto("user").json(bindMarker()); +// INSERT INTO user JSON ? +``` + +JSON inserts have extra options to indicate how missing fields should be handled: + +```java +insertInto("user").json("{\"id\":1}").defaultUnset(); +// INSERT INTO user JSON '{"id":1}' DEFAULT UNSET + +insertInto("user").json("{\"id\":1}").defaultNull(); +// INSERT INTO user JSON '{"id":1}' DEFAULT NULL +``` + +### Conditions + +For INSERT queries, there is only one possible condition: IF NOT EXISTS. It applies to both regular +and JSON inserts: + +```java +insertInto("user").json(bindMarker()).ifNotExists(); +// INSERT INTO user JSON ? IF NOT EXISTS +``` + +### Timestamp + +The USING TIMESTAMP clause specifies the timestamp at which the mutation will be applied. You can +pass either a literal value: + +```java +insertInto("user").json(bindMarker()).usingTimestamp(1234) +// INSERT INTO user JSON ? USING TIMESTAMP 1234 +``` + +Or a bind marker: + +```java +insertInto("user").json(bindMarker()).usingTimestamp(bindMarker()) +// INSERT INTO user JSON ? USING TIMESTAMP ? +``` + +If you call the method multiple times, the last value will be used. + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html diff --git a/manual/query_builder/relation/README.md b/manual/query_builder/relation/README.md new file mode 100644 index 00000000000..80ed83fa274 --- /dev/null +++ b/manual/query_builder/relation/README.md @@ -0,0 +1,205 @@ +## Relations + +A relation is a clause that appears after the WHERE keyword, and restricts the rows that the +statement operates on. + +Relations are used by the following statements: + +* [SELECT](../select/) +* [UPDATE](../update/) +* [DELETE](../delete/) +* [CREATE MATERIALIZED VIEW](../schema/materialized_view/) + +The easiest way to add a relation is with a `whereXxx` method in the fluent API: + +```java +selectFrom("sensor_data").all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumn("date").isGreaterThan(bindMarker()); +// SELECT * FROM sensor_data WHERE id=? AND date>? +``` + +You can also create it manually with one of the factory methods in [Relation], and then pass it to +`where()`: + +```java +selectFrom("user").all().where( + Relation.column("id").isEqualTo(bindMarker())); +// SELECT * FROM user WHERE id=? +``` + +If you call `where()` multiple times, the clauses will be joined with the AND keyword. You can also +add multiple relations in a single call. This is a bit more efficient since it creates less +temporary objects: + +```java +selectFrom("sensor_data").all() + .where( + Relation.column("id").isEqualTo(bindMarker()), + Relation.column("date").isGreaterThan(bindMarker())); +// SELECT * FROM sensor_data WHERE id=? AND date>? +``` + +Relations are generally composed of a left operand, an operator, and an optional right-hand-side +[term](../term/). The type of relation determines which operators are available. + +### Simple columns + +`whereColumn` operates on a single column. It supports basic arithmetic comparison operators: + +| Comparison operator | Method name | +|---------------------|--------------------------| +| `=` | `isEqualTo` | +| `<` | `isLessThan` | +| `<=` | `isLessThanOrEqualTo` | +| `>` | `isGreaterThan` | +| `>=` | `isGreaterThanOrEqualTo` | +| `!=` | `isNotEqualTo` | + +*Note: we support `!=` because it is present in the CQL grammar but, as of Cassandra 4, it is not +implemented yet.* + +See above for comparison operator examples. + +If you're using SASI indices, you can also use `like()` for wildcard comparisons: + +```java +selectFrom("user").all().whereColumn("last_name").like(literal("M%")); +// SELECT * FROM user WHERE last_name LIKE 'M%' +``` + +`in()` is like `isEqualTo()`, but with various alternatives. You can either provide each alternative as a +term: + +```java +selectFrom("user").all().whereColumn("id").in(literal(1), literal(2), literal(3)); +// SELECT * FROM user WHERE id IN (1,2,3) + +selectFrom("user").all().whereColumn("id").in(bindMarker(), bindMarker(), bindMarker()); +// SELECT * FROM user WHERE id IN (?,?,?) +``` + +Or bind the whole list of alternatives as a single variable: + +```java +selectFrom("user").all().whereColumn("id").in(bindMarker()); +// SELECT * FROM user WHERE id IN ? +``` + +For collection columns, you can check for the presence of an element with `contains()` and +`containsKey()`: + +```java +selectFrom("sensor_data") + .all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumn("date").isEqualTo(bindMarker()) + .whereColumn("readings").containsKey(literal("temperature")) + .allowFiltering(); +// SELECT * FROM sensor_data WHERE id=? AND date=? AND readings CONTAINS KEY 'temperature' ALLOW FILTERING +``` + +Finally, `isNotNull()` generates an `IS NOT NULL` check. *Note: we support `IS NOT NULL` because it +is present in the CQL grammar but, as of Cassandra 4, it is not implemented yet.* + +### Column components + +`whereMapValue` operates on an value inside of a map: + +```java +selectFrom("sensor_data") + .all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumn("date").isEqualTo(bindMarker()) + .whereMapValue("readings", literal("temperature")).isGreaterThan(literal(65)) + .allowFiltering(); +// SELECT * FROM sensor_data WHERE id=? AND date=? AND readings['temperature']>65 ALLOW FILTERING +``` + +Column components support the six basic arithmetic comparison operators. + +### Tokens + +`whereToken` hashes one or more columns into a token. It is generally used to perform range queries: + +```java +selectFrom("user") + .all() + .whereToken("id").isGreaterThan(bindMarker()) + .whereToken("id").isLessThanOrEqualTo(bindMarker()); +// SELECT * FROM user WHERE token(id)>? AND token(id)<=? +``` + +It supports the six basic arithmetic comparison operators. + +### Multi-column relations + +`whereColumns` compares a set of columns to tuple terms of the same arity. It supports the six basic +arithmetic comparison operators (using lexicographical order): + +```java +selectFrom("sensor_data") + .all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumns("date", "hour").isGreaterThan(tuple(bindMarker(), bindMarker())); +// SELECT * FROM sensor_data WHERE id=? AND (date,hour)>(?,?) +``` + +In addition, tuples support the `in()` operator. Like with regular columns, bind markers can operate +at different levels: + +```java +// Bind the whole list of alternatives (two-element tuples) as a single value: +selectFrom("test") + .all() + .whereColumn("k").isEqualTo(literal(1)) + .whereColumns("c1", "c2").in(bindMarker()); +// SELECT * FROM test WHERE k=1 AND (c1,c2) IN ? + +// Bind each alternative as a value: +selectFrom("test") + .all() + .whereColumn("k").isEqualTo(literal(1)) + .whereColumns("c1", "c2").in(bindMarker(), bindMarker(), bindMarker()); +// SELECT * FROM test WHERE k=1 AND (c1,c2) IN (?,?,?) + +// Bind each element in the alternatives as a value: +selectFrom("test") + .all() + .whereColumn("k").isEqualTo(literal(1)) + .whereColumns("c1", "c2").in( + tuple(bindMarker(), bindMarker()), + tuple(bindMarker(), bindMarker()), + tuple(bindMarker(), bindMarker())); +// SELECT * FROM test WHERE k=1 AND (c1,c2) IN ((?,?),(?,?),(?,?)) +``` + +### Custom index expressions + +`whereCustomIndex` evaluates a custom index. The argument is a free-form term (what is a legal value +depends on your index implementation): + +```java +selectFrom("foo") + .all() + .whereColumn("k").isEqualTo(literal(1)) + .whereCustomIndex("my_custom_index", literal("a text expression")); +// SELECT * FROM foo WHERE k=1 AND expr(my_custom_index,'a text expression') +``` + +### Raw snippets + +Finally, it is possible to provide a raw CQL snippet with `whereRaw()`; it will get appended to the +query as-is, without any syntax checking or escaping: + +```java +selectFrom("foo").all().whereRaw("k = 1 /*some custom comment*/ AND c<2"); +// SELECT * FROM foo WHERE k = 1 /*some custom comment*/ AND c<2 +``` + +This should be used with caution, as it's possible to generate invalid CQL that will fail at +execution time; on the other hand, it can be used as a workaround to handle new CQL features that +are not yet covered by the query builder. + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[Relation]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/relation/Relation.html diff --git a/manual/query_builder/schema/.nav b/manual/query_builder/schema/.nav new file mode 100644 index 00000000000..650e895a197 --- /dev/null +++ b/manual/query_builder/schema/.nav @@ -0,0 +1,7 @@ +keyspace +table +index +materialized_view +type +function +aggregate \ No newline at end of file diff --git a/manual/query_builder/schema/README.md b/manual/query_builder/schema/README.md new file mode 100644 index 00000000000..fba9e417f47 --- /dev/null +++ b/manual/query_builder/schema/README.md @@ -0,0 +1,47 @@ +# Schema builder + +The schema builder is an additional API provided by [java-driver-query-builder](../) that enables +one to *generate CQL DDL queries programmatically**. For example it could be used to: + +* based on application configuration, generate schema queries instead of building CQL strings by + hand. +* given a Java class that represents a table, view, or user defined type, generate representative + schema DDL `CREATE` queries. + +Here is an example that demonstrates creating a keyspace and a table using [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +try (CqlSession session = CqlSession.builder().build()) { + CreateKeyspace createKs = createKeyspace("cycling").withSimpleStrategy(1); + session.execute(createKs.build()); + + CreateTable createTable = + createTable("cycling", "cyclist_name") + .withPartitionKey("id", DataTypes.UUID) + .withColumn("lastname", DataTypes.TEXT) + .withColumn("firstname", DataTypes.TEXT); + + session.execute(createTable.build()); +} +``` + +The [general concepts](../#general-concepts) and [non goals](../#non-goals) defined for the query +builder also apply for the schema builder. + +### Building DDL Queries + +The schema builder offers functionality for creating, altering and dropping elements of a CQL +schema. For a complete tour of the API, browse the child pages in the manual for each schema +element type: + +* [keyspace](keyspace/) +* [table](table/) +* [index](index/) +* [materialized view](materialized_view/) +* [type](type/) +* [function](function/) +* [aggregate](aggregate/) + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html diff --git a/manual/query_builder/schema/aggregate/README.md b/manual/query_builder/schema/aggregate/README.md new file mode 100644 index 00000000000..09a8d835338 --- /dev/null +++ b/manual/query_builder/schema/aggregate/README.md @@ -0,0 +1,79 @@ +## Aggregate + +Aggregates enable users to apply User-defined functions (UDF) to rows in a data set and combine +their values into a final result, for example average or standard deviation. [SchemaBuilderDsl] +offers API methods for creating and dropping aggregates. + +### Creating an aggregate (CREATE AGGREGATE) + +To start a `CREATE AGGREGATE` query, use `createAggregate` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateAggregateStart create = createAggregate("average"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the aggregate should +only be created if it doesn't already exist, i.e.: + +```java +CreateAggregateStart create = createAggregate("cycling", "average").ifNotExists(); +``` + +You may also specify that you would like to replace an existing aggregate by the same signature if +it exists. In this case, use `orReplace`: + +```java +CreateAggregateStart create = createAggregate("cycling", "average").orReplace(); +``` + +One may also specify the parameters of an aggregate using `withParameter`: + +```java +CreateAggregateStart create = createAggregate("cycling", "average") + .withParameter(DataTypes.INT); +``` + +To complete an aggregate, one must then provide the following: + +* The state function (`withSFunc`) to execute on each row +* The type of the value returned by the state function (`withSType`) + +In addition, while optional, it is typical that the following is also provided: + +* The final function to be executed after the state function is evaluated against all rows + (`withFinalFunc`) +* The initial condition (`withInitCond`) which defines the initial value(s) to be passed in to the + first parameter of the state function. + +For example, the following defines a complete `CREATE AGGREGATE` statement: + +```java +createAggregate("cycling", "average") + .withParameter(DataTypes.INT) + .withSFunc("avgstate") + .withSType(DataTypes.tupleOf(DataTypes.INT, DataTypes.BIGINT)) + .withFinalFunc("avgfinal") + .withInitCond(tuple(literal(0), literal(0))); + +// CREATE AGGREGATE cycling.average (int) SFUNC avgstate STYPE tuple FINALFUNC avgfinal INITCOND (0,0) +``` + +### Dropping an aggregate (DROP AGGREGATE) + +To create a `DROP AGGREGATE` query, use `dropAggregate`: + +```java +dropAggregate("cycling", "average"); +// DROP AGGREGATE cycling.average +``` + +You may also specify `ifExists`: + +```java +dropAggregate("average").ifExists(); +// DROP AGGREGATE IF EXISTS average +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html diff --git a/manual/query_builder/schema/function/README.md b/manual/query_builder/schema/function/README.md new file mode 100644 index 00000000000..56717bfd96a --- /dev/null +++ b/manual/query_builder/schema/function/README.md @@ -0,0 +1,95 @@ +## Function + +User-defined functions (UDF) enable users to create user code written in JSR-232 compliant scripting +languages that can be evaluated in CQL queries. [SchemaBuilderDsl] offers API methods for creating +and dropping UDFs. + +### Creating a Function (CREATE FUNCTION) + +To start a `CREATE FUNCTION` query, use `createFunction` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateFunctionStart create = createFunction("log"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the UDF should only +be created if it doesn't already exist, i.e.: + +```java +CreateFunctionStart create = createFunction("cycling", "log").ifNotExists(); +``` + +You may also specify that you would like to replace an existing function by the same signature if it +exists. In this case, use `orReplace`: + +```java +CreateFunctionStart create = createFunction("cycling", "log").orReplace(); +``` + +One may also specify the parameters of a function using `withParameter`: + +``` +createFunction("cycling", "left") + .withParameter("colName", DataTypes.TEXT) + .withParameter("num", DataTypes.DOUBLE) +``` + +There are a number of steps that must be executed to complete a function: + +* Specify whether the function is called on null input (`calledOnNull`) or if it should simply + return null (`returnsNullOnNull`). +* Specify the return type of the function using `returnsType` +* Specify language of the function body using `withJavaLanguage`, `withJavaScriptLanguage`, or + `withLanguage` +* Specify the function body with `as` or `asQuoted` + +For example, the following defines a complete `CREATE FUNCTION` statement: + +```java +createFunction("cycling", "log") + .withParameter("input", DataTypes.DOUBLE) + .calledOnNull() + .returnsType(DataTypes.DOUBLE) + .withJavaLanguage() + .asQuoted("return Double.valueOf(Math.log(input.doubleValue()));"); + +// CREATE FUNCTION cycling.log (columnname text,num int) CALLED ON NULL INPUT RETURNS double LANGUAGE java +// AS 'return Double.valueOf(Math.log(input.doubleValue()));' +``` + +Note that when providing a function body, the `as` method does not implicitly quote your function +body. If you would like to have the API handle this for you, use `asQuoted`. This will surround +your function body in single quotes if the body itself does not contain a single quote, otherwise it +will surround your function body in two dollar signs (`$$`) mimicking a postgres-style string +literal, i.e.: + +```java +createFunction("sayhi") + .withParameter("input", DataTypes.TEXT) + .returnsNullOnNull() + .returnsType(DataTypes.TEXT) + .withJavaScriptLanguage() + .asQuoted("'hi ' + input;"); +// CREATE FUNCTION sayhi (input text) RETURNS NULL ON NULL INPUT RETURNS text LANGUAGE javascript AS $$ 'hi ' + input; $$ +``` + + +### Dropping a Function (DROP FUNCTION) + +To create a `DROP FUNCTION` query, use `dropFunction`: + +```java +dropFunction("cycling", "log"); +// DROP FUNCTION cycling.log +``` + +You may also specify `ifExists`: + +```java +dropFunction("log").ifExists(); +// DROP FUNCTION IF EXISTS log +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html diff --git a/manual/query_builder/schema/index/README.md b/manual/query_builder/schema/index/README.md new file mode 100644 index 00000000000..8624f2c8a30 --- /dev/null +++ b/manual/query_builder/schema/index/README.md @@ -0,0 +1,102 @@ +# Index + +An index provides a means of expanding the query capabilities of a table. [SchemaBuilderDsl] offers +API methods for creating and dropping indices. Unlike other schema members, there is no mechanism +to alter an index. + +### Creating an Index (CREATE INDEX) + +To start a `CREATE INDEX` query, use `createIndex` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +// an index name is not required +CreateIndexStart create = createIndex(); + +create = createIndex("my_idx"); +``` + +Unlike other keyspace elements, there is not option to provide the keyspace name, instead it is +implied from the indexed table's keyspace. + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the index should +only be created if it doesn't already exist, i.e.: + +```java +CreateIndexStart create = createIndex("my_idx").ifNotExists(); +``` + +Note one small difference with `IF NOT EXISTS` with indices is that the criteria also applies to +whether or not the table and column specification has an index already, not just the name of the +index. + +At this stage, the query cannot be completed yet. You need to provide at least: + +* The table the index applies to using `onTable` +* The column the index applies to using an `andColumn*` implementation. + +For example: + +```java +createIndex().onTable("tbl").andColumnKeys("addresses"); +// CREATE INDEX ON tbl (KEYS(addresses)) +``` + +#### Custom Indices + +Cassandra supports indices with a custom implementation, specified by an input class name. The +class implementation may be specified using `custom(className)`, for example: + +```java +createIndex() + .custom("org.apache.cassandra.index.MyCustomIndex") + .onTable("tbl") + .andColumn("x"); +// CREATE CUSTOM INDEX ON tbl (x) USING 'org.apache.cassandra.index.MyCustomIndex' +``` + +One popular custom index implementation is SASI (SSTable Attached Secondary Index). To use SASI, +use `usingSASI` and optionally `withSASIOptions`: + +```java +createIndex() + .usingSASI() + .onTable("tbl") + .andColumn("x") + .withSASIOptions(ImmutableMap.of("mode", "CONTAINS", "tokenization_locale", "en")); +// CREATE CUSTOM INDEX ON tbl (x) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS={'mode':'CONTAINS','tokenization_locale':'en'} +``` + +#### Column Index Types + +When indexing columns, one may simply use `andColumn`. However, when indexing collection columns +there are several additional options available: + +* `andColumnKeys`: Creates an index on a map column's keys. +* `andColumnValues`: Creates an index on a map column's values. +* `andColumnEntries`: Creates an index on a map column's entries. +* `andColumnFull`: Creates an index of a frozen collection's full value. + +#### Index options + +After specifying the columns for the index, you may use `withOption` to provide free-form options on +the index. These are really only applicable to custom index implementations. + +### Dropping an Index (DROP INDEX) + +To create a `DROP INDEX` query, use `dropIndex`: + +```java +dropIndex("ks", "my_idx"); +// DROP INDEX ks.my_idx +``` + +You may also specify `ifExists`: + +```java +dropIndex("my_idx").ifExists(); +// DROP INDEX IF EXISTS my_idx +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html diff --git a/manual/query_builder/schema/keyspace/README.md b/manual/query_builder/schema/keyspace/README.md new file mode 100644 index 00000000000..5a8a434f6bf --- /dev/null +++ b/manual/query_builder/schema/keyspace/README.md @@ -0,0 +1,88 @@ +## Keyspace + +A keyspace is a top-level namespace that defines a name, replication strategy and configurable +options. [SchemaBuilderDsl] offers API methods for creating, altering and dropping keyspaces. + +### Creating a Keyspace (CREATE KEYSPACE) + +To start a `CREATE KEYSPACE` query, use `createKeyspace` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateKeyspaceStart create = createKeyspace("cycling"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the keyspace should +only be created if it doesn't already exist, i.e.: + +```java +CreateKeyspaceStart create = createKeyspace("cycling").ifNotExists(); +``` + +Note that, at this stage, the query cannot be completed yet. You need to provide at least a +replication strategy. The two most widely used ones are SimpleStrategy and NetworkTopologyStrategy. + +To provide a replication strategy, use one of the following API methods on `CreateKeyspaceStart`: + +* `withSimpleStrategy(int replicationFactor)` +* `withNetworkTopologyStrategy(Map replications)` +* `withReplicationOptions(Map replicationOptions)` + +For example, the following builds a completed `CreateKeyspace` using `NetworkTopologyStrategy` with +a replication factor of 2 in `east` and 3 in `west`: + +```java +CreateKeyspace create = createKeyspace("cycling") + .withNetworkTopologyStrategy(ImmutableMap.of("east", 2, "west", 3)); +// CREATE KEYSPACE cycling WITH replication={'class':'NetworkTopologyStrategy','east':2,'west':3} +``` + +Optionally, once a replication factor is provided, one may provide additional configuration when +creating a keyspace: + +* `withDurableWrites(boolean durableWrites)` +* `withOption(String name, Object value)` + +### Altering a Keyspace (ALTER KEYSPACE) + +To start an `ALTER KEYSPACE` query, use `alterKeyspace`: + +```java +AlterKeyspaceStart alterKeyspace = alterKeyspace("cycling"); +``` + +From here, you can modify the keyspace's replication and other settings: + +* `withSimpleStrategy(int replicationFactor)` +* `withNetworkTopologyStrategy(Map replications)` +* `withReplicationOptions(Map replicationOptions)` +* `withDurableWrites(boolean durableWrites)` +* `withOption(String name, Object value)` + +At least one of these operations must be used to return a completed `AlterKeyspace`, i.e.: + +```java +alterKeyspace("cycling").withDurableWrites(true); +// ALTER KEYSPACE cycling WITH durable_writes=true +``` + +### Dropping a keyspace (DROP KEYSPACE) + +To create a `DROP KEYSPACE` query, use `dropKeyspace`: + +```java +dropKeyspace("cycling"); +// DROP KEYSPACE cycling +``` + +You may also specify `ifExists`: + +```java +dropKeyspace("cycling").ifExists(); +// DROP KEYSPACE IF EXISTS cycling +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html + + diff --git a/manual/query_builder/schema/materialized_view/README.md b/manual/query_builder/schema/materialized_view/README.md new file mode 100644 index 00000000000..59e289462c1 --- /dev/null +++ b/manual/query_builder/schema/materialized_view/README.md @@ -0,0 +1,89 @@ +## Materialized View + +Materialized Views are an experimental feature introduced in Apache Cassandra 3.0 that provide a +mechanism for server-side denormalization from a base table into a view that is updated when the +base table is updated. [SchemaBuilderDsl] offers API methods for creating, altering and dropping +materialized views. + +### Creating a Materialized View (CREATE MATERIALIZED VIEW) + +To start a `CREATE MATERIALIZED VIEW` query, use `createMaterializedView` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateMaterializedViewStart create = createMaterializedView("cycling", "cyclist_by_age"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the view should only +be created if it doesn't already exist, i.e.: + +```java +CreateMaterializedViewStart create = createMaterializedView("cycling", "cyclist_by_age").ifNotExists(); +``` + +There are a number of steps that must be executed to complete a materialized view: + +* Specify the base table using `asSelectFrom` +* Specify the columns to include in the view via `column` or `columns` +* Specify the where clause using [relations](../../relation) +* Specify the partition key columns using `withPartitionKey` and `withClusteringColumn` + +For example, the following defines a complete `CREATE MATERIALIZED VIEW` statement: + +```java +createMaterializedView("cycling", "cyclist_by_age") + .asSelectFrom("cycling", "cyclist") + .columns("age", "name", "country") + .whereColumn("age") + .isNotNull() + .whereColumn("cid") + .isNotNull() + .withPartitionKey("age") + .withClusteringColumn("cid"); +// CREATE MATERIALIZED VIEW cycling.cyclist_by_age AS +// SELECT age,name,country FROM cycling.cyclist WHERE age IS NOT NULL AND cid IS NOT NULL PRIMARY KEY(age,cid) +``` + +Please note that not all WHERE clause relations may be compatible with materialized views. + +Like a [table](../table), one may additionally provide configuration such as clustering order, +compaction options and so on. Refer to [RelationStructure] for documentation on additional +configuration that may be provided for a view. + +### Altering a Materialized View (ALTER MATERIALIZED VIEW) + +To start a `ALTER MATERIALIZED VIEW` query, use `alterMaterializedView`: + +```java +alterMaterializedView("cycling", "cyclist_by_age"); +``` + +Unlike a table, you may not alter, drop or rename columns on a materialized view. Instead, one may +only alter the options defined in [RelationStructure]. For example: + +```java +alterMaterializedView("cycling", "cyclist_by_age") + .withGcGraceSeconds(0) + .withCaching(true, RowsPerPartition.NONE); +// ALTER MATERIALIZED VIEW cycling.cyclist_by_age WITH gc_grace_seconds=0 AND caching={'keys':'ALL','rows_per_partition':'NONE'} +``` + +### Dropping a View (DROP MATERIALIZED VIEW) + +To create a `DROP MATERIALIZED VIEW` query, use `dropMaterializedView`: + +```java +dropMaterializedView("cycling", "cyclist_by_age"); +// DROP MATERIALIZED VIEW cycling.cyclist_by_age +``` + +You may also specify `ifExists`: + +```java +dropTable("cyclist_by_age").ifExists(); +// DROP MATERIALIZED VIEW IF EXISTS cyclist_by_age +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[RelationStructure]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.html diff --git a/manual/query_builder/schema/table/README.md b/manual/query_builder/schema/table/README.md new file mode 100644 index 00000000000..cf30d41bec8 --- /dev/null +++ b/manual/query_builder/schema/table/README.md @@ -0,0 +1,112 @@ +## Table + +Data in Apache Cassandra is stored in tables. [SchemaBuilderDsl] offers API methods for creating, +altering, and dropping tables. + +### Creating a Table (CREATE TABLE) + +To start a `CREATE TABLE` query, use `createTable` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateTableStart create = createTable("cycling", "cyclist_name"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the table should +only be created if it doesn't already exist, i.e.: + +```java +CreateTableStart create = createTable("cycling", "cyclist_name").ifNotExists(); +``` + +Note that, at this stage, the query cannot be completed yet. You need to provide at least one +partition key column using `withPartitionKey()`, i.e.: + +```java +CreateTable create = createTable("cycling", "cyclist_name").withPartitionKey("id", DataTypes.UUID); +// CREATE TABLE cycling.cyclist_name (id UUID PRIMARY KEY) +``` + +A table with only one column is not so typical however. At this point you may provide partition, +clustering, regular and static columns using any of the following API methods: + +* `withPrimaryKey(name, dataType)` +* `withClusteringColumn(name, dataType)` +* `withColumn(name, dataType)` +* `withStaticColumn(name, dataType)` + +Primary key precedence is driven by the order of `withPrimaryKey` and `withClusteringKey` +invocations, for example: + + +```java +CreateTable create = createTable("cycling", "cyclist_by_year_and_name") + .withPartitionKey("race_year", DataTypes.INT) + .withPartitionKey("race_name", DataTypes.TEXT) + .withClusteringColumn("rank", DataTypes.INT) + .withColumn("cyclist_name", DataTypes.TEXT); +// CREATE TABLE cycling.cyclist_by_year_and_name (race_year int,race_name text,rank int,cyclist_name text,PRIMARY KEY((race_year,race_name),rank)) +``` + +After providing the column specification, clustering order and many table options may be provided. +Refer to [CreateTableWithOptions] for the variety of configuration options available. + +The following configures compaction and compression options and includes a clustering order. + +```java +CreateTableWithOptions create = createTable("cycling", "cyclist_by_year_and_name") + .withPartitionKey("race_year", DataTypes.INT) + .withPartitionKey("race_name", DataTypes.TEXT) + .withClusteringColumn("rank", DataTypes.INT) + .withColumn("cyclist_name", DataTypes.TEXT) + .withCompaction(leveledCompactionStrategy()) + .withSnappyCompression() + .withClusteringOrder("rank", ClusteringOrder.DESC); +// CREATE TABLE cycling.cyclist_by_year_and_name (race_year int,race_name text,rank int,cyclist_name text,PRIMARY KEY((race_year,race_name),rank)) +// WITH CLUSTERING ORDER BY (rank DESC) +// AND compaction={'class':'LeveledCompactionStrategy'} +// AND compression={'class':'SnappyCompressor'} +``` + +### Altering a Table (ALTER TABLE) + +To start an `ALTER TABLE` query, use `alterTable`: + +```java +alterTable("cycling", "cyclist_name"); +``` + +From here, you can modify the table in the following ways: + +* `dropCompactStorage()`: Drops `COMPACT STORAGE` from a table, removing thrift compatibility mode + and migrates to a CQL-compatible format. +* `addColumn(columnName, dataType)`: Adds a new column to the table. +* `alterColumn(columnName, dataType)`: Changes the type of an existing column. This is not + recommended. +* `dropColumn(columnName)`: Removes an existing column from the table. +* `renameColumn(from, to)`: Renames a column. +* API methods from [AlterTableWithOptions] + +Invoking any of these methods returns a complete query and you may make successive calls to the same +API methods, with exception to alter column, which may only be invoked once. + +### Dropping a Table (DROP TABLE) + +To create a `DROP TABLE` query, use `dropTable`: + +```java +dropTable("cycling", "cyclist_name"); +// DROP TABLE cycling.cyclist_name +``` + +You may also specify `ifExists`: + +```java +dropTable("cyclist_name").ifExists(); +// DROP TABLE IF EXISTS cyclist_name +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[CreateTableWithOptions]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.html +[AlterTableWithOptions]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.html diff --git a/manual/query_builder/schema/type/README.md b/manual/query_builder/schema/type/README.md new file mode 100644 index 00000000000..6642e70d336 --- /dev/null +++ b/manual/query_builder/schema/type/README.md @@ -0,0 +1,91 @@ +## Type + +User-defined types are special types that can associate multiple named fields to a single column. +[SchemaBuilderDsl] offers API methods for creating, altering, and dropping types. + +### Creating a Type (CREATE TYPE) + +To start a `CREATE TYPE` query, use `createType` in [SchemaBuilderDsl]: + +```java +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; + +CreateTypeStart create = createType("mykeyspace", "address"); +``` + +Like all other `CREATE` queries, one may supply `ifNotExists()` to require that the type should only +be created if it doesn't already exist, i.e.: + +```java +CreateTypeStart create = createType("address").ifNotExists(); +``` + +Note that, at this stage, the query cannot be completed yet. You need to provide at least one field +using `withField()`, i.e.: + +```java +CreateType create = createType("mykeyspace", "address").withField("street", DataTypes.TEXT); +// CREATE TYPE mykeyspace.address (street text) +``` + +A type with only one field is not entirely useful. You may continue to make successive calls to +`withField` to specify additional fields, i.e.: + +```java +CreateType create = createType("mykeyspace", "address") + .withField("street", DataTypes.TEXT) + .withField("city", DataTypes.TEXT) + .withField("zip_code", DataTypes.INT) + .withField("phones", DataTypes.setOf(DataTypes.TEXT)); +// CREATE TYPE mykeyspace.address (street text,city text,zip_code int,phones set) +``` + +### Using a created Type in Schema Builder API + +After creating a UDT, one may wonder how to use it in other schema statements. To do so, utilize +`udt(name,frozen)` from [SchemaBuilderDsl], i.e: + +```java +CreateTable users = createTable("mykeyspace", "users") + .withPartitionKey("id", DataTypes.UUID) + .withColumn("name", udt("fullname", true)) + .withColumn("name", DataTypes.setOf(udt("direct_reports", true))) + .withColumn("addresses", DataTypes.mapOf(DataTypes.TEXT, udt("address", true))); +// CREATE TABLE mykeyspace.users (id uuid PRIMARY KEY,name set>,addresses map>) +``` + +### Altering a Type (ALTER TYPE) + +To start an `ALTER TYPE` query, use `alterType`: + +```java +alterTable("mykeyspace", "address"); +``` + +From here, you can modify the type in the following ways: + +* `addField(fieldName, dataType)`: Adds a new field to the type. +* `alterField(fieldName, dataType)`: Changes the type of an existing field. This is not + recommended. +* `renameField(from, to)`: Renames a field. + +Invoking any of these methods returns a complete query. You may make successive calls to +`renameField`, but not the other methods. + +### Dropping a Type (DROP TYPE) + +To create a `DROP TYPE` query, use `dropType`: + +```java +dropType("mykeyspace", "address"); +// DROP TYPE mykeyspace.address +``` + +You may also specify `ifExists`: + +```java +dropTable("address").ifExists(); +// DROP TYPE IF EXISTS address +``` + +[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html diff --git a/manual/query_builder/select/README.md b/manual/query_builder/select/README.md new file mode 100644 index 00000000000..b3bc49f9353 --- /dev/null +++ b/manual/query_builder/select/README.md @@ -0,0 +1,395 @@ +## SELECT + +Start your SELECT with the `selectFrom` method in [QueryBuilderDsl]. There are several variants +depending on whether your table name is qualified, and whether you use case-sensitive identifiers or +case-insensitive strings: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +SelectFrom selectUser = selectFrom("user"); +``` + +Note that, at this stage, the query can't be built yet. You need at least one selector. + +### Selectors + +A selector is something that appears after the `SELECT` keyword, and will become a column in the +result set. Its simplest form is a column identifier, but it can be a more complex expression. + +The easiest way to add a selector is with one of the fluent shortcuts: + +```java +selectFrom("user") + .column("first_name") + .column("last_name"); +// SELECT first_name,last_name FROM user +``` + +You can also create it manually with one of the factory methods in [Selector], and then pass it to +`selector()`: + +```java +selectFrom("user").selector( + Selector.column("first_name")); +// SELECT first_name FROM user +``` + +If you have multiple selectors, you can also use `selectors()` to add them all in a single call. +This is a bit more efficient since it creates less temporary objects: + +```java +selectFrom("user").selectors( + Selector.column("first_name"), + Selector.column("last_name")); +// SELECT first_name,last_name FROM user +``` + +Use an alias to give a selector a different name in the result set: + +```java +selectFrom("user").column("first_name").as("first"); +// SELECT first_name AS first FROM user + +selectFrom("user").selector( + Selector.column("first_name").as("first")); +// SELECT first_name AS first FROM user +``` + +The query builder provides many kinds of selectors. Some of them only work with newer Cassandra +versions, always check what your target platform supports. + +#### Star selector and count + +`all` is the classic "star" selector that returns all columns. It cannot be aliased, and must be the +only selector: + +```java +selectFrom("user").all(); +// SELECT * FROM user + +selectFrom("user").all().as("everything"); +// throws IllegalStateException: Can't alias the * selector +``` + +If you add it to a query that already had other selectors, they will get removed: + +```java +selectFrom("user").column("first_name").all(); +// SELECT * FROM user +``` + +If you add other selectors to a query that already had the star selector, the star selector gets +removed: + +```java +selectFrom("user").all().column("first_name"); +// SELECT first_name FROM user +``` + +If you add multiple selectors at once, and one of them is the star selector, an exception is thrown: + +```java +selectFrom("user").selectors( + Selector.column("first_name"), + Selector.all(), + Selector.column("last_name")); +// throws IllegalArgumentException: Can't pass the * selector to selectors() +``` + +`countAll` counts the number of rows: + +```java +selectFrom("user").countAll(); +// SELECT count(*) FROM user +``` + +#### Columns + +We've already shown how `column` works: + +```java +selectFrom("user") + .column("first_name") + .column("last_name"); +// SELECT first_name,last_name FROM user +``` + +When all your selectors are simple columns, there is a convenience shortcut to add them in one call: + +```java +selectFrom("user").columns("first_name", "last_name"); +// SELECT first_name,last_name FROM user +``` + +#### Arithmetic operations + +Selectors can be combined with arithmetic operations. + +| CQL Operator | Selector name | +|--------------|---------------| +| `a+b` | `add` | +| `a-b` | `subtract` | +| `-a` | `negate` | +| `a*b` | `multiply` | +| `a/b` | `divide` | +| `a%b` | `remainder` | + +```java +selectFrom("rooms") + .multiply(Selector.column("length"), Selector.column("width")) + .as("surface"); +// SELECT length*width AS surface FROM rooms +``` + +Operations can be nested, and will get parenthesized according to the usual precedence rules: + +```java +selectFrom("foo") + .multiply( + Selector.negate(Selector.column("a")), + Selector.add(Selector.column("b"), Selector.column("c"))); +// SELECT -a*(b+c) FROM foo +``` + +Note: as shown in the examples above, arithmetic operations can get verbose very quickly. If you +have common expressions that get reused throughout your application code, consider writing your own +shortcuts: + +```java +public static Selector multiplyColumns(String left, String right) { + return Selector.multiply(Selector.column(left), Selector.column(right)); +} + +selectFrom("rooms") + .selector(multiplyColumns("length", "width")) + .as("surface"); +// SELECT length*width AS surface FROM rooms +``` + +#### Casts + +Casting is closely related to arithmetic operations; it allows you to coerce a selector to a +different data type. For example, if `height` and `weight` are two `int` columns, the following +expression uses integer division and returns an `int`: + +```java +selectFrom("user") + .divide( + Selector.multiply(Selector.column("weight"), literal(10_000)), + Selector.multiply(Selector.column("height"), Selector.column("height"))) + .as("bmi"); +// SELECT weight*10000/(height*height) AS bmi FROM user +``` + +What if you want a floating-point result instead? You have to introduce a cast: + +```java +selectFrom("user") + .divide( + Selector.multiply( + Selector.cast(Selector.column("weight"), DataTypes.DOUBLE), + literal(10_000)), + Selector.multiply(Selector.column("height"), Selector.column("height"))) + .as("bmi"); +// SELECT CAST(weight AS double)*10000/(height*height) AS bmi FROM user +``` + +Type hints are similar to casts, with a subtle difference: a cast applies to an expression with an +already well-established type, whereas a hint is used with a literal, where the type can be +ambiguous. + +```java +selectFrom("foo").divide( + // A literal 1 can be any numeric type (int, bigint, double, etc.) + // It defaults to int, which is wrong here if we want a floating-point result. + Selector.typeHint(literal(1), DataTypes.DOUBLE), + Selector.column("a")); +// SELECT (double)1/a FROM foo +``` + +#### Sub-elements + +These selectors extract an element from a complex column, for example: + +* a field from a user-defined type: + + ```java + selectFrom("user").field("address", "street"); + // SELECT address.street FROM user + ``` + +* an element, or range of elements, in a set or a map: + + ```java + selectFrom("product").element("features", literal("color")); + // SELECT features['color'] FROM product + + selectFrom("movie").range("ratings", literal(3), literal(4)); + // SELECT ratings[3..4] FROM movie + + selectFrom("movie").range("ratings", literal(3), null); + // SELECT ratings[3..] FROM movie + + selectFrom("movie").range("ratings", null, literal(3)); + // SELECT ratings[..3] FROM movie + ``` + +#### Collections of selectors + +Groups of selectors can be extracted as a single collection, such as: + +* a list or set. All inner selectors must return the same CQL type: + + ```java + selectFrom("user").listOf( + Selector.column("first_name"), + Selector.column("last_name")); + // SELECT [first_name,last_name] FROM user + + selectFrom("user").setOf( + Selector.column("first_name"), + Selector.column("last_name")); + // SELECT {first_name,last_name} FROM user + ``` + +* a map. All key and value selectors must have consistent types. In most cases, Cassandra will + require a type hint for the outer map, so the query builder can generate that for you if you + provide the key and value types: + + ```java + Map mapSelector = new HashMap<>(); + mapSelector.put(literal("first"), Selector.column("first_name")); + mapSelector.put(literal("last"), Selector.column("last_name")); + + selectFrom("user").mapOf(mapSelector, DataTypes.TEXT, DataTypes.TEXT); + // SELECT (map){'first':first_name,'last':last_name} FROM user + ``` + +* a tuple. This time the types can be heterogeneous: + + ```java + selectFrom("user").tupleOf( + Selector.column("first_name"), + Selector.column("birth_date")); + // SELECT (first_name,birth_date) FROM user + ``` + +#### Functions + +Function calls take a function name (optionally qualified with a keyspace), and a list of selectors +that will be passed as arguments: + +```java +selectFrom("user").function("utils", "bmi", Selector.column("weight"), Selector.column("height")); +// SELECT utils.bmi(weight,height) FROM user +``` + +The built-in functions `ttl` and `writetime` have convenience shortcuts: + +```java +selectFrom("user").writeTime("first_name").ttl("last_name"); +// SELECT writetime(first_name),ttl(last_name) FROM user +``` + +#### Literals + +Occasionally, you'll need to inline a CQL literal in your query; this is not very useful as a +top-level selector, but could happen as part of an arithmetic operation: + +```java +selectFrom("foo").quotient(literal(1), Selector.column("a")); +// SELECT 1/a FROM foo +``` + +See the [terms](../term/#literals) section for more details on literals. + +#### Raw snippets + +Lastly, a selector can be expressed as a raw CQL snippet, that will get appended to the query as-is, +without any syntax checking or escaping: + +```java +selectFrom("user").raw("first_name, last_name /*some random comment*/"); +// SELECT first_name, last_name /*some random comment*/ FROM user +``` + +This should be used with caution, as it's possible to generate invalid CQL that will fail at +execution time; on the other hand, it can be used as a workaround to handle new CQL features that +are not yet covered by the query builder. + +### Relations + +Relations get added with the `whereXxx()` methods: + +```java +selectFrom("user").all().whereColumn("id").isEqualTo(literal(1)); +// SELECT * FROM user WHERE id=1 +``` + +You can also create and add them manually: + +```java +selectFrom("user").all().where( + Relation.column("id").isEqualTo(literal(1))); +// SELECT * FROM user WHERE id=1 +``` + +Like selectors, they also have fluent shortcuts to build and add in a single call: + + +Relations are a common feature used by many types of statements, so they have a +[dedicated page](../relation) in this manual. + +### Other clauses + +The remaining SELECT clauses have a straightforward syntax. Refer to the javadocs for the fine +print. + +Groupings: + +```java +selectFrom("sensor_data") + .function("max", Selector.column("reading")) + .whereColumn("id").isEqualTo(bindMarker()) + .groupBy("date"); +// SELECT max(reading) FROM sensor_data WHERE id=? GROUP BY date +``` + +Orderings: + +```java +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; + +selectFrom("sensor_data") + .column("reading") + .whereColumn("id").isEqualTo(bindMarker()) + .orderBy("date", ClusteringOrder.DESC); +// SELECT reading FROM sensor_data WHERE id=? ORDER BY date DESC +``` + +Limits: + +```java +selectFrom("sensor_data") + .column("reading") + .whereColumn("id").isEqualTo(bindMarker()) + .limit(10); +// SELECT reading FROM sensor_data WHERE id=? LIMIT 10 + +selectFrom("sensor_data") + .column("reading") + .whereColumn("id").isEqualTo(bindMarker()) + .perPartitionLimit(bindMarker("l")); +// SELECT reading FROM sensor_data WHERE id IN ? PER PARTITION LIMIT :l +``` + +Filtering: + +```java +selectFrom("user").all().allowFiltering(); +// SELECT * FROM user ALLOW FILTERING +``` + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html diff --git a/manual/query_builder/term/README.md b/manual/query_builder/term/README.md new file mode 100644 index 00000000000..c4fd300cf04 --- /dev/null +++ b/manual/query_builder/term/README.md @@ -0,0 +1,109 @@ +## Terms + +A term is an expression that does not involve the value of a column. It is used: + +* as an argument to some selectors, for example the indices of [sub-element](../select/#sub-element) + selectors; +* as the right operand of [relations](../relation). + +To create a term, call one of the factory methods in [QueryBuilderDsl]: + +### Literals + +`literal()` takes a Java object and inlines it as a CQL literal: + +```java +selectFrom("user").all().whereColumn("id").isEqualTo(literal(1)); +// SELECT * FROM user WHERE id=1 +``` + +The argument is converted according to the driver's +[default type mappings](../../core/#cql-to-java-type-mapping). If there is no default mapping, you +will get a `CodecNotFoundException`. + +If you use [custom codecs](../../core/custom_codecs), you might need to inline a custom Java type. +You can pass a [CodecRegistry] as the second argument (most likely, this will be the registry of +your session): + +```java +MyCustomId myCustomId = ...; +CodecRegistry registry = session.getContext().codecRegistry(); +selectFrom("user").all().whereColumn("id").isEqualTo(literal(myCustomId, registry)); +``` + +Alternatively, you can pass a codec directly: + +```java +TypeCodec codec = ...; +selectFrom("user").all().whereColumn("id").isEqualTo(literal(myCustomId, codec)); +``` + +### Function calls + +`function()` invokes a built-in or user-defined function. It takes a function name (optionally +qualified with a keyspace), and a list of terms that will be passed as arguments: + +```java +selectFrom("sensor_data") + .all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumn("date").isEqualTo(function("system", "currentDate")); +// SELECT * FROM sensor_data WHERE id=? AND date=system.currentdate() +``` + +### Arithmetic operations + +Terms can be combined with arithmetic operations. + +| CQL Operator | Selector name | +|--------------|---------------| +| `a+b` | `add` | +| `a-b` | `subtract` | +| `-a` | `negate` | +| `a*b` | `multiply` | +| `a/b` | `divide` | +| `a%b` | `remainder` | + +```java +selectFrom("sensor_data") + .all() + .whereColumn("id").isEqualTo(bindMarker()) + .whereColumn("unix_timestamp").isGreaterThan( + subtract(function("toUnixTimestamp", function("now")), + literal(3600))); +// SELECT * FROM sensor_data WHERE id=? AND unix_timestamp>tounixtimestamp(now())-3600 +``` + +Operations can be nested, and will get parenthesized according to the usual precedence rules. + +### Type hints + +`typeHint` forces a term to a particular CQL type. For instance, it could be used to ensure that an +expression uses floating-point division: + +```java +selectFrom("test") + .all() + .whereColumn("k").isEqualTo(literal(1)) + .whereColumn("c").isGreaterThan(divide( + typeHint(literal(1), DataTypes.DOUBLE), + literal(3))); +// SELECT * FROM test WHERE k=1 AND c>(double)1/3 +``` + +### Raw CQL snippets + +Finally, it is possible to provide a raw CQL snippet with `raw()`; it will get appended to the query +as-is, without any syntax checking or escaping: + +```java +selectFrom("sensor_data").all().whereColumn("id").isEqualTo(raw(" 1 /*some random comment*/")); +// SELECT * FROM sensor_data WHERE id= 1 /*some random comment*/ +``` + +This should be used with caution, as it's possible to generate invalid CQL that will fail at +execution time; on the other hand, it can be used as a workaround to handle new CQL features that +are not yet covered by the query builder. + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[CodecRegistry]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.html \ No newline at end of file diff --git a/manual/query_builder/update/README.md b/manual/query_builder/update/README.md new file mode 100644 index 00000000000..a57708c81a6 --- /dev/null +++ b/manual/query_builder/update/README.md @@ -0,0 +1,226 @@ +## UPDATE + +To start an UPDATE query, use one of the `update` methods in [QueryBuilderDsl]. There are several +variants depending on whether your table name is qualified, and whether you use case-sensitive +identifiers or case-insensitive strings: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; + +UpdateStart update = update("user"); +``` + +Note that, at this stage, the query can't be built yet. You need at least one +[assignment](#assignments) and one [relation](#relations). + +### Timestamp + +The USING TIMESTAMP clause comes right after the table, and specifies the timestamp at which the +mutation will be applied. You can pass either a literal value: + +```java +update("user").usingTimestamp(1234); +// UPDATE user USING TIMESTAMP 1234... +``` + +Or a bind marker: + +```java +update("user").usingTimestamp(bindMarker()); +// UPDATE user USING TIMESTAMP ?... +``` + +If you call the method multiple times, the last value will be used. + +### Assignments + +An assignment is an operation that appears after the SET keyword. You need at least one for a valid +update query. + +The easiest way to add an assignment is with one of the fluent methods: + +```java +update("user").setColumn("v", bindMarker()); +// UPDATE user SET v=?... +``` + +You can also create it manually with one of the factory methods in [Assignment], and then pass it to +`set()`: + +```java +update("user").set( + Assignment.setColumn("v", bindMarker())); +// UPDATE user SET v=?... +``` + +If you have multiple assignments, you can add them all in a single call. This is a bit more +efficient since it creates less temporary objects: + +```java +update("user").set( + Assignment.setColumn("v1", bindMarker()), + Assignment.setColumn("v2", bindMarker())) +// UPDATE user SET v1=?,v2=?... +``` + +#### Simple columns + +As shown already, `setColumn` changes the value of a column. It can take a bind marker or a literal +(which must have the same CQL type as the column): + +```java +update("user").setColumn("last_name", literal("Doe")); +// UPDATE user SET last_name='Doe'... +``` + +#### UDT fields + +`setField` modifies a field inside of a UDT column: + +```java +update("user").setField("address", "street", bindMarker()); +// UPDATE user SET address.street=?... +``` + +#### Counters + +Counter columns can be incremented by a given amount: + +```java +update("foo").increment("c", bindMarker()); +// UPDATE foo SET c+=?... + +update("foo").increment("c", literal(4)); +// UPDATE foo SET c+=4... +``` + +There is a shortcut to increment by 1: + +```java +update("foo").increment("c"); +// UPDATE foo SET c+=1... +``` + +All those methods have a `decrement` counterpart: + +```java +update("foo").decrement("c"); +// UPDATE foo SET c-=1... +``` + +#### Collections + +`mapValue` changes a value in a map. The key is expressed as a term (here a literal value) : + +```java +update("product").setMapValue("features", literal("color"), bindMarker()) +// UPDATE product SET features['color']=?... +``` + +`append` operates on any CQL collection type (list, set or map). If you pass a literal, it must also +be a collection, with the same CQL type of elements: + +```java +update("foo").append("l", bindMarker()); +// UPDATE foo SET l+=?... + +List value = Arrays.asList(1, 2, 3); +update("foo").append("l", literal(value)); +// UPDATE foo SET l+=[1,2,3]... + +Set value = new HashSet<>(Arrays.asList(1, 2, 3)); +update("foo").append("s", literal(value)) +// UPDATE foo SET s+={1,2,3}... + +Map value = new HashMap<>(); +value.put(1, "bar"); +value.put(2, "baz"); +update("foo").append("m", literal(value)); +// UPDATE foo SET m+={1:'bar',2:'baz'}... +``` + +If you only have one element to append, there are shortcuts to avoid creating a collection in your +code: + +```java +update("foo").appendListElement("l", literal(1)); +// UPDATE foo SET l+=[1]... + +update("foo").appendSetElement("s", literal(1)); +// UPDATE foo SET s+={1}... + +update("foo").appendMapEntry("m", literal(1), literal("bar")); +// UPDATE foo SET m+={1:'bar'}... +``` + +All those methods have a `prepend` counterpart: + +```java +update("foo").prepend("l", bindMarker()); +// UPDATE foo SET l=?+l... +``` + +As well as a `remove` counterpart: + +```java +update("foo").remove("l", bindMarker()); +// UPDATE foo SET l-=?... +``` + +### Relations + +Once you have at least one assignment, relations can be added with the fluent `whereXxx()` methods: + +```java +update("foo").setColumn("v", bindMarker()).whereColumn("k").isEqualTo(bindMarker()); +// UPDATE foo SET v=? WHERE k=? +``` + +Or you can build and add them manually: + +```java +update("foo").setColumn("v", bindMarker()).where( + Relation.column("k").isEqualTo(bindMarker())); +// UPDATE foo SET v=? WHERE k=? +``` + +Once there is at least one assignment and one relation, the statement can be built: + +```java +SimpleStatement statement = update("foo") + .setColumn("k", bindMarker()) + .whereColumn("k").isEqualTo(bindMarker()) + .build(); +``` + +Relations are a common feature used by many types of statements, so they have a +[dedicated page](../relation) in this manual. + +### Conditions + +Conditions get added with the fluent `ifXxx()` methods: + +```java +update("foo") + .setColumn("v", bindMarker()) + .whereColumn("k").isEqualTo(bindMarker()) + .ifColumn("v").isEqualTo(bindMarker()); +// UPDATE foo SET v=? WHERE k=? IF v=? +``` + +Or you can build and add them manually: + +```java +update("foo") + .setColumn("v", bindMarker()) + .whereColumn("k").isEqualTo(bindMarker()) + .if_( + Condition.column("v").isEqualTo(bindMarker())); +// UPDATE foo SET v=? WHERE k=? IF v=? +``` + +Conditions are a common feature used by UPDATE and DELETE, so they have a +[dedicated page](../condition) in this manual. + +[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[Assignment]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/update/Assignment.html diff --git a/pom.xml b/pom.xml index 10916a99d70..7b30b3b4842 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ core test-infra integration-tests + query-builder diff --git a/query-builder/pom.xml b/query-builder/pom.xml new file mode 100644 index 00000000000..31a96de0e7e --- /dev/null +++ b/query-builder/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-alpha4-SNAPSHOT + + + java-driver-query-builder + jar + + DataStax Java driver for Apache Cassandra® - query builder + + + + com.datastax.oss + java-driver-core + ${project.version} + + + com.google.guava + guava + + + junit + junit + test + + + org.assertj + assertj-core + test + + + + + + + maven-shade-plugin + + + + shade + + + + + com.google.guava:guava + + + + + com.google + com.datastax.oss.driver.shaded.guava + + + true + + + + + + + diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BindMarker.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BindMarker.java new file mode 100644 index 00000000000..0234d4fbb15 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BindMarker.java @@ -0,0 +1,33 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.querybuilder.term.Term; + +/** + * A bind marker in the query. + * + *

          It can be anonymous or named, for example: + * + *

          {@code
          + * selectFrom("foo").all().whereColumn("k").isEqualTo(bindMarker())
          + * // SELECT * FROM foo WHERE k=?
          + *
          + * selectFrom("foo").all().whereColumn("k").isEqualTo(bindMarker("key"))
          + * // SELECT * FROM foo WHERE k=:key
          + * }
          + */ +public interface BindMarker extends Term {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java new file mode 100644 index 00000000000..17afee6e867 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java @@ -0,0 +1,75 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder; +import com.datastax.oss.driver.api.core.cql.Statement; + +/** + * End state for the query builder DSL, which allows the generation of a CQL query. + * + *

          The API returns this type as soon as there is enough information for a minimal query (in most + * cases, it's still possible to call more methods to keep building). + */ +public interface BuildableQuery { + + /** + * Builds the CQL query as a raw string. + * + *

          Use this if you plan to pass the query to {@link CqlSession#execute(String)} or {@link + * CqlSession#prepare(String)} without any further customization. + */ + String asCql(); + + /** + * Builds the CQL query and wraps it in a simple statement. + * + *

          This is a similar to: + * + *

          {@code
          +   * SimpleStatement.newInstance(asCql())
          +   * }
          + * + * In addition, some query implementation might try to infer additional statement properties (such + * as {@link Statement#isIdempotent()}). + */ + default SimpleStatement build() { + return SimpleStatement.newInstance(asCql()); + } + + /** + * Builds the CQL query and wraps it in a simple statement builder. + * + *

          This is equivalent to {@link #build()}, but the builder might be slightly more efficient if + * you plan to customize multiple properties on the statement, for example: + * + *

          {@code
          +   * SimpleStatementBuilder builder =
          +   *     selectFrom("foo")
          +   *         .all()
          +   *         .whereColumn("k").isEqualTo(bindMarker("k"))
          +   *         .whereColumn("c").isLessThan(bindMarker("c"))
          +   *         .builder();
          +   * SimpleStatement statement =
          +   *     builder.addNamedValue("k", 1).addNamedValue("c", 2).withTracing().build();
          +   * }
          + */ + default SimpleStatementBuilder builder() { + return SimpleStatement.builder(asCql()); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java new file mode 100644 index 00000000000..89fc70d0a81 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +/** An element in the query builder DSL, that will generate part of a CQL query. */ +public interface CqlSnippet { + void appendTo(StringBuilder builder); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Literal.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Literal.java new file mode 100644 index 00000000000..0bb9f6b94a0 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Literal.java @@ -0,0 +1,29 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.api.querybuilder.term.Term; + +/** + * A value that will be appended as a CQL literal. + * + *

          For convenience, it can be used both as a selector and a term. The only downside is that the + * {@link #as(CqlIdentifier)} method is only valid when used as a selector; make sure you don't use + * it elsewhere, or you will generate invalid CQL that will fail at execution time. + */ +public interface Literal extends Selector, Term {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java new file mode 100644 index 00000000000..25492365da0 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java @@ -0,0 +1,388 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection; +import com.datastax.oss.driver.api.querybuilder.insert.InsertInto; +import com.datastax.oss.driver.api.querybuilder.relation.Relation; +import com.datastax.oss.driver.api.querybuilder.select.SelectFrom; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.api.querybuilder.update.UpdateStart; +import com.datastax.oss.driver.internal.core.metadata.schema.ShallowUserDefinedType; +import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; +import com.datastax.oss.driver.internal.querybuilder.DefaultLiteral; +import com.datastax.oss.driver.internal.querybuilder.DefaultRaw; +import com.datastax.oss.driver.internal.querybuilder.delete.DefaultDelete; +import com.datastax.oss.driver.internal.querybuilder.insert.DefaultInsert; +import com.datastax.oss.driver.internal.querybuilder.select.DefaultBindMarker; +import com.datastax.oss.driver.internal.querybuilder.select.DefaultSelect; +import com.datastax.oss.driver.internal.querybuilder.term.BinaryArithmeticTerm; +import com.datastax.oss.driver.internal.querybuilder.term.FunctionTerm; +import com.datastax.oss.driver.internal.querybuilder.term.OppositeTerm; +import com.datastax.oss.driver.internal.querybuilder.term.TupleTerm; +import com.datastax.oss.driver.internal.querybuilder.term.TypeHintTerm; +import com.datastax.oss.driver.internal.querybuilder.update.DefaultUpdate; +import java.util.Arrays; + +/** A Domain-Specific Language to build CQL queries using Java code. */ +public class QueryBuilderDsl { + + /** Starts a SELECT query for a qualified table. */ + public static SelectFrom selectFrom(CqlIdentifier keyspace, CqlIdentifier table) { + return new DefaultSelect(keyspace, table); + } + + /** + * Shortcut for {@link #selectFrom(CqlIdentifier, CqlIdentifier) + * selectFrom(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table))} + */ + public static SelectFrom selectFrom(String keyspace, String table) { + return selectFrom(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } + + /** Starts a SELECT query for an unqualified table. */ + public static SelectFrom selectFrom(CqlIdentifier table) { + return selectFrom(null, table); + } + + /** Shortcut for {@link #selectFrom(CqlIdentifier) selectFrom(CqlIdentifier.fromCql(table))} */ + public static SelectFrom selectFrom(String table) { + return selectFrom(CqlIdentifier.fromCql(table)); + } + + /** Starts an INSERT query for a qualified table. */ + public static InsertInto insertInto(CqlIdentifier keyspace, CqlIdentifier table) { + return new DefaultInsert(keyspace, table); + } + + /** + * Shortcut for {@link #insertInto(CqlIdentifier, CqlIdentifier) + * insertInto(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table))}. + */ + public static InsertInto insertInto(String keyspace, String table) { + return insertInto(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } + + /** Starts an INSERT query for an unqualified table. */ + public static InsertInto insertInto(CqlIdentifier table) { + return insertInto(null, table); + } + + /** Shortcut for {@link #insertInto(CqlIdentifier) insertInto(CqlIdentifier.fromCql(table))}. */ + public static InsertInto insertInto(String table) { + return insertInto(CqlIdentifier.fromCql(table)); + } + + /** Starts an UPDATE query for a qualified table. */ + public static UpdateStart update(CqlIdentifier keyspace, CqlIdentifier table) { + return new DefaultUpdate(keyspace, table); + } + + /** + * Shortcut for {@link #update(CqlIdentifier, CqlIdentifier) + * update(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table))} + */ + public static UpdateStart update(String keyspace, String table) { + return update(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } + + /** Starts an UPDATE query for an unqualified table. */ + public static UpdateStart update(CqlIdentifier table) { + return update(null, table); + } + + /** Shortcut for {@link #update(CqlIdentifier) update(CqlIdentifier.fromCql(table))} */ + public static UpdateStart update(String table) { + return update(CqlIdentifier.fromCql(table)); + } + + /** Starts a DELETE query for a qualified table. */ + public static DeleteSelection deleteFrom(CqlIdentifier keyspace, CqlIdentifier table) { + return new DefaultDelete(keyspace, table); + } + + /** + * Shortcut for {@link #deleteFrom(CqlIdentifier, CqlIdentifier) + * deleteFrom(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table))} + */ + public static DeleteSelection deleteFrom(String keyspace, String table) { + return deleteFrom(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } + + /** Starts a DELETE query for an unqualified table. */ + public static DeleteSelection deleteFrom(CqlIdentifier table) { + return deleteFrom(null, table); + } + + /** Shortcut for {@link #deleteFrom(CqlIdentifier) deleteFrom(CqlIdentifier.fromCql(table))} */ + public static DeleteSelection deleteFrom(String table) { + return deleteFrom(CqlIdentifier.fromCql(table)); + } + + /** + * An ordered set of anonymous terms, as in {@code WHERE (a, b) = (1, 2)} (on the right-hand + * side). + * + *

          For example, this can be used as the right operand of {@link Relation#columns(String...)}. + */ + public static Term tuple(Iterable components) { + return new TupleTerm(components); + } + + /** Var-arg equivalent of {@link #tuple(Iterable)}. */ + public static Term tuple(Term... components) { + return tuple(Arrays.asList(components)); + } + + /** The sum of two terms, as in {@code WHERE k = left + right}. */ + public static Term add(Term left, Term right) { + return new BinaryArithmeticTerm(ArithmeticOperator.SUM, left, right); + } + + /** The difference of two terms, as in {@code WHERE k = left - right}. */ + public static Term subtract(Term left, Term right) { + return new BinaryArithmeticTerm(ArithmeticOperator.DIFFERENCE, left, right); + } + + /** The product of two terms, as in {@code WHERE k = left * right}. */ + public static Term multiply(Term left, Term right) { + return new BinaryArithmeticTerm(ArithmeticOperator.PRODUCT, left, right); + } + + /** The quotient of two terms, as in {@code WHERE k = left / right}. */ + public static Term divide(Term left, Term right) { + return new BinaryArithmeticTerm(ArithmeticOperator.QUOTIENT, left, right); + } + + /** The remainder of two terms, as in {@code WHERE k = left % right}. */ + public static Term remainder(Term left, Term right) { + return new BinaryArithmeticTerm(ArithmeticOperator.REMAINDER, left, right); + } + + /** The opposite of a term, as in {@code WHERE k = -argument}. */ + public static Term negate(Term argument) { + return new OppositeTerm(argument); + } + + /** A function call as a term, as in {@code WHERE = f(arguments)}. */ + public static Term function(CqlIdentifier functionId, Iterable arguments) { + return function(null, functionId, arguments); + } + + /** Var-arg equivalent of {@link #function(CqlIdentifier, Iterable)}. */ + public static Term function(CqlIdentifier functionId, Term... arguments) { + return function(functionId, Arrays.asList(arguments)); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, Iterable) + * function(CqlIdentifier.fromCql(functionName), arguments)}. + */ + public static Term function(String functionName, Iterable arguments) { + return function(CqlIdentifier.fromCql(functionName), arguments); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, Term...) + * function(CqlIdentifier.fromCql(functionName), arguments)}. + */ + public static Term function(String functionName, Term... arguments) { + return function(CqlIdentifier.fromCql(functionName), arguments); + } + + /** A function call as a term, as in {@code WHERE = ks.f(arguments)}. */ + public static Term function( + CqlIdentifier keyspaceId, CqlIdentifier functionId, Iterable arguments) { + return new FunctionTerm(keyspaceId, functionId, arguments); + } + + /** Var-arg equivalent of {@link #function(CqlIdentifier, CqlIdentifier, Iterable)}. */ + public static Term function( + CqlIdentifier keyspaceId, CqlIdentifier functionId, Term... arguments) { + return function(keyspaceId, functionId, Arrays.asList(arguments)); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, CqlIdentifier, Iterable) + * function(CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments)}. + */ + public static Term function(String keyspaceName, String functionName, Iterable arguments) { + return function( + CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, CqlIdentifier, Term...) + * function(CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments)}. + */ + public static Term function(String keyspaceName, String functionName, Term... arguments) { + return function( + CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments); + } + + /** + * Provides a type hint for an expression, as in {@code WHERE k = (double)1/3}. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link #udt(CqlIdentifier)}. + */ + public static Term typeHint(Term term, DataType targetType) { + return new TypeHintTerm(term, targetType); + } + + /** A call to the built-in {@code now} function as a term. */ + public static Term now() { + return function("now"); + } + + /** A call to the built-in {@code currentTimestamp} function as a term. */ + public static Term currentTimestamp() { + return function("currenttimestamp"); + } + + /** A call to the built-in {@code currentDate} function as a term. */ + public static Term currentDate() { + return function("currentdate"); + } + + /** A call to the built-in {@code currentTime} function as a term. */ + public static Term currentTime() { + return function("currenttime"); + } + + /** A call to the built-in {@code currentTimeUuid} function as a term. */ + public static Term currentTimeUuid() { + return function("currenttimeuuid"); + } + + /** A call to the built-in {@code minTimeUuid} function as a term. */ + public static Term minTimeUuid(Term argument) { + return function("mintimeuuid", argument); + } + + /** A call to the built-in {@code maxTimeUuid} function as a term. */ + public static Term maxTimeUuid(Term argument) { + return function("maxtimeuuid", argument); + } + + /** A call to the built-in {@code toDate} function as a term. */ + public static Term toDate(Term argument) { + return function("todate", argument); + } + + /** A call to the built-in {@code toTimestamp} function as a term. */ + public static Term toTimestamp(Term argument) { + return function("totimestamp", argument); + } + + /** A call to the built-in {@code toUnixTimestamp} function as a term. */ + public static Term toUnixTimestamp(Term argument) { + return function("tounixtimestamp", argument); + } + + /** + * A literal term, as in {@code WHERE k = 1}. + * + *

          This method can process any type for which there is a default Java to CQL mapping, namely: + * primitive types ({@code Integer=>int, Long=>bigint, String=>text, etc.}), and collections, + * tuples, and user defined types thereof. + * + *

          A null argument will be rendered as {@code NULL}. + * + *

          For custom mappings, use {@link #literal(Object, CodecRegistry)} or {@link #literal(Object, + * TypeCodec)}. + * + * @throws CodecNotFoundException if there is no default CQL mapping for the Java type of {@code + * value}. + */ + public static Literal literal(Object value) { + return literal(value, CodecRegistry.DEFAULT); + } + + /** + * A literal term, as in {@code WHERE k = 1}. + * + *

          This is an alternative to {@link #literal(Object)} for custom type mappings. The provided + * registry should contain a codec that can format the value. Typically, this will be your + * session's registry, which is accessible via {@code session.getContext().codecRegistry()}. + * + * @see DriverContext#codecRegistry() + * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can + * handle {@code value}. + */ + public static Literal literal(Object value, CodecRegistry codecRegistry) { + return literal(value, (value == null) ? null : codecRegistry.codecFor(value)); + } + + /** + * A literal term, as in {@code WHERE k = 1}. + * + *

          This is an alternative to {@link #literal(Object)} for custom type mappings. The value will + * be turned into a string with {@link TypeCodec#format(Object)}, and inlined in the query. + */ + public static Literal literal(T value, TypeCodec codec) { + return new DefaultLiteral<>(value, codec); + } + + /** + * A raw CQL snippet. + * + *

          The contents will be appended to the query as-is, without any syntax checking or escaping. + * This method should be used with caution, as it's possible to generate invalid CQL that will + * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL + * features that are not yet covered by the query builder. + */ + public static Raw raw(String raw) { + return new DefaultRaw(raw); + } + + /** Creates an anonymous bind marker, which appears as {@code ?} in the generated CQL. */ + public static BindMarker bindMarker() { + return bindMarker((CqlIdentifier) null); + } + + /** Creates a named bind marker, which appears as {@code :id} in the generated CQL. */ + public static BindMarker bindMarker(CqlIdentifier id) { + return new DefaultBindMarker(id); + } + + /** Shortcut for {@link #bindMarker(CqlIdentifier) bindMarker(CqlIdentifier.fromCql(name))} */ + public static BindMarker bindMarker(String name) { + return bindMarker(CqlIdentifier.fromCql(name)); + } + + /** + * Shortcut to reference a UDT in methods that use a {@link DataType}, such as {@link + * #typeHint(Term, DataType)} and {@link Selector#cast(Selector, DataType)}. + */ + public static UserDefinedType udt(CqlIdentifier name) { + return new ShallowUserDefinedType(null, name, false); + } + + /** Shortcut for {@link #udt(CqlIdentifier) udt(CqlIdentifier.fromCql(name))}. */ + public static UserDefinedType udt(String name) { + return udt(CqlIdentifier.fromCql(name)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java new file mode 100644 index 00000000000..4a31d4c2277 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java @@ -0,0 +1,39 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.condition.Condition; +import com.datastax.oss.driver.api.querybuilder.relation.Relation; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.api.querybuilder.term.Term; + +/** + * A raw CQL snippet that will be appended to the query as-is, without any syntax checking or + * escaping. + * + *

          To build an instance of this type, use {@link QueryBuilderDsl#raw(String)}. + * + *

          It should be used with caution, as it's possible to generate invalid CQL that will fail at + * execution time; on the other hand, it can be used as a workaround to handle new CQL features that + * are not yet covered by the query builder. + * + *

          For convenience, there is a single raw element in the query builder; it can be used in several + * places: as a selector, relation, etc. The only downside is that the {@link #as(CqlIdentifier)} + * method is only valid when used as a selector; make sure you don't use it elsewhere, or you will + * generate invalid CQL that will fail at execution time. + */ +public interface Raw extends Selector, Relation, Condition, Term {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java new file mode 100644 index 00000000000..49d611e7d61 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java @@ -0,0 +1,583 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart; +import com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart; +import com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart; +import com.datastax.oss.driver.api.querybuilder.schema.AlterTypeStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateAggregateStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateFunctionStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateIndexStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspaceStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateTable; +import com.datastax.oss.driver.api.querybuilder.schema.CreateTableStart; +import com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions; +import com.datastax.oss.driver.api.querybuilder.schema.CreateTypeStart; +import com.datastax.oss.driver.api.querybuilder.schema.Drop; +import com.datastax.oss.driver.api.querybuilder.schema.RelationStructure; +import com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy; +import com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy; +import com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy; +import com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy; +import com.datastax.oss.driver.internal.core.metadata.schema.ShallowUserDefinedType; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultAlterKeyspace; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultAlterMaterializedView; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultAlterTable; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultAlterType; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateAggregate; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateFunction; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateIndex; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateKeyspace; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateMaterializedView; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateTable; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultCreateType; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultDrop; +import com.datastax.oss.driver.internal.querybuilder.schema.DefaultDropKeyspace; +import com.datastax.oss.driver.internal.querybuilder.schema.compaction.DefaultLeveledCompactionStrategy; +import com.datastax.oss.driver.internal.querybuilder.schema.compaction.DefaultSizeTieredCompactionStrategy; +import com.datastax.oss.driver.internal.querybuilder.schema.compaction.DefaultTimeWindowCompactionStrategy; + +/** A Domain-Specific Language to build CQL DDL queries using Java code. */ +public class SchemaBuilderDsl { + + /** Starts a CREATE KEYSPACE query. */ + public static CreateKeyspaceStart createKeyspace(CqlIdentifier keyspaceName) { + return new DefaultCreateKeyspace(keyspaceName); + } + + /** + * Shortcut for {@link #createKeyspace(CqlIdentifier) + * createKeyspace(CqlIdentifier.fromCql(keyspaceName))} + */ + public static CreateKeyspaceStart createKeyspace(String keyspaceName) { + return createKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + + /** Starts an ALTER KEYSPACE query. */ + public static AlterKeyspaceStart alterKeyspace(CqlIdentifier keyspaceName) { + return new DefaultAlterKeyspace(keyspaceName); + } + + /** + * Shortcut for {@link #alterKeyspace(CqlIdentifier) + * alterKeyspace(CqlIdentifier.fromCql(keyspaceName)}. + */ + public static AlterKeyspaceStart alterKeyspace(String keyspaceName) { + return alterKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + + /** Starts a DROP KEYSPACE query. */ + public static Drop dropKeyspace(CqlIdentifier keyspaceName) { + return new DefaultDropKeyspace(keyspaceName); + } + + /** + * Shortcut for {@link #dropKeyspace(CqlIdentifier) + * dropKeyspace(CqlIdentifier.fromCql(keyspaceName)}. + */ + public static Drop dropKeyspace(String keyspaceName) { + return dropKeyspace(CqlIdentifier.fromCql(keyspaceName)); + } + + /** + * Starts a CREATE TABLE query with the given table name. This assumes the keyspace name is + * already qualified for the Session or Statement. + */ + public static CreateTableStart createTable(CqlIdentifier tableName) { + return new DefaultCreateTable(tableName); + } + + /** Starts a CREATE TABLE query with the given table name for the given keyspace name. */ + public static CreateTableStart createTable(CqlIdentifier keyspace, CqlIdentifier tableName) { + return new DefaultCreateTable(keyspace, tableName); + } + + /** + * Shortcut for {@link #createTable(CqlIdentifier) createTable(CqlIdentifier.fromCql(tableName)} + */ + public static CreateTableStart createTable(String tableName) { + return createTable(CqlIdentifier.fromCql(tableName)); + } + + /** + * Shortcut for {@link #createTable(CqlIdentifier,CqlIdentifier) + * createTable(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(tableName)} + */ + public static CreateTableStart createTable(String keyspace, String tableName) { + return createTable(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(tableName)); + } + + /** + * Starts an ALTER TABLE query with the given table name. This assumes the keyspace name is + * already qualified for the Session or Statement. + */ + public static AlterTableStart alterTable(CqlIdentifier tableName) { + return new DefaultAlterTable(tableName); + } + + /** Starts an ALTER TABLE query with the given table name for the given keyspace name. */ + public static AlterTableStart alterTable(CqlIdentifier keyspace, CqlIdentifier tableName) { + return new DefaultAlterTable(keyspace, tableName); + } + + /** Shortcut for {@link #alterTable(CqlIdentifier) alterTable(CqlIdentifier.fromCql(tableName)} */ + public static AlterTableStart alterTable(String tableName) { + return alterTable(CqlIdentifier.fromCql(tableName)); + } + + /** + * Shortcut for {@link #alterTable(CqlIdentifier,CqlIdentifier) + * alterTable(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(tableName)} + */ + public static AlterTableStart alterTable(String keyspace, String tableName) { + return alterTable(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(tableName)); + } + + /** + * Starts a DROP TABLE query. This assumes the keyspace name is already qualified for the Session + * or Statement. + */ + public static Drop dropTable(CqlIdentifier tableName) { + return new DefaultDrop(tableName, "TABLE"); + } + + /** Shortcut for {@link #dropTable(CqlIdentifier) dropTable(CqlIdentifier.fromCql(tableName)}. */ + public static Drop dropTable(String tableName) { + return dropTable(CqlIdentifier.fromCql(tableName)); + } + + /** Starts a DROP TABLE query for the given table name for the given keyspace name. */ + public static Drop dropTable(CqlIdentifier keyspace, CqlIdentifier tableName) { + return new DefaultDrop(keyspace, tableName, "TABLE"); + } + + /** + * Shortcut for {@link #dropTable(CqlIdentifier,CqlIdentifier) + * dropTable(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(tableName)}. + */ + public static Drop dropTable(String keyspace, String tableName) { + return dropTable(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(tableName)); + } + + /** + * Starts a CREATE MATERIALIZED VIEW query with the given view name. This assumes the keyspace + * name is already qualified for the Session or Statement. + */ + public static CreateMaterializedViewStart createMaterializedView(CqlIdentifier viewName) { + return new DefaultCreateMaterializedView(viewName); + } + + /** + * Starts a CREATE MATERIALIZED VIEW query with the given view name for the given keyspace name. + */ + public static CreateMaterializedViewStart createMaterializedView( + CqlIdentifier keyspace, CqlIdentifier viewName) { + return new DefaultCreateMaterializedView(keyspace, viewName); + } + + /** + * Shortcut for {@link #createMaterializedView(CqlIdentifier) + * createMaterializedView(CqlIdentifier.fromCql(viewName)} + */ + public static CreateMaterializedViewStart createMaterializedView(String viewName) { + return createMaterializedView(CqlIdentifier.fromCql(viewName)); + } + + /** + * Shortcut for {@link #createMaterializedView(CqlIdentifier,CqlIdentifier) + * createMaterializedView(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(viewName)} + */ + public static CreateMaterializedViewStart createMaterializedView( + String keyspace, String viewName) { + return createMaterializedView(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(viewName)); + } + + /** + * Starts an ALTER MATERIALIZED VIEW query with the given view name. This assumes the keyspace + * name is already qualified for the Session or Statement. + */ + public static AlterMaterializedViewStart alterMaterializedView(CqlIdentifier viewName) { + return new DefaultAlterMaterializedView(viewName); + } + + /** + * Starts an ALTER MATERIALIZED VIEW query with the given view name for the given keyspace name. + */ + public static AlterMaterializedViewStart alterMaterializedView( + CqlIdentifier keyspace, CqlIdentifier viewName) { + return new DefaultAlterMaterializedView(keyspace, viewName); + } + + /** + * Shortcut for {@link #alterMaterializedView(CqlIdentifier) + * alterMaterializedView(CqlIdentifier.fromCql(viewName)} + */ + public static AlterMaterializedViewStart alterMaterializedView(String viewName) { + return alterMaterializedView(CqlIdentifier.fromCql(viewName)); + } + + /** + * Shortcut for {@link #alterMaterializedView(CqlIdentifier,CqlIdentifier) + * alterMaterializedView(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(viewName)} + */ + public static AlterMaterializedViewStart alterMaterializedView(String keyspace, String viewName) { + return alterMaterializedView(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(viewName)); + } + + /** + * Starts a DROP MATERIALIZED VIEW query. This assumes the keyspace name is already qualified for + * the Session or Statement. + */ + public static Drop dropMaterializedView(CqlIdentifier viewName) { + return new DefaultDrop(viewName, "MATERIALIZED VIEW"); + } + + /** + * Shortcut for {@link #dropMaterializedView(CqlIdentifier) + * dropMaterializedView(CqlIdentifier.fromCql(viewName)}. + */ + public static Drop dropMaterializedView(String viewName) { + return dropMaterializedView(CqlIdentifier.fromCql(viewName)); + } + + /** Starts a DROP MATERIALIZED VIEW query for the given view name for the given keyspace name. */ + public static Drop dropMaterializedView(CqlIdentifier keyspace, CqlIdentifier viewName) { + return new DefaultDrop(keyspace, viewName, "MATERIALIZED VIEW"); + } + + /** + * Shortcut for {@link #dropMaterializedView(CqlIdentifier,CqlIdentifier) + * dropMaterializedView(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(viewName)}. + */ + public static Drop dropMaterializedView(String keyspace, String viewName) { + return dropMaterializedView(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(viewName)); + } + + /** + * Starts a CREATE TYPE query with the given type name. This assumes the keyspace name is already + * qualified for the Session or Statement. + */ + public static CreateTypeStart createType(CqlIdentifier typeName) { + return new DefaultCreateType(typeName); + } + + /** Starts a CREATE TYPE query with the given type name for the given keyspace name. */ + public static CreateTypeStart createType(CqlIdentifier keyspace, CqlIdentifier typeName) { + return new DefaultCreateType(keyspace, typeName); + } + + /** Shortcut for {@link #createType(CqlIdentifier) createType(CqlIdentifier.fromCql(typeName)}. */ + public static CreateTypeStart createType(String typeName) { + return createType(CqlIdentifier.fromCql(typeName)); + } + + /** + * Shortcut for {@link #createType(CqlIdentifier,CqlIdentifier) + * createType(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(typeName)}. + */ + public static CreateTypeStart createType(String keyspace, String typeName) { + return createType(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(typeName)); + } + + /** + * Starts an ALTER TYPE query with the given type name. This assumes the keyspace name is already + * qualified for the Session or Statement. + */ + public static AlterTypeStart alterType(CqlIdentifier typeName) { + return new DefaultAlterType(typeName); + } + + /** Starts an ALTER TYPE query with the given type name for the given keyspace name. */ + public static AlterTypeStart alterType(CqlIdentifier keyspace, CqlIdentifier typeName) { + return new DefaultAlterType(keyspace, typeName); + } + + /** Shortcut for {@link #alterType(CqlIdentifier) alterType(CqlIdentifier.fromCql(typeName)} */ + public static AlterTypeStart alterType(String typeName) { + return alterType(CqlIdentifier.fromCql(typeName)); + } + + /** + * Shortcut for {@link #alterType(CqlIdentifier,CqlIdentifier) + * alterType(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(typeName)} + */ + public static AlterTypeStart alterType(String keyspace, String typeName) { + return alterType(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(typeName)); + } + + /** + * Starts a DROP TYPE query. This assumes the keyspace name is already qualified for the Session + * or Statement. + */ + public static Drop dropType(CqlIdentifier typeName) { + return new DefaultDrop(typeName, "TYPE"); + } + + /** Shortcut for {@link #dropType(CqlIdentifier) dropType(CqlIdentifier.fromCql(typeName)}. */ + public static Drop dropType(String typeName) { + return dropType(CqlIdentifier.fromCql(typeName)); + } + + /** Starts a DROP TYPE query for the given view name for the given type name. */ + public static Drop dropType(CqlIdentifier keyspace, CqlIdentifier typeName) { + return new DefaultDrop(keyspace, typeName, "TYPE"); + } + + /** + * Shortcut for {@link #dropType(CqlIdentifier,CqlIdentifier) + * dropType(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(typeName)}. + */ + public static Drop dropType(String keyspace, String typeName) { + return dropType(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(typeName)); + } + + /** + * Starts a CREATE INDEX query with no name. When this is used a name will be generated on the + * server side, usually with a format of tableName_idx_columnName. + */ + public static CreateIndexStart createIndex() { + return new DefaultCreateIndex(); + } + + /** Starts a CREATE INDEX query with the given name. */ + public static CreateIndexStart createIndex(CqlIdentifier indexName) { + return new DefaultCreateIndex(indexName); + } + + /** + * Shortcut for {@link #createIndex(CqlIdentifier) createIndex(CqlIdentifier.fromCql(indexName)}. + */ + public static CreateIndexStart createIndex(String indexName) { + return createIndex(CqlIdentifier.fromCql(indexName)); + } + + /** + * Starts a DROP INDEX query. This assumes the keyspace name is already qualified for the Session + * or Statement. + */ + public static Drop dropIndex(CqlIdentifier indexName) { + return new DefaultDrop(indexName, "INDEX"); + } + + /** Shortcut for {@link #dropIndex(CqlIdentifier) dropIndex(CqlIdentifier.fromCql(indexName)}. */ + public static Drop dropIndex(String indexName) { + return dropIndex(CqlIdentifier.fromCql(indexName)); + } + + /** Starts a DROP INDEX query for the given index for the given keyspace name. */ + public static Drop dropIndex(CqlIdentifier keyspace, CqlIdentifier indexName) { + return new DefaultDrop(keyspace, indexName, "INDEX"); + } + + /** + * Shortcut for {@link #dropIndex(CqlIdentifier, CqlIdentifier)} + * dropIndex(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(indexName)}. + */ + public static Drop dropIndex(String keyspace, String indexName) { + return dropIndex(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(indexName)); + } + + /** + * Starts a CREATE FUNCTION query with the given function name. This assumes the keyspace name is + * already qualified for the Session or Statement. + */ + public static CreateFunctionStart createFunction(CqlIdentifier functionName) { + return new DefaultCreateFunction(functionName); + } + + /** Starts a CREATE FUNCTION query with the given function name for the given keyspace name. */ + public static CreateFunctionStart createFunction( + CqlIdentifier keyspace, CqlIdentifier functionName) { + return new DefaultCreateFunction(keyspace, functionName); + } + /** + * Shortcut for {@link #createFunction(CqlIdentifier) + * createFunction(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(functionName)} + */ + public static CreateFunctionStart createFunction(String functionName) { + return new DefaultCreateFunction(CqlIdentifier.fromCql(functionName)); + } + /** + * Shortcut for {@link #createFunction(CqlIdentifier, CqlIdentifier) + * createFunction(CqlIdentifier.fromCql(keyspace, functionName)} + */ + public static CreateFunctionStart createFunction(String keyspace, String functionName) { + return new DefaultCreateFunction( + CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(functionName)); + } + + /** + * Starts a DROP FUNCTION query. This assumes the keyspace name is already qualified for the + * Session or Statement. + */ + public static Drop dropFunction(CqlIdentifier functionName) { + return new DefaultDrop(functionName, "FUNCTION"); + } + + /** Starts a DROP FUNCTION query for the given function name for the given keyspace name. */ + public static Drop dropFunction(CqlIdentifier keyspace, CqlIdentifier functionName) { + return new DefaultDrop(keyspace, functionName, "FUNCTION"); + } + + /** + * Shortcut for {@link #dropFunction(CqlIdentifier) + * dropFunction(CqlIdentifier.fromCql(functionName)}. + */ + public static Drop dropFunction(String functionName) { + return new DefaultDrop(CqlIdentifier.fromCql(functionName), "FUNCTION"); + } + + /** + * Shortcut for {@link #dropFunction(CqlIdentifier, CqlIdentifier) + * dropFunction(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(functionName)}. + */ + public static Drop dropFunction(String keyspace, String functionName) { + return new DefaultDrop( + CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(functionName), "FUNCTION"); + } + + /** + * Starts a CREATE AGGREGATE query with the given aggregate name. This assumes the keyspace name + * is already qualified for the Session or Statement. + */ + public static CreateAggregateStart createAggregate(CqlIdentifier aggregateName) { + return new DefaultCreateAggregate(aggregateName); + } + + /** Starts a CREATE AGGREGATE query with the given aggregate name for the given keyspace name. */ + public static CreateAggregateStart createAggregate( + CqlIdentifier keyspace, CqlIdentifier aggregateName) { + return new DefaultCreateAggregate(keyspace, aggregateName); + } + + /** + * Shortcut for {@link #createAggregate(CqlIdentifier) + * CreateAggregateStart(CqlIdentifier.fromCql(keyspaceName),CqlIdentifier.fromCql(aggregateName)} + */ + public static CreateAggregateStart createAggregate(String aggregateName) { + return new DefaultCreateAggregate(CqlIdentifier.fromCql(aggregateName)); + } + + /** + * Shortcut for {@link #createAggregate(CqlIdentifier, CqlIdentifier) + * CreateAggregateStart(CqlIdentifier.fromCql(keyspace, aggregateName)} + */ + public static CreateAggregateStart createAggregate(String keyspace, String aggregateName) { + return new DefaultCreateAggregate( + CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(aggregateName)); + } + + /** + * Starts an DROP AGGREGATE query. This assumes the keyspace name is already qualified for the + * Session or Statement. + */ + public static Drop dropAggregate(CqlIdentifier aggregateName) { + return new DefaultDrop(aggregateName, "AGGREGATE"); + } + + /** Starts an DROP AGGREGATE query for the given aggregate name for the given keyspace name. */ + public static Drop dropAggregate(CqlIdentifier keyspace, CqlIdentifier aggregateName) { + return new DefaultDrop(keyspace, aggregateName, "AGGREGATE"); + } + + /** + * Shortcut for {@link #dropAggregate(CqlIdentifier) + * dropAggregate(CqlIdentifier.fromCql(aggregateName)}. + */ + public static Drop dropAggregate(String aggregateName) { + return new DefaultDrop(CqlIdentifier.fromCql(aggregateName), "AGGREGATE"); + } + + /** + * Shortcut for {@link #dropAggregate(CqlIdentifier, CqlIdentifier) + * dropAggregate(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(aggregateName)}. + */ + public static Drop dropAggregate(String keyspace, String aggregateName) { + return new DefaultDrop( + CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(aggregateName), "AGGREGATE"); + } + + /** + * Compaction options for Size Tiered Compaction Strategy (STCS). + * + * @see CreateTableWithOptions#withCompaction(CompactionStrategy) + */ + public static SizeTieredCompactionStrategy sizeTieredCompactionStrategy() { + return new DefaultSizeTieredCompactionStrategy(); + } + + /** + * Compaction options for Leveled Compaction Strategy (LCS). + * + * @see CreateTableWithOptions#withCompaction(CompactionStrategy) + */ + public static LeveledCompactionStrategy leveledCompactionStrategy() { + return new DefaultLeveledCompactionStrategy(); + } + + /** + * Compaction options for Time Window Compaction Strategy (TWCS). + * + * @see CreateTableWithOptions#withCompaction(CompactionStrategy) + */ + public static TimeWindowCompactionStrategy timeWindowCompactionStrategy() { + return new DefaultTimeWindowCompactionStrategy(); + } + + /** + * Shortcut for creating a user-defined {@link DataType} for use in UDT and Table builder + * definitions, such as {@link CreateTable#withColumn(CqlIdentifier, DataType)}. + */ + public static UserDefinedType udt(CqlIdentifier name, boolean frozen) { + return new ShallowUserDefinedType(null, name, frozen); + } + + /** Shortcut for {@link #udt(CqlIdentifier,boolean) udt(CqlIdentifier.fromCql(name),frozen)}. */ + public static UserDefinedType udt(String name, boolean frozen) { + return udt(CqlIdentifier.fromCql(name), frozen); + } + + /** + * Specifies the rows_per_partition configuration for table caching options. + * + * @see RelationStructure#withCaching(boolean, RowsPerPartition) + */ + public static class RowsPerPartition { + + private final String value; + + private RowsPerPartition(String value) { + this.value = value; + } + + public static RowsPerPartition ALL = new RowsPerPartition("ALL"); + + public static RowsPerPartition NONE = new RowsPerPartition("NONE"); + + public static RowsPerPartition rows(int rowNumber) { + return new RowsPerPartition(Integer.toString(rowNumber)); + } + + public String getValue() { + return value; + } + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/Condition.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/Condition.java new file mode 100644 index 00000000000..5cf00aebec1 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/Condition.java @@ -0,0 +1,92 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.condition; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.CqlSnippet; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.internal.querybuilder.condition.DefaultConditionBuilder; +import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; +import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnLeftOperand; +import com.datastax.oss.driver.internal.querybuilder.lhs.FieldLeftOperand; + +/** + * A condition in a {@link ConditionalStatement}. + * + *

          To build instances of this type, use the factory methods, such as {@link #column(String) + * column}, {@link #field(String, String)} field}, etc. + * + *

          They are used as arguments to the {@link ConditionalStatement#if_(Iterable)} method, for + * example: + * + *

          {@code
          + * deleteFrom("foo").whereColumn("k").isEqualTo(bindMarker())
          + *     .if_(Condition.column("v").isEqualTo(literal(1)))
          + * // DELETE FROM foo WHERE k=? IF v=1
          + * }
          + * + * There are also shortcuts in the fluent API when you build a statement, for example: + * + *
          {@code
          + * deleteFrom("foo").whereColumn("k").isEqualTo(bindMarker())
          + *     .ifColumn("v").isEqualTo(literal(1))
          + * // DELETE FROM foo WHERE k=? IF v=1
          + * }
          + */ +public interface Condition extends CqlSnippet { + + /** Builds a condition on a column for a conditional statement, as in {@code DELETE... IF k=1}. */ + static ConditionBuilder column(CqlIdentifier columnId) { + return new DefaultConditionBuilder(new ColumnLeftOperand(columnId)); + } + + /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))}. */ + static ConditionBuilder column(String columnName) { + return column(CqlIdentifier.fromCql(columnName)); + } + + /** + * Builds a condition on a field in a UDT column for a conditional statement, as in {@code + * DELETE... IF address.street='test'}. + */ + static ConditionBuilder field(CqlIdentifier columnId, CqlIdentifier fieldId) { + return new DefaultConditionBuilder(new FieldLeftOperand(columnId, fieldId)); + } + + /** + * Shortcut for {@link #field(CqlIdentifier, CqlIdentifier) + * field(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName))}. + */ + static ConditionBuilder field(String columnName, String fieldName) { + return field(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName)); + } + + /** + * Builds a condition on an element in a collection column for a conditional statement, as in + * {@code DELETE... IF m[0]=1}. + */ + static ConditionBuilder element(CqlIdentifier columnId, Term index) { + return new DefaultConditionBuilder(new ColumnComponentLeftOperand(columnId, index)); + } + + /** + * Shortcut for {@link #element(CqlIdentifier, Term) element(CqlIdentifier.fromCql(columnName), + * index)}. + */ + static ConditionBuilder element(String columnName, Term index) { + return element(CqlIdentifier.fromCql(columnName), index); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionBuilder.java new file mode 100644 index 00000000000..35aae1708d2 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionBuilder.java @@ -0,0 +1,22 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.condition; + +import com.datastax.oss.driver.api.querybuilder.relation.ArithmeticRelationBuilder; +import com.datastax.oss.driver.api.querybuilder.relation.InRelationBuilder; + +public interface ConditionBuilder + extends ArithmeticRelationBuilder, InRelationBuilder {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java new file mode 100644 index 00000000000..8d1ff67212d --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java @@ -0,0 +1,151 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.condition; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.internal.querybuilder.condition.DefaultConditionBuilder; +import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; +import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnLeftOperand; +import com.datastax.oss.driver.internal.querybuilder.lhs.FieldLeftOperand; +import java.util.Arrays; + +/** + * A statement that can be applied conditionally, such as UPDATE or DELETE. + * + *

          Not that this does not covert INSERT... IF NOT EXISTS, which is handled separately. + */ +public interface ConditionalStatement> { + + /** + * Adds an IF EXISTS condition. + * + *

          If any column conditions were added before, they will be cleared. + */ + SelfT ifExists(); + + /** + * Adds an IF condition. All conditions are logically joined with AND. If {@link #ifExists()} was + * invoked on this statement before, it will get cancelled. + * + *

          To create the argument, use one of the factory methods in {@link Condition}, for example + * {@link Condition#column(CqlIdentifier) column}. + * + *

          If you add multiple conditions as once, consider {@link #if_(Iterable)} as a more efficient + * alternative. + */ + SelfT if_(Condition condition); + + /** + * Adds multiple IF conditions at once. All conditions are logically joined with AND. If {@link + * #ifExists()} was invoked on this statement before, it will get cancelled. + * + *

          This is slightly more efficient than adding the relations one by one (since the underlying + * implementation of this object is immutable). + * + *

          To create the arguments, use one of the factory methods in {@link Condition}, for example + * {@link Condition#column(CqlIdentifier) column}. + */ + SelfT if_(Iterable conditions); + + /** Var-arg equivalent of {@link #if_(Iterable)}. */ + default SelfT if_(Condition... conditions) { + return if_(Arrays.asList(conditions)); + } + + /** + * Adds an IF condition on a simple column, as in {@code DELETE... IF k=1}. + * + *

          This is the equivalent of creating a condition with {@link Condition#column(CqlIdentifier)} + * and passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifColumn(CqlIdentifier columnId) { + return new DefaultConditionBuilder.Fluent<>(this, new ColumnLeftOperand(columnId)); + } + + /** + * Shortcut for {@link #ifColumn(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))}. + * + *

          This is the equivalent of creating a condition with {@link Condition#column(String)} and + * passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifColumn(String columnName) { + return ifColumn(CqlIdentifier.fromCql(columnName)); + } + + /** + * Adds an IF condition on a field in a UDT column for a conditional statement, as in {@code + * DELETE... IF address.street='test'}. + * + *

          This is the equivalent of creating a condition with {@link Condition#field(CqlIdentifier, + * CqlIdentifier)} and passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifField(CqlIdentifier columnId, CqlIdentifier fieldId) { + return new DefaultConditionBuilder.Fluent<>(this, new FieldLeftOperand(columnId, fieldId)); + } + + /** + * Shortcut for {@link #ifField(CqlIdentifier, CqlIdentifier) + * field(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName))}. + * + *

          This is the equivalent of creating a condition with {@link Condition#field(String, String)} + * and passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifField(String columnName, String fieldName) { + return ifField(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName)); + } + + /** + * Adds an IF condition on an element in a collection column for a conditional statement, as in + * {@code DELETE... IF m[0]=1}. + * + *

          This is the equivalent of creating a condition with {@link Condition#element(CqlIdentifier, + * Term)} and passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifElement(CqlIdentifier columnId, Term index) { + return new DefaultConditionBuilder.Fluent<>( + this, new ColumnComponentLeftOperand(columnId, index)); + } + + /** + * Shortcut for {@link #ifElement(CqlIdentifier, Term) element(CqlIdentifier.fromCql(columnName), + * index)}. + * + *

          This is the equivalent of creating a condition with {@link Condition#element(String, Term)} + * and passing it to {@link #if_(Condition)}. + */ + default ConditionBuilder ifElement(String columnName, Term index) { + return ifElement(CqlIdentifier.fromCql(columnName), index); + } + + /** + * Adds a raw CQL snippet as a condition. + * + *

          This is the equivalent of creating a condition with {@link QueryBuilderDsl#raw(String)} and + * passing it to {@link #if_(Condition)}. + * + *

          The contents will be appended to the query as-is, without any syntax checking or escaping. + * This method should be used with caution, as it's possible to generate invalid CQL that will + * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL + * features that are not yet covered by the query builder. + * + * @see QueryBuilderDsl#raw(String) + */ + default SelfT ifRaw(String raw) { + return if_(QueryBuilderDsl.raw(raw)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/Delete.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/Delete.java new file mode 100644 index 00000000000..bbd9aac0058 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/Delete.java @@ -0,0 +1,24 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.delete; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement; +import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; + +/** A complete DELETE statement, with at least one WHERE clause. */ +public interface Delete + extends OngoingWhereClause, ConditionalStatement, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java new file mode 100644 index 00000000000..5bcfd945221 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java @@ -0,0 +1,162 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.delete; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.BindMarker; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import java.util.Arrays; + +/** + * An in-progress DELETE statement: it targets a table and optionally a list of columns to delete; + * it needs at least one WHERE relation to become buildable. + */ +public interface DeleteSelection extends OngoingWhereClause { + + /** + * Adds a selector. + * + *

          To create the argument, use one of the factory methods in {@link Selector}, for example + * {@link Selector#column(CqlIdentifier) column}. This type also provides shortcuts to create and + * add the selector in one call, for example {@link #column(CqlIdentifier)} for {@code + * selector(column(...))}. + * + *

          Note that the only valid arguments for DELETE are a column, a field in a UDT column (nested + * UDTs are not supported), and an element in a collection column (nested collections are not + * supported). + * + *

          If you add multiple selectors as once, consider {@link #selectors(Iterable)} as a more + * efficient alternative. + */ + DeleteSelection selector(Selector selector); + + /** + * Adds multiple selectors at once. + * + *

          This is slightly more efficient than adding the selectors one by one (since the underlying + * implementation of this object is immutable). + * + *

          To create the arguments, use one of the factory methods in {@link Selector}, for example + * {@link Selector#column(CqlIdentifier) column}. + * + *

          Note that the only valid arguments for DELETE are a column, a field in a UDT column (nested + * UDTs are not supported), and an element in a collection column (nested collections are not + * supported). + * + * @see #selector(Selector) + */ + DeleteSelection selectors(Iterable additionalSelectors); + + /** Var-arg equivalent of {@link #selectors(Iterable)}. */ + default DeleteSelection selectors(Selector... additionalSelectors) { + return selectors(Arrays.asList(additionalSelectors)); + } + + /** + * Deletes a particular column by its CQL identifier. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.column(columnId))}. + * + * @see Selector#column(CqlIdentifier) + */ + default DeleteSelection column(CqlIdentifier columnId) { + return selector(Selector.column(columnId)); + } + + /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))} */ + default DeleteSelection column(String columnName) { + return column(CqlIdentifier.fromCql(columnName)); + } + + /** + * Deletes a field inside of a UDT column, as in {@code DELETE user.name}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.field(udtColumnId, + * fieldId))}. + * + * @see Selector#field(CqlIdentifier, CqlIdentifier) + */ + default DeleteSelection field(CqlIdentifier udtColumnId, CqlIdentifier fieldId) { + return selector(Selector.field(udtColumnId, fieldId)); + } + + /** + * Shortcut for {@link #field(CqlIdentifier, CqlIdentifier) + * field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName))}. + * + * @see Selector#field(String, String) + */ + default DeleteSelection field(String udtColumnName, String fieldName) { + return field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName)); + } + + /** + * Deletes an element in a collection column, as in {@code DELETE m['key']}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.element(collectionId, + * index))}. + * + * @see Selector#element(CqlIdentifier, Term) + */ + default DeleteSelection element(CqlIdentifier collectionId, Term index) { + return selector(Selector.element(collectionId, index)); + } + + /** + * Shortcut for {@link #element(CqlIdentifier, Term) + * element(CqlIdentifier.fromCql(collectionName), index)}. + * + * @see Selector#element(String, Term) + */ + default DeleteSelection element(String collectionName, Term index) { + return element(CqlIdentifier.fromCql(collectionName), index); + } + + /** + * Specifies an element to delete as a raw CQL snippet. + * + *

          This is a shortcut for {@link #selector(Selector) selector(QueryBuilderDsl.raw(raw))}. + * + *

          The contents will be appended to the query as-is, without any syntax checking or escaping. + * This method should be used with caution, as it's possible to generate invalid CQL that will + * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL + * features that are not yet covered by the query builder. + * + * @see QueryBuilderDsl#raw(String) + */ + default DeleteSelection raw(String raw) { + return selector(QueryBuilderDsl.raw(raw)); + } + + /** + * Adds a USING TIMESTAMP clause to this statement with a literal value. + * + *

          If this method or {@link #usingTimestamp(BindMarker)} is called multiple times, the last + * value is used. + */ + DeleteSelection usingTimestamp(long timestamp); + + /** + * Adds a USING TIMESTAMP clause to this statement with a bind marker. + * + *

          If this method or {@link #usingTimestamp(long)} is called multiple times, the last value is + * used. + */ + DeleteSelection usingTimestamp(BindMarker bindMarker); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java new file mode 100644 index 00000000000..f195dad4c10 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java @@ -0,0 +1,42 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.insert; + +import com.datastax.oss.driver.api.querybuilder.BindMarker; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +/** A complete INSERT statement that is ready to be built. */ +public interface Insert extends BuildableQuery { + + /** Adds an IF NOT EXISTS clause to this statement. */ + Insert ifNotExists(); + + /** + * Adds a USING TIMESTAMP clause to this statement with a literal value. + * + *

          If this method or {@link #usingTimestamp(BindMarker)} is called multiple times, the last + * value is used. + */ + Insert usingTimestamp(long timestamp); + + /** + * Adds a USING TIMESTAMP clause to this statement with a bind marker. + * + *

          If this method or {@link #usingTimestamp(long)} is called multiple times, the last value is + * used. + */ + Insert usingTimestamp(BindMarker bindMarker); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java new file mode 100644 index 00000000000..abcd9957b8a --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java @@ -0,0 +1,31 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.insert; + +import com.datastax.oss.driver.api.querybuilder.BindMarker; + +/** + * The beginning of an INSERT statement; at this point only the table is known, it might become a + * JSON insert or a regular one, depending on which method is called next. + */ +public interface InsertInto extends OngoingValues { + + /** Makes this statement an INSERT JSON with the provided JSON string. */ + JsonInsert json(String json); + + /** Makes this statement an INSERT JSON with a bind marker, as in {@code INSERT JSON ?}. */ + JsonInsert json(BindMarker bindMarker); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsert.java new file mode 100644 index 00000000000..d901f79d512 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsert.java @@ -0,0 +1,34 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.insert; + +/** An INSERT JSON statement. */ +public interface JsonInsert extends Insert { + + /** + * Adds a DEFAULT NULL clause to this statement. + * + *

          If this or {@link #defaultUnset()} is called multiple times, the last value is used. + */ + JsonInsert defaultNull(); + + /** + * Adds a DEFAULT UNSET clause to this statement. + * + *

          If this or {@link #defaultNull()} is called multiple times, the last value is used. + */ + JsonInsert defaultUnset(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/OngoingValues.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/OngoingValues.java new file mode 100644 index 00000000000..7babfd7c34d --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/OngoingValues.java @@ -0,0 +1,38 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.insert; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.term.Term; + +public interface OngoingValues { + + /** + * Sets a value for a column, as in {@code INSERT INTO ... (c) VALUES (?)}. + * + *

          If this is called twice for the same column, the previous entry is discarded and the new + * entry will remain at its current position. + */ + RegularInsert value(CqlIdentifier columnId, Term value); + + /** + * Shortcut for {@link #value(CqlIdentifier, Term) value(CqlIdentifier.fromCql(columnName), + * value)}. + */ + default RegularInsert value(String columnName, Term value) { + return value(CqlIdentifier.fromCql(columnName), value); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsert.java new file mode 100644 index 00000000000..93878a2569e --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsert.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.insert; + +/** A regular (not JSON) INSERT statement. */ +public interface RegularInsert extends OngoingValues, Insert {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java new file mode 100644 index 00000000000..2d010e5da7b --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +import com.datastax.oss.driver.api.querybuilder.term.Term; + +public interface ArithmeticRelationBuilder { + + /** + * Builds an '=' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isEqualTo(Term rightOperand) { + return build("=", rightOperand); + } + + /** + * Builds a '<' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isLessThan(Term rightOperand) { + return build("<", rightOperand); + } + + /** + * Builds a '<=' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isLessThanOrEqualTo(Term rightOperand) { + return build("<=", rightOperand); + } + + /** + * Builds a '>' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isGreaterThan(Term rightOperand) { + return build(">", rightOperand); + } + + /** + * Builds a '>=' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isGreaterThanOrEqualTo(Term rightOperand) { + return build(">=", rightOperand); + } + + /** + * Builds a '!=' relation with the given term. + * + *

          Use one of the static factory method in {@link Term} to create the argument. + */ + default ResultT isNotEqualTo(Term rightOperand) { + return build("!=", rightOperand); + } + + ResultT build(String operator, Term rightOperand); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnComponentRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnComponentRelationBuilder.java new file mode 100644 index 00000000000..9d83da4b6fa --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnComponentRelationBuilder.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +public interface ColumnComponentRelationBuilder + extends ArithmeticRelationBuilder {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnRelationBuilder.java new file mode 100644 index 00000000000..90168fd21e5 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ColumnRelationBuilder.java @@ -0,0 +1,42 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +import com.datastax.oss.driver.api.querybuilder.term.Term; + +public interface ColumnRelationBuilder + extends ArithmeticRelationBuilder, InRelationBuilder { + + /** Builds a LIKE relation for the column. */ + default ResultT like(Term term) { + return build(" LIKE ", term); + } + + /** Builds an IS NOT NULL relation for the column. */ + default ResultT isNotNull() { + return build(" IS NOT NULL", null); + } + + /** Builds a CONTAINS relation for the column. */ + default ResultT contains(Term term) { + return build(" CONTAINS ", term); + } + + /** Builds a CONTAINS KEY relation for the column. */ + default ResultT containsKey(Term term) { + return build(" CONTAINS KEY ", term); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java new file mode 100644 index 00000000000..5f1151e56d4 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +import com.datastax.oss.driver.api.querybuilder.BindMarker; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import java.util.Arrays; + +public interface InRelationBuilder { + + /** + * Builds an IN relation where the whole set of possible values is a bound variable, as in {@code + * IN ?}. + */ + default ResultT in(BindMarker bindMarker) { + return build(" IN ", bindMarker); + } + + /** + * Builds an IN relation where the arguments are the possible values, as in {@code IN (term1, + * term2...)}. + */ + default ResultT in(Iterable alternatives) { + return build(" IN ", QueryBuilderDsl.tuple(alternatives)); + } + + /** Var-arg equivalent of {@link #in(Iterable)} . */ + default ResultT in(Term... alternatives) { + return in(Arrays.asList(alternatives)); + } + + ResultT build(String operator, Term rightOperand); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/MultiColumnRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/MultiColumnRelationBuilder.java new file mode 100644 index 00000000000..03d52b6a787 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/MultiColumnRelationBuilder.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +public interface MultiColumnRelationBuilder + extends ArithmeticRelationBuilder, InRelationBuilder {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java new file mode 100644 index 00000000000..3315a9e9e39 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java @@ -0,0 +1,226 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.internal.querybuilder.DefaultRaw; +import com.datastax.oss.driver.internal.querybuilder.relation.CustomIndexRelation; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnComponentRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultMultiColumnRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultTokenRelationBuilder; +import com.google.common.collect.Iterables; +import java.util.Arrays; + +/** A statement that is ready to accept relations in its WHERE clause. */ +public interface OngoingWhereClause> { + + /** + * Adds a relation in the WHERE clause. All relations are logically joined with AND. + * + *

          To create the argument, use one of the factory methods in {@link Relation}, for example + * {@link Relation#column(CqlIdentifier) column}. + * + *

          If you add multiple selectors as once, consider {@link #where(Iterable)} as a more efficient + * alternative. + */ + SelfT where(Relation relation); + + /** + * Adds multiple relations at once. All relations are logically joined with AND. + * + *

          This is slightly more efficient than adding the relations one by one (since the underlying + * implementation of this object is immutable). + * + *

          To create the arguments, use one of the factory methods in {@link Relation}, for example + * {@link Relation#column(CqlIdentifier) column}. + * + * @see #where(Relation) + */ + SelfT where(Iterable additionalRelations); + + /** Var-arg equivalent of {@link #where(Iterable)}. */ + default SelfT where(Relation... additionalRelations) { + return where(Arrays.asList(additionalRelations)); + } + + /** + * Adds a relation testing a column. + * + *

          This must be chained with an operator call, for example: + * + *

          {@code
          +   * selectFrom("foo").all().whereColumn("k").isEqualTo(bindMarker());
          +   * }
          + * + * This is the equivalent of creating a relation with {@link Relation#column(CqlIdentifier)} and + * passing it to {@link #where(Relation)}. + */ + default ColumnRelationBuilder whereColumn(CqlIdentifier id) { + return new DefaultColumnRelationBuilder.Fluent<>(this, id); + } + + /** + * Shortcut for {@link #whereColumn(CqlIdentifier) whereColumn(CqlIdentifier.fromCql(name))}. + * + *

          This is the equivalent of creating a relation with {@link Relation#column(String)} and + * passing it to {@link #where(Relation)}. + */ + default ColumnRelationBuilder whereColumn(String name) { + return whereColumn(CqlIdentifier.fromCql(name)); + } + + /** + * Adds a relation testing a value in a map (Cassandra 4 and above). + * + *

          This is the equivalent of creating a relation with {@link Relation#mapValue(CqlIdentifier, + * Term)} and passing it to {@link #where(Relation)}. + */ + default ColumnComponentRelationBuilder whereMapValue(CqlIdentifier columnId, Term index) { + return new DefaultColumnComponentRelationBuilder.Fluent<>(this, columnId, index); + } + + /** + * Shortcut for {@link #whereMapValue(CqlIdentifier, Term) + * whereMapValue(CqlIdentifier.fromCql(columnName), index)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#mapValue(String, Term)} + * and passing it to {@link #where(Relation)}. + */ + default ColumnComponentRelationBuilder whereMapValue(String columnName, Term index) { + return whereMapValue(CqlIdentifier.fromCql(columnName), index); + } + + /** + * Adds a relation testing a token generated from a set of columns. + * + *

          This is the equivalent of creating a relation with {@link Relation#tokenFromIds(Iterable)} + * and passing it to {@link #where(Relation)}. + */ + default TokenRelationBuilder whereTokenFromIds(Iterable identifiers) { + return new DefaultTokenRelationBuilder.Fluent<>(this, identifiers); + } + + /** + * Var-arg equivalent of {@link #whereTokenFromIds(Iterable)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#token(CqlIdentifier...)} + * and passing it to {@link #where(Relation)}. + */ + default TokenRelationBuilder whereToken(CqlIdentifier... identifiers) { + return whereTokenFromIds(Arrays.asList(identifiers)); + } + + /** + * Equivalent of {@link #whereTokenFromIds(Iterable)} with raw strings; the names are converted + * with {@link CqlIdentifier#fromCql(String)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#token(Iterable)} and + * passing it to {@link #where(Relation)}. + */ + default TokenRelationBuilder whereToken(Iterable names) { + return whereTokenFromIds(Iterables.transform(names, CqlIdentifier::fromCql)); + } + + /** + * Var-arg equivalent of {@link #whereToken(Iterable)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#token(String...)} and + * passing it to {@link #where(Relation)}. + */ + default TokenRelationBuilder whereToken(String... names) { + return whereToken(Arrays.asList(names)); + } + + /** + * Adds a multi-column relation, as in {@code WHERE (c1, c2, c3) IN ...}. + * + *

          This is the equivalent of creating a relation with {@link Relation#columnIds(Iterable)} and + * passing it to {@link #where(Relation)}. + */ + default MultiColumnRelationBuilder whereColumnIds(Iterable identifiers) { + return new DefaultMultiColumnRelationBuilder.Fluent<>(this, identifiers); + } + + /** + * Var-arg equivalent of {@link #whereColumnIds(Iterable)}. + * + *

          This is the equivalent of creating a relation with {@link + * Relation#columns(CqlIdentifier...)} and passing it to {@link #where(Relation)}. + */ + default MultiColumnRelationBuilder whereColumns(CqlIdentifier... identifiers) { + return whereColumnIds(Arrays.asList(identifiers)); + } + + /** + * Equivalent of {@link #whereColumnIds(Iterable)} with raw strings; the names are converted with + * {@link CqlIdentifier#fromCql(String)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#columns(Iterable)} and + * passing it to {@link #where(Relation)}. + */ + default MultiColumnRelationBuilder whereColumns(Iterable names) { + return whereColumnIds(Iterables.transform(names, CqlIdentifier::fromCql)); + } + + /** + * Var-arg equivalent of {@link #whereColumns(Iterable)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#columns(String...)} and + * passing it to {@link #where(Relation)}. + */ + default MultiColumnRelationBuilder whereColumns(String... names) { + return whereColumns(Arrays.asList(names)); + } + + /** + * Adds a relation on a custom index. + * + *

          This is the equivalent of creating a relation with {@link + * Relation#customIndex(CqlIdentifier, Term)} and passing it to {@link #where(Relation)}. + */ + default SelfT whereCustomIndex(CqlIdentifier indexId, Term expression) { + return where(new CustomIndexRelation(indexId, expression)); + } + + /** + * Shortcut for {@link #whereCustomIndex(CqlIdentifier, Term) + * whereCustomIndex(CqlIdentifier.fromCql(indexName), expression)}. + * + *

          This is the equivalent of creating a relation with {@link Relation#customIndex(String, + * Term)} and passing it to {@link #where(Relation)}. + */ + default SelfT whereCustomIndex(String indexName, Term expression) { + return whereCustomIndex(CqlIdentifier.fromCql(indexName), expression); + } + + /** + * Adds a raw CQL snippet as a relation. + * + *

          This is the equivalent of creating a relation with {@link QueryBuilderDsl#raw(String)} and + * passing it to {@link #where(Relation)}. + * + *

          The contents will be appended to the query as-is, without any syntax checking or escaping. + * This method should be used with caution, as it's possible to generate invalid CQL that will + * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL + * features that are not yet covered by the query builder. + */ + default SelfT whereRaw(String raw) { + return where(new DefaultRaw(raw)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java new file mode 100644 index 00000000000..146e936fac1 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java @@ -0,0 +1,159 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.CqlSnippet; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.internal.querybuilder.relation.CustomIndexRelation; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnComponentRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultMultiColumnRelationBuilder; +import com.datastax.oss.driver.internal.querybuilder.relation.DefaultTokenRelationBuilder; +import com.google.common.collect.Iterables; +import java.util.Arrays; + +/** + * A relation in a WHERE clause. + * + *

          To build instances of this type, use the factory methods, such as {@link #column(String) + * column}, {@link #token(String...) token}, etc. + * + *

          They are used as arguments to the {@link OngoingWhereClause#where(Iterable) where} method, for + * example: + * + *

          {@code
          + * selectFrom("foo").all().whereColumn("k").isEqualTo(literal(1))
          + * // SELECT * FROM foo WHERE k=1
          + * }
          + * + * There are also shortcuts in the fluent API when you build a statement, for example: + * + *
          {@code
          + * selectFrom("foo").all().whereColumn("k").isEqualTo(literal(1))
          + * // SELECT * FROM foo WHERE k=1
          + * }
          + */ +public interface Relation extends CqlSnippet { + + /** + * Builds a relation testing a column. + * + *

          This must be chained with an operator call, for example: + * + *

          {@code
          +   * Relation r = Relation.column("k").isEqualTo(bindMarker());
          +   * }
          + */ + static ColumnRelationBuilder column(CqlIdentifier id) { + return new DefaultColumnRelationBuilder(id); + } + + /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(name))} */ + static ColumnRelationBuilder column(String name) { + return column(CqlIdentifier.fromCql(name)); + } + + /** Builds a relation testing a value in a map (Cassandra 4 and above). */ + static ColumnComponentRelationBuilder mapValue(CqlIdentifier columnId, Term index) { + // The concept could easily be extended to list elements and tuple components, so use a generic + // name internally, we'll add other shortcuts if necessary. + return new DefaultColumnComponentRelationBuilder(columnId, index); + } + + /** + * Shortcut for {@link #mapValue(CqlIdentifier, Term) mapValue(CqlIdentifier.fromCql(columnName), + * index)} + */ + static ColumnComponentRelationBuilder mapValue(String columnName, Term index) { + return mapValue(CqlIdentifier.fromCql(columnName), index); + } + + /** Builds a relation testing a token generated from a set of columns. */ + static TokenRelationBuilder tokenFromIds(Iterable identifiers) { + return new DefaultTokenRelationBuilder(identifiers); + } + + /** Var-arg equivalent of {@link #tokenFromIds(Iterable)}. */ + static TokenRelationBuilder token(CqlIdentifier... identifiers) { + return tokenFromIds(Arrays.asList(identifiers)); + } + + /** + * Equivalent of {@link #tokenFromIds(Iterable)} with raw strings; the names are converted with + * {@link CqlIdentifier#fromCql(String)}. + */ + static TokenRelationBuilder token(Iterable names) { + return tokenFromIds(Iterables.transform(names, CqlIdentifier::fromCql)); + } + + /** Var-arg equivalent of {@link #token(Iterable)}. */ + static TokenRelationBuilder token(String... names) { + return token(Arrays.asList(names)); + } + + /** Builds a multi-column relation, as in {@code WHERE (c1, c2, c3) IN ...}. */ + static MultiColumnRelationBuilder columnIds(Iterable identifiers) { + return new DefaultMultiColumnRelationBuilder(identifiers); + } + + /** Var-arg equivalent of {@link #columnIds(Iterable)}. */ + static MultiColumnRelationBuilder columns(CqlIdentifier... identifiers) { + return columnIds(Arrays.asList(identifiers)); + } + + /** + * Equivalent of {@link #columnIds(Iterable)} with raw strings; the names are converted with + * {@link CqlIdentifier#fromCql(String)}. + */ + static MultiColumnRelationBuilder columns(Iterable names) { + return columnIds(Iterables.transform(names, CqlIdentifier::fromCql)); + } + + /** Var-arg equivalent of {@link #columns(Iterable)}. */ + static MultiColumnRelationBuilder columns(String... names) { + return columns(Arrays.asList(names)); + } + + /** Builds a relation on a custom index. */ + static Relation customIndex(CqlIdentifier indexId, Term expression) { + return new CustomIndexRelation(indexId, expression); + } + + /** + * Shortcut for {@link #customIndex(CqlIdentifier, Term) + * customIndex(CqlIdentifier.fromCql(indexName), expression)} + */ + static Relation customIndex(String indexName, Term expression) { + return customIndex(CqlIdentifier.fromCql(indexName), expression); + } + + /** + * Whether this relation is idempotent. + * + *

          That is, whether it always selects the same rows when used multiple times. For example, + * {@code WHERE c=1} is idempotent, {@code WHERE c=now()} isn't. + * + *

          This is used internally by the query builder to compute the {@link Statement#isIdempotent()} + * flag on the UPDATE and DELETE statements generated by {@link BuildableQuery#build()} (this is + * not relevant for SELECT statement, which are always idempotent). If a term is ambiguous (for + * example a raw snippet or a call to a user function in the right operands), the builder is + * pessimistic and assumes the term is not idempotent. + */ + boolean isIdempotent(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/TokenRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/TokenRelationBuilder.java new file mode 100644 index 00000000000..291e953c9c5 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/TokenRelationBuilder.java @@ -0,0 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.relation; + +public interface TokenRelationBuilder extends ArithmeticRelationBuilder {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspace.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspace.java new file mode 100644 index 00000000000..0394fc1f0c8 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspace.java @@ -0,0 +1,23 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterKeyspace + extends BuildableQuery, + KeyspaceOptions, + KeyspaceReplicationOptions {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceStart.java new file mode 100644 index 00000000000..b10f7fb7c1c --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceStart.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface AlterKeyspaceStart + extends KeyspaceOptions, KeyspaceReplicationOptions {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedView.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedView.java new file mode 100644 index 00000000000..e28001f7701 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedView.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterMaterializedView + extends RelationOptions, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewStart.java new file mode 100644 index 00000000000..6644e790c23 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewStart.java @@ -0,0 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface AlterMaterializedViewStart extends RelationOptions {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java new file mode 100644 index 00000000000..89667a9efb6 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java @@ -0,0 +1,55 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface AlterTableAddColumn { + /** + * Adds a column definition in the ALTER TABLE statement. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + AlterTableAddColumnEnd addColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #addColumn(CqlIdentifier, DataType) + * addColumn(CqlIdentifier.asCql(columnName), dataType)}. + */ + default AlterTableAddColumnEnd addColumn(String columnName, DataType dataType) { + return addColumn(CqlIdentifier.fromCql(columnName), dataType); + } + + /** + * Adds a static column definition in the ALTER TABLE statement. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + AlterTableAddColumnEnd addStaticColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #addStaticColumn(CqlIdentifier, DataType) + * addStaticColumn(CqlIdentifier.asCql(columnName), dataType)}. + */ + default AlterTableAddColumnEnd addStaticColumn(String columnName, DataType dataType) { + return addStaticColumn(CqlIdentifier.fromCql(columnName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumnEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumnEnd.java new file mode 100644 index 00000000000..db6908122da --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumnEnd.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterTableAddColumnEnd extends AlterTableAddColumn, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumn.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumn.java new file mode 100644 index 00000000000..a3dfa551f54 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumn.java @@ -0,0 +1,50 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface AlterTableDropColumn { + /** + * Adds column(s) to drop to ALTER TABLE specification. This may be repeated with successive calls + * to drop columns. + */ + AlterTableDropColumnEnd dropColumns(CqlIdentifier... columnNames); + + /** Shortcut for {@link #dropColumns(CqlIdentifier...)}. */ + default AlterTableDropColumnEnd dropColumns(String... columnNames) { + CqlIdentifier ids[] = new CqlIdentifier[columnNames.length]; + for (int i = 0; i < columnNames.length; i++) { + ids[i] = CqlIdentifier.fromCql(columnNames[i]); + } + return dropColumns(ids); + } + + /** + * Adds a column to drop to ALTER TABLE specification. This may be repeated with successive calls + * to drop columns. Shortcut for {@link #dropColumns(CqlIdentifier...) #dropColumns(columnName)}. + */ + default AlterTableDropColumnEnd dropColumn(CqlIdentifier columnName) { + return dropColumns(columnName); + } + + /** + * Shortcut for {@link #dropColumn(CqlIdentifier) dropColumn(CqlIdentifier.fromCql(columnName))}. + */ + default AlterTableDropColumnEnd dropColumn(String columnName) { + return dropColumns(CqlIdentifier.fromCql(columnName)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumnEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumnEnd.java new file mode 100644 index 00000000000..cab0ba75032 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableDropColumnEnd.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterTableDropColumnEnd extends AlterTableDropColumn, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumn.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumn.java new file mode 100644 index 00000000000..85cc851d880 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumn.java @@ -0,0 +1,35 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface AlterTableRenameColumn { + + /** + * Adds a column rename to ALTER TABLE specification. This may be repeated with successive calls + * to rename columns. + */ + AlterTableRenameColumnEnd renameColumn(CqlIdentifier from, CqlIdentifier to); + + /** + * Shortcut for {@link #renameColumn(CqlIdentifier,CqlIdentifier) + * renameField(CqlIdentifier.fromCql(from),CqlIdentifier.fromCql(to))}. + */ + default AlterTableRenameColumnEnd renameColumn(String from, String to) { + return renameColumn(CqlIdentifier.fromCql(from), CqlIdentifier.fromCql(to)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumnEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumnEnd.java new file mode 100644 index 00000000000..40580000927 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableRenameColumnEnd.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterTableRenameColumnEnd extends AlterTableRenameColumn, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java new file mode 100644 index 00000000000..cf75c489853 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java @@ -0,0 +1,48 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface AlterTableStart + extends AlterTableWithOptions, + AlterTableAddColumn, + AlterTableDropColumn, + AlterTableRenameColumn { + + /** Completes ALTER TABLE specifying that compact storage should be removed from the table. */ + BuildableQuery dropCompactStorage(); + + /** + * Completes ALTER TABLE specifying the the type of a column should be changed. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + BuildableQuery alterColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #alterColumn(CqlIdentifier,DataType) + * alterColumn(CqlIdentifier.fromCql(columnName,dataType)}. + */ + default BuildableQuery alterColumn(String columnName, DataType dataType) { + return alterColumn(CqlIdentifier.fromCql(columnName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.java new file mode 100644 index 00000000000..ab09de5c25c --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.java @@ -0,0 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface AlterTableWithOptions extends RelationOptions {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptionsEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptionsEnd.java new file mode 100644 index 00000000000..4c442713b71 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptionsEnd.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterTableWithOptionsEnd + extends RelationOptions, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameField.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameField.java new file mode 100644 index 00000000000..29205114a9a --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameField.java @@ -0,0 +1,35 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface AlterTypeRenameField { + + /** + * Adds a field rename to ALTER TYPE specification. This may be repeated with successive calls to + * rename fields. + */ + AlterTypeRenameFieldEnd renameField(CqlIdentifier from, CqlIdentifier to); + + /** + * Shortcut for {@link #renameField(CqlIdentifier,CqlIdentifier) + * renameField(CqlIdentifier.fromCql(from),CqlIdentifier.fromCql(to))}. + */ + default AlterTypeRenameFieldEnd renameField(String from, String to) { + return renameField(CqlIdentifier.fromCql(from), CqlIdentifier.fromCql(to)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameFieldEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameFieldEnd.java new file mode 100644 index 00000000000..0104b7c0445 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeRenameFieldEnd.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface AlterTypeRenameFieldEnd extends AlterTypeRenameField, BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java new file mode 100644 index 00000000000..1f5b9f70a44 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java @@ -0,0 +1,57 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface AlterTypeStart extends AlterTypeRenameField { + + /** + * Completes ALTER TYPE specifying the the type of a field should be changed. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + BuildableQuery alterField(CqlIdentifier fieldName, DataType dataType); + + /** + * Shortcut for {@link #alterField(CqlIdentifier,DataType) + * alterField(CqlIdentifier.fromCql(columnName,dataType)}. + */ + default BuildableQuery alterField(String fieldName, DataType dataType) { + return alterField(CqlIdentifier.fromCql(fieldName), dataType); + } + + /** + * Completes ALTER TYPE by adding a field definition in the ALTER TYPE statement. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + BuildableQuery addField(CqlIdentifier fieldName, DataType dataType); + + /** + * Shortcut for {@link #addField(CqlIdentifier, DataType) addField(CqlIdentifier.asCql(fieldName), + * dataType)}. + */ + default BuildableQuery addField(String fieldName, DataType dataType) { + return addField(CqlIdentifier.fromCql(fieldName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateEnd.java new file mode 100644 index 00000000000..d0e3e2751bc --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateEnd.java @@ -0,0 +1,43 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.term.Term; + +public interface CreateAggregateEnd extends BuildableQuery { + + /** + * Adds INITCOND to the aggregate query. Defines the initial condition, values, of the first + * parameter in the SFUNC. + */ + CreateAggregateEnd withInitCond(Term term); + + /** + * Adds FINALFUNC to the create aggregate query. This is used to specify what type is returned + * from the state function. + */ + CreateAggregateEnd withFinalFunc(CqlIdentifier finalFunc); + + /** + * Shortcut for {@link #withFinalFunc(CqlIdentifier) + * withFinalFunc(CqlIdentifier.fromCql(finalFuncName))}. + */ + default CreateAggregateEnd withFinalFunc(String finalFuncName) { + return withFinalFunc(CqlIdentifier.fromCql(finalFuncName)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java new file mode 100644 index 00000000000..6cea70d4433 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java @@ -0,0 +1,53 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface CreateAggregateStart { + /** + * Adds IF NOT EXISTS to the create aggregate specification. This indicates that the aggregate + * should not be created if it already exists. + */ + CreateAggregateStart ifNotExists(); + + /** + * Adds OR REPLACE to the create aggregate specification. This indicates that the aggregate should + * replace an existing aggregate with the same name if it exists. + */ + CreateAggregateStart orReplace(); + + /** + * Adds a parameter definition in the CREATE AGGREGATE statement. + * + *

          Parameter keys are added in the order of their declaration. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateAggregateStart withParameter(DataType paramType); + + /** Adds SFUNC to the create aggregate specification. This is the state function for each row. */ + CreateAggregateStateFunc withSFunc(CqlIdentifier sfuncName); + + /** Shortcut for {@link #withSFunc(CqlIdentifier) withSFunc(CqlIdentifier.fromCql(sfuncName))}. */ + default CreateAggregateStateFunc withSFunc(String sfuncName) { + return withSFunc(CqlIdentifier.fromCql(sfuncName)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java new file mode 100644 index 00000000000..59c96f1316f --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java @@ -0,0 +1,33 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface CreateAggregateStateFunc { + + /** + * Adds STYPE to the create aggregate query. This is used to specify what type is returned from + * the state function. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateAggregateEnd withSType(DataType dataType); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionEnd.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionEnd.java new file mode 100644 index 00000000000..9316eadd3eb --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionEnd.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateFunctionEnd extends BuildableQuery {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java new file mode 100644 index 00000000000..3ea40f593dd --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java @@ -0,0 +1,66 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface CreateFunctionStart { + + /** + * Adds IF NOT EXISTS to the create function specification. This indicates that the function + * should not be created if it already exists. + */ + CreateFunctionStart ifNotExists(); + + /** + * Adds OR REPLACE to the create function specification. This indicates that the function should + * replace an existing function with the same name if it exists. + */ + CreateFunctionStart orReplace(); + + /** + * Adds a parameter definition in the CREATE FUNCTION statement. + * + *

          Parameter keys are added in the order of their declaration. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateFunctionStart withParameter(CqlIdentifier paramName, DataType paramType); + + /** + * Shortcut for {@link #withParameter(CqlIdentifier, DataType) + * withParameter(CqlIdentifier.asCql(paramName), dataType)}. + */ + default CreateFunctionStart withParameter(String paramName, DataType paramType) { + return withParameter(CqlIdentifier.fromCql(paramName), paramType); + } + + /** + * Adds RETURNS NULL ON NULL to the create function specification. This indicates that the body of + * the function should be skipped when null input is provided. + */ + CreateFunctionWithNullOption returnsNullOnNull(); + + /** + * Adds CALLED ON NULL to the create function specification. This indicates that the body of the + * function not be skipped when null input is provided. + */ + CreateFunctionWithNullOption calledOnNull(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithLanguage.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithLanguage.java new file mode 100644 index 00000000000..d592bd7655f --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithLanguage.java @@ -0,0 +1,44 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateFunctionWithLanguage { + + /** + * Adds AS to the create function specification. This is used to specify the body of the function. + * Note that it is expected that the provided body is properly quoted as this method does not make + * that decision for the user. For simple cases, one should wrap the input in single quotes, i.e. + * 'myBody'. If the body itself contains single quotes, one could use a + * postgres-style string literal, which is surrounded in two dollar signs, i.e. $$ myBody $$ + * . + */ + CreateFunctionEnd as(String functionBody); + + /** + * Adds AS to the create function specification and quotes the function body. Assumes that if the + * input body contains at least one single quote, to quote the body with two dollar signs, i.e. + * $$ myBody $$, otherwise the body is quoted with single quotes, i.e. + * ' myBody '. If the function body is already quoted {@link #as(String)} should be used + * instead. + */ + default CreateFunctionEnd asQuoted(String functionBody) { + if (functionBody.contains("'")) { + return as("$$ " + functionBody + " $$"); + } else { + return as('\'' + functionBody + '\''); + } + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java new file mode 100644 index 00000000000..571c8e8983f --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java @@ -0,0 +1,32 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface CreateFunctionWithNullOption { + /** + * Adds RETURNS to the create function specification. This is used to specify what type is + * returned from the function. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateFunctionWithType returnsType(DataType dataType); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithType.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithType.java new file mode 100644 index 00000000000..bcd21d503ab --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithType.java @@ -0,0 +1,40 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateFunctionWithType { + /** + * Adds LANGUAGE to the create function specification. This is used to specify what language is + * used in the function body. + */ + CreateFunctionWithLanguage withLanguage(String language); + + /** + * Adds "LANGUAGE java" to create function specification. Shortcut for {@link + * #withLanguage(String) withLanguage("java")}. + */ + default CreateFunctionWithLanguage withJavaLanguage() { + return withLanguage("java"); + } + + /** + * Adds "LANGUAGE javascript" to create function specification. Shortcut for {@link + * #withLanguage(String) withLanguage("javascript")}. + */ + default CreateFunctionWithLanguage withJavaScriptLanguage() { + return withLanguage("javascript"); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndex.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndex.java new file mode 100644 index 00000000000..c52f2c2a2c0 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndex.java @@ -0,0 +1,31 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import java.util.Map; + +public interface CreateIndex extends OptionProvider, BuildableQuery { + + /** + * Convenience method for when {@link CreateIndexStart#usingSASI()} is used, provides SASI + * specific options that are provided under the index 'OPTIONS' property. Is equivalent to {@link + * #withOption(String, Object) withOption("OPTIONS", sasiOptions)}. + */ + default CreateIndex withSASIOptions(Map sasiOptions) { + return withOption("OPTIONS", sasiOptions); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexOnTable.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexOnTable.java new file mode 100644 index 00000000000..628892b828d --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexOnTable.java @@ -0,0 +1,110 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface CreateIndexOnTable { + + /** Specifies the column to create the index on. */ + default CreateIndex andColumn(CqlIdentifier columnName) { + return andColumn(columnName, null); + } + + /** Shortcut for {@link #andColumn(CqlIdentifier) andColumn(CqlIdentifier.fromCql(columnName)}. */ + default CreateIndex andColumn(String columnName) { + return andColumn(CqlIdentifier.fromCql(columnName)); + } + + /** + * Specifies to create the index on the given columns' keys, this must be done against a map + * column. + */ + default CreateIndex andColumnKeys(CqlIdentifier columnName) { + return andColumn(columnName, "KEYS"); + } + + /** + * Shortcut for {@link #andColumnKeys(CqlIdentifier) + * andColumnKeys(CqlIdentifier.fromCql(columnName)}. + */ + default CreateIndex andColumnKeys(String columnName) { + return andColumnKeys(CqlIdentifier.fromCql(columnName)); + } + + /** + * Specifies to create the index on the given columns' values, this must be done against a + * map column. + */ + default CreateIndex andColumnValues(CqlIdentifier columnName) { + return andColumn(columnName, "VALUES"); + } + + /** + * Shortcut for {@link #andColumnValues(CqlIdentifier) + * andColumnValues(CqlIdentifier.fromCql(columnName)}. + */ + default CreateIndex andColumnValues(String columnName) { + return andColumnValues(CqlIdentifier.fromCql(columnName)); + } + + /** + * Specifies to create the index on the given columns' entries (key-value pairs), this must be + * done against a map column. + */ + default CreateIndex andColumnEntries(CqlIdentifier columnName) { + return andColumn(columnName, "ENTRIES"); + } + + /** + * Shortcut for {@link #andColumnEntries(CqlIdentifier) + * andColumnEntries(CqlIdentifier.fromCql(columnName)}. + */ + default CreateIndex andColumnEntries(String columnName) { + return andColumnEntries(CqlIdentifier.fromCql(columnName)); + } + + /** + * Specifies to create the index on the given columns' entire value, this must be done against a + * frozen collection column. + */ + default CreateIndex andColumnFull(CqlIdentifier columnName) { + return andColumn(columnName, "FULL"); + } + + /** + * Shortcut for {@link #andColumnFull(CqlIdentifier) + * andColumnFull(CqlIdentifier.fromCql(columnName)}. + */ + default CreateIndex andColumnFull(String columnName) { + return andColumnFull(CqlIdentifier.fromCql(columnName)); + } + + /** + * Specifies to create the index on a given column with the given index type. This method should + * not be used in the general case, unless there is an additional index type to use beyond KEYS, + * VALUES, ENTRIES, or FULL. + */ + CreateIndex andColumn(CqlIdentifier columnName, String indexType); + + /** + * Shortcut for {@link #andColumn(CqlIdentifier,String) + * andColumn(CqlIdentifier.fromCql(columnName),indexType}. + */ + default CreateIndex andColumn(String columnName, String indexType) { + return andColumn(CqlIdentifier.fromCql(columnName), indexType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexStart.java new file mode 100644 index 00000000000..1489651d4ed --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexStart.java @@ -0,0 +1,68 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import java.util.Map; + +public interface CreateIndexStart { + + /** + * Adds IF NOT EXISTS to the create index specification. This indicates that the index should not + * be created if it already exists. + */ + CreateIndexStart ifNotExists(); + + /** + * Adds CUSTOM specification to the index for the given class name. The class name will added to + * the end of the CREATE INDEX specification with USING 'classname'. + */ + CreateIndexStart custom(String className); + + /** + * Declares that the index is a "SSTable Attached Secondary Index" (SASI) type index. This is a + * custom index with the class org.apache.cassandra.index.SASIIndex. + * + * @see CreateIndex#withSASIOptions(Map) + */ + default CreateIndexStart usingSASI() { + return custom("org.apache.cassandra.index.sasi.SASIIndex"); + } + + /** Indicates which table this index is on. */ + CreateIndexOnTable onTable(CqlIdentifier keyspace, CqlIdentifier table); + + /** + * Indicates which table this index is on. This assumes the keyspace name is already qualified for + * the Session or Statement. + */ + default CreateIndexOnTable onTable(CqlIdentifier table) { + return onTable(null, table); + } + + /** + * Shortcut for {@link #onTable(CqlIdentifier,CqlIdentifier) + * onTable(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(table))}. + */ + default CreateIndexOnTable onTable(String keyspace, String table) { + return onTable(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } + + /** Shortcut for {@link #onTable(CqlIdentifier) onTable(CqlIdentifier.fromCql(table))}. */ + default CreateIndexOnTable onTable(String table) { + return onTable(CqlIdentifier.fromCql(table)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspace.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspace.java new file mode 100644 index 00000000000..03dd8fe0c6e --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspace.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateKeyspace extends BuildableQuery, KeyspaceOptions {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceStart.java new file mode 100644 index 00000000000..38a4bbfd212 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceStart.java @@ -0,0 +1,24 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateKeyspaceStart extends KeyspaceReplicationOptions { + /** + * Adds IF NOT EXISTS to the create keyspace specification. This indicates that the keyspace + * should not be created it already exists. + */ + CreateKeyspaceStart ifNotExists(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedView.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedView.java new file mode 100644 index 00000000000..057ffeb6bfb --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedView.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateMaterializedView + extends BuildableQuery, RelationStructure {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKey.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKey.java new file mode 100644 index 00000000000..e9032359193 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKey.java @@ -0,0 +1,39 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateMaterializedViewPrimaryKey + extends CreateMaterializedViewPrimaryKeyStart, + RelationStructure, + BuildableQuery { + /** + * Adds a clustering column to primary key definition. + * + *

          Clustering columns are added in the order of their declaration. + */ + CreateMaterializedViewPrimaryKey withClusteringColumn(CqlIdentifier columnName); + + /** + * Shortcut for {@link #withClusteringColumn(CqlIdentifier) + * withClusteringColumn(CqlIdentifier.asCql(columnName)}. + */ + default CreateMaterializedViewPrimaryKey withClusteringColumn(String columnName) { + return withClusteringColumn(CqlIdentifier.fromCql(columnName)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKeyStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKeyStart.java new file mode 100644 index 00000000000..d2288aee14c --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewPrimaryKeyStart.java @@ -0,0 +1,36 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface CreateMaterializedViewPrimaryKeyStart { + + /** + * Adds a partition key to primary key definition. + * + *

          Partition keys are added in the order of their declaration. + */ + CreateMaterializedViewPrimaryKey withPartitionKey(CqlIdentifier columnName); + + /** + * Shortcut for {@link #withPartitionKey(CqlIdentifier) + * withPartitionKey(CqlIdentifier.asCql(columnName)}. + */ + default CreateMaterializedViewPrimaryKey withPartitionKey(String columnName) { + return withPartitionKey(CqlIdentifier.fromCql(columnName)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelection.java new file mode 100644 index 00000000000..52882f31b07 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelection.java @@ -0,0 +1,60 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.google.common.collect.Iterables; +import java.util.Arrays; + +public interface CreateMaterializedViewSelection { + + /** Selects all columns from the base table. */ + CreateMaterializedViewWhereStart all(); + + /** Selects a particular column by its CQL identifier. */ + CreateMaterializedViewSelectionWithColumns column(CqlIdentifier columnName); + + /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))} */ + default CreateMaterializedViewSelectionWithColumns column(String columnName) { + return column(CqlIdentifier.fromCql(columnName)); + } + + /** + * Convenience method to select multiple simple columns at once, as in {@code SELECT a,b,c}. + * + *

          This is the same as calling {@link #column(CqlIdentifier)} for each element. + */ + CreateMaterializedViewSelectionWithColumns columnsIds(Iterable columnIds); + + /** Var-arg equivalent of {@link #columnsIds(Iterable)}. */ + default CreateMaterializedViewSelectionWithColumns columns(CqlIdentifier... columnIds) { + return columnsIds(Arrays.asList(columnIds)); + } + + /** + * Convenience method to select multiple simple columns at once, as in {@code SELECT a,b,c}. + * + *

          This is the same as calling {@link #column(String)} for each element. + */ + default CreateMaterializedViewSelectionWithColumns columns(Iterable columnNames) { + return columnsIds(Iterables.transform(columnNames, CqlIdentifier::fromInternal)); + } + + /** Var-arg equivalent of {@link #columns(Iterable)}. */ + default CreateMaterializedViewSelectionWithColumns columns(String... columnNames) { + return columns(Arrays.asList(columnNames)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelectionWithColumns.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelectionWithColumns.java new file mode 100644 index 00000000000..3c88c0fd505 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewSelectionWithColumns.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateMaterializedViewSelectionWithColumns + extends CreateMaterializedViewSelection, CreateMaterializedViewWhereStart {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewStart.java new file mode 100644 index 00000000000..10228659f8a --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewStart.java @@ -0,0 +1,51 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; + +public interface CreateMaterializedViewStart { + + /** + * Adds IF NOT EXISTS to the create table specification. This indicates that the table should not + * be created if it already exists. + */ + CreateMaterializedViewStart ifNotExists(); + + /** + * Specifies the base table for the materialized view. This assumes the keyspace name is already + * qualified for the Session or Statement. + */ + CreateMaterializedViewSelection asSelectFrom(CqlIdentifier table); + + /** + * Shortcut for {@link #asSelectFrom(CqlIdentifier) asSelectFrom(CqlIdentifier.fromCql(table)}. + */ + default CreateMaterializedViewSelection asSelectFrom(String table) { + return asSelectFrom(CqlIdentifier.fromCql(table)); + } + + /** Specifies the base table for the materialized view. */ + CreateMaterializedViewSelection asSelectFrom(CqlIdentifier keyspace, CqlIdentifier table); + + /** + * Shortcut for {@link #asSelectFrom(CqlIdentifier,CqlIdentifier) + * asSelectFrom(CqlIdentifier.fromCql(keyspace),CqlIdentifier.fromCql(table)}. + */ + default CreateMaterializedViewSelection asSelectFrom(String keyspace, String table) { + return asSelectFrom(CqlIdentifier.fromCql(keyspace), CqlIdentifier.fromCql(table)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhere.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhere.java new file mode 100644 index 00000000000..58806f4fb64 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhere.java @@ -0,0 +1,19 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateMaterializedViewWhere + extends CreateMaterializedViewWhereStart, CreateMaterializedViewPrimaryKeyStart {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhereStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhereStart.java new file mode 100644 index 00000000000..f2817017dd6 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewWhereStart.java @@ -0,0 +1,21 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; + +public interface CreateMaterializedViewWhereStart + extends OngoingWhereClause {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java new file mode 100644 index 00000000000..0901c2e5b4f --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java @@ -0,0 +1,81 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface CreateTable extends BuildableQuery, OngoingPartitionKey, CreateTableWithOptions { + + /** + * Adds a clustering column definition in the CREATE TABLE statement. + * + *

          This includes the column declaration (you don't need an additional {@link + * #withColumn(CqlIdentifier, DataType) addColumn} call). + * + *

          Clustering key columns are added in the order of their declaration. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateTable withClusteringColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #withClusteringColumn(CqlIdentifier, DataType) + * withClusteringColumn(CqlIdentifier.asCql(columnName), dataType)}. + */ + default CreateTable withClusteringColumn(String columnName, DataType dataType) { + return withClusteringColumn(CqlIdentifier.fromCql(columnName), dataType); + } + + /** + * Adds a column definition in the CREATE TABLE statement. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateTable withColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #withColumn(CqlIdentifier, DataType) + * withColumn(CqlIdentifier.asCql(columnName), dataType)}. + */ + default CreateTable withColumn(String columnName, DataType dataType) { + return withColumn(CqlIdentifier.fromCql(columnName), dataType); + } + + /** + * Adds a static column definition in the CREATE TABLE statement. + * + *

          This includes the column declaration (you don't need an additional {@link + * #withColumn(CqlIdentifier, DataType) addColumn} call). + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateTable withStaticColumn(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #withStaticColumn(CqlIdentifier, DataType) + * withStaticColumn(CqlIdentifier.asCql(columnName), dataType)}. + */ + default CreateTable withStaticColumn(String columnName, DataType dataType) { + return withStaticColumn(CqlIdentifier.fromCql(columnName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableStart.java new file mode 100644 index 00000000000..ee9e3dce8a2 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableStart.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateTableStart extends OngoingPartitionKey { + + /** + * Adds IF NOT EXISTS to the create table specification. This indicates that the table should not + * be created if it already exists. + */ + CreateTableStart ifNotExists(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.java new file mode 100644 index 00000000000..8d1627797a3 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateTableWithOptions + extends BuildableQuery, RelationStructure { + + /** Enables COMPACT STORAGE in the CREATE TABLE statement. */ + CreateTableWithOptions withCompactStorage(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateType.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateType.java new file mode 100644 index 00000000000..963dd99310c --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateType.java @@ -0,0 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface CreateType extends BuildableQuery, OngoingCreateType {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeStart.java new file mode 100644 index 00000000000..9cea9e862ed --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeStart.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface CreateTypeStart extends OngoingCreateType { + + /** + * Adds IF NOT EXISTS to the create type specification. This indicates that the type should not be + * created if it already exists. + */ + CreateTypeStart ifNotExists(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/Drop.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/Drop.java new file mode 100644 index 00000000000..928257f6ee9 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/Drop.java @@ -0,0 +1,24 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; + +public interface Drop extends BuildableQuery { + + /** Adds 'IF EXISTS" to the drop specification. */ + Drop ifExists(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java new file mode 100644 index 00000000000..028908b2ce5 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java @@ -0,0 +1,28 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +public interface KeyspaceOptions> + extends OptionProvider { + + /** + * Adjusts durable writes configuration for this keyspace. If set to false, data written to the + * keyspace will bypass the commit log. + */ + default SelfT withDurableWrites(boolean durableWrites) { + return withOption("durable_writes", durableWrites); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceReplicationOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceReplicationOptions.java new file mode 100644 index 00000000000..e08690ffb44 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceReplicationOptions.java @@ -0,0 +1,65 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public interface KeyspaceReplicationOptions { + /** + * Adds SimpleStrategy replication options with the given replication factor. + * + *

          Note that using this will overwrite any previous use of this method or {@link + * #withNetworkTopologyStrategy(Map)}. + */ + default TargetT withSimpleStrategy(int replicationFactor) { + ImmutableMap replication = + ImmutableMap.builder() + .put("class", "SimpleStrategy") + .put("replication_factor", replicationFactor) + .build(); + + return withReplicationOptions(replication); + } + + /** + * Adds NetworkTopologyStrategy replication options with the given data center replication + * factors. + * + *

          Note that using this will overwrite any previous use of this method or {@link + * #withSimpleStrategy(int)}. + * + * @param replications Mapping of data center name to replication factor to use for that data + * center. + */ + default TargetT withNetworkTopologyStrategy(Map replications) { + ImmutableMap.Builder replicationBuilder = + ImmutableMap.builder().put("class", "NetworkTopologyStrategy"); + + for (Map.Entry replication : replications.entrySet()) { + replicationBuilder.put(replication.getKey(), replication.getValue()); + } + + return withReplicationOptions(replicationBuilder.build()); + } + + /** + * Adds 'replication' options. One should only use this when they have a custom replication + * strategy, otherwise it is advisable to use {@link #withSimpleStrategy(int)} or {@link + * #withNetworkTopologyStrategy(Map)}. + */ + TargetT withReplicationOptions(Map replicationOptions); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java new file mode 100644 index 00000000000..439cbf34a6d --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java @@ -0,0 +1,42 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface OngoingCreateType { + + /** + * Adds a field definition in the CREATE TYPE statement. + * + *

          Fields keys are added in the order of their declaration. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateType withField(CqlIdentifier identifier, DataType dataType); + + /** + * Shortcut for {@link #withField(CqlIdentifier, DataType)} (CqlIdentifier, DataType) + * withField(CqlIdentifier.asCql(columnName), dataType)}. + */ + default CreateType withField(String columnName, DataType dataType) { + return withField(CqlIdentifier.fromCql(columnName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java new file mode 100644 index 00000000000..5de44f6ca92 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java @@ -0,0 +1,45 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; + +public interface OngoingPartitionKey { + + /** + * Adds a partition key column definition. + * + *

          This includes the column declaration (you don't need an additional {@link + * CreateTable#withColumn(CqlIdentifier, DataType) addColumn} call). + * + *

          Partition keys are added in the order of their declaration. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + */ + CreateTable withPartitionKey(CqlIdentifier columnName, DataType dataType); + + /** + * Shortcut for {@link #withPartitionKey(CqlIdentifier, DataType) + * withPartitionKey(CqlIdentifier.asCql(columnName), dataType)}. + */ + default CreateTable withPartitionKey(String columnName, DataType dataType) { + return withPartitionKey(CqlIdentifier.fromCql(columnName), dataType); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java new file mode 100644 index 00000000000..19e033ab4f7 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import java.util.Map; + +public interface OptionProvider> { + /** + * Adds a free-form option. This is useful for custom options or new options that have not yet + * been added to this API. + */ + SelfT withOption(String name, Object value); + + Map getOptions(); +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java new file mode 100644 index 00000000000..89d4e619da6 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java @@ -0,0 +1,291 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition; + +import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy; +import com.google.common.collect.ImmutableMap; + +public interface RelationOptions> + extends OptionProvider { + + /** + * Defines the false-positive probability for SSTable bloom filters. + * + *

          If no call was made to this method, the default value set is: + * + *

            + *
          • 0.01 for the size-tiered compaction strategy; + *
          • 0.1 for the leveled compaction strategy. + *
          + */ + default SelfT withBloomFilterFpChance(double bloomFilterFpChance) { + return withOption("bloom_filter_fp_chance", bloomFilterFpChance); + } + + /** + * Defines whether or not change data capture is enabled. + * + *

          Note that using this option with a version of Apache Cassandra less than 3.8 or DataStax + * Enterprise 5.0 will raise a syntax error. + * + *

          If no call is made to this method, the default value set is {@code false}. + */ + default SelfT withCDC(boolean enabled) { + return withOption("cdc", enabled); + } + + /** + * Defines the caching criteria. + * + *

          If no call is made to this method, the default value is determined by the global caching + * properties in cassandra.yaml. + * + * @param keys If true, caches all keys, otherwise none. + * @param rowsPerPartition Whether to cache ALL, NONE or the first N rows per partition. + */ + default SelfT withCaching(boolean keys, RowsPerPartition rowsPerPartition) { + return withOption( + "caching", + ImmutableMap.of( + "keys", keys ? "ALL" : "NONE", "rows_per_partition", rowsPerPartition.getValue())); + } + + /** Defines documentation for this relation. */ + default SelfT withComment(String comment) { + return withOption("comment", comment); + } + + /** + * Defines the compaction strategy to use. + * + * @see SchemaBuilderDsl#sizeTieredCompactionStrategy() + * @see SchemaBuilderDsl#leveledCompactionStrategy() + * @see SchemaBuilderDsl#timeWindowCompactionStrategy() + */ + default SelfT withCompaction(CompactionStrategy compactionStrategy) { + return withOption("compaction", compactionStrategy.getOptions()); + } + + /** + * Configures compression using the LZ4 algorithm with the given chunk length and crc check + * chance. + * + * @see #withCompression(String, int, double) + */ + default SelfT withLZ4Compression(int chunkLengthKB, double crcCheckChance) { + return withCompression("LZ4Compressor", chunkLengthKB, crcCheckChance); + } + + /** + * Configures compression using the LZ4 algorithm using the default configuration (64kb + * chunk_length, and 1.0 crc_check_chance). + * + * @see #withCompression(String, int, double) + */ + default SelfT withLZ4Compression() { + return withCompression("LZ4Compressor"); + } + + /** + * Configures compression using the Snappy algorithm with the given chunk length and crc check + * chance. + * + * @see #withCompression(String, int, double) + */ + default SelfT withSnappyCompression(int chunkLengthKB, double crcCheckChance) { + return withCompression("SnappyCompressor", chunkLengthKB, crcCheckChance); + } + + /** + * Configures compression using the Snappy algorithm using the default configuration (64kb + * chunk_length, and 1.0 crc_check_chance). + * + * @see #withCompression(String, int, double) + */ + default SelfT withSnappyCompression() { + return withCompression("SnappyCompressor"); + } + + /** + * Configures compression using the Deflate algorithm with the given chunk length and crc check + * chance. + * + * @see #withCompression(String, int, double) + */ + default SelfT withDeflateCompression(int chunkLengthKB, double crcCheckChance) { + return withCompression("DeflateCompressor", chunkLengthKB, crcCheckChance); + } + + /** + * Configures compression using the Deflate algorithm using the default configuration (64kb + * chunk_length, and 1.0 crc_check_chance). + * + * @see #withCompression(String, int, double) + */ + default SelfT withDeflateCompression() { + return withCompression("DeflateCompressor"); + } + + /** + * Configures compression using the given algorithm using the default configuration (64kb + * chunk_length, and 1.0 crc_check_chance). + * + *

          Unless specifying a custom compression algorithm implementation, it is recommended to use + * {@link #withLZ4Compression()}, {@link #withSnappyCompression()}, or {@link + * #withDeflateCompression()}. + * + * @see #withCompression(String, int, double) + */ + default SelfT withCompression(String compressionAlgorithmName) { + return withOption("compression", ImmutableMap.of("class", compressionAlgorithmName)); + } + + /** + * Configures compression using the given algorithm, chunk length and crc check chance. + * + *

          Unless specifying a custom compression algorithm implementation, it is recommended to use + * {@link #withLZ4Compression()}, {@link #withSnappyCompression()}, or {@link + * #withDeflateCompression()}. + * + * @param compressionAlgorithmName The class name of the compression algorithm. + * @param chunkLengthKB The chunk length in KB of compression blocks. Defaults to 64. + * @param crcCheckChance The probability (0.0 to 1.0) that checksum will be checked on each read. + * Defaults to 1.0. + */ + default SelfT withCompression( + String compressionAlgorithmName, int chunkLengthKB, double crcCheckChance) { + return withOption( + "compression", + ImmutableMap.of( + "class", + compressionAlgorithmName, + "chunk_length_kb", + chunkLengthKB, + "crc_check_chance", + crcCheckChance)); + } + + /** Defines that compression should be disabled. */ + default SelfT withNoCompression() { + return withOption("compression", ImmutableMap.of("sstable_compression", "")); + } + + /** + * Defines the probability of read repairs being invoked over all replicas in the current data + * center. + * + *

          If no call is made to this method, the default value set is 0.0. + * + * @param dcLocalReadRepairChance the probability. + * @return this {@code TableOptions} object. + */ + default SelfT withDcLocalReadRepairChance(double dcLocalReadRepairChance) { + return withOption("dclocal_read_repair_chance", dcLocalReadRepairChance); + } + + /** + * Defines the default 'time to live' (expiration time) of writes in seconds. + * + *

          If no call is made to this method, the default value is 0 (no TTL). + */ + default SelfT withDefaultTimeToLiveSeconds(int ttl) { + return withOption("default_time_to_live", ttl); + } + + /** + * Defines the time to wait before garbage collecting tombstones (deletion markers). + * + *

          The default value allows a great deal of time for consistency to be achieved prior to + * deletion. In many deployments this interval can be reduced, and in a single-node cluster it can + * be safely set to zero. + * + *

          If no call is made to this method, the default value set is 864000 secs (10 days). + */ + default SelfT withGcGraceSeconds(int gcGraceSeconds) { + return withOption("gc_grace_seconds", gcGraceSeconds); + } + + /** + * Defines the memtable flush period in milliseconds. + * + *

          If set, this forces flushing of memtables after the specified time elapses. + * + *

          If no call is made to this method, the default value is 0 (unset). + */ + default SelfT withMemtableFlushPeriodInMs(int memtableFlushPeriodInMs) { + return withOption("memtable_flush_period_in_ms", memtableFlushPeriodInMs); + } + + /** + * Defines the minimum index interval. This is the gap between index entries in the index summary. + * A lower value will increase the size of the index (more RAM usage) but potentially improve disk + * I/O. + * + *

          If no call is made to this method, the default value set is 128. + */ + default SelfT withMinIndexInterval(int min) { + return withOption("min_index_interval", min); + } + + /** + * Defines the maximum index interval. + * + *

          If no call is made to this method, the default value set is 2048. + * + * @see #withMinIndexInterval(int) + */ + default SelfT withMaxIndexInterval(int max) { + return withOption("max_index_interval", max); + } + + /** + * Defines the probability with which read repairs should be invoked on non-quorum reads. The + * value must be between 0 and 1. + * + *

          If no call is made to this method, the default value set is 0.1. + */ + default SelfT withReadRepairChance(double readRepairChance) { + return withOption("read_repair_chance", readRepairChance); + } + + /** + * Defines the configuration for coordinator to replica speculative retries. + * + *

          This overrides the normal read timeout when read_repair_chance is not 1.0, sending a request + * to other replica(s) to service reads. + * + *

          Valid values include: + * + *

            + *
          • ALWAYS: Retry reads of all replicas. + *
          • Xpercentile: Retry reads based on the effect on throughput and latency. + *
          • Yms: Retry reads after specified milliseconds. + *
          • NONE: Do not retry reads. + *
          + * + *

          Using the speculative retry property, you can configure rapid read protection in Cassandra + * 2.0.2 and later. Use this property to retry a request after some milliseconds have passed or + * after a percentile of the typical read latency has been reached, which is tracked per table. + * + *

          If no call is made to this method, the default value set is {@code 99percentile}. + */ + default SelfT withSpeculativeRetry(String speculativeRetry) { + return withOption("speculative_retry", speculativeRetry); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java new file mode 100644 index 00000000000..3e1fbe53bb3 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java @@ -0,0 +1,66 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public interface RelationStructure> + extends RelationOptions { + + /** + * Adds the provided CLUSTERING ORDER. + * + *

          They will be appended in the iteration order of the provided map. If an ordering was already + * defined for a given identifier, it will be removed and the new ordering will appear in its + * position in the provided map. + */ + SelfT withClusteringOrderByIds(Map orderings); + + /** + * Shortcut for {@link #withClusteringOrderByIds(Map)} with the columns specified as + * case-insensitive names. They will be wrapped with {@link CqlIdentifier#fromCql(String)}. + * + *

          Note that it's possible for two different case-sensitive names to resolve to the same + * identifier, for example "foo" and "Foo"; if this happens, a runtime exception will be thrown. + */ + default SelfT withClusteringOrder(Map orderings) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map.Entry entry : orderings.entrySet()) { + builder.put(CqlIdentifier.fromCql(entry.getKey()), entry.getValue()); + } + // build() throws if there are duplicate keys + return withClusteringOrderByIds(builder.build()); + } + + /** + * Adds the provided clustering order. + * + *

          If clustering order was already defined for this identifier, it will be removed and the new + * clause will be appended at the end of the current clustering order. + */ + SelfT withClusteringOrder(CqlIdentifier columnName, ClusteringOrder order); + + /** + * Shortcut for {@link #withClusteringOrder(CqlIdentifier, ClusteringOrder) + * withClusteringOrder(CqlIdentifier.fromCql(columnName), order)}. + */ + default SelfT withClusteringOrder(String columnName, ClusteringOrder order) { + return withClusteringOrder(CqlIdentifier.fromCql(columnName), order); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java new file mode 100644 index 00000000000..11b88e1edcc --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema.compaction; + +import com.datastax.oss.driver.api.querybuilder.schema.OptionProvider; + +public interface CompactionStrategy> + extends OptionProvider { + + default SelfT withEnabled(boolean enabled) { + return withOption("enabled", enabled); + } + + default SelfT withTombstoneCompactionIntervalInSeconds(int seconds) { + return withOption("tombstone_compaction_interval", seconds); + } + + default SelfT withTombstoneThreshold(double threshold) { + return withOption("tombstone_threshold", threshold); + } + + default SelfT withUncheckedTombstoneCompaction(boolean enabled) { + return withOption("unchecked_tombstone_compaction", enabled); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java new file mode 100644 index 00000000000..ee9e61533c9 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java @@ -0,0 +1,24 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema.compaction; + +public interface LeveledCompactionStrategy> + extends CompactionStrategy { + + default SelfT withSSTableSizeInMB(int ssTableSizeInMB) { + return withOption("sstable_size_in_mb", ssTableSizeInMB); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java new file mode 100644 index 00000000000..86d6e357e10 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java @@ -0,0 +1,49 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema.compaction; + +public interface SizeTieredCompactionStrategy> + extends CompactionStrategy { + + default SelfT withMaxThreshold(int maxThreshold) { + return withOption("max_threshold", maxThreshold); + } + + default SelfT withMinThreshold(int minThreshold) { + return withOption("min_threshold", minThreshold); + } + + default SelfT withMinSSTableSizeInBytes(long bytes) { + return withOption("min_sstable_size", bytes); + } + + default SelfT withOnlyPurgeRepairedTombstones(boolean enabled) { + return withOption("only_purge_repaired_tombstones", enabled); + } + + default SelfT withBucketHigh(double bucketHigh) { + return withOption("bucket_high", bucketHigh); + } + + default SelfT withBucketLow(double bucketHigh) { + return withOption("bucket_low", bucketHigh); + } + + // 2.1 only + default SelfT withColdReadsToOmit(double ratio) { + return withOption("cold_reads_to_omit", ratio); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java new file mode 100644 index 00000000000..6396425dbb2 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.schema.compaction; + +public interface TimeWindowCompactionStrategy> + extends CompactionStrategy, SizeTieredCompactionStrategy { + + enum CompactionWindowUnit { + MINUTES, + HOURS, + DAYS + } + + enum TimestampResolution { + MICROSECONDS, + MILLISECONDS + } + + default SelfT withCompactionWindow(long size, CompactionWindowUnit unit) { + return withOption("compaction_window_size", size) + .withOption("compaction_window_unit", unit.toString()); + } + + default SelfT withUnsafeAggressiveSSTableExpiration(boolean enabled) { + return withOption("unsafe_aggressive_sstable_expiration", enabled); + } + + default SelfT withTimestampResolution(TimestampResolution timestampResolution) { + return withOption("timestamp_resolution", timestampResolution.toString()); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java new file mode 100644 index 00000000000..655c01fb5b2 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java @@ -0,0 +1,749 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.select; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.google.common.collect.Iterables; +import java.util.Arrays; +import java.util.Map; + +/** + * A SELECT query that accepts additional selectors (that is, elements in the SELECT clause to + * return as columns in the result set, as in: {@code SELECT count(*), sku, price...}). + */ +public interface OngoingSelection { + + /** + * Adds a selector. + * + *

          To create the argument, use one of the factory methods in {@link Selector}, for example + * {@link Selector#column(CqlIdentifier) column}. This type also provides shortcuts to create and + * add the selector in one call, for example {@link #column(CqlIdentifier)} for {@code + * selector(Selector.column(...))}. + * + *

          If you add multiple selectors as once, consider {@link #selectors(Iterable)} as a more + * efficient alternative. + */ + Select selector(Selector selector); + + /** + * Adds multiple selectors at once. + * + *

          This is slightly more efficient than adding the selectors one by one (since the underlying + * implementation of this object is immutable). + * + *

          To create the arguments, use one of the factory methods in {@link Selector}, for example + * {@link Selector#column(CqlIdentifier) column}. + * + * @throws IllegalArgumentException if one of the selectors is {@link Selector#all()} ({@code *} + * can only be used on its own). + * @see #selector(Selector) + */ + Select selectors(Iterable additionalSelectors); + + /** Var-arg equivalent of {@link #selectors(Iterable)}. */ + default Select selectors(Selector... additionalSelectors) { + return selectors(Arrays.asList(additionalSelectors)); + } + + /** + * Selects all columns, as in {@code SELECT *}. + * + *

          This will clear any previously configured selector. Similarly, if any other selector is + * added later, it will cancel this one. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.all())}. + * + * @see Selector#all() + */ + default Select all() { + return selector(Selector.all()); + } + + /** + * Selects the count of all returned rows, as in {@code SELECT count(*)}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.countAll())}. + * + * @see Selector#countAll() + */ + default Select countAll() { + return selector(Selector.countAll()); + } + + /** + * Selects a particular column by its CQL identifier. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.column(columnId))}. + * + * @see Selector#column(CqlIdentifier) + */ + default Select column(CqlIdentifier columnId) { + return selector(Selector.column(columnId)); + } + + /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))} */ + default Select column(String columnName) { + return column(CqlIdentifier.fromCql(columnName)); + } + + /** + * Convenience method to select multiple simple columns at once, as in {@code SELECT a,b,c}. + * + *

          This is the same as calling {@link #column(CqlIdentifier)} for each element. + */ + default Select columnsIds(Iterable columnIds) { + return selectors(Iterables.transform(columnIds, Selector::column)); + } + + /** Var-arg equivalent of {@link #columnsIds(Iterable)}. */ + default Select columns(CqlIdentifier... columnIds) { + return columnsIds(Arrays.asList(columnIds)); + } + + /** + * Convenience method to select multiple simple columns at once, as in {@code SELECT a,b,c}. + * + *

          This is the same as calling {@link #column(String)} for each element. + */ + default Select columns(Iterable columnNames) { + return selectors(Iterables.transform(columnNames, Selector::column)); + } + + /** Var-arg equivalent of {@link #columns(Iterable)}. */ + default Select columns(String... columnNames) { + return columns(Arrays.asList(columnNames)); + } + + /** + * Selects the sum of two arguments, as in {@code SELECT col1 + col2}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.add(left, right))}. + * + * @see Selector#add(Selector, Selector) + */ + default Select add(Selector left, Selector right) { + return selector(Selector.add(left, right)); + } + + /** + * Selects the difference of two terms, as in {@code SELECT col1 - col2}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.subtract(left, right))}. + * + * @see Selector#subtract(Selector, Selector) + */ + default Select subtract(Selector left, Selector right) { + return selector(Selector.subtract(left, right)); + } + + /** + * Selects the product of two arguments, as in {@code SELECT col1 * col2}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.multiply(left, right))}. + * + *

          The arguments will be parenthesized if they are instances of {@link Selector#add} or {@link + * Selector#subtract}. If they are raw selectors, you might have to parenthesize them yourself. + * + * @see Selector#multiply(Selector, Selector) + */ + default Select multiply(Selector left, Selector right) { + return selector(Selector.multiply(left, right)); + } + + /** + * Selects the quotient of two arguments, as in {@code SELECT col1 / col2}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.divide(left, right))}. + * + *

          The arguments will be parenthesized if they are instances of {@link Selector#add} or {@link + * Selector#subtract}. If they are raw selectors, you might have to parenthesize them yourself. + * + * @see Selector#divide(Selector, Selector) + */ + default Select divide(Selector left, Selector right) { + return selector(Selector.divide(left, right)); + } + + /** + * Selects the remainder of two arguments, as in {@code SELECT col1 % col2}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.remainder(left, + * right))}. + * + *

          The arguments will be parenthesized if they are instances of {@link Selector#add} or {@link + * Selector#subtract}. If they are raw selectors, you might have to parenthesize them yourself. + * + * @see Selector#remainder(Selector, Selector) + */ + default Select remainder(Selector left, Selector right) { + return selector(Selector.remainder(left, right)); + } + + /** + * Selects the opposite of an argument, as in {@code SELECT -col1}. + * + *

          This is available in Cassandra 4 and above. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.negate(argument))}. + * + *

          The argument will be parenthesized if it is an instance of {@link Selector#add} or {@link + * Selector#subtract}. If it is a raw selector, you might have to parenthesize it yourself. + * + * @see Selector#negate(Selector) + */ + default Select negate(Selector argument) { + return selector(Selector.negate(argument)); + } + + /** + * Selects a field inside of a UDT column, as in {@code SELECT user.name}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.field(udt, fieldId))}. + * + * @see Selector#field(Selector, CqlIdentifier) + */ + default Select field(Selector udt, CqlIdentifier fieldId) { + return selector(Selector.field(udt, fieldId)); + } + + /** + * Shortcut for {@link #field(Selector, CqlIdentifier) field(udt, + * CqlIdentifier.fromCql(fieldName))}. + */ + default Select field(Selector udt, String fieldName) { + return field(udt, CqlIdentifier.fromCql(fieldName)); + } + + /** + * Shortcut to select a UDT field when the UDT is a simple column (as opposed to a more complex + * selection, like a nested UDT). + * + *

          In other words, this is a shortcut for {{@link #field(Selector, CqlIdentifier) + * field(QueryBuilderDsl.column(udtColumnId), fieldId)}. + * + * @see Selector#field(CqlIdentifier, CqlIdentifier) + */ + default Select field(CqlIdentifier udtColumnId, CqlIdentifier fieldId) { + return field(Selector.column(udtColumnId), fieldId); + } + + /** + * Shortcut for {@link #field(CqlIdentifier, CqlIdentifier) + * field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName))}. + * + * @see Selector#field(String, String) + */ + default Select field(String udtColumnName, String fieldName) { + return field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName)); + } + + /** + * Selects an element in a collection column, as in {@code SELECT m['key']}. + * + *

          As of Cassandra 4, this is only allowed for map and set columns. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.element(collection, + * index))}. + * + * @see Selector#element(Selector, Term) + */ + default Select element(Selector collection, Term index) { + return selector(Selector.element(collection, index)); + } + + /** + * Shortcut for element selection when the target collection is a simple column. + * + *

          In other words, this is the equivalent of {@link #element(Selector, Term) + * element(QueryBuilderDsl.column(collection), index)}. + * + * @see Selector#element(CqlIdentifier, Term) + */ + default Select element(CqlIdentifier collectionId, Term index) { + return element(Selector.column(collectionId), index); + } + + /** + * Shortcut for {@link #element(CqlIdentifier, Term) + * element(CqlIdentifier.fromCql(collectionName), index)}. + * + * @see Selector#element(String, Term) + */ + default Select element(String collectionName, Term index) { + return element(CqlIdentifier.fromCql(collectionName), index); + } + + /** + * Selects a slice in a collection column, as in {@code SELECT s[4..8]}. + * + *

          As of Cassandra 4, this is only allowed for set and map columns. Those collections are + * ordered, the elements (or keys in the case of a map), will be compared to the bounds for + * inclusions. Either bound can be unspecified, but not both. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.range(collection, left, + * right))}. + * + * @param left the left bound (inclusive). Can be {@code null} to indicate that the slice is only + * right-bound. + * @param right the right bound (inclusive). Can be {@code null} to indicate that the slice is + * only left-bound. + * @see Selector#range(Selector, Term, Term) + */ + default Select range(Selector collection, Term left, Term right) { + return selector(Selector.range(collection, left, right)); + } + + /** + * Shortcut for slice selection when the target collection is a simple column. + * + *

          In other words, this is the equivalent of {@link #range(Selector, Term, Term)} + * range(Selector.column(collectionId), left, right)}. + * + * @see Selector#range(CqlIdentifier, Term, Term) + */ + default Select range(CqlIdentifier collectionId, Term left, Term right) { + return range(Selector.column(collectionId), left, right); + } + + /** + * Shortcut for {@link #range(CqlIdentifier, Term, Term) + * range(CqlIdentifier.fromCql(collectionName), left, right)}. + * + * @see Selector#range(String, Term, Term) + */ + default Select range(String collectionName, Term left, Term right) { + return range(CqlIdentifier.fromCql(collectionName), left, right); + } + + /** + * Selects a group of elements as a list, as in {@code SELECT [a,b,c]}. + * + *

          None of the selectors should be aliased (the query builder checks this at runtime), and they + * should all produce the same data type (the query builder can't check this, so the query will + * fail at execution time). + * + *

          This is a shortcut for {@link #selector(Selector) + * selector(Selector.listOf(elementSelectors))}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#listOf(Iterable) + */ + default Select listOf(Iterable elementSelectors) { + return selector(Selector.listOf(elementSelectors)); + } + + /** Var-arg equivalent of {@link #listOf(Iterable)}. */ + default Select listOf(Selector... elementSelectors) { + return listOf(Arrays.asList(elementSelectors)); + } + + /** + * Selects a group of elements as a set, as in {@code SELECT {a,b,c}}. + * + *

          None of the selectors should be aliased (the query builder checks this at runtime), and they + * should all produce the same data type (the query builder can't check this, so the query will + * fail at execution time). + * + *

          This is a shortcut for {@link #selector(Selector) + * selector(Selector.setOf(elementSelectors))}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#setOf(Iterable) + */ + default Select setOf(Iterable elementSelectors) { + return selector(Selector.setOf(elementSelectors)); + } + + /** Var-arg equivalent of {@link #setOf(Iterable)}. */ + default Select setOf(Selector... elementSelectors) { + return setOf(Arrays.asList(elementSelectors)); + } + + /** + * Selects a group of elements as a tuple, as in {@code SELECT (a,b,c)}. + * + *

          None of the selectors should be aliased (the query builder checks this at runtime). + * + *

          This is a shortcut for {@link #selector(Selector) + * selector(Selector.tupleOf(elementSelectors))}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#tupleOf(Iterable) + */ + default Select tupleOf(Iterable elementSelectors) { + return selector(Selector.tupleOf(elementSelectors)); + } + + /** Var-arg equivalent of {@link #tupleOf(Iterable)}. */ + default Select tupleOf(Selector... elementSelectors) { + return tupleOf(Arrays.asList(elementSelectors)); + } + + /** + * Selects a group of elements as a map, as in {@code SELECT {a:b,c:d}}. + * + *

          None of the selectors should be aliased (the query builder checks this at runtime). In + * addition, all key selectors should produce the same type, and all value selectors as well (the + * key and value types can be different); the query builder can't check this, so the query will + * fail at execution time if the types are not uniform. + * + *

          This is a shortcut for {@link #selector(Selector) + * selector(Selector.mapOf(elementSelectors))}. + * + *

          Note that Cassandra often has trouble inferring the exact map type. This will manifest as + * the error message: + * + *

          +   *   Cannot infer type for term xxx in selection clause (try using a cast to force a type)
          +   * 
          + * + * If you run into this, consider providing the types explicitly with {@link #mapOf(Map, DataType, + * DataType)}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#mapOf(Map) + */ + default Select mapOf(Map elementSelectors) { + return selector(Selector.mapOf(elementSelectors)); + } + + /** + * Selects a group of elements as a map and force the resulting map type, as in {@code SELECT + * (map){a:b,c:d}}. + * + *

          To create the data types, use the constants and static methods in {@link DataTypes}, or + * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.mapOf(elementSelectors, + * keyType, valueType))}. + * + * @see #mapOf(Map) + * @see Selector#mapOf(Map, DataType, DataType) + */ + default Select mapOf( + Map elementSelectors, DataType keyType, DataType valueType) { + return selector(Selector.mapOf(elementSelectors, keyType, valueType)); + } + + /** + * Provides a type hint for a selector, as in {@code SELECT (double)1/3}. + * + *

          Use the constants and static methods in {@link DataTypes} to create the data type. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.typeHint(selector, + * targetType))}. + * + * @see Selector#typeHint(Selector, DataType) + */ + default Select typeHint(Selector selector, DataType targetType) { + return selector(Selector.typeHint(selector, targetType)); + } + + /** + * Selects the result of a function call, as is {@code SELECT f(a,b)} + * + *

          None of the arguments should be aliased (the query builder checks this at runtime). + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.function(functionId, + * arguments))}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#function(CqlIdentifier, Iterable) + */ + default Select function(CqlIdentifier functionId, Iterable arguments) { + return selector(Selector.function(functionId, arguments)); + } + + /** + * Var-arg equivalent of {@link #function(CqlIdentifier, Iterable)}. + * + * @see Selector#function(CqlIdentifier, Selector...) + */ + default Select function(CqlIdentifier functionId, Selector... arguments) { + return function(functionId, Arrays.asList(arguments)); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, Iterable) + * function(CqlIdentifier.fromCql(functionName), arguments)}. + * + * @see Selector#function(String, Iterable) + */ + default Select function(String functionName, Iterable arguments) { + return function(CqlIdentifier.fromCql(functionName), arguments); + } + + /** + * Var-arg equivalent of {@link #function(String, Iterable)}. + * + * @see Selector#function(String, Selector...) + */ + default Select function(String functionName, Selector... arguments) { + return function(functionName, Arrays.asList(arguments)); + } + + /** + * Selects the result of a function call, as is {@code SELECT f(a,b)} + * + *

          None of the arguments should be aliased (the query builder checks this at runtime). + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.function(keyspaceId, + * functionId, arguments))}. + * + * @throws IllegalArgumentException if any of the selectors is aliased. + * @see Selector#function(CqlIdentifier, CqlIdentifier, Iterable) + */ + default Select function( + CqlIdentifier keyspaceId, CqlIdentifier functionId, Iterable arguments) { + return selector(Selector.function(keyspaceId, functionId, arguments)); + } + + /** + * Var-arg equivalent of {@link #function(CqlIdentifier,CqlIdentifier, Iterable)}. + * + * @see Selector#function(CqlIdentifier, CqlIdentifier, Selector...) + */ + default Select function( + CqlIdentifier keyspaceId, CqlIdentifier functionId, Selector... arguments) { + return function(keyspaceId, functionId, Arrays.asList(arguments)); + } + + /** + * Shortcut for {@link #function(CqlIdentifier, CqlIdentifier, Iterable) + * function(CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments)}. + * + * @see Selector#function(String, String, Iterable) + */ + default Select function(String keyspaceName, String functionName, Iterable arguments) { + return function( + CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments); + } + + /** + * Var-arg equivalent of {@link #function(String, String, Iterable)}. + * + * @see Selector#function(String, String, Selector...) + */ + default Select function(String keyspaceName, String functionName, Selector... arguments) { + return function(keyspaceName, functionName, Arrays.asList(arguments)); + } + + /** + * Shortcut to select the result of the built-in {@code writetime} function, as in {@code SELECT + * writetime(c)}. + * + * @see Selector#writeTime(CqlIdentifier) + */ + default Select writeTime(CqlIdentifier columnId) { + return selector(Selector.writeTime(columnId)); + } + + /** + * Shortcut for {@link #writeTime(CqlIdentifier) writeTime(CqlIdentifier.fromCql(columnName))}. + * + * @see Selector#writeTime(String) + */ + default Select writeTime(String columnName) { + return writeTime(CqlIdentifier.fromCql(columnName)); + } + + /** + * Shortcut to select the result of the built-in {@code ttl} function, as in {@code SELECT + * ttl(c)}. + * + * @see Selector#ttl(CqlIdentifier) + */ + default Select ttl(CqlIdentifier columnId) { + return selector(Selector.ttl(columnId)); + } + + /** + * Shortcut for {@link #ttl(CqlIdentifier) ttl(CqlIdentifier.fromCql(columnName))}. + * + * @see Selector#ttl(String) + */ + default Select ttl(String columnName) { + return ttl(CqlIdentifier.fromCql(columnName)); + } + + /** + * Casts a selector to a type, as in {@code SELECT CAST(a AS double)}. + * + *

          To create the data type, use the constants and static methods in {@link DataTypes}, or + * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * + *

          This is a shortcut for {@link #selector(Selector) selector(Selector.function(keyspaceId, + * functionId, arguments))}. + * + * @throws IllegalArgumentException if the selector is aliased. + * @see Selector#cast(Selector, DataType) + */ + default Select cast(Selector selector, DataType targetType) { + return selector(Selector.cast(selector, targetType)); + } + + /** + * Shortcut to select the result of the built-in {@code toDate} function. + * + * @see Selector#toDate(CqlIdentifier) + */ + default Select toDate(CqlIdentifier columnId) { + return selector(Selector.toDate(columnId)); + } + + /** Shortcut for {@link #toDate(CqlIdentifier) toDate(CqlIdentifier.fromCql(columnName))}. */ + default Select toDate(String columnName) { + return toDate(CqlIdentifier.fromCql(columnName)); + } + + /** + * Shortcut to select the result of the built-in {@code toTimestamp} function. + * + * @see Selector#toTimestamp(CqlIdentifier) + */ + default Select toTimestamp(CqlIdentifier columnId) { + return selector(Selector.toTimestamp(columnId)); + } + + /** + * Shortcut for {@link #toTimestamp(CqlIdentifier) + * toTimestamp(CqlIdentifier.fromCql(columnName))}. + */ + default Select toTimestamp(String columnName) { + return toTimestamp(CqlIdentifier.fromCql(columnName)); + } + + /** + * Shortcut to select the result of the built-in {@code toUnixTimestamp} function. + * + * @see Selector#toUnixTimestamp(CqlIdentifier) + */ + default Select toUnixTimestamp(CqlIdentifier columnId) { + return selector(Selector.toUnixTimestamp(columnId)); + } + + /** + * Shortcut for {@link #toUnixTimestamp(CqlIdentifier) + * toUnixTimestamp(CqlIdentifier.fromCql(columnName))}. + */ + default Select toUnixTimestamp(String columnName) { + return toUnixTimestamp(CqlIdentifier.fromCql(columnName)); + } + + /** + * Selects literal value, as in {@code WHERE k = 1}. + * + *

          This method can process any type for which there is a default Java to CQL mapping, namely: + * primitive types ({@code Integer=>int, Long=>bigint, String=>text, etc.}), and collections, + * tuples, and user defined types thereof. + * + *

          A null argument will be rendered as {@code NULL}. + * + *

          For custom mappings, use {@link #literal(Object, CodecRegistry)} or {@link #literal(Object, + * TypeCodec)}. + * + * @throws CodecNotFoundException if there is no default CQL mapping for the Java type of {@code + * value}. + * @see QueryBuilderDsl#literal(Object) + */ + default Select literal(Object value) { + return literal(value, CodecRegistry.DEFAULT); + } + + /** + * Selects a literal value, as in {@code WHERE k = 1}. + * + *

          This is an alternative to {@link #literal(Object)} for custom type mappings. The provided + * registry should contain a codec that can format the value. Typically, this will be your + * session's registry, which is accessible via {@code session.getContext().codecRegistry()}. + * + * @see DriverContext#codecRegistry() + * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can + * handle {@code value}. + * @see QueryBuilderDsl#literal(Object, CodecRegistry) + */ + default Select literal(Object value, CodecRegistry codecRegistry) { + return literal(value, (value == null) ? null : codecRegistry.codecFor(value)); + } + + /** + * Selects a literal value, as in {@code WHERE k = 1}. + * + *

          This is an alternative to {@link #literal(Object)} for custom type mappings. The value will + * be turned into a string with {@link TypeCodec#format(Object)}, and inlined in the query. + * + * @see QueryBuilderDsl#literal(Object, TypeCodec) + */ + default Select literal(T value, TypeCodec codec) { + return selector(QueryBuilderDsl.literal(value, codec)); + } + + /** + * Selects an arbitrary expression expressed as a raw string. + * + *

          The contents will be appended to the query as-is, without any syntax checking or escaping. + * This method should be used with caution, as it's possible to generate invalid CQL that will + * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL + * features that are not yet covered by the query builder. + * + *

          This is a shortcut for {@link #selector(Selector) + * selector(QueryBuilderDsl.raw(rawExpression))}. + */ + default Select raw(String rawExpression) { + return selector(QueryBuilderDsl.raw(rawExpression)); + } + + /** + * Aliases the last added selector, as in {@code SELECT count(*) AS total}. + * + *

          It is the caller's responsibility to ensure that this method is called at most once after + * each selector, and that this selector can legally be aliased: + * + *

            + *
          • if it is called multiple times ({@code countAll().as("total1").as("total2")}), the last + * alias will override the previous ones. + *
          • if it is called before any selector was set, or after {@link #all()}, an {@link + * IllegalStateException} is thrown. + *
          • if it is called after a {@link #raw(String)} selector that already defines an alias, the + * query will fail at runtime. + *
          + */ + Select as(CqlIdentifier alias); + + /** Shortcut for {@link #as(CqlIdentifier) as(CqlIdentifier.fromCql(alias))} */ + default Select as(String alias) { + return as(CqlIdentifier.fromCql(alias)); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java new file mode 100644 index 00000000000..ac308a68a7c --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java @@ -0,0 +1,180 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder.select; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.api.querybuilder.BindMarker; +import com.datastax.oss.driver.api.querybuilder.BuildableQuery; +import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.util.Arrays; +import java.util.Map; + +/** + * A complete SELECT query. + * + *

          It knows about the table and at least one selector, and is therefore buildable. Additional + * selectors and clauses can still be added before building. + */ +public interface Select extends OngoingSelection, OngoingWhereClause, Bu *

          As of version 4.0, Apache Cassandra only allows grouping by columns, therefore you can use * the shortcuts {@link #groupByColumns(Iterable)} or {@link #groupByColumnIds(Iterable)}. */ - Select groupBy(Iterable selectors); + @NonNull + Select groupBy(@NonNull Iterable selectors); /** Var-arg equivalent of {@link #groupBy(Iterable)}. */ - default Select groupBy(Selector... selectors) { + @NonNull + default Select groupBy(@NonNull Selector... selectors) { return groupBy(Arrays.asList(selectors)); } @@ -51,12 +55,14 @@ default Select groupBy(Selector... selectors) { * Shortcut for {@link #groupBy(Iterable)} where all the clauses are simple columns. The arguments * are wrapped with {@link Selector#column(CqlIdentifier)}. */ - default Select groupByColumnIds(Iterable columnIds) { + @NonNull + default Select groupByColumnIds(@NonNull Iterable columnIds) { return groupBy(Iterables.transform(columnIds, Selector::column)); } /** Var-arg equivalent of {@link #groupByColumnIds(Iterable)}. */ - default Select groupByColumnIds(CqlIdentifier... columnIds) { + @NonNull + default Select groupByColumnIds(@NonNull CqlIdentifier... columnIds) { return groupByColumnIds(Arrays.asList(columnIds)); } @@ -64,12 +70,14 @@ default Select groupByColumnIds(CqlIdentifier... columnIds) { * Shortcut for {@link #groupBy(Iterable)} where all the clauses are simple columns. The arguments * are wrapped with {@link Selector#column(String)}. */ - default Select groupByColumns(Iterable columnNames) { + @NonNull + default Select groupByColumns(@NonNull Iterable columnNames) { return groupBy(Iterables.transform(columnNames, Selector::column)); } /** Var-arg equivalent of {@link #groupByColumns(Iterable)}. */ - default Select groupByColumns(String... columnNames) { + @NonNull + default Select groupByColumns(@NonNull String... columnNames) { return groupByColumns(Arrays.asList(columnNames)); } @@ -79,15 +87,18 @@ default Select groupByColumns(String... columnNames) { *

          As of version 4.0, Apache Cassandra only allows grouping by columns, therefore you can use * the shortcuts {@link #groupBy(String)} or {@link #groupBy(CqlIdentifier)}. */ - Select groupBy(Selector selector); + @NonNull + Select groupBy(@NonNull Selector selector); /** Shortcut for {@link #groupBy(Selector) groupBy(Selector.column(columnId))}. */ - default Select groupBy(CqlIdentifier columnId) { + @NonNull + default Select groupBy(@NonNull CqlIdentifier columnId) { return groupBy(Selector.column(columnId)); } /** Shortcut for {@link #groupBy(Selector) groupBy(Selector.column(columnName))}. */ - default Select groupBy(String columnName) { + @NonNull + default Select groupBy(@NonNull String columnName) { return groupBy(Selector.column(columnName)); } @@ -98,7 +109,8 @@ default Select groupBy(String columnName) { * defined for a given identifier, it will be removed and the new ordering will appear in its * position in the provided map. */ - Select orderByIds(Map orderings); + @NonNull + Select orderByIds(@NonNull Map orderings); /** * Shortcut for {@link #orderByIds(Map)} with the columns specified as case-insensitive names. @@ -109,7 +121,8 @@ default Select groupBy(String columnName) { * * @throws IllegalArgumentException if two names resolve to the same identifier. */ - default Select orderBy(Map orderings) { + @NonNull + default Select orderBy(@NonNull Map orderings) { return orderByIds(CqlIdentifiers.wrapKeys(orderings)); } @@ -119,13 +132,15 @@ default Select orderBy(Map orderings) { *

          If an ordering was already defined for this identifier, it will be removed and the new * clause will be appended at the end of the current list for this query. */ - Select orderBy(CqlIdentifier columnId, ClusteringOrder order); + @NonNull + Select orderBy(@NonNull CqlIdentifier columnId, @NonNull ClusteringOrder order); /** * Shortcut for {@link #orderBy(CqlIdentifier, ClusteringOrder) * orderBy(CqlIdentifier.fromCql(columnName), order)}. */ - default Select orderBy(String columnName, ClusteringOrder order) { + @NonNull + default Select orderBy(@NonNull String columnName, @NonNull ClusteringOrder order) { return orderBy(CqlIdentifier.fromCql(columnName), order); } @@ -135,6 +150,7 @@ default Select orderBy(String columnName, ClusteringOrder order) { *

          If this method or {@link #limit(BindMarker)} is called multiple times, the last value is * used. */ + @NonNull Select limit(int limit); /** @@ -144,8 +160,10 @@ default Select orderBy(String columnName, ClusteringOrder order) { * example {@link QueryBuilderDsl#bindMarker() bindMarker()}. * *

          If this method or {@link #limit(int)} is called multiple times, the last value is used. + * {@code null} can be passed to cancel a previous limit. */ - Select limit(BindMarker bindMarker); + @NonNull + Select limit(@Nullable BindMarker bindMarker); /** * Adds a PER PARTITION LIMIT clause to this query with a literal value. @@ -153,6 +171,7 @@ default Select orderBy(String columnName, ClusteringOrder order) { *

          If this method or {@link #perPartitionLimit(BindMarker)} is called multiple times, the last * value is used. */ + @NonNull Select perPartitionLimit(int limit); /** @@ -162,14 +181,16 @@ default Select orderBy(String columnName, ClusteringOrder order) { * example {@link QueryBuilderDsl#bindMarker() bindMarker()}. * *

          If this method or {@link #perPartitionLimit(int)} is called multiple times, the last value - * is used. + * is used. {@code null} can be passed to cancel a previous limit. */ - Select perPartitionLimit(BindMarker bindMarker); + @NonNull + Select perPartitionLimit(@Nullable BindMarker bindMarker); /** * Adds an ALLOW FILTERING clause to this query. * *

          This method is idempotent, calling it multiple times will only add a single clause. */ + @NonNull Select allowFiltering(); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/SelectFrom.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/SelectFrom.java index 65a7b6d8771..c4abb196a04 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/SelectFrom.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/SelectFrom.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.querybuilder.select; +import edu.umd.cs.findbugs.annotations.NonNull; + /** * The beginning of a SELECT query. * @@ -26,7 +28,9 @@ public interface SelectFrom extends OngoingSelection { // Implementation note - this interface exists to make the following a compile-time error: // selectFrom("foo").distinct().build() + @NonNull SelectFrom json(); + @NonNull SelectFrom distinct(); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java index 1a08ca40a68..d80f5c8c258 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java @@ -37,6 +37,8 @@ import com.datastax.oss.driver.internal.querybuilder.select.SetSelector; import com.datastax.oss.driver.internal.querybuilder.select.TupleSelector; import com.datastax.oss.driver.internal.querybuilder.select.TypeHintSelector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Arrays; import java.util.Map; @@ -64,22 +66,26 @@ public interface Selector extends CqlSnippet { /** Selects all columns, as in {@code SELECT *}. */ + @NonNull static Selector all() { return AllSelector.INSTANCE; } /** Selects the count of all returned rows, as in {@code SELECT count(*)}. */ + @NonNull static Selector countAll() { return new CountAllSelector(); } /** Selects a particular column by its CQL identifier. */ - static Selector column(CqlIdentifier columnId) { + @NonNull + static Selector column(@NonNull CqlIdentifier columnId) { return new ColumnSelector(columnId); } /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(columnName))} */ - static Selector column(String columnName) { + @NonNull + static Selector column(@NonNull String columnName) { return column(CqlIdentifier.fromCql(columnName)); } @@ -88,7 +94,8 @@ static Selector column(String columnName) { * *

          This is available in Cassandra 4 and above. */ - static Selector add(Selector left, Selector right) { + @NonNull + static Selector add(@NonNull Selector left, @NonNull Selector right) { return new BinaryArithmeticSelector(ArithmeticOperator.SUM, left, right); } @@ -97,7 +104,8 @@ static Selector add(Selector left, Selector right) { * *

          This is available in Cassandra 4 and above. */ - static Selector subtract(Selector left, Selector right) { + @NonNull + static Selector subtract(@NonNull Selector left, @NonNull Selector right) { return new BinaryArithmeticSelector(ArithmeticOperator.DIFFERENCE, left, right); } @@ -109,7 +117,8 @@ static Selector subtract(Selector left, Selector right) { *

          The arguments will be parenthesized if they are instances of {@link #add} or {@link * #subtract}. If they are raw selectors, you might have to parenthesize them yourself. */ - static Selector multiply(Selector left, Selector right) { + @NonNull + static Selector multiply(@NonNull Selector left, @NonNull Selector right) { return new BinaryArithmeticSelector(ArithmeticOperator.PRODUCT, left, right); } @@ -121,7 +130,8 @@ static Selector multiply(Selector left, Selector right) { *

          The arguments will be parenthesized if they are instances of {@link #add} or {@link * #subtract}. If they are raw selectors, you might have to parenthesize them yourself. */ - static Selector divide(Selector left, Selector right) { + @NonNull + static Selector divide(@NonNull Selector left, @NonNull Selector right) { return new BinaryArithmeticSelector(ArithmeticOperator.QUOTIENT, left, right); } @@ -133,7 +143,8 @@ static Selector divide(Selector left, Selector right) { *

          The arguments will be parenthesized if they are instances of {@link #add} or {@link * #subtract}. If they are raw selectors, you might have to parenthesize them yourself. */ - static Selector remainder(Selector left, Selector right) { + @NonNull + static Selector remainder(@NonNull Selector left, @NonNull Selector right) { return new BinaryArithmeticSelector(ArithmeticOperator.REMAINDER, left, right); } @@ -145,12 +156,14 @@ static Selector remainder(Selector left, Selector right) { *

          The argument will be parenthesized if it is an instance of {@link #add} or {@link * #subtract}. If it is a raw selector, you might have to parenthesize it yourself. */ - static Selector negate(Selector argument) { + @NonNull + static Selector negate(@NonNull Selector argument) { return new OppositeSelector(argument); } /** Selects a field inside of a UDT column, as in {@code SELECT user.name}. */ - static Selector field(Selector udt, CqlIdentifier fieldId) { + @NonNull + static Selector field(@NonNull Selector udt, @NonNull CqlIdentifier fieldId) { return new FieldSelector(udt, fieldId); } @@ -158,7 +171,8 @@ static Selector field(Selector udt, CqlIdentifier fieldId) { * Shortcut for {@link #field(Selector, CqlIdentifier) getUdtField(udt, * CqlIdentifier.fromCql(fieldName))}. */ - static Selector field(Selector udt, String fieldName) { + @NonNull + static Selector field(@NonNull Selector udt, @NonNull String fieldName) { return field(udt, CqlIdentifier.fromCql(fieldName)); } @@ -166,7 +180,8 @@ static Selector field(Selector udt, String fieldName) { * Shortcut to select a UDT field when the UDT is a simple column (as opposed to a more complex * selection, like a nested UDT). */ - static Selector field(CqlIdentifier udtColumnId, CqlIdentifier fieldId) { + @NonNull + static Selector field(@NonNull CqlIdentifier udtColumnId, @NonNull CqlIdentifier fieldId) { return field(column(udtColumnId), fieldId); } @@ -174,7 +189,8 @@ static Selector field(CqlIdentifier udtColumnId, CqlIdentifier fieldId) { * Shortcut for {@link #field(CqlIdentifier, CqlIdentifier) * field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName))}. */ - static Selector field(String udtColumnName, String fieldName) { + @NonNull + static Selector field(@NonNull String udtColumnName, @NonNull String fieldName) { return field(CqlIdentifier.fromCql(udtColumnName), CqlIdentifier.fromCql(fieldName)); } @@ -184,7 +200,8 @@ static Selector field(String udtColumnName, String fieldName) { *

          As of Cassandra 4, this is only allowed in SELECT for map and set columns. DELETE accepts * list elements as well. */ - static Selector element(Selector collection, Term index) { + @NonNull + static Selector element(@NonNull Selector collection, @NonNull Term index) { return new ElementSelector(collection, index); } @@ -194,7 +211,8 @@ static Selector element(Selector collection, Term index) { *

          In other words, this is the equivalent of {@link #element(Selector, Term) * element(column(collectionId), index)}. */ - static Selector element(CqlIdentifier collectionId, Term index) { + @NonNull + static Selector element(@NonNull CqlIdentifier collectionId, @NonNull Term index) { return element(column(collectionId), index); } @@ -202,7 +220,8 @@ static Selector element(CqlIdentifier collectionId, Term index) { * Shortcut for {@link #element(CqlIdentifier, Term) * element(CqlIdentifier.fromCql(collectionName), index)}. */ - static Selector element(String collectionName, Term index) { + @NonNull + static Selector element(@NonNull String collectionName, @NonNull Term index) { return element(CqlIdentifier.fromCql(collectionName), index); } @@ -218,7 +237,8 @@ static Selector element(String collectionName, Term index) { * @param right the right bound (inclusive). Can be {@code null} to indicate that the slice is * only left-bound. */ - static Selector range(Selector collection, Term left, Term right) { + @NonNull + static Selector range(@NonNull Selector collection, @NonNull Term left, @NonNull Term right) { return new RangeSelector(collection, left, right); } @@ -228,7 +248,9 @@ static Selector range(Selector collection, Term left, Term right) { *

          In other words, this is the equivalent of {@link #range(Selector, Term, Term)} * range(column(collectionId), left, right)}. */ - static Selector range(CqlIdentifier collectionId, Term left, Term right) { + @NonNull + static Selector range( + @NonNull CqlIdentifier collectionId, @NonNull Term left, @NonNull Term right) { return range(column(collectionId), left, right); } @@ -236,7 +258,8 @@ static Selector range(CqlIdentifier collectionId, Term left, Term right) { * Shortcut for {@link #range(CqlIdentifier, Term, Term) * range(CqlIdentifier.fromCql(collectionName), left, right)}. */ - static Selector range(String collectionName, Term left, Term right) { + @NonNull + static Selector range(@NonNull String collectionName, @NonNull Term left, @NonNull Term right) { return range(CqlIdentifier.fromCql(collectionName), left, right); } @@ -249,12 +272,14 @@ static Selector range(String collectionName, Term left, Term right) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ - static Selector listOf(Iterable elementSelectors) { + @NonNull + static Selector listOf(@NonNull Iterable elementSelectors) { return new ListSelector(elementSelectors); } /** Var-arg equivalent of {@link #listOf(Iterable)}. */ - static Selector listOf(Selector... elementSelectors) { + @NonNull + static Selector listOf(@NonNull Selector... elementSelectors) { return listOf(Arrays.asList(elementSelectors)); } @@ -267,12 +292,14 @@ static Selector listOf(Selector... elementSelectors) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ - static Selector setOf(Iterable elementSelectors) { + @NonNull + static Selector setOf(@NonNull Iterable elementSelectors) { return new SetSelector(elementSelectors); } /** Var-arg equivalent of {@link #setOf(Iterable)}. */ - static Selector setOf(Selector... elementSelectors) { + @NonNull + static Selector setOf(@NonNull Selector... elementSelectors) { return setOf(Arrays.asList(elementSelectors)); } @@ -283,12 +310,14 @@ static Selector setOf(Selector... elementSelectors) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ - static Selector tupleOf(Iterable elementSelectors) { + @NonNull + static Selector tupleOf(@NonNull Iterable elementSelectors) { return new TupleSelector(elementSelectors); } /** Var-arg equivalent of {@link #tupleOf(Iterable)}. */ - static Selector tupleOf(Selector... elementSelectors) { + @NonNull + static Selector tupleOf(@NonNull Selector... elementSelectors) { return tupleOf(Arrays.asList(elementSelectors)); } @@ -312,7 +341,8 @@ static Selector tupleOf(Selector... elementSelectors) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ - static Selector mapOf(Map elementSelectors) { + @NonNull + static Selector mapOf(@NonNull Map elementSelectors) { return mapOf(elementSelectors, null, null); } @@ -325,8 +355,11 @@ static Selector mapOf(Map elementSelectors) { * * @see #mapOf(Map) */ + @NonNull static Selector mapOf( - Map elementSelectors, DataType keyType, DataType valueType) { + @NonNull Map elementSelectors, + @Nullable DataType keyType, + @Nullable DataType valueType) { return new MapSelector(elementSelectors, keyType, valueType); } @@ -336,7 +369,8 @@ static Selector mapOf( *

          To create the data type, use the constants and static methods in {@link DataTypes}, or * {@link QueryBuilderDsl#udt(CqlIdentifier)}. */ - static Selector typeHint(Selector selector, DataType targetType) { + @NonNull + static Selector typeHint(@NonNull Selector selector, @NonNull DataType targetType) { return new TypeHintSelector(selector, targetType); } @@ -347,12 +381,15 @@ static Selector typeHint(Selector selector, DataType targetType) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ - static Selector function(CqlIdentifier functionId, Iterable arguments) { + @NonNull + static Selector function( + @NonNull CqlIdentifier functionId, @NonNull Iterable arguments) { return new FunctionSelector(null, functionId, arguments); } /** Var-arg equivalent of {@link #function(CqlIdentifier, Iterable)}. */ - static Selector function(CqlIdentifier functionId, Selector... arguments) { + @NonNull + static Selector function(@NonNull CqlIdentifier functionId, @NonNull Selector... arguments) { return function(functionId, Arrays.asList(arguments)); } @@ -360,12 +397,14 @@ static Selector function(CqlIdentifier functionId, Selector... arguments) { * Shortcut for {@link #function(CqlIdentifier, Iterable) * function(CqlIdentifier.fromCql(functionName), arguments)}. */ - static Selector function(String functionName, Iterable arguments) { + @NonNull + static Selector function(@NonNull String functionName, @NonNull Iterable arguments) { return function(CqlIdentifier.fromCql(functionName), arguments); } /** Var-arg equivalent of {@link #function(String, Iterable)}. */ - static Selector function(String functionName, Selector... arguments) { + @NonNull + static Selector function(@NonNull String functionName, @NonNull Selector... arguments) { return function(functionName, Arrays.asList(arguments)); } @@ -376,14 +415,20 @@ static Selector function(String functionName, Selector... arguments) { * * @throws IllegalArgumentException if any of the selectors is aliased. */ + @NonNull static Selector function( - CqlIdentifier keyspaceId, CqlIdentifier functionId, Iterable arguments) { + @Nullable CqlIdentifier keyspaceId, + @NonNull CqlIdentifier functionId, + @NonNull Iterable arguments) { return new FunctionSelector(keyspaceId, functionId, arguments); } /** Var-arg equivalent of {@link #function(CqlIdentifier, CqlIdentifier, Iterable)}. */ + @NonNull static Selector function( - CqlIdentifier keyspaceId, CqlIdentifier functionId, Selector... arguments) { + @Nullable CqlIdentifier keyspaceId, + @NonNull CqlIdentifier functionId, + @NonNull Selector... arguments) { return function(keyspaceId, functionId, Arrays.asList(arguments)); } @@ -391,13 +436,21 @@ static Selector function( * Shortcut for {@link #function(CqlIdentifier, CqlIdentifier, Iterable)} * function(CqlIdentifier.fromCql(functionName), arguments)}. */ - static Selector function(String keyspaceName, String functionName, Iterable arguments) { + @NonNull + static Selector function( + @Nullable String keyspaceName, + @NonNull String functionName, + @NonNull Iterable arguments) { return function( - CqlIdentifier.fromCql(keyspaceName), CqlIdentifier.fromCql(functionName), arguments); + keyspaceName == null ? null : CqlIdentifier.fromCql(keyspaceName), + CqlIdentifier.fromCql(functionName), + arguments); } /** Var-arg equivalent of {@link #function(String, String, Iterable)}. */ - static Selector function(String keyspaceName, String functionName, Selector... arguments) { + @NonNull + static Selector function( + @Nullable String keyspaceName, @NonNull String functionName, @NonNull Selector... arguments) { return function(keyspaceName, functionName, Arrays.asList(arguments)); } @@ -405,14 +458,16 @@ static Selector function(String keyspaceName, String functionName, Selector... a * Shortcut to select the result of the built-in {@code writetime} function, as in {@code SELECT * writetime(c)}. */ - static Selector writeTime(CqlIdentifier columnId) { + @NonNull + static Selector writeTime(@NonNull CqlIdentifier columnId) { return function("writetime", column(columnId)); } /** * Shortcut for {@link #writeTime(CqlIdentifier) writeTime(CqlIdentifier.fromCql(columnName))}. */ - static Selector writeTime(String columnName) { + @NonNull + static Selector writeTime(@NonNull String columnName) { return writeTime(CqlIdentifier.fromCql(columnName)); } @@ -420,12 +475,14 @@ static Selector writeTime(String columnName) { * Shortcut to select the result of the built-in {@code ttl} function, as in {@code SELECT * ttl(c)}. */ - static Selector ttl(CqlIdentifier columnId) { + @NonNull + static Selector ttl(@NonNull CqlIdentifier columnId) { return function("ttl", column(columnId)); } /** Shortcut for {@link #ttl(CqlIdentifier) ttl(CqlIdentifier.fromCql(columnName))}. */ - static Selector ttl(String columnName) { + @NonNull + static Selector ttl(@NonNull String columnName) { return ttl(CqlIdentifier.fromCql(columnName)); } @@ -437,24 +494,28 @@ static Selector ttl(String columnName) { * * @throws IllegalArgumentException if the selector is aliased. */ - static Selector cast(Selector selector, DataType targetType) { + @NonNull + static Selector cast(@NonNull Selector selector, @NonNull DataType targetType) { return new CastSelector(selector, targetType); } /** Shortcut to select the result of the built-in {@code toDate} function on a simple column. */ - static Selector toDate(CqlIdentifier columnId) { + @NonNull + static Selector toDate(@NonNull CqlIdentifier columnId) { return function("todate", Selector.column(columnId)); } /** Shortcut for {@link #toDate(CqlIdentifier) toDate(CqlIdentifier.fromCql(columnName))}. */ - static Selector toDate(String columnName) { + @NonNull + static Selector toDate(@NonNull String columnName) { return toDate(CqlIdentifier.fromCql(columnName)); } /** * Shortcut to select the result of the built-in {@code toTimestamp} function on a simple column. */ - static Selector toTimestamp(CqlIdentifier columnId) { + @NonNull + static Selector toTimestamp(@NonNull CqlIdentifier columnId) { return function("totimestamp", Selector.column(columnId)); } @@ -462,7 +523,8 @@ static Selector toTimestamp(CqlIdentifier columnId) { * Shortcut for {@link #toTimestamp(CqlIdentifier) * toTimestamp(CqlIdentifier.fromCql(columnName))}. */ - static Selector toTimestamp(String columnName) { + @NonNull + static Selector toTimestamp(@NonNull String columnName) { return toTimestamp(CqlIdentifier.fromCql(columnName)); } @@ -470,7 +532,8 @@ static Selector toTimestamp(String columnName) { * Shortcut to select the result of the built-in {@code toUnixTimestamp} function on a simple * column. */ - static Selector toUnixTimestamp(CqlIdentifier columnId) { + @NonNull + static Selector toUnixTimestamp(@NonNull CqlIdentifier columnId) { return function("tounixtimestamp", Selector.column(columnId)); } @@ -478,18 +541,22 @@ static Selector toUnixTimestamp(CqlIdentifier columnId) { * Shortcut for {@link #toUnixTimestamp(CqlIdentifier) * toUnixTimestamp(CqlIdentifier.fromCql(columnName))}. */ - static Selector toUnixTimestamp(String columnName) { + @NonNull + static Selector toUnixTimestamp(@NonNull String columnName) { return toUnixTimestamp(CqlIdentifier.fromCql(columnName)); } /** Aliases the selector, as in {@code SELECT count(*) AS total}. */ - Selector as(CqlIdentifier alias); + @NonNull + Selector as(@NonNull CqlIdentifier alias); /** Shortcut for {@link #as(CqlIdentifier) as(CqlIdentifier.fromCql(alias))} */ - default Selector as(String alias) { + @NonNull + default Selector as(@NonNull String alias) { return as(CqlIdentifier.fromCql(alias)); } /** @return null if the selector is not aliased. */ + @Nullable CqlIdentifier getAlias(); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java index 0480a16c715..f52104a92f2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java @@ -37,12 +37,14 @@ import com.datastax.oss.driver.internal.querybuilder.update.RemoveListElementAssignment; import com.datastax.oss.driver.internal.querybuilder.update.RemoveMapEntryAssignment; import com.datastax.oss.driver.internal.querybuilder.update.RemoveSetElementAssignment; +import edu.umd.cs.findbugs.annotations.NonNull; /** An assignment that appears after the SET keyword in an UPDATE statement. */ public interface Assignment extends CqlSnippet { /** Assigns a value to a column, as in {@code SET c=?}. */ - static Assignment setColumn(CqlIdentifier columnId, Term value) { + @NonNull + static Assignment setColumn(@NonNull CqlIdentifier columnId, @NonNull Term value) { return new DefaultAssignment(new ColumnLeftOperand(columnId), "=", value); } @@ -50,12 +52,15 @@ static Assignment setColumn(CqlIdentifier columnId, Term value) { * Shortcut for {@link #setColumn(CqlIdentifier, Term) * setColumn(CqlIdentifier.fromCql(columnName), value)}. */ - static Assignment setColumn(String columnName, Term value) { + @NonNull + static Assignment setColumn(@NonNull String columnName, @NonNull Term value) { return setColumn(CqlIdentifier.fromCql(columnName), value); } /** Assigns a value to a field of a UDT, as in {@code SET address.zip=?}. */ - static Assignment setField(CqlIdentifier columnId, CqlIdentifier fieldId, Term value) { + @NonNull + static Assignment setField( + @NonNull CqlIdentifier columnId, @NonNull CqlIdentifier fieldId, @NonNull Term value) { return new DefaultAssignment(new FieldLeftOperand(columnId, fieldId), "=", value); } @@ -63,12 +68,16 @@ static Assignment setField(CqlIdentifier columnId, CqlIdentifier fieldId, Term v * Shortcut for {@link #setField(CqlIdentifier, CqlIdentifier, Term) * setField(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName), value)}. */ - static Assignment setField(String columnName, String fieldName, Term value) { + @NonNull + static Assignment setField( + @NonNull String columnName, @NonNull String fieldName, @NonNull Term value) { return setField(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName), value); } /** Assigns a value to an entry in a map column, as in {@code SET map[?]=?}. */ - static Assignment setMapValue(CqlIdentifier columnId, Term index, Term value) { + @NonNull + static Assignment setMapValue( + @NonNull CqlIdentifier columnId, @NonNull Term index, @NonNull Term value) { return new DefaultAssignment(new ColumnComponentLeftOperand(columnId, index), "=", value); } @@ -76,12 +85,15 @@ static Assignment setMapValue(CqlIdentifier columnId, Term index, Term value) { * Shortcut for {@link #setMapValue(CqlIdentifier, Term, Term) * setMapValue(CqlIdentifier.fromCql(columnName), index, value)}. */ - static Assignment setMapValue(String columnName, Term index, Term value) { + @NonNull + static Assignment setMapValue( + @NonNull String columnName, @NonNull Term index, @NonNull Term value) { return setMapValue(CqlIdentifier.fromCql(columnName), index, value); } /** Increments a counter, as in {@code SET c+=?}. */ - static Assignment increment(CqlIdentifier columnId, Term amount) { + @NonNull + static Assignment increment(@NonNull CqlIdentifier columnId, @NonNull Term amount) { return new CounterAssignment(new ColumnLeftOperand(columnId), "+=", amount); } @@ -89,22 +101,26 @@ static Assignment increment(CqlIdentifier columnId, Term amount) { * Shortcut for {@link #increment(CqlIdentifier, Term) * increment(CqlIdentifier.fromCql(columnName), amount)} */ - static Assignment increment(String columnName, Term amount) { + @NonNull + static Assignment increment(@NonNull String columnName, @NonNull Term amount) { return increment(CqlIdentifier.fromCql(columnName), amount); } /** Increments a counter by 1, as in {@code SET c+=1} . */ - static Assignment increment(CqlIdentifier columnId) { + @NonNull + static Assignment increment(@NonNull CqlIdentifier columnId) { return increment(columnId, QueryBuilderDsl.literal(1)); } /** Shortcut for {@link #increment(CqlIdentifier) CqlIdentifier.fromCql(columnName)}. */ - static Assignment increment(String columnName) { + @NonNull + static Assignment increment(@NonNull String columnName) { return increment(CqlIdentifier.fromCql(columnName)); } /** Decrements a counter, as in {@code SET c-=?}. */ - static Assignment decrement(CqlIdentifier columnId, Term amount) { + @NonNull + static Assignment decrement(@NonNull CqlIdentifier columnId, @NonNull Term amount) { return new CounterAssignment(new ColumnLeftOperand(columnId), "-=", amount); } @@ -112,17 +128,20 @@ static Assignment decrement(CqlIdentifier columnId, Term amount) { * Shortcut for {@link #decrement(CqlIdentifier, Term) * decrement(CqlIdentifier.fromCql(columnName), amount)} */ - static Assignment decrement(String columnName, Term amount) { + @NonNull + static Assignment decrement(@NonNull String columnName, @NonNull Term amount) { return decrement(CqlIdentifier.fromCql(columnName), amount); } /** Decrements a counter by 1, as in {@code SET c-=1} . */ - static Assignment decrement(CqlIdentifier columnId) { + @NonNull + static Assignment decrement(@NonNull CqlIdentifier columnId) { return decrement(columnId, QueryBuilderDsl.literal(1)); } /** Shortcut for {@link #decrement(CqlIdentifier) CqlIdentifier.fromCql(columnName)}. */ - static Assignment decrement(String columnName) { + @NonNull + static Assignment decrement(@NonNull String columnName) { return decrement(CqlIdentifier.fromCql(columnName)); } @@ -131,7 +150,8 @@ static Assignment decrement(String columnName) { * *

          The term must be a collection of the same type as the column. */ - static Assignment append(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment append(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new AppendAssignment(new ColumnLeftOperand(columnId), suffix); } @@ -139,7 +159,8 @@ static Assignment append(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #append(CqlIdentifier, Term) append(CqlIdentifier.fromCql(columnName), * suffix)}. */ - static Assignment append(String columnName, Term suffix) { + @NonNull + static Assignment append(@NonNull String columnName, @NonNull Term suffix) { return append(CqlIdentifier.fromCql(columnName), suffix); } @@ -148,7 +169,8 @@ static Assignment append(String columnName, Term suffix) { * *

          The term must be of the same type as the column's elements. */ - static Assignment appendListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment appendListElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new AppendListElementAssignment(columnId, suffix); } @@ -156,7 +178,8 @@ static Assignment appendListElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #appendListElement(CqlIdentifier, Term) * appendListElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment appendListElement(String columnName, Term suffix) { + @NonNull + static Assignment appendListElement(@NonNull String columnName, @NonNull Term suffix) { return appendListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -165,7 +188,8 @@ static Assignment appendListElement(String columnName, Term suffix) { * *

          The term must be of the same type as the column's elements. */ - static Assignment appendSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment appendSetElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new AppendSetElementAssignment(columnId, suffix); } @@ -173,7 +197,8 @@ static Assignment appendSetElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #appendSetElement(CqlIdentifier, Term) * appendSetElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment appendSetElement(String columnName, Term suffix) { + @NonNull + static Assignment appendSetElement(@NonNull String columnName, @NonNull Term suffix) { return appendSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -182,7 +207,9 @@ static Assignment appendSetElement(String columnName, Term suffix) { * *

          The terms must be of the same type as the column's keys and values respectively. */ - static Assignment appendMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + static Assignment appendMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return new AppendMapEntryAssignment(columnId, key, value); } @@ -190,7 +217,9 @@ static Assignment appendMapEntry(CqlIdentifier columnId, Term key, Term value) { * Shortcut for {@link #appendMapEntry(CqlIdentifier, Term, Term) * appendMapEntry(CqlIdentifier.fromCql(columnName), key, value)}. */ - static Assignment appendMapEntry(String columnName, Term key, Term value) { + @NonNull + static Assignment appendMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return appendMapEntry(CqlIdentifier.fromCql(columnName), key, value); } @@ -199,7 +228,8 @@ static Assignment appendMapEntry(String columnName, Term key, Term value) { * *

          The term must be a collection of the same type as the column. */ - static Assignment prepend(CqlIdentifier columnId, Term prefix) { + @NonNull + static Assignment prepend(@NonNull CqlIdentifier columnId, @NonNull Term prefix) { return new PrependAssignment(columnId, prefix); } @@ -207,7 +237,8 @@ static Assignment prepend(CqlIdentifier columnId, Term prefix) { * Shortcut for {@link #prepend(CqlIdentifier, Term) prepend(CqlIdentifier.fromCql(columnName), * prefix)}. */ - static Assignment prepend(String columnName, Term prefix) { + @NonNull + static Assignment prepend(@NonNull String columnName, @NonNull Term prefix) { return prepend(CqlIdentifier.fromCql(columnName), prefix); } @@ -216,7 +247,8 @@ static Assignment prepend(String columnName, Term prefix) { * *

          The term must be of the same type as the column's elements. */ - static Assignment prependListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment prependListElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new PrependListElementAssignment(columnId, suffix); } @@ -224,7 +256,8 @@ static Assignment prependListElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #prependListElement(CqlIdentifier, Term) * prependListElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment prependListElement(String columnName, Term suffix) { + @NonNull + static Assignment prependListElement(@NonNull String columnName, @NonNull Term suffix) { return prependListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -233,7 +266,8 @@ static Assignment prependListElement(String columnName, Term suffix) { * *

          The term must be of the same type as the column's elements. */ - static Assignment prependSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment prependSetElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new PrependSetElementAssignment(columnId, suffix); } @@ -241,7 +275,8 @@ static Assignment prependSetElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #prependSetElement(CqlIdentifier, Term) * prependSetElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment prependSetElement(String columnName, Term suffix) { + @NonNull + static Assignment prependSetElement(@NonNull String columnName, @NonNull Term suffix) { return prependSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -250,7 +285,9 @@ static Assignment prependSetElement(String columnName, Term suffix) { * *

          The terms must be of the same type as the column's keys and values respectively. */ - static Assignment prependMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + static Assignment prependMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return new PrependMapEntryAssignment(columnId, key, value); } @@ -258,7 +295,9 @@ static Assignment prependMapEntry(CqlIdentifier columnId, Term key, Term value) * Shortcut for {@link #prependMapEntry(CqlIdentifier, Term, Term) * prependMapEntry(CqlIdentifier.fromCql(columnName), key, value)}. */ - static Assignment prependMapEntry(String columnName, Term key, Term value) { + @NonNull + static Assignment prependMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return prependMapEntry(CqlIdentifier.fromCql(columnName), key, value); } @@ -272,7 +311,8 @@ static Assignment prependMapEntry(String columnName, Term key, Term value) { * and it would be possible to generate an expression such as {@code counter-=1} with this method, * a collection removal is idempotent while a counter decrement isn't. */ - static Assignment remove(CqlIdentifier columnId, Term collectionToRemove) { + @NonNull + static Assignment remove(@NonNull CqlIdentifier columnId, @NonNull Term collectionToRemove) { return new DefaultAssignment(new ColumnLeftOperand(columnId), "-=", collectionToRemove); } @@ -280,7 +320,8 @@ static Assignment remove(CqlIdentifier columnId, Term collectionToRemove) { * Shortcut for {@link #remove(CqlIdentifier, Term) remove(CqlIdentifier.fromCql(columnName), * collectionToRemove)}. */ - static Assignment remove(String columnName, Term collectionToRemove) { + @NonNull + static Assignment remove(@NonNull String columnName, @NonNull Term collectionToRemove) { return remove(CqlIdentifier.fromCql(columnName), collectionToRemove); } @@ -289,7 +330,8 @@ static Assignment remove(String columnName, Term collectionToRemove) { * *

          The term must be of the same type as the column's elements. */ - static Assignment removeListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment removeListElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new RemoveListElementAssignment(columnId, suffix); } @@ -297,7 +339,8 @@ static Assignment removeListElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #removeListElement(CqlIdentifier, Term) * removeListElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment removeListElement(String columnName, Term suffix) { + @NonNull + static Assignment removeListElement(@NonNull String columnName, @NonNull Term suffix) { return removeListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -306,7 +349,8 @@ static Assignment removeListElement(String columnName, Term suffix) { * *

          The term must be of the same type as the column's elements. */ - static Assignment removeSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + static Assignment removeSetElement(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return new RemoveSetElementAssignment(columnId, suffix); } @@ -314,7 +358,8 @@ static Assignment removeSetElement(CqlIdentifier columnId, Term suffix) { * Shortcut for {@link #removeSetElement(CqlIdentifier, Term) * removeSetElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - static Assignment removeSetElement(String columnName, Term suffix) { + @NonNull + static Assignment removeSetElement(@NonNull String columnName, @NonNull Term suffix) { return removeSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -323,7 +368,9 @@ static Assignment removeSetElement(String columnName, Term suffix) { * *

          The terms must be of the same type as the column's keys and values respectively. */ - static Assignment removeMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + static Assignment removeMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return new RemoveMapEntryAssignment(columnId, key, value); } @@ -331,7 +378,9 @@ static Assignment removeMapEntry(CqlIdentifier columnId, Term key, Term value) { * Shortcut for {@link #removeMapEntry(CqlIdentifier, Term, Term) * removeMapEntry(CqlIdentifier.fromCql(columnName), key, value)}. */ - static Assignment removeMapEntry(String columnName, Term key, Term value) { + @NonNull + static Assignment removeMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return removeMapEntry(CqlIdentifier.fromCql(columnName), key, value); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java index 8da1e6d9e42..8ac57142489 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; public interface OngoingAssignment { @@ -32,7 +33,8 @@ public interface OngoingAssignment { *

          If you add multiple assignments as one, consider {@link #set(Iterable)} as a more efficient * alternative. */ - UpdateWithAssignments set(Assignment assignment); + @NonNull + UpdateWithAssignments set(@NonNull Assignment assignment); /** * Adds multiple assignments at once. @@ -43,10 +45,12 @@ public interface OngoingAssignment { *

          To create the argument, use one of the factory methods in {@link Assignment}, for example * Assignment{@link #setColumn(CqlIdentifier, Term)}. */ - UpdateWithAssignments set(Iterable additionalAssignments); + @NonNull + UpdateWithAssignments set(@NonNull Iterable additionalAssignments); /** Var-arg equivalent of {@link #set(Iterable)}. */ - default UpdateWithAssignments set(Assignment... assignments) { + @NonNull + default UpdateWithAssignments set(@NonNull Assignment... assignments) { return set(Arrays.asList(assignments)); } @@ -57,7 +61,8 @@ default UpdateWithAssignments set(Assignment... assignments) { * * @see Assignment#setColumn(CqlIdentifier, Term) */ - default UpdateWithAssignments setColumn(CqlIdentifier columnId, Term value) { + @NonNull + default UpdateWithAssignments setColumn(@NonNull CqlIdentifier columnId, @NonNull Term value) { return set(Assignment.setColumn(columnId, value)); } @@ -67,7 +72,8 @@ default UpdateWithAssignments setColumn(CqlIdentifier columnId, Term value) { * * @see Assignment#setColumn(String, Term) */ - default UpdateWithAssignments setColumn(String columnName, Term value) { + @NonNull + default UpdateWithAssignments setColumn(@NonNull String columnName, @NonNull Term value) { return setColumn(CqlIdentifier.fromCql(columnName), value); } @@ -79,8 +85,9 @@ default UpdateWithAssignments setColumn(String columnName, Term value) { * * @see Assignment#setField(CqlIdentifier, CqlIdentifier, Term) */ + @NonNull default UpdateWithAssignments setField( - CqlIdentifier columnId, CqlIdentifier fieldId, Term value) { + @NonNull CqlIdentifier columnId, @NonNull CqlIdentifier fieldId, @NonNull Term value) { return set(Assignment.setField(columnId, fieldId, value)); } @@ -90,7 +97,9 @@ default UpdateWithAssignments setField( * * @see Assignment#setField(String, String, Term) */ - default UpdateWithAssignments setField(String columnName, String fieldName, Term value) { + @NonNull + default UpdateWithAssignments setField( + @NonNull String columnName, @NonNull String fieldName, @NonNull Term value) { return setField(CqlIdentifier.fromCql(columnName), CqlIdentifier.fromCql(fieldName), value); } @@ -102,7 +111,9 @@ default UpdateWithAssignments setField(String columnName, String fieldName, Term * * @see Assignment#setMapValue(CqlIdentifier, Term, Term) */ - default UpdateWithAssignments setMapValue(CqlIdentifier columnId, Term index, Term value) { + @NonNull + default UpdateWithAssignments setMapValue( + @NonNull CqlIdentifier columnId, @NonNull Term index, @NonNull Term value) { return set(Assignment.setMapValue(columnId, index, value)); } @@ -112,7 +123,9 @@ default UpdateWithAssignments setMapValue(CqlIdentifier columnId, Term index, Te * * @see Assignment#setMapValue(String, Term, Term) */ - default UpdateWithAssignments setMapValue(String columnName, Term index, Term value) { + @NonNull + default UpdateWithAssignments setMapValue( + @NonNull String columnName, @NonNull Term index, @NonNull Term value) { return setMapValue(CqlIdentifier.fromCql(columnName), index, value); } @@ -123,7 +136,8 @@ default UpdateWithAssignments setMapValue(String columnName, Term index, Term va * * @see Assignment#increment(CqlIdentifier, Term) */ - default UpdateWithAssignments increment(CqlIdentifier columnId, Term amount) { + @NonNull + default UpdateWithAssignments increment(@NonNull CqlIdentifier columnId, @NonNull Term amount) { return set(Assignment.increment(columnId, amount)); } @@ -133,7 +147,8 @@ default UpdateWithAssignments increment(CqlIdentifier columnId, Term amount) { * * @see Assignment#increment(String, Term) */ - default UpdateWithAssignments increment(String columnName, Term amount) { + @NonNull + default UpdateWithAssignments increment(@NonNull String columnName, @NonNull Term amount) { return increment(CqlIdentifier.fromCql(columnName), amount); } @@ -145,7 +160,8 @@ default UpdateWithAssignments increment(String columnName, Term amount) { * * @see Assignment#increment(CqlIdentifier) */ - default UpdateWithAssignments increment(CqlIdentifier columnId) { + @NonNull + default UpdateWithAssignments increment(@NonNull CqlIdentifier columnId) { return increment(columnId, QueryBuilderDsl.literal(1)); } @@ -154,7 +170,8 @@ default UpdateWithAssignments increment(CqlIdentifier columnId) { * * @see Assignment#increment(CqlIdentifier) */ - default UpdateWithAssignments increment(String columnName) { + @NonNull + default UpdateWithAssignments increment(@NonNull String columnName) { return increment(CqlIdentifier.fromCql(columnName)); } @@ -165,7 +182,8 @@ default UpdateWithAssignments increment(String columnName) { * * @see Assignment#decrement(CqlIdentifier, Term) */ - default UpdateWithAssignments decrement(CqlIdentifier columnId, Term amount) { + @NonNull + default UpdateWithAssignments decrement(@NonNull CqlIdentifier columnId, @NonNull Term amount) { return set(Assignment.decrement(columnId, amount)); } @@ -175,7 +193,8 @@ default UpdateWithAssignments decrement(CqlIdentifier columnId, Term amount) { * * @see Assignment#decrement(String, Term) */ - default UpdateWithAssignments decrement(String columnName, Term amount) { + @NonNull + default UpdateWithAssignments decrement(@NonNull String columnName, @NonNull Term amount) { return decrement(CqlIdentifier.fromCql(columnName), amount); } @@ -186,7 +205,8 @@ default UpdateWithAssignments decrement(String columnName, Term amount) { * * @see Assignment#decrement(CqlIdentifier) */ - default UpdateWithAssignments decrement(CqlIdentifier columnId) { + @NonNull + default UpdateWithAssignments decrement(@NonNull CqlIdentifier columnId) { return decrement(columnId, QueryBuilderDsl.literal(1)); } @@ -195,7 +215,8 @@ default UpdateWithAssignments decrement(CqlIdentifier columnId) { * * @see Assignment#decrement(String) */ - default UpdateWithAssignments decrement(String columnName) { + @NonNull + default UpdateWithAssignments decrement(@NonNull String columnName) { return decrement(CqlIdentifier.fromCql(columnName)); } @@ -208,7 +229,8 @@ default UpdateWithAssignments decrement(String columnName) { * * @see Assignment#append(CqlIdentifier, Term) */ - default UpdateWithAssignments append(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments append(@NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.append(columnId, suffix)); } @@ -218,7 +240,8 @@ default UpdateWithAssignments append(CqlIdentifier columnId, Term suffix) { * * @see Assignment#append(String, Term) */ - default UpdateWithAssignments append(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments append(@NonNull String columnName, @NonNull Term suffix) { return append(CqlIdentifier.fromCql(columnName), suffix); } @@ -232,7 +255,9 @@ default UpdateWithAssignments append(String columnName, Term suffix) { * * @see Assignment#appendListElement(CqlIdentifier, Term) */ - default UpdateWithAssignments appendListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments appendListElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.appendListElement(columnId, suffix)); } @@ -242,7 +267,9 @@ default UpdateWithAssignments appendListElement(CqlIdentifier columnId, Term suf * * @see Assignment#appendListElement(String, Term) */ - default UpdateWithAssignments appendListElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments appendListElement( + @NonNull String columnName, @NonNull Term suffix) { return appendListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -256,7 +283,9 @@ default UpdateWithAssignments appendListElement(String columnName, Term suffix) * * @see Assignment#appendSetElement(CqlIdentifier, Term) */ - default UpdateWithAssignments appendSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments appendSetElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.appendSetElement(columnId, suffix)); } @@ -264,7 +293,8 @@ default UpdateWithAssignments appendSetElement(CqlIdentifier columnId, Term suff * Shortcut for {@link #appendSetElement(CqlIdentifier, Term) * appendSetElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - default UpdateWithAssignments appendSetElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments appendSetElement(@NonNull String columnName, @NonNull Term suffix) { return appendSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -278,7 +308,9 @@ default UpdateWithAssignments appendSetElement(String columnName, Term suffix) { * * @see Assignment#appendMapEntry(CqlIdentifier, Term, Term) */ - default UpdateWithAssignments appendMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + default UpdateWithAssignments appendMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return set(Assignment.appendMapEntry(columnId, key, value)); } @@ -288,7 +320,9 @@ default UpdateWithAssignments appendMapEntry(CqlIdentifier columnId, Term key, T * * @see Assignment#appendMapEntry(String, Term, Term) */ - default UpdateWithAssignments appendMapEntry(String columnName, Term key, Term value) { + @NonNull + default UpdateWithAssignments appendMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return appendMapEntry(CqlIdentifier.fromCql(columnName), key, value); } @@ -301,7 +335,8 @@ default UpdateWithAssignments appendMapEntry(String columnName, Term key, Term v * * @see Assignment#prepend(CqlIdentifier, Term) */ - default UpdateWithAssignments prepend(CqlIdentifier columnId, Term prefix) { + @NonNull + default UpdateWithAssignments prepend(@NonNull CqlIdentifier columnId, @NonNull Term prefix) { return set(Assignment.prepend(columnId, prefix)); } @@ -311,7 +346,8 @@ default UpdateWithAssignments prepend(CqlIdentifier columnId, Term prefix) { * * @see Assignment#prepend(String, Term) */ - default UpdateWithAssignments prepend(String columnName, Term prefix) { + @NonNull + default UpdateWithAssignments prepend(@NonNull String columnName, @NonNull Term prefix) { return prepend(CqlIdentifier.fromCql(columnName), prefix); } @@ -325,7 +361,9 @@ default UpdateWithAssignments prepend(String columnName, Term prefix) { * * @see Assignment#prependListElement(CqlIdentifier, Term) */ - default UpdateWithAssignments prependListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments prependListElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.prependListElement(columnId, suffix)); } @@ -335,7 +373,9 @@ default UpdateWithAssignments prependListElement(CqlIdentifier columnId, Term su * * @see Assignment#prependListElement(String, Term) */ - default UpdateWithAssignments prependListElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments prependListElement( + @NonNull String columnName, @NonNull Term suffix) { return prependListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -349,7 +389,9 @@ default UpdateWithAssignments prependListElement(String columnName, Term suffix) * * @see Assignment#prependSetElement(CqlIdentifier, Term) */ - default UpdateWithAssignments prependSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments prependSetElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.prependSetElement(columnId, suffix)); } @@ -359,7 +401,9 @@ default UpdateWithAssignments prependSetElement(CqlIdentifier columnId, Term suf * * @see Assignment#prependSetElement(String, Term) */ - default UpdateWithAssignments prependSetElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments prependSetElement( + @NonNull String columnName, @NonNull Term suffix) { return prependSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -373,7 +417,9 @@ default UpdateWithAssignments prependSetElement(String columnName, Term suffix) * * @see Assignment#prependMapEntry(CqlIdentifier, Term, Term) */ - default UpdateWithAssignments prependMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + default UpdateWithAssignments prependMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return set(Assignment.prependMapEntry(columnId, key, value)); } @@ -383,7 +429,9 @@ default UpdateWithAssignments prependMapEntry(CqlIdentifier columnId, Term key, * * @see Assignment#prependMapEntry(String, Term, Term) */ - default UpdateWithAssignments prependMapEntry(String columnName, Term key, Term value) { + @NonNull + default UpdateWithAssignments prependMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return prependMapEntry(CqlIdentifier.fromCql(columnName), key, value); } @@ -402,7 +450,9 @@ default UpdateWithAssignments prependMapEntry(String columnName, Term key, Term * * @see Assignment#remove(CqlIdentifier, Term) */ - default UpdateWithAssignments remove(CqlIdentifier columnId, Term collectionToRemove) { + @NonNull + default UpdateWithAssignments remove( + @NonNull CqlIdentifier columnId, @NonNull Term collectionToRemove) { return set(Assignment.remove(columnId, collectionToRemove)); } @@ -412,7 +462,9 @@ default UpdateWithAssignments remove(CqlIdentifier columnId, Term collectionToRe * * @see Assignment#remove(String, Term) */ - default UpdateWithAssignments remove(String columnName, Term collectionToRemove) { + @NonNull + default UpdateWithAssignments remove( + @NonNull String columnName, @NonNull Term collectionToRemove) { return remove(CqlIdentifier.fromCql(columnName), collectionToRemove); } @@ -426,7 +478,9 @@ default UpdateWithAssignments remove(String columnName, Term collectionToRemove) * * @see Assignment#removeListElement(CqlIdentifier, Term) */ - default UpdateWithAssignments removeListElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments removeListElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.removeListElement(columnId, suffix)); } @@ -436,7 +490,9 @@ default UpdateWithAssignments removeListElement(CqlIdentifier columnId, Term suf * * @see Assignment#removeListElement(String, Term) */ - default UpdateWithAssignments removeListElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments removeListElement( + @NonNull String columnName, @NonNull Term suffix) { return removeListElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -450,7 +506,9 @@ default UpdateWithAssignments removeListElement(String columnName, Term suffix) * * @see Assignment#removeSetElement(CqlIdentifier, Term) */ - default UpdateWithAssignments removeSetElement(CqlIdentifier columnId, Term suffix) { + @NonNull + default UpdateWithAssignments removeSetElement( + @NonNull CqlIdentifier columnId, @NonNull Term suffix) { return set(Assignment.removeSetElement(columnId, suffix)); } @@ -458,7 +516,8 @@ default UpdateWithAssignments removeSetElement(CqlIdentifier columnId, Term suff * Shortcut for {@link #removeSetElement(CqlIdentifier, Term) * removeSetElement(CqlIdentifier.fromCql(columnName), suffix)}. */ - default UpdateWithAssignments removeSetElement(String columnName, Term suffix) { + @NonNull + default UpdateWithAssignments removeSetElement(@NonNull String columnName, @NonNull Term suffix) { return removeSetElement(CqlIdentifier.fromCql(columnName), suffix); } @@ -472,7 +531,9 @@ default UpdateWithAssignments removeSetElement(String columnName, Term suffix) { * * @see Assignment#removeMapEntry(CqlIdentifier, Term, Term) */ - default UpdateWithAssignments removeMapEntry(CqlIdentifier columnId, Term key, Term value) { + @NonNull + default UpdateWithAssignments removeMapEntry( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { return set(Assignment.removeMapEntry(columnId, key, value)); } @@ -482,7 +543,9 @@ default UpdateWithAssignments removeMapEntry(CqlIdentifier columnId, Term key, T * * @see Assignment#removeMapEntry(String, Term, Term) */ - default UpdateWithAssignments removeMapEntry(String columnName, Term key, Term value) { + @NonNull + default UpdateWithAssignments removeMapEntry( + @NonNull String columnName, @NonNull Term key, @NonNull Term value) { return removeMapEntry(CqlIdentifier.fromCql(columnName), key, value); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java index 2636d398f26..e0dd69167e8 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.update; import com.datastax.oss.driver.api.querybuilder.BindMarker; +import edu.umd.cs.findbugs.annotations.NonNull; /** * The beginning of an UPDATE statement. It needs at least one assignment before the WHERE clause @@ -29,6 +30,7 @@ public interface UpdateStart extends OngoingAssignment { *

          If this method or {@link #usingTimestamp(BindMarker)} is called multiple times, the last * value is used. */ + @NonNull UpdateStart usingTimestamp(long timestamp); /** @@ -37,5 +39,6 @@ public interface UpdateStart extends OngoingAssignment { *

          If this method or {@link #usingTimestamp(long)} is called multiple times, the last value is * used. */ - UpdateStart usingTimestamp(BindMarker bindMarker); + @NonNull + UpdateStart usingTimestamp(@NonNull BindMarker bindMarker); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ArithmeticOperator.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ArithmeticOperator.java index 3b9b2cf3744..ccb6949a7c5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ArithmeticOperator.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ArithmeticOperator.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.querybuilder; +import edu.umd.cs.findbugs.annotations.NonNull; + public enum ArithmeticOperator { OPPOSITE("-", 2, 2), PRODUCT("*", 2, 2), @@ -34,6 +36,7 @@ public enum ArithmeticOperator { this.precedenceRight = precedenceRight; } + @NonNull public String getSymbol() { return symbol; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java index 80e1c2e9eec..6e4117c3856 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java @@ -17,16 +17,18 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.CqlSnippet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Collection; public class CqlHelper { public static void appendIds( - Iterable ids, - StringBuilder builder, - String prefix, - String separator, - String suffix) { + @NonNull Iterable ids, + @NonNull StringBuilder builder, + @Nullable String prefix, + @NonNull String separator, + @Nullable String suffix) { boolean first = true; for (CqlIdentifier id : ids) { if (first) { @@ -45,11 +47,11 @@ public static void appendIds( } public static void append( - Iterable snippets, - StringBuilder builder, - String prefix, - String separator, - String suffix) { + @NonNull Iterable snippets, + @NonNull StringBuilder builder, + @Nullable String prefix, + @NonNull String separator, + @Nullable String suffix) { boolean first = true; for (CqlSnippet snippet : snippets) { if (first) { @@ -67,7 +69,10 @@ public static void append( } } - public static void qualify(CqlIdentifier keyspace, CqlIdentifier element, StringBuilder builder) { + public static void qualify( + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier element, + @NonNull StringBuilder builder) { if (keyspace != null) { builder.append(keyspace.asCql(true)).append('.'); } @@ -75,9 +80,9 @@ public static void qualify(CqlIdentifier keyspace, CqlIdentifier element, String } public static void buildPrimaryKey( - Collection partitionKeyColumns, - Collection clusteringKeyColumns, - StringBuilder builder) { + @NonNull Collection partitionKeyColumns, + @NonNull Collection clusteringKeyColumns, + @NonNull StringBuilder builder) { builder.append("PRIMARY KEY("); boolean firstKey = true; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java index 117b039b4f8..77dcd67b6b2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java @@ -20,6 +20,8 @@ import com.datastax.oss.driver.api.querybuilder.Literal; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -29,11 +31,12 @@ public class DefaultLiteral implements Literal { private final TypeCodec codec; private final CqlIdentifier alias; - public DefaultLiteral(T value, TypeCodec codec) { + public DefaultLiteral(@Nullable T value, @Nullable TypeCodec codec) { this(value, codec, null); } - public DefaultLiteral(T value, TypeCodec codec, CqlIdentifier alias) { + public DefaultLiteral( + @Nullable T value, @Nullable TypeCodec codec, @Nullable CqlIdentifier alias) { Preconditions.checkArgument( value == null || codec != null, "Must provide a codec if the value is not null"); this.value = value; @@ -42,7 +45,7 @@ public DefaultLiteral(T value, TypeCodec codec, CqlIdentifier alias) { } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { if (value == null) { builder.append("NULL"); } else { @@ -58,19 +61,23 @@ public boolean isIdempotent() { return true; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new DefaultLiteral<>(value, codec, alias); } + @Nullable public T getValue() { return value; } + @Nullable public TypeCodec getCodec() { return codec; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultRaw.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultRaw.java index a766624f704..c60d85d0290 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultRaw.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultRaw.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.Raw; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -28,23 +30,24 @@ public class DefaultRaw implements Raw { private final String rawExpression; private final CqlIdentifier alias; - public DefaultRaw(String rawExpression) { + public DefaultRaw(@NonNull String rawExpression) { this(rawExpression, null); } - private DefaultRaw(String rawExpression, CqlIdentifier alias) { + private DefaultRaw(@NonNull String rawExpression, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(rawExpression); this.rawExpression = rawExpression; this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new DefaultRaw(rawExpression, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(rawExpression); if (alias != null) { builder.append(" AS ").append(alias.asCql(true)); @@ -56,10 +59,12 @@ public boolean isIdempotent() { return false; } + @NonNull public String getRawExpression() { return rawExpression; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ImmutableCollections.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ImmutableCollections.java index fb62bdd8971..86e7b1e239d 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ImmutableCollections.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/ImmutableCollections.java @@ -17,20 +17,26 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; import java.util.function.Function; public class ImmutableCollections { - public static ImmutableList append(ImmutableList list, T newElement) { + @NonNull + public static ImmutableList append(@NonNull ImmutableList list, @NonNull T newElement) { return ImmutableList.builder().addAll(list).add(newElement).build(); } - public static ImmutableList concat(ImmutableList list1, Iterable list2) { + @NonNull + public static ImmutableList concat( + @NonNull ImmutableList list1, @NonNull Iterable list2) { return ImmutableList.builder().addAll(list1).addAll(list2).build(); } - public static ImmutableList modifyLast(ImmutableList list, Function change) { + @NonNull + public static ImmutableList modifyLast( + @NonNull ImmutableList list, @NonNull Function change) { ImmutableList.Builder builder = ImmutableList.builder(); int size = list.size(); for (int i = 0; i < size - 1; i++) { @@ -48,7 +54,9 @@ public static ImmutableList modifyLast(ImmutableList list, Function1, b=>2, c=>3}, a, 4) == {b=>2, c=>3, a=>4} * } */ - public static ImmutableMap append(ImmutableMap map, K newKey, V newValue) { + @NonNull + public static ImmutableMap append( + @NonNull ImmutableMap map, @NonNull K newKey, @NonNull V newValue) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Map.Entry entry : map.entrySet()) { if (!entry.getKey().equals(newKey)) { @@ -68,7 +76,9 @@ public static ImmutableMap append(ImmutableMap map, K newKey, * concat({a=>1, b=>2, c=>3}, {c=>4, a=>5}) == {b=>2, c=>4, a=>5} * } */ - public static ImmutableMap concat(ImmutableMap map1, Map map2) { + @NonNull + public static ImmutableMap concat( + @NonNull ImmutableMap map1, @NonNull Map map2) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Map.Entry entry : map1.entrySet()) { if (!map2.containsKey(entry.getKey())) { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultCondition.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultCondition.java index b431c0fecaf..8fa66674b79 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultCondition.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultCondition.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.querybuilder.condition.Condition; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -27,14 +29,15 @@ public class DefaultCondition implements Condition { private final String operator; private final Term rightOperand; - public DefaultCondition(LeftOperand leftOperand, String operator, Term rightOperand) { + public DefaultCondition( + @NonNull LeftOperand leftOperand, @NonNull String operator, @Nullable Term rightOperand) { this.leftOperand = leftOperand; this.operator = operator; this.rightOperand = rightOperand; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { leftOperand.appendTo(builder); builder.append(operator); if (rightOperand != null) { @@ -42,14 +45,17 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public LeftOperand getLeftOperand() { return leftOperand; } + @NonNull public String getOperator() { return operator; } + @Nullable public Term getRightOperand() { return rightOperand; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultConditionBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultConditionBuilder.java index 64824d0bca8..3f3561cfe45 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultConditionBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/condition/DefaultConditionBuilder.java @@ -20,6 +20,8 @@ import com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -27,12 +29,13 @@ public class DefaultConditionBuilder implements ConditionBuilder { private final LeftOperand leftOperand; - public DefaultConditionBuilder(LeftOperand leftOperand) { + public DefaultConditionBuilder(@NonNull LeftOperand leftOperand) { this.leftOperand = leftOperand; } + @NonNull @Override - public Condition build(String operator, Term rightOperand) { + public Condition build(@NonNull String operator, @Nullable Term rightOperand) { return new DefaultCondition(leftOperand, operator, rightOperand); } @@ -43,13 +46,15 @@ public static class Fluent> private final ConditionalStatement statement; private final ConditionBuilder delegate; - public Fluent(ConditionalStatement statement, LeftOperand leftOperand) { + public Fluent( + @NonNull ConditionalStatement statement, @NonNull LeftOperand leftOperand) { this.statement = statement; this.delegate = new DefaultConditionBuilder(leftOperand); } + @NonNull @Override - public StatementT build(String operator, Term rightOperand) { + public StatementT build(@NonNull String operator, @Nullable Term rightOperand) { return statement.if_(delegate.build(operator, rightOperand)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java index b7082667cd2..1c6872f1a59 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java @@ -28,6 +28,8 @@ import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.internal.querybuilder.select.ElementSelector; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -41,18 +43,18 @@ public class DefaultDelete implements DeleteSelection, Delete { private final boolean ifExists; private final ImmutableList conditions; - public DefaultDelete(CqlIdentifier keyspace, CqlIdentifier table) { + public DefaultDelete(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { this(keyspace, table, ImmutableList.of(), ImmutableList.of(), null, false, ImmutableList.of()); } public DefaultDelete( - CqlIdentifier keyspace, - CqlIdentifier table, - ImmutableList selectors, - ImmutableList relations, - Object timestamp, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier table, + @NonNull ImmutableList selectors, + @NonNull ImmutableList relations, + @Nullable Object timestamp, boolean ifExists, - ImmutableList conditions) { + @NonNull ImmutableList conditions) { this.keyspace = keyspace; this.table = table; this.selectors = selectors; @@ -62,69 +64,82 @@ public DefaultDelete( this.conditions = conditions; } + @NonNull @Override - public DeleteSelection selector(Selector selector) { + public DeleteSelection selector(@NonNull Selector selector) { return withSelectors(ImmutableCollections.append(selectors, selector)); } + @NonNull @Override - public DeleteSelection selectors(Iterable additionalSelectors) { + public DeleteSelection selectors(@NonNull Iterable additionalSelectors) { return withSelectors(ImmutableCollections.concat(selectors, additionalSelectors)); } - public DeleteSelection withSelectors(ImmutableList newSelectors) { + @NonNull + public DeleteSelection withSelectors(@NonNull ImmutableList newSelectors) { return new DefaultDelete( keyspace, table, newSelectors, relations, timestamp, ifExists, conditions); } + @NonNull @Override - public Delete where(Relation relation) { + public Delete where(@NonNull Relation relation) { return withRelations(ImmutableCollections.append(relations, relation)); } + @NonNull @Override - public Delete where(Iterable additionalRelations) { + public Delete where(@NonNull Iterable additionalRelations) { return withRelations(ImmutableCollections.concat(relations, additionalRelations)); } - public Delete withRelations(ImmutableList newRelations) { + @NonNull + public Delete withRelations(@NonNull ImmutableList newRelations) { return new DefaultDelete( keyspace, table, selectors, newRelations, timestamp, ifExists, conditions); } + @NonNull @Override public DeleteSelection usingTimestamp(long newTimestamp) { return new DefaultDelete( keyspace, table, selectors, relations, newTimestamp, ifExists, conditions); } + @NonNull @Override - public DeleteSelection usingTimestamp(BindMarker newTimestamp) { + public DeleteSelection usingTimestamp(@Nullable BindMarker newTimestamp) { return new DefaultDelete( keyspace, table, selectors, relations, newTimestamp, ifExists, conditions); } + @NonNull @Override public Delete ifExists() { return new DefaultDelete( keyspace, table, selectors, relations, timestamp, true, ImmutableList.of()); } + @NonNull @Override - public Delete if_(Condition condition) { + public Delete if_(@NonNull Condition condition) { return withConditions(ImmutableCollections.append(conditions, condition)); } + @NonNull @Override - public Delete if_(Iterable additionalConditions) { + public Delete if_(@NonNull Iterable additionalConditions) { return withConditions(ImmutableCollections.concat(conditions, additionalConditions)); } - public Delete withConditions(ImmutableList newConditions) { + @NonNull + public Delete withConditions(@NonNull ImmutableList newConditions) { return new DefaultDelete( keyspace, table, selectors, relations, timestamp, false, newConditions); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("DELETE"); @@ -153,11 +168,13 @@ public String asCql() { return builder.toString(); } + @NonNull @Override public SimpleStatement build() { return builder().build(); } + @NonNull @Override public SimpleStatementBuilder builder() { return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); @@ -184,22 +201,27 @@ public boolean isIdempotent() { } } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return table; } + @NonNull public ImmutableList getSelectors() { return selectors; } + @NonNull public ImmutableList getRelations() { return relations; } + @Nullable public Object getTimestamp() { return timestamp; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index 776d92ece3d..68e41a8a091 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -29,6 +29,8 @@ import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -47,17 +49,17 @@ public enum MissingJsonBehavior { private final Object timestamp; private final boolean ifNotExists; - public DefaultInsert(CqlIdentifier keyspace, CqlIdentifier table) { + public DefaultInsert(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { this(keyspace, table, null, null, ImmutableMap.of(), null, false); } public DefaultInsert( - CqlIdentifier keyspace, - CqlIdentifier table, - Term json, - MissingJsonBehavior missingJsonBehavior, - ImmutableMap assignments, - Object timestamp, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier table, + @Nullable Term json, + @Nullable MissingJsonBehavior missingJsonBehavior, + @NonNull ImmutableMap assignments, + @Nullable Object timestamp, boolean ifNotExists) { // Note: the public API guarantees this, but check in case someone is calling the internal API // directly. @@ -72,8 +74,9 @@ public DefaultInsert( this.ifNotExists = ifNotExists; } + @NonNull @Override - public JsonInsert json(String json) { + public JsonInsert json(@NonNull String json) { return new DefaultInsert( keyspace, table, @@ -84,18 +87,21 @@ public JsonInsert json(String json) { ifNotExists); } + @NonNull @Override - public JsonInsert json(BindMarker json) { + public JsonInsert json(@NonNull BindMarker json) { return new DefaultInsert( keyspace, table, json, missingJsonBehavior, ImmutableMap.of(), timestamp, ifNotExists); } + @NonNull @Override public JsonInsert defaultNull() { return new DefaultInsert( keyspace, table, json, MissingJsonBehavior.NULL, ImmutableMap.of(), timestamp, ifNotExists); } + @NonNull @Override public JsonInsert defaultUnset() { return new DefaultInsert( @@ -108,8 +114,9 @@ public JsonInsert defaultUnset() { ifNotExists); } + @NonNull @Override - public RegularInsert value(CqlIdentifier columnId, Term value) { + public RegularInsert value(@NonNull CqlIdentifier columnId, @NonNull Term value) { return new DefaultInsert( keyspace, table, @@ -120,24 +127,28 @@ public RegularInsert value(CqlIdentifier columnId, Term value) { ifNotExists); } + @NonNull @Override public Insert ifNotExists() { return new DefaultInsert( keyspace, table, json, missingJsonBehavior, assignments, timestamp, true); } + @NonNull @Override public Insert usingTimestamp(long timestamp) { return new DefaultInsert( keyspace, table, json, missingJsonBehavior, assignments, timestamp, ifNotExists); } + @NonNull @Override - public Insert usingTimestamp(BindMarker timestamp) { + public Insert usingTimestamp(@Nullable BindMarker timestamp) { return new DefaultInsert( keyspace, table, json, missingJsonBehavior, assignments, timestamp, ifNotExists); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("INSERT INTO "); @@ -169,11 +180,13 @@ public String asCql() { return builder.toString(); } + @NonNull @Override public SimpleStatement build() { return builder().build(); } + @NonNull @Override public SimpleStatementBuilder builder() { return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); @@ -193,26 +206,32 @@ public boolean isIdempotent() { } } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return table; } + @Nullable public Object getJson() { return json; } + @Nullable public MissingJsonBehavior getMissingJsonBehavior() { return missingJsonBehavior; } + @NonNull public ImmutableMap getAssignments() { return assignments; } + @Nullable public Object getTimestamp() { return timestamp; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnComponentLeftOperand.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnComponentLeftOperand.java index f94570f0cf1..e3bd2a641f4 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnComponentLeftOperand.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnComponentLeftOperand.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -25,15 +26,25 @@ public class ColumnComponentLeftOperand implements LeftOperand { private final CqlIdentifier columnId; private final Term index; - public ColumnComponentLeftOperand(CqlIdentifier columnId, Term index) { + public ColumnComponentLeftOperand(@NonNull CqlIdentifier columnId, @NonNull Term index) { this.columnId = columnId; this.index = index; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(columnId.asCql(true)).append('['); index.appendTo(builder); builder.append(']'); } + + @NonNull + public CqlIdentifier getColumnId() { + return columnId; + } + + @NonNull + public Term getIndex() { + return index; + } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnLeftOperand.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnLeftOperand.java index 8c8441d4df8..d4d79474bac 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnLeftOperand.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/ColumnLeftOperand.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.querybuilder.lhs; import com.datastax.oss.driver.api.core.CqlIdentifier; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -23,15 +24,16 @@ public class ColumnLeftOperand implements LeftOperand { private final CqlIdentifier columnId; - public ColumnLeftOperand(CqlIdentifier columnId) { + public ColumnLeftOperand(@NonNull CqlIdentifier columnId) { this.columnId = columnId; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(columnId.asCql(true)); } + @NonNull public CqlIdentifier getColumnId() { return columnId; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/FieldLeftOperand.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/FieldLeftOperand.java index 2fdabc5f9e5..76818b737f5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/FieldLeftOperand.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/FieldLeftOperand.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.querybuilder.lhs; import com.datastax.oss.driver.api.core.CqlIdentifier; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -24,20 +25,22 @@ public class FieldLeftOperand implements LeftOperand { private final CqlIdentifier columnId; private final CqlIdentifier fieldId; - public FieldLeftOperand(CqlIdentifier columnId, CqlIdentifier fieldId) { + public FieldLeftOperand(@NonNull CqlIdentifier columnId, @NonNull CqlIdentifier fieldId) { this.columnId = columnId; this.fieldId = fieldId; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(columnId.asCql(true)).append('.').append(fieldId.asCql(true)); } + @NonNull public CqlIdentifier getColumnId() { return columnId; } + @NonNull public CqlIdentifier getFieldId() { return fieldId; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TokenLeftOperand.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TokenLeftOperand.java index aae0a8053fd..986f701fddf 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TokenLeftOperand.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TokenLeftOperand.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -24,16 +25,16 @@ public class TokenLeftOperand implements LeftOperand { private final Iterable identifiers; - public TokenLeftOperand(Iterable identifiers) { + public TokenLeftOperand(@NonNull Iterable identifiers) { this.identifiers = identifiers; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { CqlHelper.appendIds(identifiers, builder, "token(", ",", ")"); } - public Iterable getIdentifiers() { + public @NonNull Iterable getIdentifiers() { return identifiers; } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TupleLeftOperand.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TupleLeftOperand.java index 325ab41ba64..82ce28ffcc5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TupleLeftOperand.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/lhs/TupleLeftOperand.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -24,15 +25,16 @@ public class TupleLeftOperand implements LeftOperand { private final Iterable identifiers; - public TupleLeftOperand(Iterable identifiers) { + public TupleLeftOperand(@NonNull Iterable identifiers) { this.identifiers = identifiers; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { CqlHelper.appendIds(identifiers, builder, "(", ",", ")"); } + @NonNull public Iterable getIdentifiers() { return identifiers; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/CustomIndexRelation.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/CustomIndexRelation.java index 56cb0c9181c..0ed15a5f805 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/CustomIndexRelation.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/CustomIndexRelation.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.relation.Relation; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -26,13 +27,13 @@ public class CustomIndexRelation implements Relation { private final CqlIdentifier indexId; private final Term expression; - public CustomIndexRelation(CqlIdentifier indexId, Term expression) { + public CustomIndexRelation(@NonNull CqlIdentifier indexId, @NonNull Term expression) { this.indexId = indexId; this.expression = expression; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append("expr(").append(indexId.asCql(true)).append(','); expression.appendTo(builder); builder.append(')'); @@ -43,10 +44,12 @@ public boolean isIdempotent() { return false; } + @NonNull public CqlIdentifier getIndexId() { return indexId; } + @NonNull public Term getExpression() { return expression; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnComponentRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnComponentRelationBuilder.java index aaaeb47db50..63a97a831db 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnComponentRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnComponentRelationBuilder.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.querybuilder.relation.Relation; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -30,13 +32,15 @@ public class DefaultColumnComponentRelationBuilder private final CqlIdentifier columnId; private final Term index; - public DefaultColumnComponentRelationBuilder(CqlIdentifier columnId, Term index) { + public DefaultColumnComponentRelationBuilder( + @NonNull CqlIdentifier columnId, @NonNull Term index) { this.columnId = columnId; this.index = index; } + @NonNull @Override - public Relation build(String operator, Term rightOperand) { + public Relation build(@NonNull String operator, @Nullable Term rightOperand) { return new DefaultRelation( new ColumnComponentLeftOperand(columnId, index), operator, rightOperand); } @@ -48,13 +52,17 @@ public static class Fluent> private final OngoingWhereClause statement; private final ColumnComponentRelationBuilder delegate; - public Fluent(OngoingWhereClause statement, CqlIdentifier columnId, Term index) { + public Fluent( + @NonNull OngoingWhereClause statement, + @NonNull CqlIdentifier columnId, + @NonNull Term index) { this.statement = statement; this.delegate = new DefaultColumnComponentRelationBuilder(columnId, index); } + @NonNull @Override - public StatementT build(String operator, Term rightOperand) { + public StatementT build(@NonNull String operator, @Nullable Term rightOperand) { return statement.where(delegate.build(operator, rightOperand)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnRelationBuilder.java index 8294c6d2a08..65ac9f1fba2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultColumnRelationBuilder.java @@ -22,6 +22,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnLeftOperand; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -29,13 +31,14 @@ public class DefaultColumnRelationBuilder implements ColumnRelationBuilder> private final OngoingWhereClause statement; private final ColumnRelationBuilder delegate; - public Fluent(OngoingWhereClause statement, CqlIdentifier columnId) { + public Fluent( + @NonNull OngoingWhereClause statement, @NonNull CqlIdentifier columnId) { this.statement = statement; this.delegate = new DefaultColumnRelationBuilder(columnId); } + @NonNull @Override - public StatementT build(String operator, Term rightOperand) { + public StatementT build(@NonNull String operator, @Nullable Term rightOperand) { return statement.where(delegate.build(operator, rightOperand)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultMultiColumnRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultMultiColumnRelationBuilder.java index d68e3483ee6..5f66441313f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultMultiColumnRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultMultiColumnRelationBuilder.java @@ -22,6 +22,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.TupleLeftOperand; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -29,15 +31,16 @@ public class DefaultMultiColumnRelationBuilder implements MultiColumnRelationBui private final Iterable identifiers; - public DefaultMultiColumnRelationBuilder(Iterable identifiers) { + public DefaultMultiColumnRelationBuilder(@NonNull Iterable identifiers) { Preconditions.checkNotNull(identifiers); Preconditions.checkArgument( identifiers.iterator().hasNext(), "Tuple must contain at least one column"); this.identifiers = identifiers; } + @NonNull @Override - public Relation build(String operator, Term rightOperand) { + public Relation build(@NonNull String operator, @Nullable Term rightOperand) { return new DefaultRelation(new TupleLeftOperand(identifiers), operator, rightOperand); } @@ -48,13 +51,16 @@ public static class Fluent> private final OngoingWhereClause statement; private final MultiColumnRelationBuilder delegate; - public Fluent(OngoingWhereClause statement, Iterable identifiers) { + public Fluent( + @NonNull OngoingWhereClause statement, + @NonNull Iterable identifiers) { this.statement = statement; this.delegate = new DefaultMultiColumnRelationBuilder(identifiers); } + @NonNull @Override - public StatementT build(String operator, Term rightOperand) { + public StatementT build(@NonNull String operator, @Nullable Term rightOperand) { return statement.where(delegate.build(operator, rightOperand)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultRelation.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultRelation.java index b49e34b9db8..3807986e611 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultRelation.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultRelation.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -28,7 +30,8 @@ public class DefaultRelation implements Relation { private final String operator; private final Term rightOperand; - public DefaultRelation(LeftOperand leftOperand, String operator, Term rightOperand) { + public DefaultRelation( + @NonNull LeftOperand leftOperand, @NonNull String operator, @Nullable Term rightOperand) { Preconditions.checkNotNull(leftOperand); Preconditions.checkNotNull(operator); this.leftOperand = leftOperand; @@ -37,7 +40,7 @@ public DefaultRelation(LeftOperand leftOperand, String operator, Term rightOpera } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { leftOperand.appendTo(builder); builder.append(operator); if (rightOperand != null) { @@ -50,14 +53,17 @@ public boolean isIdempotent() { return rightOperand.isIdempotent(); } + @NonNull public LeftOperand getLeftOperand() { return leftOperand; } + @NonNull public String getOperator() { return operator; } + @Nullable public Term getRightOperand() { return rightOperand; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultTokenRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultTokenRelationBuilder.java index c3dd981f264..fb841d39a73 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultTokenRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/relation/DefaultTokenRelationBuilder.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.querybuilder.relation.TokenRelationBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.TokenLeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -28,12 +30,13 @@ public class DefaultTokenRelationBuilder implements TokenRelationBuilder identifiers; - public DefaultTokenRelationBuilder(Iterable identifiers) { + public DefaultTokenRelationBuilder(@NonNull Iterable identifiers) { this.identifiers = identifiers; } + @NonNull @Override - public Relation build(String operator, Term rightOperand) { + public Relation build(@NonNull String operator, @Nullable Term rightOperand) { return new DefaultRelation(new TokenLeftOperand(identifiers), operator, rightOperand); } @@ -44,13 +47,16 @@ public static class Fluent> private final OngoingWhereClause statement; private final TokenRelationBuilder delegate; - public Fluent(OngoingWhereClause statement, Iterable identifiers) { + public Fluent( + @NonNull OngoingWhereClause statement, + @NonNull Iterable identifiers) { this.statement = statement; this.delegate = new DefaultTokenRelationBuilder(identifiers); } + @NonNull @Override - public StatementT build(String operator, Term rightOperand) { + public StatementT build(@NonNull String operator, @Nullable Term rightOperand) { return statement.where(delegate.build(operator, rightOperand)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterKeyspace.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterKeyspace.java index 708e3bed40d..3e35311cfaf 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterKeyspace.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterKeyspace.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; import net.jcip.annotations.Immutable; @@ -29,36 +30,42 @@ public class DefaultAlterKeyspace implements AlterKeyspaceStart, AlterKeyspace { private final CqlIdentifier keyspaceName; private final ImmutableMap options; - public DefaultAlterKeyspace(CqlIdentifier keyspaceName) { + public DefaultAlterKeyspace(@NonNull CqlIdentifier keyspaceName) { this(keyspaceName, ImmutableMap.of()); } - public DefaultAlterKeyspace(CqlIdentifier keyspaceName, ImmutableMap options) { + public DefaultAlterKeyspace( + @NonNull CqlIdentifier keyspaceName, @NonNull ImmutableMap options) { this.keyspaceName = keyspaceName; this.options = options; } + @NonNull @Override - public AlterKeyspace withReplicationOptions(Map replicationOptions) { + public AlterKeyspace withReplicationOptions(@NonNull Map replicationOptions) { return withOption("replication", replicationOptions); } + @NonNull @Override - public AlterKeyspace withOption(String name, Object value) { + public AlterKeyspace withOption(@NonNull String name, @NonNull Object value) { return new DefaultAlterKeyspace( keyspaceName, ImmutableCollections.append(options, name, value)); } + @NonNull @Override public String asCql() { return "ALTER KEYSPACE " + keyspaceName.asCql(true) + OptionsUtils.buildOptions(options, true); } + @NonNull @Override public Map getOptions() { return options; } + @NonNull public CqlIdentifier getKeyspace() { return keyspaceName; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterMaterializedView.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterMaterializedView.java index 927ef0bb68f..d32f38ba4ce 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterMaterializedView.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterMaterializedView.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -33,27 +35,32 @@ public class DefaultAlterMaterializedView private final ImmutableMap options; - public DefaultAlterMaterializedView(CqlIdentifier viewName) { + public DefaultAlterMaterializedView(@NonNull CqlIdentifier viewName) { this(null, viewName); } - public DefaultAlterMaterializedView(CqlIdentifier keyspace, CqlIdentifier viewName) { + public DefaultAlterMaterializedView( + @Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier viewName) { this(keyspace, viewName, ImmutableMap.of()); } public DefaultAlterMaterializedView( - CqlIdentifier keyspace, CqlIdentifier viewName, ImmutableMap options) { + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier viewName, + @NonNull ImmutableMap options) { this.keyspace = keyspace; this.viewName = viewName; this.options = options; } + @NonNull @Override - public AlterMaterializedView withOption(String name, Object value) { + public AlterMaterializedView withOption(@NonNull String name, @NonNull Object value) { return new DefaultAlterMaterializedView( keyspace, viewName, ImmutableCollections.append(options, name, value)); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("ALTER MATERIALIZED VIEW "); @@ -67,15 +74,18 @@ public String toString() { return asCql(); } + @NonNull @Override public Map getOptions() { return options; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getMaterializedView() { return viewName; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterTable.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterTable.java index e004bd42ba0..d575ced177b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterTable.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterTable.java @@ -29,6 +29,8 @@ import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -54,11 +56,11 @@ public class DefaultAlterTable private final ImmutableMap options; private final boolean dropCompactStorage; - public DefaultAlterTable(CqlIdentifier tableName) { + public DefaultAlterTable(@NonNull CqlIdentifier tableName) { this(null, tableName); } - public DefaultAlterTable(CqlIdentifier keyspace, CqlIdentifier tableName) { + public DefaultAlterTable(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier tableName) { this( keyspace, tableName, @@ -74,17 +76,17 @@ public DefaultAlterTable(CqlIdentifier keyspace, CqlIdentifier tableName) { } public DefaultAlterTable( - CqlIdentifier keyspace, - CqlIdentifier tableName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier tableName, boolean dropCompactStorage, - ImmutableMap columnsToAddInOrder, - ImmutableSet columnsToAdd, - ImmutableSet columnsToAddStatic, - ImmutableSet columnsToDrop, - ImmutableMap columnsToRename, - CqlIdentifier columnToAlter, - DataType columnToAlterType, - ImmutableMap options) { + @NonNull ImmutableMap columnsToAddInOrder, + @NonNull ImmutableSet columnsToAdd, + @NonNull ImmutableSet columnsToAddStatic, + @NonNull ImmutableSet columnsToDrop, + @NonNull ImmutableMap columnsToRename, + @Nullable CqlIdentifier columnToAlter, + @Nullable DataType columnToAlterType, + @NonNull ImmutableMap options) { this.keyspace = keyspace; this.tableName = tableName; this.dropCompactStorage = dropCompactStorage; @@ -98,8 +100,10 @@ public DefaultAlterTable( this.options = options; } + @NonNull @Override - public AlterTableAddColumnEnd addColumn(CqlIdentifier columnName, DataType dataType) { + public AlterTableAddColumnEnd addColumn( + @NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultAlterTable( keyspace, tableName, @@ -114,8 +118,10 @@ public AlterTableAddColumnEnd addColumn(CqlIdentifier columnName, DataType dataT options); } + @NonNull @Override - public AlterTableAddColumnEnd addStaticColumn(CqlIdentifier columnName, DataType dataType) { + public AlterTableAddColumnEnd addStaticColumn( + @NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultAlterTable( keyspace, tableName, @@ -130,6 +136,7 @@ public AlterTableAddColumnEnd addStaticColumn(CqlIdentifier columnName, DataType options); } + @NonNull @Override public BuildableQuery dropCompactStorage() { return new DefaultAlterTable( @@ -146,8 +153,9 @@ public BuildableQuery dropCompactStorage() { options); } + @NonNull @Override - public AlterTableDropColumnEnd dropColumns(CqlIdentifier... columnNames) { + public AlterTableDropColumnEnd dropColumns(@NonNull CqlIdentifier... columnNames) { ImmutableSet.Builder builder = ImmutableSet.builder().addAll(columnsToDrop); for (CqlIdentifier columnName : columnNames) { @@ -168,8 +176,10 @@ public AlterTableDropColumnEnd dropColumns(CqlIdentifier... columnNames) { options); } + @NonNull @Override - public AlterTableRenameColumnEnd renameColumn(CqlIdentifier from, CqlIdentifier to) { + public AlterTableRenameColumnEnd renameColumn( + @NonNull CqlIdentifier from, @NonNull CqlIdentifier to) { return new DefaultAlterTable( keyspace, tableName, @@ -184,8 +194,9 @@ public AlterTableRenameColumnEnd renameColumn(CqlIdentifier from, CqlIdentifier options); } + @NonNull @Override - public BuildableQuery alterColumn(CqlIdentifier columnName, DataType dataType) { + public BuildableQuery alterColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultAlterTable( keyspace, tableName, @@ -200,8 +211,9 @@ public BuildableQuery alterColumn(CqlIdentifier columnName, DataType dataType) { options); } + @NonNull @Override - public AlterTableWithOptionsEnd withOption(String name, Object value) { + public AlterTableWithOptionsEnd withOption(@NonNull String name, @NonNull Object value) { return new DefaultAlterTable( keyspace, tableName, @@ -216,6 +228,7 @@ public AlterTableWithOptionsEnd withOption(String name, Object value) { ImmutableCollections.append(options, name, value)); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("ALTER TABLE "); @@ -294,43 +307,53 @@ public String toString() { return asCql(); } + @NonNull @Override public Map getOptions() { return options; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return tableName; } + @NonNull public ImmutableMap getColumnsToAddInOrder() { return columnsToAddInOrder; } + @NonNull public ImmutableSet getColumnsToAddRegular() { return columnsToAdd; } + @NonNull public ImmutableSet getColumnsToAddStatic() { return columnsToAddStatic; } + @NonNull public ImmutableSet getColumnsToDrop() { return columnsToDrop; } + @NonNull public ImmutableMap getColumnsToRename() { return columnsToRename; } + @Nullable public CqlIdentifier getColumnToAlter() { return columnToAlter; } + @Nullable public DataType getColumnToAlterType() { return columnToAlterType; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterType.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterType.java index 6f5c1a9be13..7a75adf0414 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterType.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultAlterType.java @@ -24,6 +24,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -42,22 +44,22 @@ public class DefaultAlterType private final CqlIdentifier fieldToAlter; private final DataType fieldToAlterType; - public DefaultAlterType(CqlIdentifier typeName) { + public DefaultAlterType(@NonNull CqlIdentifier typeName) { this(null, typeName); } - public DefaultAlterType(CqlIdentifier keyspace, CqlIdentifier typeName) { + public DefaultAlterType(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier typeName) { this(keyspace, typeName, null, null, ImmutableMap.of(), null, null); } public DefaultAlterType( - CqlIdentifier keyspace, - CqlIdentifier typeName, - CqlIdentifier fieldToAdd, - DataType fieldToAddType, - ImmutableMap fieldsToRename, - CqlIdentifier fieldToAlter, - DataType fieldToAlterType) { + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier typeName, + @Nullable CqlIdentifier fieldToAdd, + @Nullable DataType fieldToAddType, + @NonNull ImmutableMap fieldsToRename, + @Nullable CqlIdentifier fieldToAlter, + @Nullable DataType fieldToAlterType) { this.keyspace = keyspace; this.typeName = typeName; this.fieldToAdd = fieldToAdd; @@ -67,20 +69,24 @@ public DefaultAlterType( this.fieldToAlterType = fieldToAlterType; } + @NonNull @Override - public BuildableQuery alterField(CqlIdentifier fieldName, DataType dataType) { + public BuildableQuery alterField(@NonNull CqlIdentifier fieldName, @NonNull DataType dataType) { return new DefaultAlterType( keyspace, typeName, fieldToAdd, fieldToAddType, fieldsToRename, fieldName, dataType); } + @NonNull @Override - public BuildableQuery addField(CqlIdentifier fieldName, DataType dataType) { + public BuildableQuery addField(@NonNull CqlIdentifier fieldName, @NonNull DataType dataType) { return new DefaultAlterType( keyspace, typeName, fieldName, dataType, fieldsToRename, fieldToAlter, fieldToAlterType); } + @NonNull @Override - public AlterTypeRenameFieldEnd renameField(CqlIdentifier from, CqlIdentifier to) { + public AlterTypeRenameFieldEnd renameField( + @NonNull CqlIdentifier from, @NonNull CqlIdentifier to) { return new DefaultAlterType( keyspace, typeName, @@ -91,6 +97,7 @@ public AlterTypeRenameFieldEnd renameField(CqlIdentifier from, CqlIdentifier to) fieldToAlterType); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("ALTER TYPE "); @@ -138,30 +145,37 @@ public String toString() { return asCql(); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getType() { return typeName; } + @Nullable public CqlIdentifier getFieldToAdd() { return fieldToAdd; } + @Nullable public DataType getFieldToAddType() { return fieldToAddType; } + @NonNull public ImmutableMap getFieldsToRename() { return fieldsToRename; } + @Nullable public CqlIdentifier getFieldToAlter() { return fieldToAlter; } + @Nullable public DataType getFieldToAlterType() { return fieldToAlterType; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateAggregate.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateAggregate.java index 7669ad86dce..3dea78d82dd 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateAggregate.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateAggregate.java @@ -24,6 +24,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -40,24 +42,25 @@ public class DefaultCreateAggregate private final CqlIdentifier finalFunc; private final Term term; - public DefaultCreateAggregate(CqlIdentifier functionName) { + public DefaultCreateAggregate(@NonNull CqlIdentifier functionName) { this(null, functionName); } - public DefaultCreateAggregate(CqlIdentifier keyspace, CqlIdentifier functionName) { + public DefaultCreateAggregate( + @Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier functionName) { this(keyspace, functionName, false, false, ImmutableList.of(), null, null, null, null); } public DefaultCreateAggregate( - CqlIdentifier keyspace, - CqlIdentifier functionName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier functionName, boolean orReplace, boolean ifNotExists, - ImmutableList parameters, - CqlIdentifier sFunc, - DataType sType, - CqlIdentifier finalFunc, - Term term) { + @NonNull ImmutableList parameters, + @Nullable CqlIdentifier sFunc, + @Nullable DataType sType, + @Nullable CqlIdentifier finalFunc, + @Nullable Term term) { this.keyspace = keyspace; this.functionName = functionName; this.orReplace = orReplace; @@ -69,6 +72,7 @@ public DefaultCreateAggregate( this.term = term; } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -114,26 +118,30 @@ public String asCql() { return builder.toString(); } + @NonNull @Override - public CreateAggregateEnd withInitCond(Term term) { + public CreateAggregateEnd withInitCond(@NonNull Term term) { return new DefaultCreateAggregate( keyspace, functionName, orReplace, ifNotExists, parameters, sFunc, sType, finalFunc, term); } + @NonNull @Override public CreateAggregateStart ifNotExists() { return new DefaultCreateAggregate( keyspace, functionName, orReplace, true, parameters, sFunc, sType, finalFunc, term); } + @NonNull @Override public CreateAggregateStart orReplace() { return new DefaultCreateAggregate( keyspace, functionName, true, ifNotExists, parameters, sFunc, sType, finalFunc, term); } + @NonNull @Override - public CreateAggregateStart withParameter(DataType paramType) { + public CreateAggregateStart withParameter(@NonNull DataType paramType) { return new DefaultCreateAggregate( keyspace, functionName, @@ -146,20 +154,23 @@ public CreateAggregateStart withParameter(DataType paramType) { term); } + @NonNull @Override - public CreateAggregateStateFunc withSFunc(CqlIdentifier sFunc) { + public CreateAggregateStateFunc withSFunc(@NonNull CqlIdentifier sFunc) { return new DefaultCreateAggregate( keyspace, functionName, orReplace, ifNotExists, parameters, sFunc, sType, finalFunc, term); } + @NonNull @Override - public CreateAggregateEnd withSType(DataType sType) { + public CreateAggregateEnd withSType(@NonNull DataType sType) { return new DefaultCreateAggregate( keyspace, functionName, orReplace, ifNotExists, parameters, sFunc, sType, finalFunc, term); } + @NonNull @Override - public CreateAggregateEnd withFinalFunc(CqlIdentifier finalFunc) { + public CreateAggregateEnd withFinalFunc(@NonNull CqlIdentifier finalFunc) { return new DefaultCreateAggregate( keyspace, functionName, orReplace, ifNotExists, parameters, sFunc, sType, finalFunc, term); } @@ -169,10 +180,12 @@ public String toString() { return asCql(); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getFunctionName() { return functionName; } @@ -185,22 +198,27 @@ public boolean isIfNotExists() { return ifNotExists; } + @NonNull public ImmutableList getParameters() { return parameters; } + @Nullable public CqlIdentifier getsFunc() { return sFunc; } + @Nullable public DataType getsType() { return sType; } + @Nullable public CqlIdentifier getFinalFunc() { return finalFunc; } + @Nullable public Term getTerm() { return term; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateFunction.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateFunction.java index de6a274d617..77786850ab2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateFunction.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateFunction.java @@ -25,6 +25,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -46,24 +48,25 @@ public class DefaultCreateFunction private final String language; private final String functionBody; - public DefaultCreateFunction(CqlIdentifier functionName) { + public DefaultCreateFunction(@NonNull CqlIdentifier functionName) { this(null, functionName); } - public DefaultCreateFunction(CqlIdentifier keyspace, CqlIdentifier functionName) { + public DefaultCreateFunction( + @Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier functionName) { this(keyspace, functionName, false, false, ImmutableMap.of(), false, null, null, null); } public DefaultCreateFunction( - CqlIdentifier keyspace, - CqlIdentifier functionName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier functionName, boolean orReplace, boolean ifNotExists, - ImmutableMap parameters, + @NonNull ImmutableMap parameters, boolean returnsNullOnNull, - DataType returns, - String language, - String functionBody) { + @Nullable DataType returns, + @Nullable String language, + @Nullable String functionBody) { this.keyspace = keyspace; this.functionName = functionName; this.orReplace = orReplace; @@ -75,6 +78,7 @@ public DefaultCreateFunction( this.functionBody = functionBody; } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -144,8 +148,9 @@ public String toString() { return asCql(); } + @NonNull @Override - public CreateFunctionEnd as(String functionBody) { + public CreateFunctionEnd as(@NonNull String functionBody) { return new DefaultCreateFunction( keyspace, functionName, @@ -158,8 +163,9 @@ public CreateFunctionEnd as(String functionBody) { functionBody); } + @NonNull @Override - public CreateFunctionWithLanguage withLanguage(String language) { + public CreateFunctionWithLanguage withLanguage(@NonNull String language) { return new DefaultCreateFunction( keyspace, functionName, @@ -172,8 +178,9 @@ public CreateFunctionWithLanguage withLanguage(String language) { functionBody); } + @NonNull @Override - public CreateFunctionWithType returnsType(DataType returnType) { + public CreateFunctionWithType returnsType(@NonNull DataType returnType) { return new DefaultCreateFunction( keyspace, functionName, @@ -186,6 +193,7 @@ public CreateFunctionWithType returnsType(DataType returnType) { functionBody); } + @NonNull @Override public CreateFunctionStart ifNotExists() { return new DefaultCreateFunction( @@ -200,6 +208,7 @@ public CreateFunctionStart ifNotExists() { functionBody); } + @NonNull @Override public CreateFunctionStart orReplace() { return new DefaultCreateFunction( @@ -214,8 +223,10 @@ public CreateFunctionStart orReplace() { functionBody); } + @NonNull @Override - public CreateFunctionStart withParameter(CqlIdentifier paramName, DataType paramType) { + public CreateFunctionStart withParameter( + @NonNull CqlIdentifier paramName, @NonNull DataType paramType) { return new DefaultCreateFunction( keyspace, functionName, @@ -228,6 +239,7 @@ public CreateFunctionStart withParameter(CqlIdentifier paramName, DataType param functionBody); } + @NonNull @Override public CreateFunctionWithNullOption returnsNullOnNull() { return new DefaultCreateFunction( @@ -242,6 +254,7 @@ public CreateFunctionWithNullOption returnsNullOnNull() { functionBody); } + @NonNull @Override public CreateFunctionWithNullOption calledOnNull() { return new DefaultCreateFunction( @@ -256,10 +269,12 @@ public CreateFunctionWithNullOption calledOnNull() { functionBody); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getFunction() { return functionName; } @@ -272,6 +287,7 @@ public boolean isIfNotExists() { return ifNotExists; } + @NonNull public ImmutableMap getParameters() { return parameters; } @@ -280,14 +296,17 @@ public boolean isReturnsNullOnNull() { return returnsNullOnNull; } + @Nullable public DataType getReturnType() { return returnType; } + @Nullable public String getLanguage() { return language; } + @Nullable public String getFunctionBody() { return functionBody; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateIndex.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateIndex.java index fb263126322..c307bbba178 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateIndex.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateIndex.java @@ -22,6 +22,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -47,18 +49,18 @@ public DefaultCreateIndex() { this(null); } - public DefaultCreateIndex(CqlIdentifier indexName) { + public DefaultCreateIndex(@Nullable CqlIdentifier indexName) { this(indexName, false, null, null, ImmutableMap.of(), null, ImmutableMap.of()); } public DefaultCreateIndex( - CqlIdentifier indexName, + @Nullable CqlIdentifier indexName, boolean ifNotExists, - CqlIdentifier keyspace, - CqlIdentifier table, - ImmutableMap columnToIndexType, - String usingClass, - ImmutableMap options) { + @Nullable CqlIdentifier keyspace, + @Nullable CqlIdentifier table, + @NonNull ImmutableMap columnToIndexType, + @Nullable String usingClass, + @NonNull ImmutableMap options) { this.indexName = indexName; this.ifNotExists = ifNotExists; this.keyspace = keyspace; @@ -68,8 +70,9 @@ public DefaultCreateIndex( this.options = options; } + @NonNull @Override - public CreateIndex andColumn(CqlIdentifier column, String indexType) { + public CreateIndex andColumn(@NonNull CqlIdentifier column, @Nullable String indexType) { // use placeholder index type when none present as immutable map does not allow null values. if (indexType == null) { indexType = NO_INDEX_TYPE; @@ -85,26 +88,30 @@ public CreateIndex andColumn(CqlIdentifier column, String indexType) { options); } + @NonNull @Override public CreateIndexStart ifNotExists() { return new DefaultCreateIndex( indexName, true, keyspace, table, columnToIndexType, usingClass, options); } + @NonNull @Override - public CreateIndexStart custom(String className) { + public CreateIndexStart custom(@NonNull String className) { return new DefaultCreateIndex( indexName, ifNotExists, keyspace, table, columnToIndexType, className, options); } + @NonNull @Override - public CreateIndexOnTable onTable(CqlIdentifier keyspace, CqlIdentifier table) { + public CreateIndexOnTable onTable(CqlIdentifier keyspace, @NonNull CqlIdentifier table) { return new DefaultCreateIndex( indexName, ifNotExists, keyspace, table, columnToIndexType, usingClass, options); } + @NonNull @Override - public CreateIndex withOption(String name, Object value) { + public CreateIndex withOption(@NonNull String name, @NonNull Object value) { return new DefaultCreateIndex( indexName, ifNotExists, @@ -115,6 +122,7 @@ public CreateIndex withOption(String name, Object value) { ImmutableCollections.append(options, name, value)); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("CREATE "); @@ -177,11 +185,13 @@ public String toString() { return asCql(); } + @NonNull @Override public Map getOptions() { return options; } + @Nullable public CqlIdentifier getIndex() { return indexName; } @@ -190,18 +200,22 @@ public boolean isIfNotExists() { return ifNotExists; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @Nullable public CqlIdentifier getTable() { return table; } + @NonNull public ImmutableMap getColumnToIndexType() { return columnToIndexType; } + @Nullable public String getUsingClass() { return usingClass; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateKeyspace.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateKeyspace.java index 180f08a3918..c3908b6e72b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateKeyspace.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateKeyspace.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspaceStart; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; import net.jcip.annotations.Immutable; @@ -30,33 +31,39 @@ public class DefaultCreateKeyspace implements CreateKeyspace, CreateKeyspaceStar private final boolean ifNotExists; private final ImmutableMap options; - public DefaultCreateKeyspace(CqlIdentifier keyspaceName) { + public DefaultCreateKeyspace(@NonNull CqlIdentifier keyspaceName) { this(keyspaceName, false, ImmutableMap.of()); } public DefaultCreateKeyspace( - CqlIdentifier keyspaceName, boolean ifNotExists, ImmutableMap options) { + @NonNull CqlIdentifier keyspaceName, + boolean ifNotExists, + @NonNull ImmutableMap options) { this.keyspaceName = keyspaceName; this.ifNotExists = ifNotExists; this.options = options; } + @NonNull @Override - public CreateKeyspace withOption(String name, Object value) { + public CreateKeyspace withOption(@NonNull String name, @NonNull Object value) { return new DefaultCreateKeyspace( keyspaceName, ifNotExists, ImmutableCollections.append(options, name, value)); } + @NonNull @Override public CreateKeyspaceStart ifNotExists() { return new DefaultCreateKeyspace(keyspaceName, true, options); } + @NonNull @Override - public CreateKeyspace withReplicationOptions(Map replicationOptions) { + public CreateKeyspace withReplicationOptions(@NonNull Map replicationOptions) { return withOption("replication", replicationOptions); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -76,11 +83,13 @@ public String toString() { return asCql(); } + @NonNull @Override public Map getOptions() { return options; } + @NonNull public CqlIdentifier getKeyspace() { return keyspaceName; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateMaterializedView.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateMaterializedView.java index 9853ce01a53..710dbfb02df 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateMaterializedView.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateMaterializedView.java @@ -31,6 +31,8 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -59,11 +61,12 @@ public class DefaultCreateMaterializedView private final ImmutableMap options; - public DefaultCreateMaterializedView(CqlIdentifier viewName) { + public DefaultCreateMaterializedView(@NonNull CqlIdentifier viewName) { this(null, viewName); } - public DefaultCreateMaterializedView(CqlIdentifier keyspace, CqlIdentifier viewName) { + public DefaultCreateMaterializedView( + @Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier viewName) { this( keyspace, viewName, @@ -79,17 +82,17 @@ public DefaultCreateMaterializedView(CqlIdentifier keyspace, CqlIdentifier viewN } public DefaultCreateMaterializedView( - CqlIdentifier keyspace, - CqlIdentifier viewName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier viewName, boolean ifNotExists, - CqlIdentifier baseTableKeyspace, - CqlIdentifier baseTable, - ImmutableList selectors, - ImmutableList whereRelations, - ImmutableSet partitionKeyColumns, - ImmutableSet clusteringKeyColumns, - ImmutableMap orderings, - ImmutableMap options) { + @Nullable CqlIdentifier baseTableKeyspace, + @Nullable CqlIdentifier baseTable, + @NonNull ImmutableList selectors, + @NonNull ImmutableList whereRelations, + @NonNull ImmutableSet partitionKeyColumns, + @NonNull ImmutableSet clusteringKeyColumns, + @NonNull ImmutableMap orderings, + @NonNull ImmutableMap options) { this.keyspace = keyspace; this.viewName = viewName; this.ifNotExists = ifNotExists; @@ -103,6 +106,7 @@ public DefaultCreateMaterializedView( this.options = options; } + @NonNull @Override public CreateMaterializedViewWhereStart all() { return new DefaultCreateMaterializedView( @@ -119,8 +123,9 @@ public CreateMaterializedViewWhereStart all() { options); } + @NonNull @Override - public CreateMaterializedViewSelectionWithColumns column(CqlIdentifier columnName) { + public CreateMaterializedViewSelectionWithColumns column(@NonNull CqlIdentifier columnName) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -135,8 +140,10 @@ public CreateMaterializedViewSelectionWithColumns column(CqlIdentifier columnNam options); } + @NonNull @Override - public CreateMaterializedViewSelectionWithColumns columnsIds(Iterable columnIds) { + public CreateMaterializedViewSelectionWithColumns columnsIds( + @NonNull Iterable columnIds) { ImmutableList.Builder columnSelectors = ImmutableList.builder(); for (CqlIdentifier column : columnIds) { columnSelectors.add(Selector.column(column)); @@ -155,8 +162,9 @@ public CreateMaterializedViewSelectionWithColumns columnsIds(Iterable additionalRelations) { + public CreateMaterializedViewWhere where(@NonNull Iterable additionalRelations) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -187,8 +196,9 @@ public CreateMaterializedViewWhere where(Iterable additionalRelations) options); } + @NonNull @Override - public CreateMaterializedViewPrimaryKey withPartitionKey(CqlIdentifier columnName) { + public CreateMaterializedViewPrimaryKey withPartitionKey(@NonNull CqlIdentifier columnName) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -203,8 +213,9 @@ public CreateMaterializedViewPrimaryKey withPartitionKey(CqlIdentifier columnNam options); } + @NonNull @Override - public CreateMaterializedViewPrimaryKey withClusteringColumn(CqlIdentifier columnName) { + public CreateMaterializedViewPrimaryKey withClusteringColumn(@NonNull CqlIdentifier columnName) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -219,6 +230,7 @@ public CreateMaterializedViewPrimaryKey withClusteringColumn(CqlIdentifier colum options); } + @NonNull @Override public CreateMaterializedViewStart ifNotExists() { return new DefaultCreateMaterializedView( @@ -235,19 +247,22 @@ public CreateMaterializedViewStart ifNotExists() { options); } + @NonNull @Override - public CreateMaterializedViewSelection asSelectFrom(CqlIdentifier table) { + public CreateMaterializedViewSelection asSelectFrom(@NonNull CqlIdentifier table) { return asSelectFrom(null, table); } + @NonNull @Override - public CreateMaterializedViewSelection asSelectFrom(CqlIdentifier keyspace, CqlIdentifier table) { + public CreateMaterializedViewSelection asSelectFrom( + CqlIdentifier baseTableKeyspace, @NonNull CqlIdentifier baseTable) { return new DefaultCreateMaterializedView( - this.keyspace, + keyspace, viewName, ifNotExists, - keyspace, - table, + baseTableKeyspace, + baseTable, selectors, whereRelations, partitionKeyColumns, @@ -256,20 +271,23 @@ public CreateMaterializedViewSelection asSelectFrom(CqlIdentifier keyspace, CqlI options); } + @NonNull @Override public CreateMaterializedView withClusteringOrderByIds( - Map orderings) { + @NonNull Map orderings) { return withClusteringOrders(ImmutableCollections.concat(this.orderings, orderings)); } + @NonNull @Override public CreateMaterializedView withClusteringOrder( - CqlIdentifier columnName, ClusteringOrder order) { + @NonNull CqlIdentifier columnName, @NonNull ClusteringOrder order) { return withClusteringOrders(ImmutableCollections.append(orderings, columnName, order)); } + @NonNull public CreateMaterializedView withClusteringOrders( - ImmutableMap orderings) { + @NonNull ImmutableMap orderings) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -284,8 +302,9 @@ public CreateMaterializedView withClusteringOrders( options); } + @NonNull @Override - public CreateMaterializedView withOption(String name, Object value) { + public CreateMaterializedView withOption(@NonNull String name, @NonNull Object value) { return new DefaultCreateMaterializedView( keyspace, viewName, @@ -300,6 +319,7 @@ public CreateMaterializedView withOption(String name, Object value) { ImmutableCollections.append(options, name, value)); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("CREATE MATERIALIZED VIEW "); @@ -366,15 +386,18 @@ public String toString() { return asCql(); } + @NonNull @Override public Map getOptions() { return options; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getMaterializedView() { return viewName; } @@ -383,30 +406,37 @@ public boolean isIfNotExists() { return ifNotExists; } + @Nullable public CqlIdentifier getBaseTableKeyspace() { return baseTableKeyspace; } + @Nullable public CqlIdentifier getBaseTable() { return baseTable; } + @NonNull public ImmutableList getSelectors() { return selectors; } + @NonNull public ImmutableList getWhereRelations() { return whereRelations; } + @NonNull public ImmutableSet getPartitionKeyColumns() { return partitionKeyColumns; } + @NonNull public ImmutableSet getClusteringKeyColumns() { return clusteringKeyColumns; } + @NonNull public ImmutableMap getOrderings() { return orderings; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateTable.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateTable.java index beb6d69f9dd..1de5651c2ec 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateTable.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateTable.java @@ -27,6 +27,8 @@ import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -50,11 +52,11 @@ public class DefaultCreateTable implements CreateTableStart, CreateTable, Create private final ImmutableMap orderings; - public DefaultCreateTable(CqlIdentifier tableName) { + public DefaultCreateTable(@NonNull CqlIdentifier tableName) { this(null, tableName); } - public DefaultCreateTable(CqlIdentifier keyspace, CqlIdentifier tableName) { + public DefaultCreateTable(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier tableName) { this( keyspace, tableName, @@ -70,17 +72,17 @@ public DefaultCreateTable(CqlIdentifier keyspace, CqlIdentifier tableName) { } public DefaultCreateTable( - CqlIdentifier keyspace, - CqlIdentifier tableName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier tableName, boolean ifNotExists, boolean compactStorage, - ImmutableMap columnsInOrder, - ImmutableSet partitionKeyColumns, - ImmutableSet clusteringKeyColumns, - ImmutableSet staticColumns, - ImmutableSet regularColumns, - ImmutableMap orderings, - ImmutableMap options) { + @NonNull ImmutableMap columnsInOrder, + @NonNull ImmutableSet partitionKeyColumns, + @NonNull ImmutableSet clusteringKeyColumns, + @NonNull ImmutableSet staticColumns, + @NonNull ImmutableSet regularColumns, + @NonNull ImmutableMap orderings, + @NonNull ImmutableMap options) { this.keyspace = keyspace; this.tableName = tableName; this.ifNotExists = ifNotExists; @@ -94,6 +96,7 @@ public DefaultCreateTable( this.options = options; } + @NonNull @Override public CreateTableStart ifNotExists() { return new DefaultCreateTable( @@ -110,8 +113,10 @@ public CreateTableStart ifNotExists() { options); } + @NonNull @Override - public CreateTable withPartitionKey(CqlIdentifier columnName, DataType dataType) { + public CreateTable withPartitionKey( + @NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultCreateTable( keyspace, tableName, @@ -126,8 +131,10 @@ public CreateTable withPartitionKey(CqlIdentifier columnName, DataType dataType) options); } + @NonNull @Override - public CreateTable withClusteringColumn(CqlIdentifier columnName, DataType dataType) { + public CreateTable withClusteringColumn( + @NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultCreateTable( keyspace, tableName, @@ -142,8 +149,9 @@ public CreateTable withClusteringColumn(CqlIdentifier columnName, DataType dataT options); } + @NonNull @Override - public CreateTable withColumn(CqlIdentifier columnName, DataType dataType) { + public CreateTable withColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultCreateTable( keyspace, tableName, @@ -158,8 +166,10 @@ public CreateTable withColumn(CqlIdentifier columnName, DataType dataType) { options); } + @NonNull @Override - public CreateTable withStaticColumn(CqlIdentifier columnName, DataType dataType) { + public CreateTable withStaticColumn( + @NonNull CqlIdentifier columnName, @NonNull DataType dataType) { return new DefaultCreateTable( keyspace, tableName, @@ -174,6 +184,7 @@ public CreateTable withStaticColumn(CqlIdentifier columnName, DataType dataType) options); } + @NonNull @Override public CreateTableWithOptions withCompactStorage() { return new DefaultCreateTable( @@ -190,20 +201,23 @@ public CreateTableWithOptions withCompactStorage() { options); } + @NonNull @Override public CreateTableWithOptions withClusteringOrderByIds( - Map orderings) { + @NonNull Map orderings) { return withClusteringOrders(ImmutableCollections.concat(this.orderings, orderings)); } + @NonNull @Override public CreateTableWithOptions withClusteringOrder( - CqlIdentifier columnName, ClusteringOrder order) { + @NonNull CqlIdentifier columnName, @NonNull ClusteringOrder order) { return withClusteringOrders(ImmutableCollections.append(orderings, columnName, order)); } + @NonNull public CreateTableWithOptions withClusteringOrders( - ImmutableMap orderings) { + @NonNull ImmutableMap orderings) { return new DefaultCreateTable( keyspace, tableName, @@ -218,6 +232,7 @@ public CreateTableWithOptions withClusteringOrders( options); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -308,8 +323,9 @@ public String toString() { return asCql(); } + @NonNull @Override - public CreateTable withOption(String name, Object value) { + public CreateTable withOption(@NonNull String name, @NonNull Object value) { return new DefaultCreateTable( keyspace, tableName, @@ -324,15 +340,18 @@ public CreateTable withOption(String name, Object value) { ImmutableCollections.append(options, name, value)); } + @NonNull @Override public Map getOptions() { return options; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return tableName; } @@ -345,26 +364,32 @@ public boolean isCompactStorage() { return compactStorage; } + @NonNull public ImmutableMap getColumnsInOrder() { return columnsInOrder; } + @NonNull public ImmutableSet getPartitionKeyColumns() { return partitionKeyColumns; } + @NonNull public ImmutableSet getClusteringKeyColumns() { return clusteringKeyColumns; } + @NonNull public ImmutableSet getStaticColumns() { return staticColumns; } + @NonNull public ImmutableSet getRegularColumns() { return regularColumns; } + @NonNull public ImmutableMap getOrderings() { return orderings; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateType.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateType.java index 03ef6b525e7..de5d1841bfe 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateType.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultCreateType.java @@ -22,6 +22,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -33,27 +35,28 @@ public class DefaultCreateType implements CreateTypeStart, CreateType { private final boolean ifNotExists; private final ImmutableMap fieldsInOrder; - public DefaultCreateType(CqlIdentifier typeName) { + public DefaultCreateType(@NonNull CqlIdentifier typeName) { this(null, typeName); } - public DefaultCreateType(CqlIdentifier keyspace, CqlIdentifier typeName) { + public DefaultCreateType(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier typeName) { this(keyspace, typeName, false, ImmutableMap.of()); } public DefaultCreateType( - CqlIdentifier keyspace, - CqlIdentifier typeName, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier typeName, boolean ifNotExists, - ImmutableMap fieldsInOrder) { + @NonNull ImmutableMap fieldsInOrder) { this.keyspace = keyspace; this.typeName = typeName; this.ifNotExists = ifNotExists; this.fieldsInOrder = fieldsInOrder; } + @NonNull @Override - public CreateType withField(CqlIdentifier fieldName, DataType dataType) { + public CreateType withField(@NonNull CqlIdentifier fieldName, @NonNull DataType dataType) { return new DefaultCreateType( keyspace, typeName, @@ -61,11 +64,13 @@ public CreateType withField(CqlIdentifier fieldName, DataType dataType) { ImmutableCollections.append(fieldsInOrder, fieldName, dataType)); } + @NonNull @Override public CreateTypeStart ifNotExists() { return new DefaultCreateType(keyspace, typeName, true, fieldsInOrder); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -105,10 +110,12 @@ public String toString() { return asCql(); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getType() { return typeName; } @@ -117,6 +124,7 @@ public boolean isIfNotExists() { return ifNotExists; } + @NonNull public ImmutableMap getFieldsInOrder() { return fieldsInOrder; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDrop.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDrop.java index 14bdf653216..49a692a1f84 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDrop.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDrop.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.schema.Drop; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -29,27 +31,35 @@ public class DefaultDrop implements Drop { private final boolean ifExists; - public DefaultDrop(CqlIdentifier itemName, String schemaTypeName) { + public DefaultDrop(@NonNull CqlIdentifier itemName, @NonNull String schemaTypeName) { this(null, itemName, schemaTypeName); } - public DefaultDrop(CqlIdentifier keyspace, CqlIdentifier itemName, String schemaTypeName) { + public DefaultDrop( + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier itemName, + @NonNull String schemaTypeName) { this(keyspace, itemName, schemaTypeName, false); } public DefaultDrop( - CqlIdentifier keyspace, CqlIdentifier itemName, String schemaTypeName, boolean ifExists) { + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier itemName, + @NonNull String schemaTypeName, + boolean ifExists) { this.keyspace = keyspace; this.itemName = itemName; this.schemaTypeName = schemaTypeName; this.ifExists = ifExists; } + @NonNull @Override public Drop ifExists() { return new DefaultDrop(keyspace, itemName, schemaTypeName, true); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("DROP ").append(schemaTypeName).append(' '); @@ -68,14 +78,17 @@ public String toString() { return asCql(); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getName() { return itemName; } + @NonNull public String getSchemaType() { return schemaTypeName; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDropKeyspace.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDropKeyspace.java index 203787826ce..2ded8b95e20 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDropKeyspace.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/DefaultDropKeyspace.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.schema.Drop; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -25,20 +26,22 @@ public class DefaultDropKeyspace implements Drop { private final CqlIdentifier keyspaceName; private final boolean ifExists; - public DefaultDropKeyspace(CqlIdentifier keyspaceName) { + public DefaultDropKeyspace(@NonNull CqlIdentifier keyspaceName) { this(keyspaceName, false); } - public DefaultDropKeyspace(CqlIdentifier keyspaceName, boolean ifExists) { + public DefaultDropKeyspace(@NonNull CqlIdentifier keyspaceName, boolean ifExists) { this.keyspaceName = keyspaceName; this.ifExists = ifExists; } + @NonNull @Override public Drop ifExists() { return new DefaultDropKeyspace(keyspaceName, true); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("DROP KEYSPACE "); @@ -57,6 +60,7 @@ public String toString() { return asCql(); } + @NonNull public CqlIdentifier getKeyspace() { return keyspaceName; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/OptionsUtils.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/OptionsUtils.java index 226eb587052..83ff28503ae 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/OptionsUtils.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/OptionsUtils.java @@ -15,11 +15,12 @@ */ package com.datastax.oss.driver.internal.querybuilder.schema; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; public class OptionsUtils { - - public static String buildOptions(Map options, boolean first) { + @NonNull + public static String buildOptions(@NonNull Map options, boolean first) { StringBuilder builder = new StringBuilder(); for (Map.Entry option : options.entrySet()) { if (first) { @@ -34,7 +35,8 @@ public static String buildOptions(Map options, boolean first) { return builder.toString(); } - private static String extractOptionValue(Object option) { + @NonNull + private static String extractOptionValue(@NonNull Object option) { StringBuilder optionValue = new StringBuilder(); if (option instanceof String) { optionValue.append("'").append((String) option).append("'"); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/Utils.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/Utils.java index c7feb4ebf76..2c8ccdd6e6a 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/Utils.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/Utils.java @@ -16,15 +16,19 @@ package com.datastax.oss.driver.internal.querybuilder.schema; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; public class Utils { /** Convenience method for creating a new {@link ImmutableSet} with an appended value. */ - public static ImmutableSet appendSet(ImmutableSet set, E newValue) { + @NonNull + public static ImmutableSet appendSet(@NonNull ImmutableSet set, @NonNull E newValue) { return ImmutableSet.builder().addAll(set).add(newValue).build(); } /** Convenience method for creating a new {@link ImmutableSet} with concatenated iterable. */ - public static ImmutableSet concatSet(ImmutableSet set, Iterable toConcat) { + @NonNull + public static ImmutableSet concatSet( + @NonNull ImmutableSet set, @NonNull Iterable toConcat) { return ImmutableSet.builder().addAll(set).addAll(toConcat).build(); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultCompactionStrategy.java index c30e06167bf..42b93c27d50 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultCompactionStrategy.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; import net.jcip.annotations.Immutable; @@ -26,18 +27,20 @@ public abstract class DefaultCompactionStrategy options; - protected DefaultCompactionStrategy(String className) { + protected DefaultCompactionStrategy(@NonNull String className) { this(ImmutableMap.of("class", className)); } - protected DefaultCompactionStrategy(ImmutableMap options) { + protected DefaultCompactionStrategy(@NonNull ImmutableMap options) { this.options = options; } + @NonNull public ImmutableMap getInternalOptions() { return options; } + @NonNull @Override public Map getOptions() { return options; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultLeveledCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultLeveledCompactionStrategy.java index 8770d7acc03..d0ddd4a7420 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultLeveledCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultLeveledCompactionStrategy.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -29,12 +30,13 @@ public DefaultLeveledCompactionStrategy() { super("LeveledCompactionStrategy"); } - protected DefaultLeveledCompactionStrategy(ImmutableMap options) { + protected DefaultLeveledCompactionStrategy(@NonNull ImmutableMap options) { super(options); } + @NonNull @Override - public DefaultLeveledCompactionStrategy withOption(String name, Object value) { + public DefaultLeveledCompactionStrategy withOption(@NonNull String name, @NonNull Object value) { return new DefaultLeveledCompactionStrategy( ImmutableCollections.append(getInternalOptions(), name, value)); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultSizeTieredCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultSizeTieredCompactionStrategy.java index 5c580857f2e..91c339fd8a4 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultSizeTieredCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultSizeTieredCompactionStrategy.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -29,12 +30,14 @@ public DefaultSizeTieredCompactionStrategy() { super("SizeTieredCompactionStrategy"); } - protected DefaultSizeTieredCompactionStrategy(ImmutableMap options) { + protected DefaultSizeTieredCompactionStrategy(@NonNull ImmutableMap options) { super(options); } + @NonNull @Override - public DefaultSizeTieredCompactionStrategy withOption(String name, Object value) { + public DefaultSizeTieredCompactionStrategy withOption( + @NonNull String name, @NonNull Object value) { return new DefaultSizeTieredCompactionStrategy( ImmutableCollections.append(getInternalOptions(), name, value)); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultTimeWindowCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultTimeWindowCompactionStrategy.java index 6cc80e2ba52..e69ca8c93a3 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultTimeWindowCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/schema/compaction/DefaultTimeWindowCompactionStrategy.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -28,12 +29,14 @@ public DefaultTimeWindowCompactionStrategy() { super("TimeWindowCompactionStrategy"); } - protected DefaultTimeWindowCompactionStrategy(ImmutableMap options) { + protected DefaultTimeWindowCompactionStrategy(@NonNull ImmutableMap options) { super(options); } + @NonNull @Override - public DefaultTimeWindowCompactionStrategy withOption(String name, Object value) { + public DefaultTimeWindowCompactionStrategy withOption( + @NonNull String name, @NonNull Object value) { return new DefaultTimeWindowCompactionStrategy( ImmutableCollections.append(getInternalOptions(), name, value)); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/AllSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/AllSelector.java index e9e054ec225..ed0c2977420 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/AllSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/AllSelector.java @@ -17,20 +17,24 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public enum AllSelector implements Selector { INSTANCE; + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { throw new IllegalStateException("Can't alias the '*' selector"); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append('*'); } + @Nullable @Override public CqlIdentifier getAlias() { return null; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ArithmeticSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ArithmeticSelector.java index cb737d76424..61998499cd5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ArithmeticSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ArithmeticSelector.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -25,17 +26,18 @@ public abstract class ArithmeticSelector implements Selector { protected final ArithmeticOperator operator; - protected ArithmeticSelector(ArithmeticOperator operator) { + protected ArithmeticSelector(@NonNull ArithmeticOperator operator) { Preconditions.checkNotNull(operator); this.operator = operator; } + @NonNull public ArithmeticOperator getOperator() { return operator; } protected static void appendAndMaybeParenthesize( - int myPrecedence, Selector child, StringBuilder builder) { + int myPrecedence, @NonNull Selector child, @NonNull StringBuilder builder) { boolean parenthesize = (child instanceof ArithmeticSelector) && (((ArithmeticSelector) child).operator.getPrecedenceLeft() < myPrecedence); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/BinaryArithmeticSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/BinaryArithmeticSelector.java index ffb714466a0..c1cdf8cd766 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/BinaryArithmeticSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/BinaryArithmeticSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -29,12 +31,16 @@ public class BinaryArithmeticSelector extends ArithmeticSelector { private final Selector right; private final CqlIdentifier alias; - public BinaryArithmeticSelector(ArithmeticOperator operator, Selector left, Selector right) { + public BinaryArithmeticSelector( + @NonNull ArithmeticOperator operator, @NonNull Selector left, @NonNull Selector right) { this(operator, left, right, null); } public BinaryArithmeticSelector( - ArithmeticOperator operator, Selector left, Selector right, CqlIdentifier alias) { + @NonNull ArithmeticOperator operator, + @NonNull Selector left, + @NonNull Selector right, + @Nullable CqlIdentifier alias) { super(operator); Preconditions.checkNotNull(left); Preconditions.checkNotNull(right); @@ -43,13 +49,14 @@ public BinaryArithmeticSelector( this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new BinaryArithmeticSelector(operator, left, right, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { appendAndMaybeParenthesize(operator.getPrecedenceLeft(), left, builder); builder.append(operator.getSymbol()); appendAndMaybeParenthesize(operator.getPrecedenceRight(), right, builder); @@ -58,14 +65,17 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getLeft() { return left; } + @NonNull public Selector getRight() { return right; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CastSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CastSelector.java index ccc3b2cc157..b397cdcaf16 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CastSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CastSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -29,11 +31,12 @@ public class CastSelector implements Selector { private final DataType targetType; private final CqlIdentifier alias; - public CastSelector(Selector selector, DataType targetType) { + public CastSelector(@NonNull Selector selector, @NonNull DataType targetType) { this(selector, targetType, null); } - public CastSelector(Selector selector, DataType targetType, CqlIdentifier alias) { + public CastSelector( + @NonNull Selector selector, @NonNull DataType targetType, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(selector); Preconditions.checkNotNull(targetType); Preconditions.checkArgument(selector.getAlias() == null, "Inner selector can't be aliased"); @@ -43,7 +46,7 @@ public CastSelector(Selector selector, DataType targetType, CqlIdentifier alias) } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append("CAST("); selector.appendTo(builder); builder.append(" AS ").append(targetType.asCql(false, true)).append(')'); @@ -52,19 +55,23 @@ public void appendTo(StringBuilder builder) { } } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new CastSelector(selector, targetType, alias); } + @NonNull public Selector getSelector() { return selector; } + @NonNull public DataType getTargetType() { return targetType; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java index e03a7b4478c..5c76bf659c0 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java @@ -20,6 +20,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -32,7 +34,10 @@ public abstract class CollectionSelector implements Selector { private final CqlIdentifier alias; protected CollectionSelector( - Iterable elementSelectors, String opening, String closing, CqlIdentifier alias) { + @NonNull Iterable elementSelectors, + @NonNull String opening, + @NonNull String closing, + @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(elementSelectors); Preconditions.checkArgument( elementSelectors.iterator().hasNext(), "Must have at least one selector"); @@ -46,14 +51,16 @@ protected CollectionSelector( } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { CqlHelper.append(elementSelectors, builder, opening, ",", closing); } + @NonNull public Iterable getElementSelectors() { return elementSelectors; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ColumnSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ColumnSelector.java index 5cee023c22e..6404bcc15e7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ColumnSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ColumnSelector.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -27,33 +29,36 @@ public class ColumnSelector implements Selector { private final CqlIdentifier columnId; private final CqlIdentifier alias; - public ColumnSelector(CqlIdentifier columnId) { + public ColumnSelector(@NonNull CqlIdentifier columnId) { this(columnId, null); } - public ColumnSelector(CqlIdentifier columnId, CqlIdentifier alias) { + public ColumnSelector(@NonNull CqlIdentifier columnId, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(columnId); this.columnId = columnId; this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new ColumnSelector(columnId, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(columnId.asCql(true)); if (alias != null) { builder.append(" AS ").append(alias.asCql(true)); } } + @NonNull public CqlIdentifier getColumnId() { return columnId; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CountAllSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CountAllSelector.java index 82f901fce3a..07357e7c773 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CountAllSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CountAllSelector.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -29,23 +31,25 @@ public CountAllSelector() { this(null); } - public CountAllSelector(CqlIdentifier alias) { + public CountAllSelector(@Nullable CqlIdentifier alias) { this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new CountAllSelector(alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append("count(*)"); if (alias != null) { builder.append(" AS ").append(alias.asCql(true)); } } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultBindMarker.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultBindMarker.java index c8c633a33da..231a812a98f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultBindMarker.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultBindMarker.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.BindMarker; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -24,7 +26,7 @@ public class DefaultBindMarker implements BindMarker { private final CqlIdentifier id; - public DefaultBindMarker(CqlIdentifier id) { + public DefaultBindMarker(@Nullable CqlIdentifier id) { this.id = id; } @@ -33,7 +35,7 @@ public DefaultBindMarker() { } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { if (id == null) { builder.append('?'); } else { @@ -46,6 +48,7 @@ public boolean isIdempotent() { return true; } + @Nullable public CqlIdentifier getId() { return id; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java index 770e4c80c77..b5f3bd25f1f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java @@ -29,6 +29,8 @@ import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import net.jcip.annotations.Immutable; @@ -49,7 +51,7 @@ public class DefaultSelect implements SelectFrom, Select { private final Object perPartitionLimit; private final boolean allowsFiltering; - public DefaultSelect(CqlIdentifier keyspace, CqlIdentifier table) { + public DefaultSelect(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { this( keyspace, table, @@ -72,16 +74,16 @@ public DefaultSelect(CqlIdentifier keyspace, CqlIdentifier table) { * make sure you do it yourself. */ public DefaultSelect( - CqlIdentifier keyspace, - CqlIdentifier table, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier table, boolean isJson, boolean isDistinct, - ImmutableList selectors, - ImmutableList relations, - ImmutableList groupByClauses, - ImmutableMap orderings, - Object limit, - Object perPartitionLimit, + @NonNull ImmutableList selectors, + @NonNull ImmutableList relations, + @NonNull ImmutableList groupByClauses, + @NonNull ImmutableMap orderings, + @Nullable Object limit, + @Nullable Object perPartitionLimit, boolean allowsFiltering) { this.groupByClauses = groupByClauses; this.orderings = orderings; @@ -101,6 +103,7 @@ public DefaultSelect( this.allowsFiltering = allowsFiltering; } + @NonNull @Override public SelectFrom json() { return new DefaultSelect( @@ -117,6 +120,7 @@ public SelectFrom json() { allowsFiltering); } + @NonNull @Override public SelectFrom distinct() { return new DefaultSelect( @@ -133,8 +137,9 @@ public SelectFrom distinct() { allowsFiltering); } + @NonNull @Override - public Select selector(Selector selector) { + public Select selector(@NonNull Selector selector) { ImmutableList newSelectors; if (selector == AllSelector.INSTANCE) { // '*' cancels any previous one @@ -148,8 +153,9 @@ public Select selector(Selector selector) { return withSelectors(newSelectors); } + @NonNull @Override - public Select selectors(Iterable additionalSelectors) { + public Select selectors(@NonNull Iterable additionalSelectors) { ImmutableList.Builder newSelectors = ImmutableList.builder(); if (!SELECT_ALL.equals(selectors)) { // previous '*' gets cancelled newSelectors.addAll(selectors); @@ -163,8 +169,9 @@ public Select selectors(Iterable additionalSelectors) { return withSelectors(newSelectors.build()); } + @NonNull @Override - public Select as(CqlIdentifier alias) { + public Select as(@NonNull CqlIdentifier alias) { if (SELECT_ALL.equals(selectors)) { throw new IllegalStateException("Can't alias the * selector"); } else if (selectors.isEmpty()) { @@ -173,7 +180,8 @@ public Select as(CqlIdentifier alias) { return withSelectors(ImmutableCollections.modifyLast(selectors, last -> last.as(alias))); } - public Select withSelectors(ImmutableList newSelectors) { + @NonNull + public Select withSelectors(@NonNull ImmutableList newSelectors) { return new DefaultSelect( keyspace, table, @@ -188,17 +196,20 @@ public Select withSelectors(ImmutableList newSelectors) { allowsFiltering); } + @NonNull @Override - public Select where(Relation relation) { + public Select where(@NonNull Relation relation) { return withRelations(ImmutableCollections.append(relations, relation)); } + @NonNull @Override - public Select where(Iterable additionalRelations) { + public Select where(@NonNull Iterable additionalRelations) { return withRelations(ImmutableCollections.concat(relations, additionalRelations)); } - public Select withRelations(ImmutableList newRelations) { + @NonNull + public Select withRelations(@NonNull ImmutableList newRelations) { return new DefaultSelect( keyspace, table, @@ -213,17 +224,20 @@ public Select withRelations(ImmutableList newRelations) { allowsFiltering); } + @NonNull @Override - public Select groupBy(Selector groupByClause) { + public Select groupBy(@NonNull Selector groupByClause) { return withGroupByClauses(ImmutableCollections.append(groupByClauses, groupByClause)); } + @NonNull @Override - public Select groupBy(Iterable newGroupByClauses) { + public Select groupBy(@NonNull Iterable newGroupByClauses) { return withGroupByClauses(ImmutableCollections.concat(groupByClauses, newGroupByClauses)); } - public Select withGroupByClauses(ImmutableList newGroupByClauses) { + @NonNull + public Select withGroupByClauses(@NonNull ImmutableList newGroupByClauses) { return new DefaultSelect( keyspace, table, @@ -238,17 +252,20 @@ public Select withGroupByClauses(ImmutableList newGroupByClauses) { allowsFiltering); } + @NonNull @Override - public Select orderBy(CqlIdentifier columnId, ClusteringOrder order) { + public Select orderBy(@NonNull CqlIdentifier columnId, @NonNull ClusteringOrder order) { return withOrderings(ImmutableCollections.append(orderings, columnId, order)); } + @NonNull @Override - public Select orderByIds(Map newOrderings) { + public Select orderByIds(@NonNull Map newOrderings) { return withOrderings(ImmutableCollections.concat(orderings, newOrderings)); } - public Select withOrderings(ImmutableMap newOrderings) { + @NonNull + public Select withOrderings(@NonNull ImmutableMap newOrderings) { return new DefaultSelect( keyspace, table, @@ -263,6 +280,7 @@ public Select withOrderings(ImmutableMap newOrde allowsFiltering); } + @NonNull @Override public Select limit(int limit) { Preconditions.checkArgument(limit > 0, "Limit must be strictly positive"); @@ -280,9 +298,9 @@ public Select limit(int limit) { allowsFiltering); } + @NonNull @Override - public Select limit(BindMarker bindMarker) { - Preconditions.checkNotNull(bindMarker); + public Select limit(@Nullable BindMarker bindMarker) { return new DefaultSelect( keyspace, table, @@ -297,6 +315,7 @@ public Select limit(BindMarker bindMarker) { allowsFiltering); } + @NonNull @Override public Select perPartitionLimit(int perPartitionLimit) { Preconditions.checkArgument( @@ -315,9 +334,9 @@ public Select perPartitionLimit(int perPartitionLimit) { allowsFiltering); } + @NonNull @Override - public Select perPartitionLimit(BindMarker bindMarker) { - Preconditions.checkNotNull(bindMarker); + public Select perPartitionLimit(@Nullable BindMarker bindMarker) { return new DefaultSelect( keyspace, table, @@ -332,6 +351,7 @@ public Select perPartitionLimit(BindMarker bindMarker) { allowsFiltering); } + @NonNull @Override public Select allowFiltering() { return new DefaultSelect( @@ -348,6 +368,7 @@ public Select allowFiltering() { true); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder(); @@ -404,21 +425,25 @@ public String asCql() { return builder.toString(); } + @NonNull @Override public SimpleStatement build() { return builder().build(); } + @NonNull @Override public SimpleStatementBuilder builder() { // SELECT statements are always idempotent return SimpleStatement.builder(asCql()).withIdempotence(true); } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return table; } @@ -431,26 +456,32 @@ public boolean isDistinct() { return isDistinct; } + @NonNull public ImmutableList getSelectors() { return selectors; } + @NonNull public ImmutableList getRelations() { return relations; } + @NonNull public ImmutableList getGroupByClauses() { return groupByClauses; } + @NonNull public ImmutableMap getOrderings() { return orderings; } + @Nullable public Object getLimit() { return limit; } + @Nullable public Object getPerPartitionLimit() { return perPartitionLimit; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ElementSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ElementSelector.java index 218c93e04d7..1577fb30b9f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ElementSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ElementSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -29,11 +31,12 @@ public class ElementSelector implements Selector { private final Term index; private final CqlIdentifier alias; - public ElementSelector(Selector collection, Term index) { + public ElementSelector(@NonNull Selector collection, @NonNull Term index) { this(collection, index, null); } - public ElementSelector(Selector collection, Term index, CqlIdentifier alias) { + public ElementSelector( + @NonNull Selector collection, @NonNull Term index, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(collection); Preconditions.checkNotNull(index); this.collection = collection; @@ -41,13 +44,14 @@ public ElementSelector(Selector collection, Term index, CqlIdentifier alias) { this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new ElementSelector(collection, index, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { collection.appendTo(builder); builder.append('['); index.appendTo(builder); @@ -57,14 +61,17 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getCollection() { return collection; } + @NonNull public Term getIndex() { return index; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FieldSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FieldSelector.java index a579f77040b..6147903f633 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FieldSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FieldSelector.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -28,11 +30,12 @@ public class FieldSelector implements Selector { private final CqlIdentifier fieldId; private final CqlIdentifier alias; - public FieldSelector(Selector udt, CqlIdentifier fieldId) { + public FieldSelector(@NonNull Selector udt, @NonNull CqlIdentifier fieldId) { this(udt, fieldId, null); } - public FieldSelector(Selector udt, CqlIdentifier fieldId, CqlIdentifier alias) { + public FieldSelector( + @NonNull Selector udt, @NonNull CqlIdentifier fieldId, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(udt); Preconditions.checkNotNull(fieldId); this.udt = udt; @@ -40,13 +43,14 @@ public FieldSelector(Selector udt, CqlIdentifier fieldId, CqlIdentifier alias) { this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new FieldSelector(udt, fieldId, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { udt.appendTo(builder); builder.append('.').append(fieldId.asCql(true)); if (alias != null) { @@ -54,14 +58,17 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getUdt() { return udt; } + @NonNull public CqlIdentifier getFieldId() { return fieldId; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FunctionSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FunctionSelector.java index a8e0517aef6..3642008405f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FunctionSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/FunctionSelector.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -26,34 +28,40 @@ public class FunctionSelector extends CollectionSelector { private final CqlIdentifier functionId; public FunctionSelector( - CqlIdentifier keyspaceId, CqlIdentifier functionId, Iterable arguments) { + @Nullable CqlIdentifier keyspaceId, + @NonNull CqlIdentifier functionId, + @NonNull Iterable arguments) { this(keyspaceId, functionId, arguments, null); } public FunctionSelector( - CqlIdentifier keyspaceId, - CqlIdentifier functionId, - Iterable elementSelectors, - CqlIdentifier alias) { + @Nullable CqlIdentifier keyspaceId, + @NonNull CqlIdentifier functionId, + @NonNull Iterable elementSelectors, + @Nullable CqlIdentifier alias) { super(elementSelectors, buildOpening(keyspaceId, functionId), ")", alias); this.keyspaceId = keyspaceId; this.functionId = functionId; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new FunctionSelector(keyspaceId, functionId, getElementSelectors(), alias); } + @Nullable public CqlIdentifier getKeyspaceId() { return keyspaceId; } + @NonNull public CqlIdentifier getFunctionId() { return functionId; } /** Returns the arguments of the function. */ + @NonNull @Override public Iterable getElementSelectors() { // Overridden only to customize the javadoc diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ListSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ListSelector.java index 2308fbf88a5..2d62fe38f59 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ListSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/ListSelector.java @@ -17,21 +17,24 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable public class ListSelector extends CollectionSelector { - public ListSelector(Iterable elementSelectors) { + public ListSelector(@NonNull Iterable elementSelectors) { this(elementSelectors, null); } - public ListSelector(Iterable elementSelectors, CqlIdentifier alias) { + public ListSelector(@NonNull Iterable elementSelectors, @Nullable CqlIdentifier alias) { super(elementSelectors, "[", "]", alias); } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new ListSelector(getElementSelectors(), alias); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java index 634e056c113..ccad55b2247 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -32,15 +34,17 @@ public class MapSelector implements Selector { private final CqlIdentifier alias; public MapSelector( - Map elementSelectors, DataType keyType, DataType valueType) { + @NonNull Map elementSelectors, + @Nullable DataType keyType, + @Nullable DataType valueType) { this(elementSelectors, keyType, valueType, null); } public MapSelector( - Map elementSelectors, - DataType keyType, - DataType valueType, - CqlIdentifier alias) { + @NonNull Map elementSelectors, + @Nullable DataType keyType, + @Nullable DataType valueType, + @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(elementSelectors); Preconditions.checkArgument( !elementSelectors.isEmpty(), "Must have at least one key/value pair"); @@ -54,13 +58,14 @@ public MapSelector( this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new MapSelector(elementSelectors, keyType, valueType, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { if (keyType != null) { assert valueType != null; builder @@ -85,18 +90,22 @@ public void appendTo(StringBuilder builder) { builder.append("}"); } + @NonNull public Map getElementSelectors() { return elementSelectors; } + @Nullable public DataType getKeyType() { return keyType; } + @Nullable public DataType getValueType() { return valueType; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/OppositeSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/OppositeSelector.java index 03ab0046fa7..b6a18cf51f5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/OppositeSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/OppositeSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -28,24 +30,25 @@ public class OppositeSelector extends ArithmeticSelector { private final Selector argument; private final CqlIdentifier alias; - public OppositeSelector(Selector argument) { + public OppositeSelector(@NonNull Selector argument) { this(argument, null); } - public OppositeSelector(Selector argument, CqlIdentifier alias) { + public OppositeSelector(@NonNull Selector argument, @Nullable CqlIdentifier alias) { super(ArithmeticOperator.OPPOSITE); Preconditions.checkNotNull(argument); this.argument = argument; this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new OppositeSelector(argument, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append('-'); appendAndMaybeParenthesize(operator.getPrecedenceLeft(), argument, builder); if (alias != null) { @@ -53,10 +56,12 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getArgument() { return argument; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/RangeSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/RangeSelector.java index 7f5d51e0a89..a21a8d0c59e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/RangeSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/RangeSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -30,11 +32,15 @@ public class RangeSelector implements Selector { private final Term right; private final CqlIdentifier alias; - public RangeSelector(Selector collection, Term left, Term right) { + public RangeSelector(@NonNull Selector collection, @Nullable Term left, @Nullable Term right) { this(collection, left, right, null); } - public RangeSelector(Selector collection, Term left, Term right, CqlIdentifier alias) { + public RangeSelector( + @NonNull Selector collection, + @Nullable Term left, + @Nullable Term right, + @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(collection); Preconditions.checkArgument( left != null || right != null, "At least one of the bounds must be specified"); @@ -44,13 +50,14 @@ public RangeSelector(Selector collection, Term left, Term right, CqlIdentifier a this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new RangeSelector(collection, left, right, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { collection.appendTo(builder); builder.append('['); if (left != null) { @@ -66,18 +73,22 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getCollection() { return collection; } + @Nullable public Term getLeft() { return left; } + @Nullable public Term getRight() { return right; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/SetSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/SetSelector.java index f91dbe3c824..a291247d546 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/SetSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/SetSelector.java @@ -17,21 +17,24 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable public class SetSelector extends CollectionSelector { - public SetSelector(Iterable elementSelectors) { + public SetSelector(@NonNull Iterable elementSelectors) { this(elementSelectors, null); } - public SetSelector(Iterable elementSelectors, CqlIdentifier alias) { + public SetSelector(@NonNull Iterable elementSelectors, @Nullable CqlIdentifier alias) { super(elementSelectors, "{", "}", alias); } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new SetSelector(getElementSelectors(), alias); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TupleSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TupleSelector.java index f1125506592..2058313eabb 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TupleSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TupleSelector.java @@ -17,21 +17,25 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.select.Selector; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable public class TupleSelector extends CollectionSelector { - public TupleSelector(Iterable elementSelectors) { + public TupleSelector(@NonNull Iterable elementSelectors) { this(elementSelectors, null); } - public TupleSelector(Iterable elementSelectors, CqlIdentifier alias) { + public TupleSelector( + @NonNull Iterable elementSelectors, @Nullable CqlIdentifier alias) { super(elementSelectors, "(", ")", alias); } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new TupleSelector(getElementSelectors(), alias); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TypeHintSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TypeHintSelector.java index 280f5d5f409..3f8f15ba729 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TypeHintSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/TypeHintSelector.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -29,11 +31,12 @@ public class TypeHintSelector implements Selector { private final DataType targetType; private final CqlIdentifier alias; - public TypeHintSelector(Selector selector, DataType targetType) { + public TypeHintSelector(@NonNull Selector selector, @NonNull DataType targetType) { this(selector, targetType, null); } - public TypeHintSelector(Selector selector, DataType targetType, CqlIdentifier alias) { + public TypeHintSelector( + @NonNull Selector selector, @NonNull DataType targetType, @Nullable CqlIdentifier alias) { Preconditions.checkNotNull(selector); Preconditions.checkNotNull(targetType); this.selector = selector; @@ -41,13 +44,14 @@ public TypeHintSelector(Selector selector, DataType targetType, CqlIdentifier al this.alias = alias; } + @NonNull @Override - public Selector as(CqlIdentifier alias) { + public Selector as(@NonNull CqlIdentifier alias) { return new TypeHintSelector(selector, targetType, alias); } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append('(').append(targetType.asCql(false, true)).append(')'); selector.appendTo(builder); if (alias != null) { @@ -55,14 +59,17 @@ public void appendTo(StringBuilder builder) { } } + @NonNull public Selector getSelector() { return selector; } + @NonNull public DataType getTargetType() { return targetType; } + @Nullable @Override public CqlIdentifier getAlias() { return alias; diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/ArithmeticTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/ArithmeticTerm.java index bd63d661cc0..fdb1c2210ae 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/ArithmeticTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/ArithmeticTerm.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -25,17 +26,18 @@ public abstract class ArithmeticTerm implements Term { protected final ArithmeticOperator operator; - protected ArithmeticTerm(ArithmeticOperator operator) { + protected ArithmeticTerm(@NonNull ArithmeticOperator operator) { Preconditions.checkNotNull(operator); this.operator = operator; } + @NonNull public ArithmeticOperator getOperator() { return operator; } protected static void appendAndMaybeParenthesize( - int myPrecedence, Term child, StringBuilder builder) { + int myPrecedence, @NonNull Term child, @NonNull StringBuilder builder) { boolean parenthesize = (child instanceof ArithmeticTerm) && (((ArithmeticTerm) child).operator.getPrecedenceLeft() < myPrecedence); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/BinaryArithmeticTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/BinaryArithmeticTerm.java index 4ec6b8a235b..a29c6cbfc53 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/BinaryArithmeticTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/BinaryArithmeticTerm.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import net.jcip.annotations.Immutable; @@ -27,7 +28,8 @@ public class BinaryArithmeticTerm extends ArithmeticTerm { private final Term left; private final Term right; - public BinaryArithmeticTerm(ArithmeticOperator operator, Term left, Term right) { + public BinaryArithmeticTerm( + @NonNull ArithmeticOperator operator, @NonNull Term left, @NonNull Term right) { super(operator); Preconditions.checkNotNull(left); Preconditions.checkNotNull(right); @@ -36,7 +38,7 @@ public BinaryArithmeticTerm(ArithmeticOperator operator, Term left, Term right) } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { appendAndMaybeParenthesize(operator.getPrecedenceLeft(), left, builder); builder.append(operator.getSymbol()); appendAndMaybeParenthesize(operator.getPrecedenceRight(), right, builder); @@ -47,10 +49,12 @@ public boolean isIdempotent() { return left.isIdempotent() && right.isIdempotent(); } + @NonNull public Term getLeft() { return left; } + @NonNull public Term getRight() { return right; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/FunctionTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/FunctionTerm.java index 8a2f9d8d482..e91ed04b775 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/FunctionTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/FunctionTerm.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -29,7 +31,9 @@ public class FunctionTerm implements Term { private final Iterable arguments; public FunctionTerm( - CqlIdentifier keyspaceId, CqlIdentifier functionId, Iterable arguments) { + @Nullable CqlIdentifier keyspaceId, + @NonNull CqlIdentifier functionId, + @NonNull Iterable arguments) { Preconditions.checkNotNull(functionId); Preconditions.checkNotNull(arguments); this.keyspaceId = keyspaceId; @@ -38,7 +42,7 @@ public FunctionTerm( } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { // The function name appears even without arguments, so don't use prefix/suffix in CqlHelper CqlHelper.qualify(keyspaceId, functionId, builder); builder.append('('); @@ -51,14 +55,17 @@ public boolean isIdempotent() { return false; } + @Nullable public CqlIdentifier getKeyspaceId() { return keyspaceId; } + @NonNull public CqlIdentifier getFunctionId() { return functionId; } + @NonNull public Iterable getArguments() { return arguments; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/OppositeTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/OppositeTerm.java index 8aac3842dce..750bea39167 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/OppositeTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/OppositeTerm.java @@ -18,21 +18,22 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class OppositeTerm extends ArithmeticTerm { - private final Term argument; + @NonNull private final Term argument; - public OppositeTerm(Term argument) { + public OppositeTerm(@NonNull Term argument) { super(ArithmeticOperator.OPPOSITE); Preconditions.checkNotNull(argument); this.argument = argument; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append('-'); appendAndMaybeParenthesize(operator.getPrecedenceLeft(), argument, builder); } @@ -42,6 +43,7 @@ public boolean isIdempotent() { return argument.isIdempotent(); } + @NonNull public Term getArgument() { return argument; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TupleTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TupleTerm.java index 5f8362a726d..c9c14dab3ec 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TupleTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TupleTerm.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -24,12 +25,12 @@ public class TupleTerm implements Term { private final Iterable components; - public TupleTerm(Iterable components) { + public TupleTerm(@NonNull Iterable components) { this.components = components; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { CqlHelper.append(components, builder, "(", ",", ")"); } @@ -43,6 +44,7 @@ public boolean isIdempotent() { return true; } + @NonNull public Iterable getComponents() { return components; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TypeHintTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TypeHintTerm.java index 1e0e5dfc4c2..3ba9b53eb9f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TypeHintTerm.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/TypeHintTerm.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -25,13 +26,13 @@ public class TypeHintTerm implements Term { private final Term term; private final DataType targetType; - public TypeHintTerm(Term term, DataType targetType) { + public TypeHintTerm(@NonNull Term term, @NonNull DataType targetType) { this.term = term; this.targetType = targetType; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append('(').append(targetType.asCql(false, true)).append(')'); term.appendTo(builder); } @@ -41,10 +42,12 @@ public boolean isIdempotent() { return term.isIdempotent(); } + @NonNull public Term getTerm() { return term; } + @NonNull public DataType getTargetType() { return targetType; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendAssignment.java index d2e0bda7894..271c0bcca16 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class AppendAssignment extends DefaultAssignment { - public AppendAssignment(LeftOperand leftOperand, Term rightOperand) { + public AppendAssignment(@NonNull LeftOperand leftOperand, @NonNull Term rightOperand) { super(leftOperand, "+=", rightOperand); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendListElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendListElementAssignment.java index 7037c8f6fc2..0005efaf7e2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendListElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendListElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class AppendListElementAssignment extends CollectionElementAssignment { - public AppendListElementAssignment(CqlIdentifier columnId, Term element) { + public AppendListElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.APPEND, null, element, '[', ']'); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendMapEntryAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendMapEntryAssignment.java index 182490107aa..c04c6e23b7e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendMapEntryAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendMapEntryAssignment.java @@ -17,12 +17,14 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class AppendMapEntryAssignment extends CollectionElementAssignment { - public AppendMapEntryAssignment(CqlIdentifier columnId, Term key, Term value) { + public AppendMapEntryAssignment( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { super(columnId, Operator.APPEND, key, value, '{', '}'); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendSetElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendSetElementAssignment.java index 2e6b80dcd8f..6edf7108d76 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendSetElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/AppendSetElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class AppendSetElementAssignment extends CollectionElementAssignment { - public AppendSetElementAssignment(CqlIdentifier columnId, Term element) { + public AppendSetElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.APPEND, null, element, '{', '}'); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CollectionElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CollectionElementAssignment.java index bc84d0bb2e7..35da1ca0f6e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CollectionElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CollectionElementAssignment.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.api.querybuilder.update.Assignment; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -45,7 +47,12 @@ public enum Operator { private final char closing; protected CollectionElementAssignment( - CqlIdentifier columnId, Operator operator, Term key, Term value, char opening, char closing) { + @NonNull CqlIdentifier columnId, + @NonNull Operator operator, + @Nullable Term key, + @NonNull Term value, + char opening, + char closing) { Preconditions.checkNotNull(columnId); Preconditions.checkNotNull(value); this.columnId = columnId; @@ -57,7 +64,7 @@ protected CollectionElementAssignment( } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { builder.append(String.format(operator.pattern, columnId.asCql(true), buildRightOperand())); } @@ -77,14 +84,17 @@ public boolean isIdempotent() { return (key == null || key.isIdempotent()) && value.isIdempotent(); } + @NonNull public CqlIdentifier getColumnId() { return columnId; } + @Nullable public Term getKey() { return key; } + @NonNull public Term getValue() { return value; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CounterAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CounterAssignment.java index eb1b386b92b..ff1280de5dd 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CounterAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/CounterAssignment.java @@ -17,12 +17,14 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class CounterAssignment extends DefaultAssignment { - public CounterAssignment(LeftOperand leftOperand, String operator, Term rightOperand) { + public CounterAssignment( + @NonNull LeftOperand leftOperand, @NonNull String operator, @NonNull Term rightOperand) { super(leftOperand, operator, rightOperand); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultAssignment.java index cbe9d52f9d5..b889831d5fd 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultAssignment.java @@ -18,6 +18,8 @@ import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.api.querybuilder.update.Assignment; import com.datastax.oss.driver.internal.querybuilder.lhs.LeftOperand; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -27,14 +29,15 @@ public class DefaultAssignment implements Assignment { private final String operator; private final Term rightOperand; - public DefaultAssignment(LeftOperand leftOperand, String operator, Term rightOperand) { + public DefaultAssignment( + @NonNull LeftOperand leftOperand, @NonNull String operator, @Nullable Term rightOperand) { this.leftOperand = leftOperand; this.operator = operator; this.rightOperand = rightOperand; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { leftOperand.appendTo(builder); builder.append(operator); if (rightOperand != null) { @@ -44,17 +47,20 @@ public void appendTo(StringBuilder builder) { @Override public boolean isIdempotent() { - return rightOperand.isIdempotent(); + return rightOperand == null || rightOperand.isIdempotent(); } + @NonNull public LeftOperand getLeftOperand() { return leftOperand; } + @NonNull public String getOperator() { return operator; } + @Nullable public Term getRightOperand() { return rightOperand; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java index 580142b94e8..278b1376536 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java @@ -28,6 +28,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.Immutable; @Immutable @@ -41,18 +43,18 @@ public class DefaultUpdate implements UpdateStart, UpdateWithAssignments, Update private final boolean ifExists; private final ImmutableList conditions; - public DefaultUpdate(CqlIdentifier keyspace, CqlIdentifier table) { + public DefaultUpdate(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { this(keyspace, table, null, ImmutableList.of(), ImmutableList.of(), false, ImmutableList.of()); } public DefaultUpdate( - CqlIdentifier keyspace, - CqlIdentifier table, - Object timestamp, - ImmutableList assignments, - ImmutableList relations, + @Nullable CqlIdentifier keyspace, + @NonNull CqlIdentifier table, + @Nullable Object timestamp, + @NonNull ImmutableList assignments, + @NonNull ImmutableList relations, boolean ifExists, - ImmutableList conditions) { + @NonNull ImmutableList conditions) { this.keyspace = keyspace; this.table = table; this.timestamp = timestamp; @@ -62,68 +64,81 @@ public DefaultUpdate( this.conditions = conditions; } + @NonNull @Override public UpdateStart usingTimestamp(long newTimestamp) { return new DefaultUpdate( keyspace, table, newTimestamp, assignments, relations, ifExists, conditions); } + @NonNull @Override - public UpdateStart usingTimestamp(BindMarker newTimestamp) { + public UpdateStart usingTimestamp(@NonNull BindMarker newTimestamp) { return new DefaultUpdate( keyspace, table, newTimestamp, assignments, relations, ifExists, conditions); } + @NonNull @Override - public UpdateWithAssignments set(Assignment assignment) { + public UpdateWithAssignments set(@NonNull Assignment assignment) { return withAssignments(ImmutableCollections.append(assignments, assignment)); } + @NonNull @Override - public UpdateWithAssignments set(Iterable additionalAssignments) { + public UpdateWithAssignments set(@NonNull Iterable additionalAssignments) { return withAssignments(ImmutableCollections.concat(assignments, additionalAssignments)); } - public UpdateWithAssignments withAssignments(ImmutableList newAssignments) { + @NonNull + public UpdateWithAssignments withAssignments(@NonNull ImmutableList newAssignments) { return new DefaultUpdate( keyspace, table, timestamp, newAssignments, relations, ifExists, conditions); } + @NonNull @Override - public Update where(Relation relation) { + public Update where(@NonNull Relation relation) { return withRelations(ImmutableCollections.append(relations, relation)); } + @NonNull @Override - public Update where(Iterable additionalRelations) { + public Update where(@NonNull Iterable additionalRelations) { return withRelations(ImmutableCollections.concat(relations, additionalRelations)); } - public Update withRelations(ImmutableList newRelations) { + @NonNull + public Update withRelations(@NonNull ImmutableList newRelations) { return new DefaultUpdate( keyspace, table, timestamp, assignments, newRelations, ifExists, conditions); } + @NonNull @Override public Update ifExists() { return new DefaultUpdate(keyspace, table, timestamp, assignments, relations, true, conditions); } + @NonNull @Override - public Update if_(Condition condition) { + public Update if_(@NonNull Condition condition) { return withConditions(ImmutableCollections.append(conditions, condition)); } + @NonNull @Override - public Update if_(Iterable additionalConditions) { + public Update if_(@NonNull Iterable additionalConditions) { return withConditions(ImmutableCollections.concat(conditions, additionalConditions)); } - public Update withConditions(ImmutableList newConditions) { + @NonNull + public Update withConditions(@NonNull ImmutableList newConditions) { return new DefaultUpdate( keyspace, table, timestamp, assignments, relations, false, newConditions); } + @NonNull @Override public String asCql() { StringBuilder builder = new StringBuilder("UPDATE "); @@ -149,11 +164,13 @@ public String asCql() { return builder.toString(); } + @NonNull @Override public SimpleStatement build() { return builder().build(); } + @NonNull @Override public SimpleStatementBuilder builder() { return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); @@ -178,22 +195,27 @@ public boolean isIdempotent() { } } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } + @NonNull public CqlIdentifier getTable() { return table; } + @Nullable public Object getTimestamp() { return timestamp; } + @NonNull public ImmutableList getAssignments() { return assignments; } + @NonNull public ImmutableList getRelations() { return relations; } @@ -202,6 +224,7 @@ public boolean isIfExists() { return ifExists; } + @NonNull public ImmutableList getConditions() { return conditions; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependAssignment.java index 8a2ec58fda7..d58a6ffa18e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependAssignment.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.api.querybuilder.update.Assignment; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable @@ -26,13 +27,13 @@ public class PrependAssignment implements Assignment { private final CqlIdentifier columnId; private final Term prefix; - public PrependAssignment(CqlIdentifier columnId, Term prefix) { + public PrependAssignment(@NonNull CqlIdentifier columnId, @NonNull Term prefix) { this.columnId = columnId; this.prefix = prefix; } @Override - public void appendTo(StringBuilder builder) { + public void appendTo(@NonNull StringBuilder builder) { String column = columnId.asCql(true); builder.append(column).append('='); prefix.appendTo(builder); @@ -45,10 +46,12 @@ public boolean isIdempotent() { return false; } + @NonNull public CqlIdentifier getColumnId() { return columnId; } + @NonNull public Term getPrefix() { return prefix; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependListElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependListElementAssignment.java index 6b425a0503e..a9bc032c432 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependListElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependListElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class PrependListElementAssignment extends CollectionElementAssignment { - public PrependListElementAssignment(CqlIdentifier columnId, Term element) { + public PrependListElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.PREPEND, null, element, '[', ']'); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependMapEntryAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependMapEntryAssignment.java index 66d6c06d56c..691ab6461be 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependMapEntryAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependMapEntryAssignment.java @@ -17,12 +17,14 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class PrependMapEntryAssignment extends CollectionElementAssignment { - public PrependMapEntryAssignment(CqlIdentifier columnId, Term key, Term value) { + public PrependMapEntryAssignment( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { super(columnId, Operator.PREPEND, key, value, '{', '}'); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependSetElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependSetElementAssignment.java index e3d2708ab8e..7924a0d6afe 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependSetElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/PrependSetElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class PrependSetElementAssignment extends CollectionElementAssignment { - public PrependSetElementAssignment(CqlIdentifier columnId, Term element) { + public PrependSetElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.PREPEND, null, element, '{', '}'); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveListElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveListElementAssignment.java index d95a35a5552..985a871fe5e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveListElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveListElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class RemoveListElementAssignment extends CollectionElementAssignment { - public RemoveListElementAssignment(CqlIdentifier columnId, Term element) { + public RemoveListElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.REMOVE, null, element, '[', ']'); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveMapEntryAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveMapEntryAssignment.java index 11e76c17c24..c87e30250fc 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveMapEntryAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveMapEntryAssignment.java @@ -17,12 +17,14 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class RemoveMapEntryAssignment extends CollectionElementAssignment { - public RemoveMapEntryAssignment(CqlIdentifier columnId, Term key, Term value) { + public RemoveMapEntryAssignment( + @NonNull CqlIdentifier columnId, @NonNull Term key, @NonNull Term value) { super(columnId, Operator.REMOVE, key, value, '{', '}'); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveSetElementAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveSetElementAssignment.java index b1fd95d4647..f863dd7f67f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveSetElementAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/RemoveSetElementAssignment.java @@ -17,12 +17,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.term.Term; +import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @Immutable public class RemoveSetElementAssignment extends CollectionElementAssignment { - public RemoveSetElementAssignment(CqlIdentifier columnId, Term element) { + public RemoveSetElementAssignment(@NonNull CqlIdentifier columnId, @NonNull Term element) { super(columnId, Operator.REMOVE, null, element, '{', '}'); } } diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CharsetCodec.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CharsetCodec.java index 3e4bce1fc6f..5b16cc80f9b 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CharsetCodec.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/CharsetCodec.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.driver.internal.querybuilder.DefaultLiteral; +import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -33,28 +34,31 @@ public class CharsetCodec implements TypeCodec { public static final CodecRegistry TEST_REGISTRY = new DefaultCodecRegistry("test", new CharsetCodec()); + @NonNull @Override public GenericType getJavaType() { return GenericType.of(Charset.class); } + @NonNull @Override public DataType getCqlType() { return DataTypes.TEXT; } + @NonNull @Override public String format(Charset value) { return "'" + value.name() + "'"; } @Override - public ByteBuffer encode(Charset value, ProtocolVersion protocolVersion) { + public ByteBuffer encode(Charset value, @NonNull ProtocolVersion protocolVersion) { throw new UnsupportedOperationException("Not used in this test"); } @Override - public Charset decode(ByteBuffer bytes, ProtocolVersion protocolVersion) { + public Charset decode(ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { throw new UnsupportedOperationException("Not used in this test"); } diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 5b41896de83..2929a3d8364 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -35,6 +35,11 @@ java-driver-core ${project.parent.version} + + com.github.spotbugs + spotbugs-annotations + true + junit junit diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index ba8e93e20f9..af1c5be28f1 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; +import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayDeque; @@ -73,35 +74,36 @@ public SortingLoadBalancingPolicy() {} @Override public void init( - Map nodes, - DistanceReporter distanceReporter, - Set contactPoints) { + @NonNull Map nodes, + @NonNull DistanceReporter distanceReporter, + @NonNull Set contactPoints) { this.nodes.addAll(nodes.values()); this.nodes.forEach(n -> distanceReporter.setDistance(n, NodeDistance.LOCAL)); } + @NonNull @Override - public Queue newQueryPlan(Request request, Session session) { + public Queue newQueryPlan(@NonNull Request request, @NonNull Session session) { return new ArrayDeque<>(nodes); } @Override - public void onAdd(Node node) { + public void onAdd(@NonNull Node node) { this.nodes.add(node); } @Override - public void onUp(Node node) { + public void onUp(@NonNull Node node) { onAdd(node); } @Override - public void onDown(Node node) { + public void onDown(@NonNull Node node) { onRemove(node); } @Override - public void onRemove(Node node) { + public void onRemove(@NonNull Node node) { this.nodes.remove(node); } From 47643a0de3967664746c6ae9f13eece33f98d8b8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 17 Jun 2018 10:42:33 -0700 Subject: [PATCH 487/742] JAVA-1624: Expose ExecutionInfo on exceptions where applicable --- changelog/README.md | 1 + .../api/core/AllNodesFailedException.java | 12 +- .../oss/driver/api/core/DriverException.java | 46 ++++++- .../api/core/DriverExecutionException.java | 9 +- .../api/core/DriverTimeoutException.java | 9 +- .../api/core/InvalidKeyspaceException.java | 9 +- .../api/core/NoNodeAvailableException.java | 9 +- .../api/core/RequestThrottlingException.java | 9 +- .../UnsupportedProtocolVersionException.java | 18 ++- .../connection/BusyConnectionException.java | 9 +- .../connection/ClosedConnectionException.java | 2 +- .../connection/ConnectionInitException.java | 9 +- .../connection/FrameTooLongException.java | 10 +- .../core/connection/HeartbeatException.java | 10 +- .../driver/api/core/cql/ExecutionInfo.java | 30 ++++- .../servererrors/AlreadyExistsException.java | 10 +- .../servererrors/BootstrappingException.java | 13 +- .../servererrors/CoordinatorException.java | 13 +- .../FunctionFailureException.java | 13 +- .../InvalidConfigurationInQueryException.java | 14 ++- .../servererrors/InvalidQueryException.java | 13 +- .../servererrors/OverloadedException.java | 13 +- .../api/core/servererrors/ProtocolError.java | 13 +- .../QueryConsistencyException.java | 4 +- .../servererrors/QueryExecutionException.java | 9 +- .../QueryValidationException.java | 9 +- .../servererrors/ReadFailureException.java | 14 ++- .../servererrors/ReadTimeoutException.java | 13 +- .../api/core/servererrors/ServerError.java | 13 +- .../api/core/servererrors/SyntaxError.java | 13 +- .../core/servererrors/TruncateException.java | 13 +- .../servererrors/UnauthorizedException.java | 13 +- .../servererrors/UnavailableException.java | 13 +- .../servererrors/WriteFailureException.java | 14 ++- .../servererrors/WriteTimeoutException.java | 14 ++- .../core/cql/CqlRequestHandlerBase.java | 44 +++++-- .../core/cql/DefaultExecutionInfo.java | 2 +- .../driver/api/core/session/ExceptionIT.java | 114 ++++++++++++++++++ 38 files changed, 494 insertions(+), 102 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java diff --git a/changelog/README.md b/changelog/README.md index 90386116e6b..e36983be778 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1624: Expose ExecutionInfo on exceptions where applicable - [improvement] JAVA-1766: Revisit nullability - [new feature] JAVA-1860: Allow reconnection at startup if no contact point is available - [improvement] JAVA-1866: Make all public policies implement AutoCloseable diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java index 580f376c778..543fa9cb56a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.shaded.guava.common.base.Joiner; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -57,13 +58,16 @@ public static AllNodesFailedException fromErrors( private final Map errors; - protected AllNodesFailedException(@NonNull String message, @NonNull Map errors) { - super(message, null, true); + protected AllNodesFailedException( + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + @NonNull Map errors) { + super(message, null, null, true); this.errors = errors; } private AllNodesFailedException(Map errors) { - this(buildMessage(errors), errors); + this(buildMessage(errors), null, errors); } private static String buildMessage(Map errors) { @@ -85,6 +89,6 @@ public Map getErrors() { @NonNull @Override public DriverException copy() { - return new AllNodesFailedException(getMessage(), errors); + return new AllNodesFailedException(getMessage(), getExecutionInfo(), errors); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java index e4dbc71d2b5..07f79d6e341 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverException.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -37,9 +39,51 @@ */ public abstract class DriverException extends RuntimeException { + private volatile ExecutionInfo executionInfo; + protected DriverException( - @Nullable String message, @Nullable Throwable cause, boolean writableStackTrace) { + @Nullable String message, + @Nullable ExecutionInfo executionInfo, + @Nullable Throwable cause, + boolean writableStackTrace) { super(message, cause, true, writableStackTrace); + this.executionInfo = executionInfo; + } + + /** + * Returns execution information about the request that led to this error. + * + *

          This is similar to the information returned for a successful query in {@link ResultSet}, + * except that some fields may be absent: + * + *

            + *
          • {@link ExecutionInfo#getCoordinator()} may be null if the error occurred before any node + * was contacted; + *
          • {@link ExecutionInfo#getErrors()} will contain the errors encountered for other nodes, + * but not this error itself; + *
          • {@link ExecutionInfo#getSuccessfulExecutionIndex()} may be -1 if the error occurred + * before any execution was started; + *
          • {@link ExecutionInfo#getPagingState()} and {@link ExecutionInfo#getTracingId()} will + * always be null; + *
          • {@link ExecutionInfo#getWarnings()} and {@link ExecutionInfo#getIncomingPayload()} will + * always be empty; + *
          • {@link ExecutionInfo#isSchemaInAgreement()} will always be true; + *
          • {@link ExecutionInfo#getResponseSizeInBytes()} and {@link + * ExecutionInfo#getCompressedResponseSizeInBytes()} will always be -1. + *
          + * + *

          Note that this is only set for exceptions that are rethrown directly to the client from a + * session call. For example, individual node errors stored in {@link + * AllNodesFailedException#getErrors()} or {@link ExecutionInfo#getErrors()} do not contain their + * own execution info, and therefore return null from this method. + */ + public ExecutionInfo getExecutionInfo() { + return executionInfo; + } + + /** This is for internal use by the driver, a client application has no reason to call it. */ + public void setExecutionInfo(ExecutionInfo executionInfo) { + this.executionInfo = executionInfo; } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java index 1c75150bbb2..e7d8e42f2b1 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverExecutionException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; import edu.umd.cs.findbugs.annotations.NonNull; @@ -27,12 +28,16 @@ */ public class DriverExecutionException extends DriverException { public DriverExecutionException(Throwable cause) { - super(null, cause, true); + this(null, cause); + } + + private DriverExecutionException(ExecutionInfo executionInfo, Throwable cause) { + super(null, executionInfo, cause, true); } @NonNull @Override public DriverException copy() { - return new DriverExecutionException(getCause()); + return new DriverExecutionException(getExecutionInfo(), getCause()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java index 31d0fcdf7ee..1966606256b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java @@ -15,17 +15,22 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; /** Thrown when a driver request timed out. */ public class DriverTimeoutException extends DriverException { public DriverTimeoutException(@NonNull String message) { - super(message, null, true); + this(message, null); + } + + private DriverTimeoutException(String message, ExecutionInfo executionInfo) { + super(message, executionInfo, null, true); } @NonNull @Override public DriverException copy() { - return new DriverTimeoutException(getMessage()); + return new DriverTimeoutException(getMessage(), getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java index 12f751988a3..0b1ec172812 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/InvalidKeyspaceException.java @@ -15,17 +15,22 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; /** Thrown when a session gets created with an invalid keyspace. */ public class InvalidKeyspaceException extends DriverException { public InvalidKeyspaceException(@NonNull String message) { - super(message, null, true); + this(message, null); + } + + private InvalidKeyspaceException(String message, ExecutionInfo executionInfo) { + super(message, executionInfo, null, true); } @NonNull @Override public DriverException copy() { - return new InvalidKeyspaceException(getMessage()); + return new InvalidKeyspaceException(getMessage(), getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java index 7c7de1418e9..db231adf219 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/NoNodeAvailableException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; @@ -26,12 +27,16 @@ */ public class NoNodeAvailableException extends AllNodesFailedException { public NoNodeAvailableException() { - super("No node was available to execute the query", Collections.emptyMap()); + this(null); + } + + private NoNodeAvailableException(ExecutionInfo executionInfo) { + super("No node was available to execute the query", executionInfo, Collections.emptyMap()); } @NonNull @Override public DriverException copy() { - return new NoNodeAvailableException(); + return new NoNodeAvailableException(getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java b/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java index 8cadb27a63e..5bd44fcb1d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/RequestThrottlingException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -27,12 +28,16 @@ public class RequestThrottlingException extends DriverException { public RequestThrottlingException(@NonNull String message) { - super(message, null, true); + this(message, null); + } + + private RequestThrottlingException(String message, ExecutionInfo executionInfo) { + super(message, executionInfo, null, true); } @NonNull @Override public DriverException copy() { - return new RequestThrottlingException(getMessage()); + return new RequestThrottlingException(getMessage(), getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index dc179f926d3..5430b0ad941 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -42,7 +43,7 @@ public static UnsupportedProtocolVersionException forSingleAttempt( String message = String.format("[%s] Host does not support protocol version %s", address, attemptedVersion); return new UnsupportedProtocolVersionException( - address, message, Collections.singletonList(attemptedVersion)); + address, message, Collections.singletonList(attemptedVersion), null); } @NonNull @@ -54,14 +55,22 @@ public static UnsupportedProtocolVersionException forNegotiation( + "Note that the driver does not support Cassandra 2.0 or lower.", address, attemptedVersions); return new UnsupportedProtocolVersionException( - address, message, ImmutableList.copyOf(attemptedVersions)); + address, message, ImmutableList.copyOf(attemptedVersions), null); } public UnsupportedProtocolVersionException( @Nullable SocketAddress address, // technically nullable, but should never be in real life @NonNull String message, @NonNull List attemptedVersions) { - super(message, null, true); + this(address, message, attemptedVersions, null); + } + + private UnsupportedProtocolVersionException( + SocketAddress address, + String message, + List attemptedVersions, + ExecutionInfo executionInfo) { + super(message, executionInfo, null, true); this.address = address; this.attemptedVersions = attemptedVersions; } @@ -81,6 +90,7 @@ public List getAttemptedVersions() { @NonNull @Override public DriverException copy() { - return new UnsupportedProtocolVersionException(address, getMessage(), attemptedVersions); + return new UnsupportedProtocolVersionException( + address, getMessage(), attemptedVersions, getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java index b4717dba81e..1c725715d54 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/BusyConnectionException.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -33,16 +34,18 @@ public BusyConnectionException(int maxAvailableIds) { this( String.format( "Connection has exceeded its maximum of %d simultaneous requests", maxAvailableIds), + null, false); } - private BusyConnectionException(String message, boolean writableStackTrace) { - super(message, null, writableStackTrace); + private BusyConnectionException( + String message, ExecutionInfo executionInfo, boolean writableStackTrace) { + super(message, executionInfo, null, writableStackTrace); } @Override @NonNull public DriverException copy() { - return new BusyConnectionException(getMessage(), true); + return new BusyConnectionException(getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java index 22af51e146b..9daee547a46 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ClosedConnectionException.java @@ -41,7 +41,7 @@ public ClosedConnectionException(@NonNull String message, @Nullable Throwable ca private ClosedConnectionException( @NonNull String message, @Nullable Throwable cause, boolean writableStackTrace) { - super(message, cause, writableStackTrace); + super(message, null, cause, writableStackTrace); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java index c9d94b82155..4112bdcd6f8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ConnectionInitException.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -29,12 +30,16 @@ */ public class ConnectionInitException extends DriverException { public ConnectionInitException(@NonNull String message, @Nullable Throwable cause) { - super(message, cause, true); + super(message, null, cause, true); + } + + private ConnectionInitException(String message, ExecutionInfo executionInfo, Throwable cause) { + super(message, executionInfo, cause, true); } @NonNull @Override public DriverException copy() { - return new ConnectionInitException(getMessage(), getCause()); + return new ConnectionInitException(getMessage(), getExecutionInfo(), getCause()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java index 1e89dc44588..e84504d089f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/FrameTooLongException.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.connection; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.SocketAddress; @@ -30,7 +31,12 @@ public class FrameTooLongException extends DriverException { private final SocketAddress address; public FrameTooLongException(@NonNull SocketAddress address, @NonNull String message) { - super(message, null, false); + this(address, message, null); + } + + private FrameTooLongException( + SocketAddress address, String message, ExecutionInfo executionInfo) { + super(message, executionInfo, null, false); this.address = address; } @@ -43,6 +49,6 @@ public SocketAddress getAddress() { @NonNull @Override public DriverException copy() { - return new FrameTooLongException(address, getMessage()); + return new FrameTooLongException(address, getMessage(), getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java index 3eaf9b44832..183f7c5366e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/HeartbeatException.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.connection; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; @@ -36,7 +37,12 @@ public class HeartbeatException extends DriverException { public HeartbeatException( @NonNull SocketAddress address, @Nullable String message, @Nullable Throwable cause) { - super(message, cause, true); + this(address, message, null, cause); + } + + public HeartbeatException( + SocketAddress address, String message, ExecutionInfo executionInfo, Throwable cause) { + super(message, executionInfo, cause, true); this.address = address; } @@ -49,6 +55,6 @@ public SocketAddress getAddress() { @NonNull @Override public DriverException copy() { - return new HeartbeatException(address, getMessage(), getCause()); + return new HeartbeatException(address, getMessage(), getExecutionInfo(), getCause()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index 18e623b4882..dc29e82817c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; +import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; @@ -32,15 +35,33 @@ import java.util.UUID; import java.util.concurrent.CompletionStage; -/** Information about the execution of a query. */ +/** + * Information about the execution of a query. + * + *

          This can be obtained either from a result set for a successful query, or from a driver + * exception for a failed query. + * + * @see ResultSet#getExecutionInfo() + * @see DriverException#getExecutionInfo() + */ public interface ExecutionInfo { /** The statement that was executed. */ @NonNull Statement getStatement(); - /** The node that was used as a coordinator to successfully complete the query. */ - @NonNull + /** + * The node that acted as a coordinator for the query. + * + *

          For successful queries, this is never {@code null}. It is the node that sent the response + * from which the result was decoded. + * + *

          For failed queries, this can either be {@code null} if the error occurred before any node + * could be contacted (for example a {@link RequestThrottlingException}), or present if a node was + * successfully contacted, but replied with an error response (any subclass of {@link + * CoordinatorException}). + */ + @Nullable Node getCoordinator(); /** @@ -58,7 +79,8 @@ public interface ExecutionInfo { * The index of the execution that completed this query. * *

          0 represents the initial, normal execution of the query, 1 the first speculative execution, - * etc. + * etc. If this execution info is attached to an error, this might not be applicable, and will + * return -1. * * @see SpeculativeExecutionPolicy */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java index 8f9755322eb..7128c8c2d4f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/AlreadyExistsException.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Thrown when a query attempts to create a keyspace or table that already exists. @@ -33,7 +35,7 @@ public class AlreadyExistsException extends QueryValidationException { public AlreadyExistsException( @NonNull Node coordinator, @NonNull String keyspace, @NonNull String table) { - this(coordinator, makeMessage(keyspace, table), keyspace, table, false); + this(coordinator, makeMessage(keyspace, table), keyspace, table, null, false); } private AlreadyExistsException( @@ -41,8 +43,9 @@ private AlreadyExistsException( @NonNull String message, @NonNull String keyspace, @NonNull String table, + @Nullable ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + super(coordinator, message, executionInfo, writableStackTrace); this.keyspace = keyspace; this.table = table; } @@ -58,6 +61,7 @@ private static String makeMessage(String keyspace, String table) { @NonNull @Override public DriverException copy() { - return new AlreadyExistsException(getCoordinator(), getMessage(), keyspace, table, true); + return new AlreadyExistsException( + getCoordinator(), getMessage(), keyspace, table, getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java index dcc98ea99d3..bd96aff1518 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/BootstrappingException.java @@ -17,9 +17,11 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Thrown when the coordinator was bootstrapping when it received a query. @@ -31,17 +33,20 @@ public class BootstrappingException extends QueryExecutionException { public BootstrappingException(@NonNull Node coordinator) { - super(coordinator, String.format("%s is bootstrapping", coordinator), false); + this(coordinator, String.format("%s is bootstrapping", coordinator), null, false); } private BootstrappingException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new BootstrappingException(getCoordinator(), getMessage(), true); + return new BootstrappingException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java index 0b3a4280a2a..975d7252747 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/CoordinatorException.java @@ -16,20 +16,29 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** A server-side error thrown by the coordinator node in response to a driver request. */ public abstract class CoordinatorException extends DriverException { + // This is also present on ExecutionInfo. But the execution info is only set for errors that are + // rethrown to the client, not on errors that get retried. It can be useful to know the node in + // the retry policy, so store it here, it might be duplicated but that doesn't matter. private final Node coordinator; protected CoordinatorException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(message, null, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(message, executionInfo, null, writableStackTrace); this.coordinator = coordinator; } + @NonNull public Node getCoordinator() { return coordinator; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java index f4321e5191a..1a9b178d0fe 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/FunctionFailureException.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * An error during the execution of a CQL function. @@ -29,17 +31,20 @@ public class FunctionFailureException extends QueryExecutionException { public FunctionFailureException(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private FunctionFailureException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new FunctionFailureException(getCoordinator(), getMessage(), true); + return new FunctionFailureException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java index 9141031077e..900d9c281ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidConfigurationInQueryException.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Indicates that a query is invalid because of some configuration problem. @@ -32,17 +34,21 @@ public class InvalidConfigurationInQueryException extends QueryValidationException { public InvalidConfigurationInQueryException(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private InvalidConfigurationInQueryException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new InvalidConfigurationInQueryException(getCoordinator(), getMessage(), true); + return new InvalidConfigurationInQueryException( + getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java index bbe5800e61a..bbf50a5b5fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/InvalidQueryException.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Indicates a syntactically correct, but invalid query. @@ -29,17 +31,20 @@ public class InvalidQueryException extends QueryValidationException { public InvalidQueryException(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private InvalidQueryException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new InvalidQueryException(getCoordinator(), getMessage(), true); + return new InvalidQueryException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java index fa66a872556..4b7b4bb6d9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/OverloadedException.java @@ -17,10 +17,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Thrown when the coordinator reported itself as being overloaded. @@ -33,17 +35,20 @@ public class OverloadedException extends QueryExecutionException { public OverloadedException(@NonNull Node coordinator) { - super(coordinator, String.format("%s is bootstrapping", coordinator), false); + super(coordinator, String.format("%s is bootstrapping", coordinator), null, false); } private OverloadedException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new OverloadedException(getCoordinator(), getMessage(), true); + return new OverloadedException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java index f342c06981b..813bfa6cae9 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ProtocolError.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Indicates that the contacted node reported a protocol error. @@ -33,17 +35,20 @@ public class ProtocolError extends CoordinatorException { public ProtocolError(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private ProtocolError( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new ProtocolError(getCoordinator(), getMessage(), true); + return new ProtocolError(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java index 521d3a4560d..67c4fcd9ca0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryConsistencyException.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; @@ -43,8 +44,9 @@ protected QueryConsistencyException( @NonNull ConsistencyLevel consistencyLevel, int received, int blockFor, + ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + super(coordinator, message, executionInfo, writableStackTrace); this.consistencyLevel = consistencyLevel; this.received = received; this.blockFor = blockFor; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java index c75fb85743a..a23d9d9dca7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryExecutionException.java @@ -15,14 +15,19 @@ */ package com.datastax.oss.driver.api.core.servererrors; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** A server-side error thrown when a valid query cannot be executed. */ public abstract class QueryExecutionException extends CoordinatorException { protected QueryExecutionException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java index c672167d5d9..448a6026892 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/QueryValidationException.java @@ -15,8 +15,10 @@ */ package com.datastax.oss.driver.api.core.servererrors; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * A server-side error thrown when a query cannot be executed because it is syntactically incorrect, @@ -25,7 +27,10 @@ public abstract class QueryValidationException extends CoordinatorException { protected QueryValidationException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java index 262af3c5839..adecf20ccbe 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadFailureException.java @@ -18,10 +18,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.net.InetAddress; import java.util.Map; @@ -62,6 +64,7 @@ public ReadFailureException( numFailures, dataPresent, reasonMap, + null, false); } @@ -74,8 +77,16 @@ private ReadFailureException( int numFailures, boolean dataPresent, @NonNull Map reasonMap, + @Nullable ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + super( + coordinator, + message, + consistencyLevel, + received, + blockFor, + executionInfo, + writableStackTrace); this.numFailures = numFailures; this.dataPresent = dataPresent; this.reasonMap = reasonMap; @@ -134,6 +145,7 @@ public DriverException copy() { numFailures, dataPresent, reasonMap, + getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java index 9d10d98fee3..1d199a695eb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ReadTimeoutException.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; @@ -50,6 +51,7 @@ consistencyLevel, formatDetails(received, blockFor, dataPresent)), received, blockFor, dataPresent, + null, false); } @@ -60,8 +62,16 @@ private ReadTimeoutException( int received, int blockFor, boolean dataPresent, + ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + super( + coordinator, + message, + consistencyLevel, + received, + blockFor, + executionInfo, + writableStackTrace); this.dataPresent = dataPresent; } @@ -98,6 +108,7 @@ public DriverException copy() { getReceived(), getBlockFor(), dataPresent, + getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java index 3452e662f00..9afe5ea45b3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/ServerError.java @@ -17,10 +17,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Indicates that the contacted node reported an internal error. @@ -35,17 +37,20 @@ public class ServerError extends CoordinatorException { public ServerError(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private ServerError( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new ServerError(getCoordinator(), getMessage(), true); + return new ServerError(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java index 31ca9ac5ae0..32c5e930741 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/SyntaxError.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * A syntax error in a query. @@ -29,17 +31,20 @@ public class SyntaxError extends QueryValidationException { public SyntaxError(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private SyntaxError( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new SyntaxError(getCoordinator(), getMessage(), true); + return new SyntaxError(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java index d0dc60cdc4a..12f265e135d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/TruncateException.java @@ -17,10 +17,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * An error during a truncation operation. @@ -33,17 +35,20 @@ public class TruncateException extends QueryExecutionException { public TruncateException(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private TruncateException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new TruncateException(getCoordinator(), getMessage(), true); + return new TruncateException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java index 34023a42ca5..e15ab02dc4b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnauthorizedException.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.api.core.servererrors; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Indicates that a query cannot be performed due to the authorization restrictions of the logged @@ -30,17 +32,20 @@ public class UnauthorizedException extends QueryValidationException { public UnauthorizedException(@NonNull Node coordinator, @NonNull String message) { - this(coordinator, message, false); + this(coordinator, message, null, false); } private UnauthorizedException( - @NonNull Node coordinator, @NonNull String message, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + @NonNull Node coordinator, + @NonNull String message, + @Nullable ExecutionInfo executionInfo, + boolean writableStackTrace) { + super(coordinator, message, executionInfo, writableStackTrace); } @NonNull @Override public DriverException copy() { - return new UnauthorizedException(getCoordinator(), getMessage(), true); + return new UnauthorizedException(getCoordinator(), getMessage(), getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java index 0f928714148..98e119791d8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/UnavailableException.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; @@ -50,6 +51,7 @@ public UnavailableException( consistencyLevel, required, alive, + null, false); } @@ -59,8 +61,9 @@ private UnavailableException( @NonNull ConsistencyLevel consistencyLevel, int required, int alive, + ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, writableStackTrace); + super(coordinator, message, executionInfo, writableStackTrace); this.consistencyLevel = consistencyLevel; this.required = required; this.alive = alive; @@ -92,6 +95,12 @@ public int getAlive() { @Override public DriverException copy() { return new UnavailableException( - getCoordinator(), getMessage(), consistencyLevel, required, alive, true); + getCoordinator(), + getMessage(), + consistencyLevel, + required, + alive, + getExecutionInfo(), + true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java index 9b3958610f1..f2589ff1b65 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteFailureException.java @@ -18,10 +18,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.net.InetAddress; import java.util.Map; @@ -62,6 +64,7 @@ public WriteFailureException( writeType, numFailures, reasonMap, + null, false); } @@ -74,8 +77,16 @@ private WriteFailureException( @NonNull WriteType writeType, int numFailures, @NonNull Map reasonMap, + @Nullable ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + super( + coordinator, + message, + consistencyLevel, + received, + blockFor, + executionInfo, + writableStackTrace); this.writeType = writeType; this.numFailures = numFailures; this.reasonMap = reasonMap; @@ -128,6 +139,7 @@ public DriverException copy() { writeType, numFailures, reasonMap, + getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java index 775726016c6..600b5e36895 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/servererrors/WriteTimeoutException.java @@ -18,10 +18,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * A server-side timeout during a write query. @@ -51,6 +53,7 @@ public WriteTimeoutException( received, blockFor, writeType, + null, false); } @@ -61,8 +64,16 @@ private WriteTimeoutException( int received, int blockFor, @NonNull WriteType writeType, + @Nullable ExecutionInfo executionInfo, boolean writableStackTrace) { - super(coordinator, message, consistencyLevel, received, blockFor, writableStackTrace); + super( + coordinator, + message, + consistencyLevel, + received, + blockFor, + executionInfo, + writableStackTrace); this.writeType = writeType; } @@ -82,6 +93,7 @@ public DriverException copy() { getReceived(), getBlockFor(), writeType, + getExecutionInfo(), true); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 0367be07d0a..edf594f0f13 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -207,7 +208,9 @@ public void onThrottleReady(boolean wasDelayed) { private ScheduledFuture scheduleTimeout(Duration timeout) { if (timeout.toNanos() > 0) { return scheduler.schedule( - () -> setFinalError(new DriverTimeoutException("Query timed out after " + timeout), null), + () -> + setFinalError( + new DriverTimeoutException("Query timed out after " + timeout), null, -1), timeout.toNanos(), TimeUnit.NANOSECONDS); } else { @@ -243,7 +246,7 @@ private void sendRequest( // We've reached the end of the query plan without finding any node to write to if (!result.isDone() && activeExecutionsCount.decrementAndGet() == 0) { // We're the last execution so fail the result - setFinalError(AllNodesFailedException.fromErrors(this.errors), null); + setFinalError(AllNodesFailedException.fromErrors(this.errors), null, -1); } } else { NodeResponseCallback nodeResponseCallback = @@ -307,7 +310,7 @@ private void setFinalResult( TimeUnit.NANOSECONDS); } } catch (Throwable error) { - setFinalError(error, callback.node); + setFinalError(error, callback.node, -1); } } @@ -337,10 +340,26 @@ public void onThrottleFailure(@NonNull RequestThrottlingException error) { session .getMetricUpdater() .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, configProfile.getName()); - setFinalError(error, null); + setFinalError(error, null, -1); } - private void setFinalError(Throwable error, Node node) { + private void setFinalError(Throwable error, Node node, int execution) { + if (error instanceof DriverException) { + ((DriverException) error) + .setExecutionInfo( + new DefaultExecutionInfo( + statement, + node, + startedSpeculativeExecutionsCount.get(), + execution, + errors, + null, + null, + true, + session, + context, + configProfile)); + } if (result.completeExceptionally(error)) { cancelScheduledTasks(); long latencyNanos = System.nanoTime() - startTimeNanos; @@ -398,7 +417,7 @@ public void operationComplete(Future future) throws Exception { Throwable error = future.cause(); if (error instanceof EncoderException && error.getCause() instanceof FrameTooLongException) { - setFinalError(error.getCause(), node); + setFinalError(error.getCause(), node, execution); } else { LOG.trace( "[{}] Failed to send request on {}, trying next node (cause: {})", @@ -500,10 +519,11 @@ public void onResponse(Frame responseFrame) { LOG.trace("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); } else { - setFinalError(new IllegalStateException("Unexpected response " + responseMessage), node); + setFinalError( + new IllegalStateException("Unexpected response " + responseMessage), node, execution); } } catch (Throwable t) { - setFinalError(t, node); + setFinalError(t, node, execution); } } @@ -545,12 +565,12 @@ private void processErrorResponse(Error errorMessage) { || prepareError instanceof FunctionFailureException || prepareError instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error on reprepare, rethrowing", logPrefix); - setFinalError(prepareError, node); + setFinalError(prepareError, node, execution); return null; } } } else if (exception instanceof RequestThrottlingException) { - setFinalError(exception, node); + setFinalError(exception, node, execution); return null; } recordError(node, exception); @@ -575,7 +595,7 @@ private void processErrorResponse(Error errorMessage) { || error instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error, rethrowing", logPrefix); metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS, configProfile.getName()); - setFinalError(error, node); + setFinalError(error, node, execution); } else { RetryDecision decision; if (error instanceof ReadTimeoutException) { @@ -655,7 +675,7 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { sendRequest(null, execution, retryCount + 1, false); break; case RETHROW: - setFinalError(error, node); + setFinalError(error, node, execution); break; case IGNORE: setFinalResult(Void.INSTANCE, null, true, this); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index 8ab68809abd..10f2cea218f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -90,7 +90,7 @@ public Statement getStatement() { return statement; } - @NonNull + @Nullable @Override public Node getCoordinator() { return coordinator; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java new file mode 100644 index 00000000000..4773e988d1b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java @@ -0,0 +1,114 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.unavailable; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class ExceptionIT { + + @ClassRule + public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + + @ClassRule + public static SessionRule sessionRule = + SessionRule.builder(simulacron) + .withOptions( + "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", + "advanced.retry-policy.class = DefaultRetryPolicy") + .build(); + + private static String QUERY_STRING = "select * from foo"; + + @Before + public void clear() { + simulacron.cluster().clearLogs(); + } + + @Test + public void should_expose_execution_info_on_exceptions() { + // Given + simulacron + .cluster() + .node(0) + .prime( + when(QUERY_STRING) + .then( + unavailable( + com.datastax.oss.simulacron.common.codec.ConsistencyLevel.ONE, 1, 0))); + simulacron + .cluster() + .node(1) + .prime(when(QUERY_STRING).then(PrimeDsl.invalid("Mock error message"))); + + // Then + assertThatThrownBy(() -> sessionRule.session().execute(QUERY_STRING)) + .isInstanceOf(InvalidQueryException.class) + .satisfies( + exception -> { + ExecutionInfo info = ((InvalidQueryException) exception).getExecutionInfo(); + assertThat(info).isNotNull(); + assertThat(info.getCoordinator().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); + assertThat(((SimpleStatement) info.getStatement()).getQuery()) + .isEqualTo(QUERY_STRING); + + // specex disabled => the initial execution completed the response + assertThat(info.getSpeculativeExecutionCount()).isEqualTo(0); + assertThat(info.getSuccessfulExecutionIndex()).isEqualTo(0); + + assertThat(info.getTracingId()).isNull(); + assertThat(info.getPagingState()).isNull(); + assertThat(info.getIncomingPayload()).isEmpty(); + assertThat(info.getWarnings()).isEmpty(); + assertThat(info.isSchemaInAgreement()).isTrue(); + assertThat(info.getResponseSizeInBytes()) + .isEqualTo(info.getCompressedResponseSizeInBytes()) + .isEqualTo(-1); + + List> errors = info.getErrors(); + assertThat(errors).hasSize(1); + Map.Entry entry0 = errors.get(0); + assertThat(entry0.getKey().getConnectAddress()) + .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); + Throwable node0Exception = entry0.getValue(); + assertThat(node0Exception).isInstanceOf(UnavailableException.class); + // ExecutionInfo is not exposed for retried errors + assertThat(((UnavailableException) node0Exception).getExecutionInfo()).isNull(); + }); + } +} From 055a29a53b0fcf87b7d7f277318aad98b146a1ac Mon Sep 17 00:00:00 2001 From: detinho Date: Sun, 10 Jun 2018 15:13:31 -0300 Subject: [PATCH 488/742] JAVA-1767: Improve message when column not in result set --- changelog/README.md | 1 + .../api/core/cql/BoundStatementBuilder.java | 12 +++- .../driver/api/core/data/AccessibleById.java | 4 +- .../api/core/data/AccessibleByName.java | 4 +- .../driver/api/core/data/GettableById.java | 60 +++++++++---------- .../driver/api/core/data/GettableByName.java | 60 +++++++++---------- .../driver/api/core/data/SettableById.java | 56 ++++++++--------- .../driver/api/core/data/SettableByName.java | 56 ++++++++--------- .../core/cql/DefaultBoundStatement.java | 12 +++- .../driver/internal/core/cql/DefaultRow.java | 12 +++- .../internal/core/data/DefaultUdtValue.java | 12 +++- .../core/data/AccessibleByIdTestBase.java | 12 ++++ 12 files changed, 175 insertions(+), 126 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index e36983be778..627d9742b9c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1767: Improve message when column not in result set - [improvement] JAVA-1624: Expose ExecutionInfo on exceptions where applicable - [improvement] JAVA-1766: Revisit nullability - [new feature] JAVA-1860: Allow reconnection at startup if no contact point is available diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index ba124db0598..6286ad67d38 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -62,12 +62,20 @@ public BoundStatementBuilder(@NonNull BoundStatement template) { @Override public int firstIndexOf(@NonNull CqlIdentifier id) { - return variableDefinitions.firstIndexOf(id); + int indexOf = variableDefinitions.firstIndexOf(id); + if (indexOf == -1) { + throw new IllegalArgumentException(id + " is not a variable in this bound statement"); + } + return indexOf; } @Override public int firstIndexOf(@NonNull String name) { - return variableDefinitions.firstIndexOf(name); + int indexOf = variableDefinitions.firstIndexOf(name); + if (indexOf == -1) { + throw new IllegalArgumentException(name + " is not a variable in this bound statement"); + } + return indexOf; } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java index 24e136317f8..f4cedb77c31 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleById.java @@ -29,6 +29,8 @@ public interface AccessibleById extends AccessibleByIndex { /** * Returns the first index where a given identifier appears (depending on the implementation, * identifiers may appear multiple times). + * + * @throws IllegalArgumentException if the id is invalid. */ int firstIndexOf(@NonNull CqlIdentifier id); @@ -38,7 +40,7 @@ public interface AccessibleById extends AccessibleByIndex { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull DataType getType(@NonNull CqlIdentifier id); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java index f96a7a435c8..ed7359b9c3e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/AccessibleByName.java @@ -45,6 +45,8 @@ public interface AccessibleByName extends AccessibleByIndex { /** * Returns the first index where a given identifier appears (depending on the implementation, * identifiers may appear multiple times). + * + * @throws IllegalArgumentException if the name is invalid. */ int firstIndexOf(@NonNull String name); @@ -54,7 +56,7 @@ public interface AccessibleByName extends AccessibleByIndex { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * GettableByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull DataType getType(@NonNull String name); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java index 37dd710d98d..45135b1639d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -54,7 +54,7 @@ public interface GettableById extends GettableByIndex, AccessibleById { * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative * methods. If you change the buffer's index or its contents in any way, any other getter * invocation for this value will have unpredictable results. - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default ByteBuffer getBytesUnsafe(@NonNull CqlIdentifier id) { @@ -70,7 +70,7 @@ default ByteBuffer getBytesUnsafe(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default boolean isNull(@NonNull CqlIdentifier id) { return isNull(firstIndexOf(id)); @@ -93,7 +93,7 @@ default boolean isNull(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default T get(@NonNull CqlIdentifier id, @NonNull TypeCodec codec) { @@ -114,7 +114,7 @@ default T get(@NonNull CqlIdentifier id, @NonNull TypeCodec codec) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -135,7 +135,7 @@ default T get(@NonNull CqlIdentifier id, @NonNull GenericType targetType) *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -166,7 +166,7 @@ default T get(@NonNull CqlIdentifier id, @NonNull Class targetClass) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -190,7 +190,7 @@ default Object getObject(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default boolean getBoolean(@NonNull CqlIdentifier id) { return getBoolean(firstIndexOf(id)); @@ -211,7 +211,7 @@ default boolean getBoolean(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default byte getByte(@NonNull CqlIdentifier id) { return getByte(firstIndexOf(id)); @@ -233,7 +233,7 @@ default byte getByte(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default double getDouble(@NonNull CqlIdentifier id) { return getDouble(firstIndexOf(id)); @@ -255,7 +255,7 @@ default double getDouble(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default float getFloat(@NonNull CqlIdentifier id) { return getFloat(firstIndexOf(id)); @@ -277,7 +277,7 @@ default float getFloat(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default int getInt(@NonNull CqlIdentifier id) { return getInt(firstIndexOf(id)); @@ -298,7 +298,7 @@ default int getInt(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default long getLong(@NonNull CqlIdentifier id) { return getLong(firstIndexOf(id)); @@ -320,7 +320,7 @@ default long getLong(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ default short getShort(@NonNull CqlIdentifier id) { return getShort(firstIndexOf(id)); @@ -337,7 +337,7 @@ default short getShort(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default Instant getInstant(@NonNull CqlIdentifier id) { @@ -355,7 +355,7 @@ default Instant getInstant(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default LocalDate getLocalDate(@NonNull CqlIdentifier id) { @@ -373,7 +373,7 @@ default LocalDate getLocalDate(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default LocalTime getLocalTime(@NonNull CqlIdentifier id) { @@ -391,7 +391,7 @@ default LocalTime getLocalTime(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default ByteBuffer getByteBuffer(@NonNull CqlIdentifier id) { @@ -409,7 +409,7 @@ default ByteBuffer getByteBuffer(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default String getString(@NonNull CqlIdentifier id) { @@ -427,7 +427,7 @@ default String getString(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default BigInteger getBigInteger(@NonNull CqlIdentifier id) { @@ -445,7 +445,7 @@ default BigInteger getBigInteger(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default BigDecimal getBigDecimal(@NonNull CqlIdentifier id) { @@ -463,7 +463,7 @@ default BigDecimal getBigDecimal(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default UUID getUuid(@NonNull CqlIdentifier id) { @@ -481,7 +481,7 @@ default UUID getUuid(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default InetAddress getInetAddress(@NonNull CqlIdentifier id) { @@ -499,7 +499,7 @@ default InetAddress getInetAddress(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default CqlDuration getCqlDuration(@NonNull CqlIdentifier id) { @@ -522,8 +522,8 @@ default CqlDuration getCqlDuration(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. - * @throws IllegalArgumentException if the column type can not be converted to a known token type. + * @throws IllegalArgumentException if the column type can not be converted to a known token type + * or if the name is invalid. */ @Nullable default Token getToken(@NonNull CqlIdentifier id) { @@ -548,7 +548,7 @@ default Token getToken(@NonNull CqlIdentifier id) { * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default List getList(@NonNull CqlIdentifier id, @NonNull Class elementsClass) { @@ -573,7 +573,7 @@ default List getList(@NonNull CqlIdentifier id, @NonNull Class element * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default Set getSet(@NonNull CqlIdentifier id, @NonNull Class elementsClass) { @@ -598,7 +598,7 @@ default Set getSet(@NonNull CqlIdentifier id, @NonNull Class elementsC * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default Map getMap( @@ -617,7 +617,7 @@ default Map getMap( *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default UdtValue getUdtValue(@NonNull CqlIdentifier id) { @@ -635,7 +635,7 @@ default UdtValue getUdtValue(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @Nullable default TupleValue getTupleValue(@NonNull CqlIdentifier id) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java index 6ff996aa9b2..4de5d9857bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -53,7 +53,7 @@ public interface GettableByName extends GettableByIndex, AccessibleByName { * make sure to {@link ByteBuffer#duplicate() duplicate} it beforehand, or only use relative * methods. If you change the buffer's index or its contents in any way, any other getter * invocation for this value will have unpredictable results. - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default ByteBuffer getBytesUnsafe(@NonNull String name) { @@ -69,7 +69,7 @@ default ByteBuffer getBytesUnsafe(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default boolean isNull(@NonNull String name) { return isNull(firstIndexOf(name)); @@ -92,7 +92,7 @@ default boolean isNull(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default T get(@NonNull String name, @NonNull TypeCodec codec) { @@ -114,7 +114,7 @@ default T get(@NonNull String name, @NonNull TypeCodec codec) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -136,7 +136,7 @@ default T get(@NonNull String name, @NonNull GenericType targetType) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -167,7 +167,7 @@ default T get(@NonNull String name, @NonNull Class targetClass) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable @@ -190,7 +190,7 @@ default Object getObject(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default boolean getBoolean(@NonNull String name) { return getBoolean(firstIndexOf(name)); @@ -211,7 +211,7 @@ default boolean getBoolean(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default byte getByte(@NonNull String name) { return getByte(firstIndexOf(name)); @@ -232,7 +232,7 @@ default byte getByte(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default double getDouble(@NonNull String name) { return getDouble(firstIndexOf(name)); @@ -253,7 +253,7 @@ default double getDouble(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default float getFloat(@NonNull String name) { return getFloat(firstIndexOf(name)); @@ -274,7 +274,7 @@ default float getFloat(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default int getInt(@NonNull String name) { return getInt(firstIndexOf(name)); @@ -295,7 +295,7 @@ default int getInt(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default long getLong(@NonNull String name) { return getLong(firstIndexOf(name)); @@ -316,7 +316,7 @@ default long getLong(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ default short getShort(@NonNull String name) { return getShort(firstIndexOf(name)); @@ -333,7 +333,7 @@ default short getShort(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default Instant getInstant(@NonNull String name) { @@ -351,7 +351,7 @@ default Instant getInstant(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default LocalDate getLocalDate(@NonNull String name) { @@ -369,7 +369,7 @@ default LocalDate getLocalDate(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default LocalTime getLocalTime(@NonNull String name) { @@ -387,7 +387,7 @@ default LocalTime getLocalTime(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default ByteBuffer getByteBuffer(@NonNull String name) { @@ -405,7 +405,7 @@ default ByteBuffer getByteBuffer(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default String getString(@NonNull String name) { @@ -423,7 +423,7 @@ default String getString(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default BigInteger getBigInteger(@NonNull String name) { @@ -441,7 +441,7 @@ default BigInteger getBigInteger(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default BigDecimal getBigDecimal(@NonNull String name) { @@ -459,7 +459,7 @@ default BigDecimal getBigDecimal(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default UUID getUuid(@NonNull String name) { @@ -477,7 +477,7 @@ default UUID getUuid(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default InetAddress getInetAddress(@NonNull String name) { @@ -495,7 +495,7 @@ default InetAddress getInetAddress(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default CqlDuration getCqlDuration(@NonNull String name) { @@ -518,8 +518,8 @@ default CqlDuration getCqlDuration(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. - * @throws IllegalArgumentException if the column type can not be converted to a known token type. + * @throws IllegalArgumentException if the column type can not be converted to a known token type + * or if the name is invalid. */ @Nullable default Token getToken(@NonNull String name) { @@ -544,7 +544,7 @@ default Token getToken(@NonNull String name) { * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default List getList(@NonNull String name, @NonNull Class elementsClass) { @@ -569,7 +569,7 @@ default List getList(@NonNull String name, @NonNull Class elementsClas * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default Set getSet(@NonNull String name, @NonNull Class elementsClass) { @@ -594,7 +594,7 @@ default Set getSet(@NonNull String name, @NonNull Class elementsClass) * Whether this method will return an empty collection or {@code null} will depend on the codec * used; by default, the driver's built-in codecs all return empty collections. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default Map getMap( @@ -613,7 +613,7 @@ default Map getMap( *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default UdtValue getUdtValue(@NonNull String name) { @@ -631,7 +631,7 @@ default UdtValue getUdtValue(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @Nullable default TupleValue getTupleValue(@NonNull String name) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index c20d45d4872..754b3fce5ef 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -53,7 +53,7 @@ public interface SettableById> * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() * duplicate} it beforehand. If you change the buffer's index or its contents in any way, * further usage of this data will have unpredictable results. - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setBytesUnsafe(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { @@ -72,7 +72,7 @@ default DataType getType(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setToNull(@NonNull CqlIdentifier id) { @@ -93,7 +93,7 @@ default T setToNull(@NonNull CqlIdentifier id) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull TypeCodec codec) { @@ -111,7 +111,7 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull TypeCodecIf you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull @@ -129,7 +129,7 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull GenericType *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull @@ -148,7 +148,7 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull Class ta *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setBoolean(@NonNull CqlIdentifier id, boolean v) { @@ -166,7 +166,7 @@ default T setBoolean(@NonNull CqlIdentifier id, boolean v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setByte(@NonNull CqlIdentifier id, byte v) { @@ -184,7 +184,7 @@ default T setByte(@NonNull CqlIdentifier id, byte v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setDouble(@NonNull CqlIdentifier id, double v) { @@ -202,7 +202,7 @@ default T setDouble(@NonNull CqlIdentifier id, double v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setFloat(@NonNull CqlIdentifier id, float v) { @@ -220,7 +220,7 @@ default T setFloat(@NonNull CqlIdentifier id, float v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setInt(@NonNull CqlIdentifier id, int v) { @@ -238,7 +238,7 @@ default T setInt(@NonNull CqlIdentifier id, int v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setLong(@NonNull CqlIdentifier id, long v) { @@ -256,7 +256,7 @@ default T setLong(@NonNull CqlIdentifier id, long v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setShort(@NonNull CqlIdentifier id, short v) { @@ -271,7 +271,7 @@ default T setShort(@NonNull CqlIdentifier id, short v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { @@ -286,7 +286,7 @@ default T setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { @@ -301,7 +301,7 @@ default T setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { @@ -316,7 +316,7 @@ default T setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { @@ -331,7 +331,7 @@ default T setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setString(@NonNull CqlIdentifier id, @Nullable String v) { @@ -346,7 +346,7 @@ default T setString(@NonNull CqlIdentifier id, @Nullable String v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { @@ -361,7 +361,7 @@ default T setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { @@ -376,7 +376,7 @@ default T setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { @@ -391,7 +391,7 @@ default T setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { @@ -406,7 +406,7 @@ default T setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { @@ -423,7 +423,7 @@ default T setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the index is invalid. */ @NonNull default T setToken(@NonNull CqlIdentifier id, @NonNull Token v) { @@ -441,7 +441,7 @@ default T setToken(@NonNull CqlIdentifier id, @NonNull Token v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setList( @@ -460,7 +460,7 @@ default T setList( *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setSet( @@ -479,7 +479,7 @@ default T setSet( *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setMap( @@ -498,7 +498,7 @@ default T setMap( *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { @@ -513,7 +513,7 @@ default T setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { *

          If you want to avoid the overhead of building a {@code CqlIdentifier}, use the variant of * this method that takes a string argument. * - * @throws IndexOutOfBoundsException if the id is invalid. + * @throws IllegalArgumentException if the id is invalid. */ @NonNull default T setTupleValue(@NonNull CqlIdentifier id, @Nullable TupleValue v) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index 79d88f817b3..6bc9335dc52 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -52,7 +52,7 @@ public interface SettableByName> * to modify elsewhere in your application, make sure to {@link ByteBuffer#duplicate() * duplicate} it beforehand. If you change the buffer's index or its contents in any way, * further usage of this data will have unpredictable results. - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setBytesUnsafe(@NonNull String name, @Nullable ByteBuffer v) { @@ -71,7 +71,7 @@ default DataType getType(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setToNull(@NonNull String name) { @@ -92,7 +92,7 @@ default T setToNull(@NonNull String name) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T set(@NonNull String name, @Nullable V v, @NonNull TypeCodec codec) { @@ -110,7 +110,7 @@ default T set(@NonNull String name, @Nullable V v, @NonNull TypeCodec cod *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull @@ -129,7 +129,7 @@ default T set(@NonNull String name, @Nullable V v, @NonNull GenericType t *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull @@ -148,7 +148,7 @@ default T set(@NonNull String name, @Nullable V v, @NonNull Class targetC *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setBoolean(@NonNull String name, boolean v) { @@ -166,7 +166,7 @@ default T setBoolean(@NonNull String name, boolean v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setByte(@NonNull String name, byte v) { @@ -184,7 +184,7 @@ default T setByte(@NonNull String name, byte v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setDouble(@NonNull String name, double v) { @@ -202,7 +202,7 @@ default T setDouble(@NonNull String name, double v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setFloat(@NonNull String name, float v) { @@ -220,7 +220,7 @@ default T setFloat(@NonNull String name, float v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setInt(@NonNull String name, int v) { @@ -238,7 +238,7 @@ default T setInt(@NonNull String name, int v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setLong(@NonNull String name, long v) { @@ -256,7 +256,7 @@ default T setLong(@NonNull String name, long v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setShort(@NonNull String name, short v) { @@ -271,7 +271,7 @@ default T setShort(@NonNull String name, short v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setInstant(@NonNull String name, @Nullable Instant v) { @@ -286,7 +286,7 @@ default T setInstant(@NonNull String name, @Nullable Instant v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setLocalDate(@NonNull String name, @Nullable LocalDate v) { @@ -301,7 +301,7 @@ default T setLocalDate(@NonNull String name, @Nullable LocalDate v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setLocalTime(@NonNull String name, @Nullable LocalTime v) { @@ -316,7 +316,7 @@ default T setLocalTime(@NonNull String name, @Nullable LocalTime v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { @@ -331,7 +331,7 @@ default T setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setString(@NonNull String name, @Nullable String v) { @@ -346,7 +346,7 @@ default T setString(@NonNull String name, @Nullable String v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setBigInteger(@NonNull String name, @Nullable BigInteger v) { @@ -361,7 +361,7 @@ default T setBigInteger(@NonNull String name, @Nullable BigInteger v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { @@ -376,7 +376,7 @@ default T setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setUuid(@NonNull String name, @Nullable UUID v) { @@ -391,7 +391,7 @@ default T setUuid(@NonNull String name, @Nullable UUID v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setInetAddress(@NonNull String name, @Nullable InetAddress v) { @@ -406,7 +406,7 @@ default T setInetAddress(@NonNull String name, @Nullable InetAddress v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { @@ -423,7 +423,7 @@ default T setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the index is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setToken(@NonNull String name, @NonNull Token v) { @@ -441,7 +441,7 @@ default T setToken(@NonNull String name, @NonNull Token v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setList( @@ -460,7 +460,7 @@ default T setList( *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setSet(@NonNull String name, @Nullable Set v, @NonNull Class elementsClass) { @@ -478,7 +478,7 @@ default T setSet(@NonNull String name, @Nullable Set v, @NonNull Class *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setMap( @@ -498,7 +498,7 @@ default T setMap( *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setUdtValue(@NonNull String name, @Nullable UdtValue v) { @@ -513,7 +513,7 @@ default T setUdtValue(@NonNull String name, @Nullable UdtValue v) { *

          This method deals with case sensitivity in the way explained in the documentation of {@link * AccessibleByName}. * - * @throws IndexOutOfBoundsException if the name is invalid. + * @throws IllegalArgumentException if the name is invalid. */ @NonNull default T setTupleValue(@NonNull String name, @Nullable TupleValue v) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 3340632f770..3555c561414 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -98,12 +98,20 @@ public DataType getType(int i) { @Override public int firstIndexOf(@NonNull CqlIdentifier id) { - return variableDefinitions.firstIndexOf(id); + int indexOf = variableDefinitions.firstIndexOf(id); + if (indexOf == -1) { + throw new IllegalArgumentException(id + " is not a variable in this bound statement"); + } + return indexOf; } @Override public int firstIndexOf(@NonNull String name) { - return variableDefinitions.firstIndexOf(name); + int indexOf = variableDefinitions.firstIndexOf(name); + if (indexOf == -1) { + throw new IllegalArgumentException(name + " is not a variable in this bound statement"); + } + return indexOf; } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java index 9f37d14e3f5..ed2f11224c7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java @@ -70,7 +70,11 @@ public DataType getType(int i) { @Override public int firstIndexOf(@NonNull CqlIdentifier id) { - return definitions.firstIndexOf(id); + int indexOf = definitions.firstIndexOf(id); + if (indexOf == -1) { + throw new IllegalArgumentException(id + " is not a column in this row"); + } + return indexOf; } @NonNull @@ -81,7 +85,11 @@ public DataType getType(@NonNull CqlIdentifier id) { @Override public int firstIndexOf(@NonNull String name) { - return definitions.firstIndexOf(name); + int indexOf = definitions.firstIndexOf(name); + if (indexOf == -1) { + throw new IllegalArgumentException(name + " is not a column in this row"); + } + return indexOf; } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index bb45457f481..a59a512f200 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -73,12 +73,20 @@ public int size() { @Override public int firstIndexOf(@NonNull CqlIdentifier id) { - return type.firstIndexOf(id); + int indexOf = type.firstIndexOf(id); + if (indexOf == -1) { + throw new IllegalArgumentException(id + " is not a field in this UDT"); + } + return indexOf; } @Override public int firstIndexOf(@NonNull String name) { - return type.firstIndexOf(name); + int indexOf = type.firstIndexOf(name); + if (indexOf == -1) { + throw new IllegalArgumentException(name + " is not a field in this UDT"); + } + return indexOf; } @NonNull diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index 39299d5b81f..4587f4bb773 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -468,4 +468,16 @@ public void should_get_with_explicit_codec_by_name() { Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_when_id_does_not_exists() { + final CqlIdentifier invalidField = CqlIdentifier.fromInternal("invalidField"); + // Given + T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); + + // When + t.setInt(invalidField, 1); + + // Then the method will throw IllegalArgumentException up to the client. + } } From 888e203219a5963260e2b3ccc6d4ce445b0d91fe Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 21 Jun 2018 21:27:25 -0700 Subject: [PATCH 489/742] JAVA-1891: Allow null items when setting values in bulk In tuple values, UDT values and bound statements. --- changelog/README.md | 1 + .../internal/core/data/ValuesHelper.java | 9 +++++++-- .../core/data/DefaultTupleValueTest.java | 15 +++++++++++++++ .../core/data/DefaultUdtValueTest.java | 19 +++++++++++++++++++ .../driver/api/core/cql/BoundStatementIT.java | 9 +++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 627d9742b9c..3dfec63fd2b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [bug] JAVA-1891: Allow null items when setting values in bulk - [improvement] JAVA-1767: Improve message when column not in result set - [improvement] JAVA-1624: Expose ExecutionInfo on exceptions where applicable - [improvement] JAVA-1766: Revisit nullability diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/ValuesHelper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/ValuesHelper.java index 2a3ae5bbf7b..e33068621d0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/ValuesHelper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/ValuesHelper.java @@ -61,7 +61,10 @@ public static ByteBuffer[] encodeValues( throw new IllegalArgumentException("Unsupported token type " + value.getClass()); } } else { - TypeCodec codec = codecRegistry.codecFor(fieldTypes.get(i), value); + TypeCodec codec = + (value == null) + ? codecRegistry.codecFor(fieldTypes.get(i)) + : codecRegistry.codecFor(fieldTypes.get(i), value); encodedValue = codec.encode(value, protocolVersion); } encodedValues[i] = encodedValue; @@ -104,7 +107,9 @@ public static ByteBuffer[] encodePreparedValues( } } else { TypeCodec codec = - codecRegistry.codecFor(variableDefinitions.get(i).getType(), value); + (value == null) + ? codecRegistry.codecFor(variableDefinitions.get(i).getType()) + : codecRegistry.codecFor(variableDefinitions.get(i).getType(), value); encodedValue = codec.encode(value, protocolVersion); } encodedValues[i] = encodedValue; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index 48e20f21e6a..bf7052a8e87 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -22,12 +22,15 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.TupleType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.SerializationHelper; import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.util.Bytes; +import java.io.UnsupportedEncodingException; import java.util.List; import org.junit.Test; +import org.mockito.Mockito; public class DefaultTupleValueTest extends AccessibleByIndexTestBase { @@ -60,6 +63,18 @@ public void should_serialize_and_deserialize() { assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); } + @Test + public void should_support_null_items_when_setting_in_bulk() throws UnsupportedEncodingException { + DefaultTupleType type = + new DefaultTupleType(ImmutableList.of(DataTypes.INT, DataTypes.TEXT), attachmentPoint); + Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + TupleValue value = type.newValue(null, "foo"); + + assertThat(value.isNull(0)).isTrue(); + assertThat(value.getString(1)).isEqualTo("foo"); + } + @Test public void should_equate_instances_with_same_values_but_different_binary_representations() { TupleType tupleType = DataTypes.tupleOf(DataTypes.VARINT); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index 0def91494e3..a276d6f8b31 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -23,11 +23,14 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.SerializationHelper; import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.datastax.oss.protocol.internal.util.Bytes; +import java.io.UnsupportedEncodingException; import java.util.List; import org.junit.Test; +import org.mockito.Mockito; public class DefaultUdtValueTest extends AccessibleByIdTestBase { @@ -78,6 +81,22 @@ public void should_serialize_and_deserialize() { assertThat(Bytes.toHexString(out.getBytesUnsafe(1))).isEqualTo("0x61"); } + @Test + public void should_support_null_items_when_setting_in_bulk() throws UnsupportedEncodingException { + UserDefinedType type = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) + .build(); + Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + UdtValue value = type.newValue(null, "foo"); + + assertThat(value.isNull(0)).isTrue(); + assertThat(value.getString(1)).isEqualTo("foo"); + } + @Test public void should_equate_instances_with_same_values_but_different_binary_representations() { UserDefinedType type = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 96ee7fd3107..2db64c6cf4d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -130,6 +131,14 @@ public void should_have_empty_result_definitions_for_update_query() { assertThat(rs.getColumnDefinitions()).hasSize(0); } + @Test + public void should_bind_null_value_when_setting_values_in_bulk() { + PreparedStatement prepared = + sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + BoundStatement boundStatement = prepared.bind(name.getMethodName(), null); + assertThat(boundStatement.get(1, TypeCodecs.INT)).isNull(); + } + @Test public void should_allow_custom_codecs_when_setting_values_in_bulk() { // v0 is an int column, but we'll bind a String to it From d40ac56ac739c4a8f5598aa86a67d256ee86e4a2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Jun 2018 13:46:32 -0700 Subject: [PATCH 490/742] JAVA-1878 Remove unnecessary defensive copy in TypeSafeDriverConfig.getProfiles --- .../oss/driver/api/core/config/DriverConfig.java | 10 ++-------- .../core/config/typesafe/TypesafeDriverConfig.java | 6 +++--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java index 294df572760..8cf64d5ad42 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java @@ -41,13 +41,7 @@ default DriverConfigProfile getDefaultProfile() { @NonNull DriverConfigProfile getProfile(@NonNull String profileName); - /** - * Returns an immutable view of all named profiles (including the default profile). - * - *

          Implementations typically return a defensive copy of their internal state; therefore this - * should not be used in performance-sensitive parts of the code, see {@link #getProfile(String)} - * instead. - */ + /** Returns an immutable view of all named profiles (including the default profile). */ @NonNull - Map getProfiles(); + Map getProfiles(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java index 9d04679370d..44c4c289263 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java @@ -36,7 +36,7 @@ public class TypesafeDriverConfig implements DriverConfig { private static final Logger LOG = LoggerFactory.getLogger(TypesafeDriverConfig.class); - private final Map profiles; + private final ImmutableMap profiles; // Only used to detect if reload saw any change private volatile Config lastLoadedConfig; @@ -136,7 +136,7 @@ public DriverConfigProfile getProfile(@NonNull String profileName) { @NonNull @Override - public Map getProfiles() { - return ImmutableMap.copyOf(profiles); + public Map getProfiles() { + return profiles; } } From a9d02ca2bdd62170b1c64032d16c90e26d9f8789 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Jun 2018 14:10:46 -0700 Subject: [PATCH 491/742] JAVA-1885: Update API leak checks for 'internal' dependencies --- .../oss/driver/api/core/metrics/Metrics.java | 1 + pom.xml | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java index 593f243b61a..254be630a44 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java @@ -38,6 +38,7 @@ public interface Metrics { * * @see Reporters * (Dropwizard Metrics manual) + * @leaks-private-api */ @NonNull MetricRegistry getRegistry(); diff --git a/pom.xml b/pom.xml index 458bb7621d4..b78ae4a9d74 100644 --- a/pom.xml +++ b/pom.xml @@ -449,6 +449,7 @@ limitations under the License.]]> + check-api-leaks javadoc @@ -463,10 +464,47 @@ limitations under the License.]]> com.datastax.oss.driver.internal.* + -preventleak com.datastax.oss.driver.internal + -preventleak - com.google.common + com.datastax.oss.driver.shaded + + -preventleak + com.typesafe.config + + -preventleak + com.codahale.metrics + + -preventleak + org.HdrHistogram + + -preventleak + io.netty + + -preventleak + jnr + -preventleak + com.kenai.constantine + -preventleak + com.kenai.jffi + -preventleak + com.kenai.jnr + + -preventleak + net.jpountz + -preventleak + org.xerial.snappy false From 14f088cdec041dc1ea7926bd5f1a0598839e167f Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Jun 2018 14:18:20 -0700 Subject: [PATCH 492/742] java-1886: Make concurrency annotations optional again (reverts JAVA-1851) --- changelog/README.md | 1 - core/pom.xml | 1 + query-builder/pom.xml | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 3dfec63fd2b..bc94916809e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -16,7 +16,6 @@ - [improvement] JAVA-1865: Add RelationMetadata.getPrimaryKey() - [improvement] JAVA-1862: Add ConsistencyLevel.isDcLocal and isSerial - [improvement] JAVA-1858: Implement Serializable in implementations, not interfaces -- [improvement] JAVA-1851: Make dependency to JCIP annotations non optional - [improvement] JAVA-1830: Surface response frame size in ExecutionInfo - [improvement] JAVA-1853: Add newValue(Object...) to TupleType and UserDefinedType - [improvement] JAVA-1815: Reorganize configuration into basic/advanced categories diff --git a/core/pom.xml b/core/pom.xml index 0d8ca273c15..4a43832f6d9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -91,6 +91,7 @@ com.github.stephenc.jcip jcip-annotations + true com.github.spotbugs diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 1b0dfbb6c78..ddc07ca6d97 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -44,6 +44,7 @@ com.github.stephenc.jcip jcip-annotations + true com.github.spotbugs From cbedaef2ee4eb8d5a50a212517bb3c6ba4b5fc5c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 25 Jun 2018 14:28:22 -0700 Subject: [PATCH 493/742] JAVA-1895: Rename PreparedStatement.getPrimaryKeyIndices to getPartitionKeyIndices --- changelog/README.md | 1 + .../oss/driver/api/core/cql/PreparedStatement.java | 8 ++++---- .../internal/core/cql/DefaultBoundStatement.java | 2 +- .../internal/core/cql/DefaultPreparedStatement.java | 10 +++++----- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index bc94916809e..325385316af 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [bug] JAVA-1895: Rename PreparedStatement.getPrimaryKeyIndices to getPartitionKeyIndices - [bug] JAVA-1891: Allow null items when setting values in bulk - [improvement] JAVA-1767: Improve message when column not in result set - [improvement] JAVA-1624: Expose ExecutionInfo on exceptions where applicable diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 18662a7803e..741546fdec3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -69,12 +69,12 @@ public interface PreparedStatement { * PreparedStatement ps2 = session.prepare("UPDATE foo SET v = ? WHERE pk1 = 1 AND pk2 = ? AND v = ?"); * * - * Then {@code ps1.getPrimaryKeyIndices()} contains 1 and 2, and {@code - * ps2.getPrimaryKeyIndices()} is empty (because one of the partition key components is hard-coded - * in the query string). + * Then {@code ps1.getPartitionKeyIndices()} contains 1 and 2, and {@code + * ps2.getPartitionKeyIndices()} is empty (because one of the partition key components is + * hard-coded in the query string). */ @NonNull - List getPrimaryKeyIndices(); + List getPartitionKeyIndices(); /** * A unique identifier for result metadata (essentially a hash of {@link diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 3555c561414..34c63e2cb1f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -256,7 +256,7 @@ public ByteBuffer getRoutingKey() { if (routingKey != null) { return routingKey; } else { - List indices = preparedStatement.getPrimaryKeyIndices(); + List indices = preparedStatement.getPartitionKeyIndices(); if (indices.isEmpty()) { return null; } else if (indices.size() == 1) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 0ecef5f3116..75eba4ec2ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -37,7 +37,7 @@ public class DefaultPreparedStatement implements PreparedStatement { private final ByteBuffer id; private final RepreparePayload repreparePayload; private final ColumnDefinitions variableDefinitions; - private final List primaryKeyIndices; + private final List partitionKeyIndices; private volatile ResultMetadata resultMetadata; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; @@ -51,7 +51,7 @@ public DefaultPreparedStatement( ByteBuffer id, String query, ColumnDefinitions variableDefinitions, - List primaryKeyIndices, + List partitionKeyIndices, ByteBuffer resultMetadataId, ColumnDefinitions resultSetDefinitions, String configProfileName, @@ -63,7 +63,7 @@ public DefaultPreparedStatement( ProtocolVersion protocolVersion, Map customPayloadForPrepare) { this.id = id; - this.primaryKeyIndices = primaryKeyIndices; + this.partitionKeyIndices = partitionKeyIndices; // It's important that we keep a reference to this object, so that it only gets evicted from // the map in DefaultSession if no client reference the PreparedStatement anymore. this.repreparePayload = new RepreparePayload(id, query, keyspace, customPayloadForPrepare); @@ -97,8 +97,8 @@ public ColumnDefinitions getVariableDefinitions() { @NonNull @Override - public List getPrimaryKeyIndices() { - return primaryKeyIndices; + public List getPartitionKeyIndices() { + return partitionKeyIndices; } @Override From 8c71047eeacbdd967d239350e9fb0d09329e0431 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 22 Jun 2018 10:10:33 -0500 Subject: [PATCH 494/742] JAVA-1890: Add more configuration options to DefaultSslEngineFactory Motivation: For some organizations, it might be more convenient to provide keystore and truststore locations for DefaultSslEngineFactory via configuration instead of relying on system properties to initialize the default SSLContext. Similarly, enabling hostname validation on an SSLEngine is straightforward, but currently a user has to create their own SslEngineFactory implementation. Modifications: Add configuration to DefaultDriverOption for configuring SSLContext using keystore and truststore file location and password, and enabling hostname validation. Add tests for verifying configuration is effective. Result: DefaultSslEngineFactory can now build SSLContext from configuration, optionally with hostname validation. --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 5 ++ .../core/ssl/DefaultSslEngineFactory.java | 82 +++++++++++++++++- core/src/main/resources/reference.conf | 13 +++ ...tSslEngineFactoryHostnameValidationIT.java | 49 +++++++++++ .../core/ssl/DefaultSslEngineFactoryIT.java | 46 ++++++++-- ...faultSslEngineFactoryPropertyBasedIT.java} | 9 +- ...FactoryPropertyBasedWithClientAuthIT.java} | 22 +++-- ...faultSslEngineFactoryWithClientAuthIT.java | 37 +++++--- manual/core/ssl/README.md | 30 +++++-- .../driver/api/testinfra/ccm/CcmBridge.java | 17 ++++ .../api/testinfra/ccm/CustomCcmRule.java | 5 ++ .../src/main/resources/client.truststore | Bin 1009 -> 1981 bytes .../main/resources/server_localhost.keystore | Bin 0 -> 2621 bytes 14 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/{DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java => DefaultSslEngineFactoryPropertyBasedIT.java} (84%) rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/{DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java => DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java} (57%) create mode 100644 test-infra/src/main/resources/server_localhost.keystore diff --git a/changelog/README.md b/changelog/README.md index 325385316af..6df0074203e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1890: Add more configuration options to DefaultSslEngineFactory - [bug] JAVA-1895: Rename PreparedStatement.getPrimaryKeyIndices to getPartitionKeyIndices - [bug] JAVA-1891: Allow null items when setting values in bulk - [improvement] JAVA-1767: Improve message when column not in result set diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 3515efdca74..95a8f7b290c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -64,6 +64,11 @@ public enum DefaultDriverOption implements DriverOption { SSL_ENGINE_FACTORY_CLASS("advanced.ssl-engine-factory.class"), SSL_CIPHER_SUITES("advanced.ssl-engine-factory.cipher-suites"), + SSL_HOSTNAME_VALIDATION("advanced.ssl-engine-factory.hostname-validation"), + SSL_KEYSTORE_PATH("advanced.ssl-engine-factory.keystore-path"), + SSL_KEYSTORE_PASSWORD("advanced.ssl-engine-factory.keystore-password"), + SSL_TRUSTSTORE_PATH("advanced.ssl-engine-factory.truststore-path"), + SSL_TRUSTSTORE_PASSWORD("advanced.ssl-engine-factory.truststore-password"), TIMESTAMP_GENERATOR_CLASS("advanced.timestamp-generator.class"), TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK("advanced.timestamp-generator.force-java-clock"), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 1ba1d63167d..9ab6fbf4d42 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -20,12 +20,19 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.InputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.security.NoSuchAlgorithmException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.SecureRandom; import java.util.List; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.TrustManagerFactory; import net.jcip.annotations.ThreadSafe; /** @@ -39,6 +46,11 @@ * advanced.ssl-engine-factory { * class = DefaultSslEngineFactory * cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] + * hostname-validation = false + * truststore-path = /path/to/client.truststore + * truststore-psasword = password123 + * keystore-path = /path/to/client.keystore + * keystore-password = password123 * } * } * @@ -50,15 +62,16 @@ public class DefaultSslEngineFactory implements SslEngineFactory { private final SSLContext sslContext; private final String[] cipherSuites; + private final boolean requireHostnameValidation; /** Builds a new instance from the driver configuration. */ public DefaultSslEngineFactory(DriverContext driverContext) { + DriverConfigProfile config = driverContext.config().getDefaultProfile(); try { - this.sslContext = SSLContext.getDefault(); - } catch (NoSuchAlgorithmException e) { + this.sslContext = buildContext(config); + } catch (Exception e) { throw new IllegalStateException("Cannot initialize SSL Context", e); } - DriverConfigProfile config = driverContext.config().getDefaultProfile(); if (config.isDefined(DefaultDriverOption.SSL_CIPHER_SUITES)) { List list = config.getStringList(DefaultDriverOption.SSL_CIPHER_SUITES); String tmp[] = new String[list.size()]; @@ -66,6 +79,12 @@ public DefaultSslEngineFactory(DriverContext driverContext) { } else { this.cipherSuites = null; } + if (config.isDefined(DefaultDriverOption.SSL_HOSTNAME_VALIDATION)) { + this.requireHostnameValidation = + config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION); + } else { + this.requireHostnameValidation = false; + } } @NonNull @@ -82,9 +101,64 @@ public SSLEngine newSslEngine(@NonNull SocketAddress remoteEndpoint) { if (cipherSuites != null) { engine.setEnabledCipherSuites(cipherSuites); } + if (requireHostnameValidation) { + SSLParameters parameters = engine.getSSLParameters(); + parameters.setEndpointIdentificationAlgorithm("HTTPS"); + engine.setSSLParameters(parameters); + } return engine; } + protected SSLContext buildContext(DriverConfigProfile config) throws Exception { + if (config.isDefined(DefaultDriverOption.SSL_KEYSTORE_PATH) + || config.isDefined(DefaultDriverOption.SSL_TRUSTSTORE_PATH)) { + SSLContext context = SSLContext.getInstance("SSL"); + + // initialize truststore if configured. + TrustManagerFactory tmf = null; + if (config.isDefined(DefaultDriverOption.SSL_TRUSTSTORE_PATH)) { + try (InputStream tsf = + Files.newInputStream( + Paths.get(config.getString(DefaultDriverOption.SSL_TRUSTSTORE_PATH)))) { + KeyStore ts = KeyStore.getInstance("JKS"); + char[] password = + config.isDefined(DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD) + ? config.getString(DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD).toCharArray() + : null; + ts.load(tsf, password); + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ts); + } + } + + // initialize keystore if configured. + KeyManagerFactory kmf = null; + if (config.isDefined(DefaultDriverOption.SSL_KEYSTORE_PATH)) { + try (InputStream ksf = + Files.newInputStream( + Paths.get(config.getString(DefaultDriverOption.SSL_KEYSTORE_PATH)))) { + KeyStore ks = KeyStore.getInstance("JKS"); + char[] password = + config.isDefined(DefaultDriverOption.SSL_KEYSTORE_PASSWORD) + ? config.getString(DefaultDriverOption.SSL_KEYSTORE_PASSWORD).toCharArray() + : null; + ks.load(ksf, password); + kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, password); + } + } + + context.init( + kmf != null ? kmf.getKeyManagers() : null, + tmf != null ? tmf.getTrustManagers() : null, + new SecureRandom()); + return context; + } else { + // if both keystore and truststore aren't configured, use default SSLContext. + return SSLContext.getDefault(); + } + } + @Override public void close() throws Exception { // nothing to do diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 3083b56666b..8ef420b6f2d 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -405,6 +405,19 @@ datastax-java-driver { # suites on the engine, which according to the JDK documentations results in "a minimum quality # of service". // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] + + # Whether or not to require validation that the hostname of the server certificate's common + # name matches the hostname of the server being connected to. + hostname-validation = false + + # The locations and passwords used to access truststore and keystore contents. + # These properties are optional. If either truststore-path or keystore-path are specified, + # the driver builds an SSLContext from these files. If neither option is specified, the + # default SSLContext is used, which is based on system property configuration. + // truststore-path = /path/to/client.truststore + // truststore-password = password123 + // keystore-path = /path/to/client.keystore + // keystore-password = password123 } # The generator that assigns a microsecond timestamp to each request. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java new file mode 100644 index 00000000000..b6a9312e21c --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java @@ -0,0 +1,49 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.ssl; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import org.junit.ClassRule; +import org.junit.Test; + +public class DefaultSslEngineFactoryHostnameValidationIT { + + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslLocalhostCn().build(); + + /** + * Ensures that SSL connectivity can be established with hostname validation enabled when the + * server's certificate has a common name that matches its hostname. In this case the certificate + * uses a CN of 'localhost' which is expected to work, but may not if localhost does not resolve + * to 127.0.0.1. + */ + @Test + public void should_connect_if_hostname_validation_enabled_and_hostname_matches() { + try (CqlSession session = + SessionUtils.newSession( + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = true", + "advanced.ssl-engine-factory.truststore-path = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.truststore-password = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + session.execute("select * from system.local"); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index 30e5011e476..dd3e66b9691 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -15,30 +15,62 @@ */ package com.datastax.oss.driver.api.core.ssl; +import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; -import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; -import org.junit.experimental.categories.Category; -@Category(IsolatedTests.class) public class DefaultSslEngineFactoryIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); @Test public void should_connect_with_ssl() { - System.setProperty( - "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); - System.setProperty( - "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); + try (CqlSession session = + SessionUtils.newSession( + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.truststore-path = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.truststore-password = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + session.execute("select * from system.local"); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_if_hostname_validation_enabled_and_hostname_does_not_match() { + // should not succeed as certificate does not have a CN that would match hostname, + // (unless hostname is node1). + try (CqlSession session = + SessionUtils.newSession( + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = true", + "advanced.ssl-engine-factory.truststore-path = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.truststore-password = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + session.execute("select * from system.local"); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_if_truststore_not_provided() { try (CqlSession session = SessionUtils.newSession( ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { session.execute("select * from system.local"); } } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_if_not_using_ssl() { + try (CqlSession session = SessionUtils.newSession(ccm)) { + session.execute("select * from system.local"); + } + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java similarity index 84% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java index d0756fcc93c..2bf3839fc6d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.ssl; -import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; @@ -26,12 +25,12 @@ import org.junit.experimental.categories.Category; @Category(IsolatedTests.class) -public class DefaultSslEngineFactoryWithClientAuthNotProvidedIT { +public class DefaultSslEngineFactoryPropertyBasedIT { - @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); - @Test(expected = AllNodesFailedException.class) - public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() { + @Test + public void should_connect_with_ssl() { System.setProperty( "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java similarity index 57% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java index 0348f60c707..522c55ca980 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithTruststoreNotProvidedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.ssl; -import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; @@ -25,13 +25,23 @@ import org.junit.experimental.categories.Category; @Category(IsolatedTests.class) -public class DefaultSslEngineFactoryWithTruststoreNotProvidedIT { +public class DefaultSslEngineFactoryPropertyBasedWithClientAuthIT { - @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); - @Test(expected = AllNodesFailedException.class) - public void should_not_connect_if_not_using_ssl() { - try (CqlSession session = SessionUtils.newSession(ccm)) { + @Test + public void should_connect_with_ssl_using_client_auth() { + System.setProperty( + "javax.net.ssl.keyStore", CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.keyStorePassword", CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD); + System.setProperty( + "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); + System.setProperty( + "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); + try (CqlSession session = + SessionUtils.newSession( + ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index ce37e377ac7..f51c56f2d9c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -15,33 +15,46 @@ */ package com.datastax.oss.driver.api.core.ssl; +import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; -import com.datastax.oss.driver.categories.IsolatedTests; import org.junit.ClassRule; import org.junit.Test; -import org.junit.experimental.categories.Category; -@Category(IsolatedTests.class) public class DefaultSslEngineFactoryWithClientAuthIT { @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslAuth().build(); @Test public void should_connect_with_ssl_using_client_auth() { - System.setProperty( - "javax.net.ssl.keyStore", CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath()); - System.setProperty( - "javax.net.ssl.keyStorePassword", CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD); - System.setProperty( - "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); - System.setProperty( - "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); try (CqlSession session = SessionUtils.newSession( - ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.keystore-path = " + + CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.keystore-password = " + + CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD, + "advanced.ssl-engine-factory.truststore-path = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.truststore-password = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + session.execute("select * from system.local"); + } + } + + @Test(expected = AllNodesFailedException.class) + public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() { + try (CqlSession session = + SessionUtils.newSession( + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.truststore-path = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), + "advanced.ssl-engine-factory.truststore-password = " + + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { session.execute("select * from system.local"); } } diff --git a/manual/core/ssl/README.md b/manual/core/ssl/README.md index 2603b0399ae..73182eb5488 100644 --- a/manual/core/ssl/README.md +++ b/manual/core/ssl/README.md @@ -82,12 +82,25 @@ datastax-java-driver { # suites on the engine, which according to the JDK documentations results in "a minimum quality # of service". // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] + + # Whether or not to require validation that the hostname of the server certificate's common + # name matches the hostname of the server being connected to. + hostname-validation = false + + # The locations and passwords used to access truststore and keystore contents. + # These properties are optional. If either truststore-path or keystore-path are specified, + # the driver builds an SSLContext from these files. If neither option is specified, the + # default SSLContext is used, which is based on system property configuration. + // truststore-path = /path/to/client.truststore + // truststore-password = password123 + // keystore-path = /path/to/client.keystore + // keystore-password = password123 } } ``` -You can then use [JSSE system properties] for specific details, like keystore locations and -passwords: +Alternatively to storing keystore and truststore information in your configuration, you can instead +use [JSSE system properties]: ``` -Djavax.net.ssl.trustStore=/path/to/client.truststore @@ -101,12 +114,13 @@ passwords: If you need more control than what system properties allow, you need to write your own engine factory. If you just need specific configuration on the `SSLEngine`, you can extend the default -factory and override `newSslEngine`. For example, here is how to enable hostname verification: +factory and override `newSslEngine`. For example, here is how you would configure custom +`AlgorithmConstraints`: ```java -public class HostnameVerificationSslEngineFactory extends DefaultSslEngineFactory { +public class CustomSslEngineFactory extends DefaultSslEngineFactory { - public HostnameVerificationSslEngineFactory(DriverContext context) { + public CustomSslEngineFactory(DriverContext context) { super(context); } @@ -114,9 +128,7 @@ public class HostnameVerificationSslEngineFactory extends DefaultSslEngineFactor public SSLEngine newSslEngine(SocketAddress remoteEndpoint) { SSLEngine engine = super.newSslEngine(remoteEndpoint); SSLParameters parameters = engine.getSSLParameters(); - // HTTPS endpoint identification includes hostname verification against certificate's common - // name. - parameters.setEndpointIdentificationAlgorithm("HTTPS"); + parameters.setAlgorithmConstraints(...); engine.setSSLParameters(parameters); return engine; } @@ -128,7 +140,7 @@ Then declare your custom implementation in the configuration: ``` datastax-java-driver { advanced.ssl-engine-factory { - class = com.mycompany.HostnameVerificationSslEngineFactory + class = com.mycompany.CustomSslEngineFactory } } ``` diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index dc3575f5289..7530c356a1b 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -101,6 +101,13 @@ public class CcmBridge implements AutoCloseable { private static final File DEFAULT_SERVER_KEYSTORE_FILE = createTempStore(DEFAULT_SERVER_KEYSTORE_PATH); + // A separate keystore where the certificate has a CN of localhost, used for hostname + // validation testing. + public static final String DEFAULT_SERVER_LOCALHOST_KEYSTORE_PATH = "/server_localhost.keystore"; + + private static final File DEFAULT_SERVER_LOCALHOST_KEYSTORE_FILE = + createTempStore(DEFAULT_SERVER_LOCALHOST_KEYSTORE_PATH); + // major DSE versions private static final Version V6_0_0 = Version.parse("6.0.0"); private static final Version V5_1_0 = Version.parse("5.1.0"); @@ -385,6 +392,16 @@ public Builder withSsl() { return this; } + public Builder withSslLocalhostCn() { + cassandraConfiguration.put("client_encryption_options.enabled", "true"); + cassandraConfiguration.put( + "client_encryption_options.keystore", + DEFAULT_SERVER_LOCALHOST_KEYSTORE_FILE.getAbsolutePath()); + cassandraConfiguration.put( + "client_encryption_options.keystore_password", DEFAULT_SERVER_KEYSTORE_PASSWORD); + return this; + } + /** Enables client authentication. This also enables encryption ({@link #withSsl()}. */ public Builder withSslAuth() { withSsl(); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index d6acbb841e4..898a49234a0 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -97,6 +97,11 @@ public Builder withSsl() { return this; } + public Builder withSslLocalhostCn() { + bridgeBuilder.withSslLocalhostCn(); + return this; + } + public Builder withSslAuth() { bridgeBuilder.withSslAuth(); return this; diff --git a/test-infra/src/main/resources/client.truststore b/test-infra/src/main/resources/client.truststore index ac2b87c44056d0f22f5d5a5f14eba5f36f08c74d..f106bdee38d3728c32626900dccda22b9b328f53 100644 GIT binary patch delta 765 zcmey!zL%fp-`jt085kItKzJk1MP?r`hc72TIWZ?AzqlkmFFz%ffq^ka!{c=)18any zsevT}1M^aYCgz0(O-!8&n3))vm{?eAa{>)`**LY@JlekVGBR?rG8i;AO!Ut+6EYBB zV-96u7UqPTX&@)gYiMC$W@Kb&W@v0+83pEA8W}*j5S_wUbWUzyRIJ~iQCFaNqeL{) zpg`m2+STq2J9Jj`oo!>;;(zVz=|6JPt?|na$$8XV`E&cNeRSWRPtG}oI}$fKOUzj6 zdS`FxKkWz1lO860e^&55_q_C@s@3YV?pYSMC3mF>{!VH$+i2x-f6|;&9^9Wf8K+k5 zU|D+UXWxoF%tG4(^^L=?ZY_CtxnAm#y7M97iy=3Q<YnPGH8Ulpw*A>B=f19G_tTE&*Hs^D=1(h9^u4xNqxaHP;M&BnTq zOuL;YFJuy_FAF~MM<-3tc%N@NYsQ6V|CxRN*rG)Na}y&Y!z6~u|M#XcZDl>Fduitd z>kHwsAu&6a9KN}CpKz^qKo&gz5h8Q){PqWW4zZ0BBrLpa1{> delta 42 xcmdnX|B;>N-`jt085kItfS7S3&qZdD`hXdqyzkdBH@v$4`QJ++HMX{-+W>J$5_bRq diff --git a/test-infra/src/main/resources/server_localhost.keystore b/test-infra/src/main/resources/server_localhost.keystore new file mode 100644 index 0000000000000000000000000000000000000000..05e7a559c5db383a354982e01e2a657a09e64a0c GIT binary patch literal 2621 zcmY+Gc{CJ`7RP6pAxlPP{Oru6Fhh*pWEnGL8|$bnkt`FkOv9u!)(PQeX$RGeAd)=?lg zFrUsd3!?LUKExAfI@tUFBCsQf4nBK`Z4b+mi|@Z%{JcPpd^(7Yrh~T7NG{(0;ltw+ zAlQXC(UfSUl5ERY=>dh`CDUi!R?9g!0ncqgbkNIXt)eF*u)io0Mko5rQ^xkXuCW(a zC`LIK4dUW-hu8i%lXSjyT+4XdxBUrsrMdc&UIcBh#5^+zl&JoR@>K$>Vr(TvmC#V? zuT6V|qRyk<3>;7!~{_WS!#SHBo^eE4l_rv;^ zzQ8xk2oJw9SPr83V@GNm0s!FWcMo<=OI<-9ie(2x1E!nBevp`hf1>jkY9wy?6!=EO z5cuh@tTVU9e1@wm*#1pWX?3n}@kyoEjF1P;n`h$0g{@lV6#^MS5pf7+-UXoXEv!Jh z98;MkU%6bBc-b*o=6j8yB*e{u2WdP7Nyzi_wxwNaChV((+>Y=}Qs}ME{Kz$yJCmd{ zgSsiYm3dp6DbyB|Trs_8Nci|DOd>%}?f6>ETpJQ}iv+z_}hWZstylC#)Ts_3e zV7~Z`_gTeR5sow(y5ao;5Q~X?e?$6CgNT4sfRv(g!IsVSjkI)cEK&wyF2e8$zsbw} z%q9ELho?9&wKL(lHfDn`v~ZIsmvDs36AvTuJywGhvjPFlIGb0St{9bpG}2#-s0q~b z5k)@>A1S-IP4(!@IvSR;-^#*|gLmoXszfPl=Em0c-EhwDON`8w9lqm>ay=_JXL@Yo z-u$g|NciHj)a80!)QLp%4UdKL(|(S<}ahjm}$rO=4=333@CbfAeQ@?cM`gI6{q zSR!KUUNc~rZI;c$m*J_vu+ktxI0hq4uZP9b9-b7ori@sTEuzG$rkji4g3(gRYpP3+ z^jtX^Um1at?^K?0KHU9$o_qdA@U)GNrysuHs!G3q=p;S$~~w0%ck2RRbazEB5nbx-Q@5*JJ{JYRt6P^3t+V7x3L` z`m@zNWzJoxjJ`mquIz;9eylpmK5NjsFN|r;pBhgc=F%#maIXif3C0VZoSw;|C)X3( zU%$#NGm-CPx$RH67TUW~bjFV6Q}!B?_21#$yfi-_+x%4fHpg2xTNbc(9a}THkJA*g zTcZ3V;#Bb9kQK>X_2kSH(zPoOTU%Zn^cipaWllG3B_@}?&ar+1 zGX#-GNZ5XxXRiB~vI`g78c?2Am*4jf_K}qZO%&l{!v@6nX1*O3r`m#PlH)>n10_}# zGo@Lq1ZwTKfwwD#5?tX4nrO-0wbFV=srSm1R zlp}!UZn0Nxk+MG$Li;c2lm9_O;G)2sUl&!k$!NmYU&%U15y8b=Q`rkE; znd7iii#5Fw-McftqVZer6xXlDqJu>)WAZ#l{?r|%c)j|~?=CwOsWr!p1b%+2Bpb_b zmU<$q8nd2`aaJ6*c)NQB^T7^l!@YUlHM0O7%4L2JB&Wcva^P#+UiYnEOY!9wLP_)W z^E$@I@TwZ1#r{?$4@{L`nimiVAOpMrBmf$X_$Lxpg@J_5`Ua5Ts+wvTEgf}?4hF5Q zdZ<2`b^q~e z6dfdbR?q6itlm|3v(DFfpB!~c-_(ajiL-L6UM9rkec1l!8{dA^3-XKBh8X#JqbHwM zJ!zT^72rDI?sc4I(x}KPpc&fVsqo8~6R1S$w`|BYTzE6TCj0X7=8o>&)k&WaMB9@M zc+kYkZRo!B?4#t`Yg4>vI98x-^tTK^B+sKWpO|yNUN9}4s>|5BcZF&@lq^WPYP)(P zUsmhB_wS^4(DEcGM}`?or}9S7UtP^%sBy_qi$QDl?ZCp;VI;j@l|_9qdG6d|%5}qh zqNa=!G0P>l#p3Fs?&mrm$F?s+k?DY&)SaVZaM<^ru5#P3?s7+YMMhL=Db^-lP4Lb| ziIBzl&|B=-QUcY^W ztNqmo8O|m-7dwiZi-zdJH8L5GRZOwKhHH46Z#KeKrmrG_jBKoeWy;;T+NbRNnN0Vc z41pGpg>%C8AMihrJqQ!&sBI8fFQRaj`AT zQ*B;KSNeb7-&^0If(u9IAV#({OM)K0LP=q>1}G-%)4fvkCmLz}F(9uX#9Eyn%1Z4|Z8es5<`mN|{g4WZEC+rKv9IFGbbx?;|&DaYr5Yy$AW>Z`i#h4t`@4csO@b zZ~TRU;5Y*l^LxCt&RBS(LC0?v1?paB4~YxYZ6sJFc)Yu|Yi8C=f{vX$I+>E)r@XWC z6%DVoY{=`tzFGpSOhDK6M&}xvUP5|tGclxAVNGN25SW9&D%5qTF7aEIL}4o?8ON&1 z_=BVT1xFWrz(~%xTOZMEr0*iPx)u}7mN^nrvEZmVQsNscrH9Pgsos?K42s*`X$ljo zCRMEC8PeGUdMWDh(+i}&)wHR%4`y87vbt{ZDMt+N>6uH%$3Q+ zoB&^RZl|v0?s{?O6bOY6fyq+aD0^>$!8pPYYhf*^(;}{QCRv|cMy-#~dU-G*t|#r? zH|QTraX8(USPwY2NuEvqIR`gIPT^aV?x?;3=|Sb*dRm1o#1LJpuS~ts&ngfHqCz51 z7mbze!z<&hYaZ$(ka4|OkCy%Qr$n~y8YvU&8Coe8o{*_Dk%#(HZrw&3Zk;1&2CJwA z<=l&Qe4WYM|CJDgt0tHhzB2s+JLf28Hg^lfL7HcrE%Bf&@#zMabsqqU+NUBF#v#)Q zLxjU0gMQBcAlPCa%)74@X;z3#Jtk2k&=+-V+Y9<=R8TN!RNjm#>hVM5qpC;Jh@vt^ zti(t&+Y*978=w(r9xkw=04Iky7yyE??~vse_`|kSCij8PA2eLDO6heVn3u0r>mv;? WNl9#?rmTaBGq6L5a&Q9U^8W=iVY Date: Thu, 21 Jun 2018 08:31:02 -0700 Subject: [PATCH 495/742] JAVA-1883: Use custom queue implementation for LBP's query plan --- changelog/README.md | 1 + .../DefaultLoadBalancingPolicy.java | 8 +- .../core/util/collection/QueryPlan.java | 112 ++++++++++++++++++ .../core/util/collection/QueryPlanTest.java | 77 ++++++++++++ 4 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java diff --git a/changelog/README.md b/changelog/README.md index 6df0074203e..7f820578c28 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1883: Use custom queue implementation for LBP's query plan - [improvement] JAVA-1890: Add more configuration options to DefaultSslEngineFactory - [bug] JAVA-1895: Rename PreparedStatement.getPrimaryKeyIndices to getPartitionKeyIndices - [bug] JAVA-1891: Allow null items when setting values in bulk diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 6b97faf3979..1aa163a54bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.util.ArrayUtils; import com.datastax.oss.driver.internal.core.util.Reflection; +import com.datastax.oss.driver.internal.core.util.collection.QueryPlan; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; @@ -42,7 +43,6 @@ import java.util.Optional; import java.util.Queue; import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntUnaryOperator; @@ -198,11 +198,7 @@ public Queue newQueryPlan(@Nullable Request request, @Nullable Session ses currentNodes.length - replicaCount, roundRobinAmount.getAndUpdate(INCREMENT)); - ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); - for (Object currentNode : currentNodes) { - queryPlan.offer((Node) currentNode); - } - return queryPlan; + return new QueryPlan(currentNodes); } private Set getReplicas(Request request, Session session) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java new file mode 100644 index 00000000000..dfe2a45757f --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java @@ -0,0 +1,112 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.collection; + +import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; +import com.datastax.oss.driver.shaded.guava.common.collect.Iterators; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import net.jcip.annotations.ThreadSafe; + +/** + * A specialized, thread-safe queue implementation for {@link + * LoadBalancingPolicy#newQueryPlan(Request, Session)}. + * + *

          All nodes must be provided at construction time. After that, the only valid mutation operation + * is {@link #poll()}, other methods throw. + * + *

          This class is not a general-purpose implementation, it is tailored for a specific use case in + * the driver. It makes a few unconventional API choices for the sake of performance (see {@link + * #QueryPlan(Object...)}. It can be reused for custom load balancing policies; if you plan to do + * so, study the source code of {@link DefaultLoadBalancingPolicy}. + */ +@ThreadSafe +public class QueryPlan extends AbstractCollection implements Queue { + + private final Object[] nodes; + private final AtomicInteger nextIndex = new AtomicInteger(); + + /** + * @param nodes the nodes to initially fill the queue with. For efficiency, there is no defensive + * copy, the provided array is used directly. The declared type is {@code Object[]} because of + * implementation details of {@link DefaultLoadBalancingPolicy}, but all elements must be + * instances of {@link Node}, otherwise instance methods will fail later. + */ + public QueryPlan(@NonNull Object... nodes) { + this.nodes = nodes; + } + + @Nullable + @Override + public Node poll() { + // We don't handle overflow. In practice it won't be an issue, since the driver stops polling + // once the query plan is empty. + int i = nextIndex.getAndIncrement(); + return (i >= nodes.length) ? null : (Node) nodes[i]; + } + + /** + * {@inheritDoc} + * + *

          The returned iterator reflects the state of the queue at the time of the call, and is not + * affected by further modifications. + */ + @NonNull + @Override + public Iterator iterator() { + int i = nextIndex.get(); + if (i >= nodes.length) { + return Collections.emptyList().iterator(); + } else { + return Iterators.forArray(Arrays.copyOfRange(nodes, i, nodes.length, Node[].class)); + } + } + + @Override + public int size() { + return Math.max(nodes.length - nextIndex.get(), 0); + } + + @Override + public boolean offer(Node node) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Node remove() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Node element() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Node peek() { + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java new file mode 100644 index 00000000000..ea6e2a7c6cf --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java @@ -0,0 +1,77 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util.collection; + +import static com.datastax.oss.driver.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.metadata.Node; +import java.util.Iterator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class QueryPlanTest { + + @Mock private Node node1; + @Mock private Node node2; + @Mock private Node node3; + + @Test + public void should_poll_elements() { + QueryPlan queryPlan = new QueryPlan(node1, node2, node3); + assertThat(queryPlan.poll()).isSameAs(node1); + assertThat(queryPlan.poll()).isSameAs(node2); + assertThat(queryPlan.poll()).isSameAs(node3); + assertThat(queryPlan.poll()).isNull(); + assertThat(queryPlan.poll()).isNull(); + } + + @Test + public void should_return_size() { + QueryPlan queryPlan = new QueryPlan(node1, node2, node3); + assertThat(queryPlan.size()).isEqualTo(3); + queryPlan.poll(); + assertThat(queryPlan.size()).isEqualTo(2); + queryPlan.poll(); + assertThat(queryPlan.size()).isEqualTo(1); + queryPlan.poll(); + assertThat(queryPlan.size()).isEqualTo(0); + queryPlan.poll(); + assertThat(queryPlan.size()).isEqualTo(0); + } + + @Test + public void should_return_iterator() { + QueryPlan queryPlan = new QueryPlan(node1, node2, node3); + Iterator iterator3 = queryPlan.iterator(); + queryPlan.poll(); + Iterator iterator2 = queryPlan.iterator(); + queryPlan.poll(); + Iterator iterator1 = queryPlan.iterator(); + queryPlan.poll(); + Iterator iterator0 = queryPlan.iterator(); + queryPlan.poll(); + Iterator iterator00 = queryPlan.iterator(); + + assertThat(iterator3).containsExactly(node1, node2, node3); + assertThat(iterator2).containsExactly(node2, node3); + assertThat(iterator1).containsExactly(node3); + assertThat(iterator0).isEmpty(); + assertThat(iterator00).isEmpty(); + } +} From 8daf3cb9fb3a3644c260980f0a7243ed28322512 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 21 Jun 2018 10:49:44 -0500 Subject: [PATCH 496/742] JAVA-1884: Add GenericType.getRawType() --- changelog/README.md | 1 + .../api/core/type/reflect/GenericType.java | 69 +++++++++++++++++++ .../core/type/reflect/GenericTypeTest.java | 39 +++++++++++ 3 files changed, 109 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 7f820578c28..76888938350 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-alpha4 (in progress) +- [improvement] JAVA-1884: Add additional methods from TypeToken to GenericType - [improvement] JAVA-1883: Use custom queue implementation for LBP's query plan - [improvement] JAVA-1890: Add more configuration options to DefaultSslEngineFactory - [bug] JAVA-1895: Rename PreparedStatement.getPrimaryKeyIndices to getPartitionKeyIndices diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index f713ba1060b..ce14e2052bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.shaded.guava.common.reflect.TypeResolver; import com.datastax.oss.driver.shaded.guava.common.reflect.TypeToken; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; @@ -187,6 +188,14 @@ public final boolean isSubtypeOf(@NonNull GenericType type) { return token.isSubtypeOf(type.token); } + /** + * Returns true if this type is known to be an array type, such as {@code int[]}, {@code T[]}, + * {@code []>} etc. + */ + public final boolean isArray() { + return token.isArray(); + } + /** Returns true if this type is one of the nine primitive types (including {@code void}). */ public final boolean isPrimitive() { return token.isPrimitive(); @@ -196,6 +205,7 @@ public final boolean isPrimitive() { * Returns the corresponding wrapper type if this is a primitive type; otherwise returns {@code * this} itself. Idempotent. */ + @NonNull public final GenericType wrap() { if (isPrimitive()) { return new GenericType<>(token.wrap()); @@ -207,6 +217,7 @@ public final GenericType wrap() { * Returns the corresponding primitive type if this is a wrapper type; otherwise returns {@code * this} itself. Idempotent. */ + @NonNull public final GenericType unwrap() { if (Primitives.allWrapperTypes().contains(token.getRawType())) { return new GenericType<>(token.unwrap()); @@ -239,6 +250,64 @@ public final GenericType where( return where(freeVariable, GenericType.of(actualType)); } + /** + * Returns the array component type if this type represents an array ({@code int[]}, {@code T[]}, + * {@code []>} etc.), or else {@code null} is returned. + */ + @Nullable + @SuppressWarnings("unchecked") + public final GenericType getComponentType() { + TypeToken componentTypeToken = token.getComponentType(); + return (componentTypeToken == null) ? null : new GenericType(componentTypeToken); + } + + /** + * Returns the raw type of {@code T}. Formally speaking, if {@code T} is returned by {@link + * java.lang.reflect.Method#getGenericReturnType}, the raw type is what's returned by {@link + * java.lang.reflect.Method#getReturnType} of the same method object. Specifically: + * + *

            + *
          • If {@code T} is a {@code Class} itself, {@code T} itself is returned. + *
          • If {@code T} is a parameterized type, the raw type of the parameterized type is returned. + *
          • If {@code T} is an array type , the returned type is the corresponding array class. For + * example: {@code List[] => List[]}. + *
          • If {@code T} is a type variable or a wildcard type, the raw type of the first upper bound + * is returned. For example: {@code => Foo}. + *
          + */ + @NonNull + public Class getRawType() { + return token.getRawType(); + } + + /** + * Returns the generic form of {@code superclass}. For example, if this is {@code + * ArrayList}, {@code Iterable} is returned given the input {@code + * Iterable.class}. + */ + @SuppressWarnings("unchecked") + @NonNull + public final GenericType getSupertype(@NonNull Class superclass) { + return new GenericType(token.getSupertype(superclass)); + } + + /** + * Returns subtype of {@code this} with {@code subclass} as the raw class. For example, if this is + * {@code Iterable} and {@code subclass} is {@code List}, {@code List} is + * returned. + */ + @SuppressWarnings("unchecked") + @NonNull + public final GenericType getSubtype(@NonNull Class subclass) { + return new GenericType(token.getSubtype(subclass)); + } + + /** Returns the represented type. */ + @NonNull + public final Type getType() { + return token.getType(); + } + /** * This method is for internal use, DO NOT use it from client code. * diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java index ff312342266..ac66cfca01a 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/type/reflect/GenericTypeTest.java @@ -85,10 +85,49 @@ public void should_unwrap_wrapper_type() { assertThat(stringType.unwrap()).isSameAs(stringType); } + @Test + public void should_return_raw_type() { + assertThat(GenericType.INTEGER.getRawType()).isEqualTo(Integer.class); + assertThat(GenericType.listOf(Integer.class).getRawType()).isEqualTo(List.class); + } + + @Test + public void should_return_super_type() { + GenericType> expectedType = iterableOf(GenericType.INTEGER); + assertThat(GenericType.listOf(Integer.class).getSupertype(Iterable.class)) + .isEqualTo(expectedType); + } + + @Test + public void should_return_sub_type() { + GenericType> superType = iterableOf(GenericType.INTEGER); + assertThat(superType.getSubtype(List.class)).isEqualTo(GenericType.listOf(GenericType.INTEGER)); + } + + @Test + public void should_return_type() { + assertThat(GenericType.INTEGER.getType()).isEqualTo(Integer.class); + } + + @Test + public void should_return_component_type() { + assertThat(GenericType.of(Integer[].class).getComponentType()).isEqualTo(GenericType.INTEGER); + } + + @Test + public void should_report_is_array() { + assertThat(GenericType.INTEGER.isArray()).isFalse(); + assertThat(GenericType.of(Integer[].class).isArray()).isTrue(); + } + private GenericType> optionalOf(GenericType elementType) { return new GenericType>() {}.where(new GenericTypeParameter() {}, elementType); } + private GenericType> iterableOf(GenericType elementType) { + return new GenericType>() {}.where(new GenericTypeParameter() {}, elementType); + } + private GenericType> mapOf(Class keyClass, Class valueClass) { return new GenericType>() {}.where(new GenericTypeParameter() {}, keyClass) .where(new GenericTypeParameter() {}, valueClass); From c5110c2d18babfb0a5594ef6d63219500bed1eae Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Tue, 26 Jun 2018 14:20:08 -0500 Subject: [PATCH 497/742] JAVA-1847: Node level request tracking (#1028) * JAVA-1847 Node level request tracking --- changelog/README.md | 1 + .../api/core/tracker/RequestTracker.java | 34 ++++++- .../core/cql/CqlRequestHandlerBase.java | 30 +++++- .../core/tracker/NoopRequestTracker.java | 19 ++++ .../internal/core/tracker/RequestLogger.java | 19 ++++ .../api/core/tracker/RequestLoggerIT.java | 99 +++++++++++++++++-- .../tracker/RequestNodeLoggerExample.java | 97 ++++++++++++++++++ 7 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java diff --git a/changelog/README.md b/changelog/README.md index 76888938350..1e2622e1ac2 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -51,6 +51,7 @@ - [improvement] JAVA-1772: Revisit multi-response callbacks - [new feature] JAVA-1537: Add remaining socket options - [bug] JAVA-1756: Propagate custom payload when preparing a statement +- [improvement] JAVA-1847: Add per-node request tracking ### 4.0.0-alpha3 diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java index 256e7e9edaa..72f7e763013 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** Tracks request execution for a session. */ public interface RequestTracker extends AutoCloseable { @@ -52,5 +53,36 @@ void onError( @NonNull Throwable error, long latencyNanos, @NonNull DriverConfigProfile configProfile, - Node node); + @Nullable Node node); + + /** + * Invoked each time a request fails at the node level. Similar to {@link #onError(Request, + * Throwable, long, DriverConfigProfile, Node)} but at a per node level. + * + * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, + * GenericType) session.execute} call until the error is propagated to the client). + * @param configProfile the configuration profile that this request was executed with. + * @param node the node that returned the error response. + */ + void onNodeError( + @NonNull Request request, + @NonNull Throwable error, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node); + + /** + * Invoked each time a request succeeds at the node level. Similar to {@link #onSuccess(Request, + * long, DriverConfigProfile, Node)} but at per Node level. + * + * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, + * GenericType) session.execute} call until the result is made available to the client). + * @param configProfile the configuration profile that this request was executed with. + * @param node the node that returned the successful response. + */ + void onNodeSuccess( + @NonNull Request request, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index edf594f0f13..fba38cdf7cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -299,14 +299,21 @@ private void setFinalResult( if (result.complete(resultSet)) { cancelScheduledTasks(); throttler.signalSuccess(this); - long latencyNanos = System.nanoTime() - startTimeNanos; - context.requestTracker().onSuccess(statement, latencyNanos, configProfile, callback.node); + long now = System.nanoTime(); + long totalLatencyNanos = now - startTimeNanos; + long nodeLatencyNanos = now - callback.start; + context + .requestTracker() + .onNodeSuccess(statement, nodeLatencyNanos, configProfile, callback.node); + context + .requestTracker() + .onSuccess(statement, totalLatencyNanos, configProfile, callback.node); session .getMetricUpdater() .updateTimer( DefaultSessionMetric.CQL_REQUESTS, configProfile.getName(), - latencyNanos, + totalLatencyNanos, TimeUnit.NANOSECONDS); } } catch (Throwable error) { @@ -417,6 +424,7 @@ public void operationComplete(Future future) throws Exception { Throwable error = future.cause(); if (error instanceof EncoderException && error.getCause() instanceof FrameTooLongException) { + trackNodeError(node, error.getCause()); setFinalError(error.getCause(), node, execution); } else { LOG.trace( @@ -425,6 +433,7 @@ public void operationComplete(Future future) throws Exception { channel, error); recordError(node, error); + trackNodeError(node, error); ((DefaultNode) node) .getMetricUpdater() .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS, configProfile.getName()); @@ -519,10 +528,12 @@ public void onResponse(Frame responseFrame) { LOG.trace("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); } else { + trackNodeError(node, new IllegalStateException("Unexpected response " + responseMessage)); setFinalError( new IllegalStateException("Unexpected response " + responseMessage), node, execution); } } catch (Throwable t) { + trackNodeError(node, t); setFinalError(t, node, execution); } } @@ -565,15 +576,18 @@ private void processErrorResponse(Error errorMessage) { || prepareError instanceof FunctionFailureException || prepareError instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error on reprepare, rethrowing", logPrefix); + trackNodeError(node, prepareError); setFinalError(prepareError, node, execution); return null; } } } else if (exception instanceof RequestThrottlingException) { + trackNodeError(node, exception); setFinalError(exception, node, execution); return null; } recordError(node, exception); + trackNodeError(node, exception); LOG.trace("[{}] Reprepare failed, trying next node", logPrefix); sendRequest(null, execution, retryCount, false); } else { @@ -589,12 +603,14 @@ private void processErrorResponse(Error errorMessage) { if (error instanceof BootstrappingException) { LOG.trace("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); + trackNodeError(node, error); sendRequest(null, execution, retryCount, false); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error, rethrowing", logPrefix); metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS, configProfile.getName()); + trackNodeError(node, error); setFinalError(error, node, execution); } else { RetryDecision decision; @@ -668,13 +684,16 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { switch (decision) { case RETRY_SAME: recordError(node, error); + trackNodeError(node, error); sendRequest(node, execution, retryCount + 1, false); break; case RETRY_NEXT: recordError(node, error); + trackNodeError(node, error); sendRequest(null, execution, retryCount + 1, false); break; case RETHROW: + trackNodeError(node, error); setFinalError(error, node, execution); break; case IGNORE: @@ -737,6 +756,11 @@ public void cancel() { } } + private void trackNodeError(Node node, Throwable error) { + long latencyNanos = System.nanoTime() - this.start; + context.requestTracker().onNodeError(statement, error, latencyNanos, configProfile, node); + } + @Override public String toString() { return logPrefix; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java index fc51074feaf..bdeed5c4e43 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java @@ -49,6 +49,25 @@ public void onError( // nothing to do } + @Override + public void onNodeError( + @NonNull Request request, + @NonNull Throwable error, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + // nothing to do + } + + @Override + public void onNodeSuccess( + @NonNull Request request, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + // nothing to do + } + @Override public void close() throws Exception { // nothing to do diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index 90994c2e81f..8752acb3e41 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -110,6 +110,25 @@ public void onError( showStackTraces); } + @Override + public void onNodeError( + @NonNull Request request, + @NonNull Throwable error, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + // Nothing to do + } + + @Override + public void onNodeSuccess( + @NonNull Request request, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + // Nothing to do + } + @Override public void close() throws Exception { // nothing to do diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 2ae165d65a4..4ba8ac65f33 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -15,9 +15,7 @@ */ package com.datastax.oss.driver.api.core.tracker; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -30,6 +28,7 @@ import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -37,6 +36,8 @@ import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.codec.ConsistencyLevel; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; @@ -48,7 +49,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.internal.verification.VerificationModeFactory; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.verification.Timeout; import org.slf4j.LoggerFactory; @Category(ParallelizableTests.class) @@ -58,10 +61,10 @@ public class RequestLoggerIT { private static final String QUERY = "SELECT release_version FROM system.local"; @Rule - public SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + public SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(3)); @Rule - public SessionRule sessionRule = + public SessionRule sessionRuleRequest = SessionRule.builder(simulacronRule) .withOptions( "advanced.request-tracker.class = com.datastax.oss.driver.internal.core.tracker.RequestLogger", @@ -73,6 +76,28 @@ public class RequestLoggerIT { "advanced.request-tracker.logs.max-value-length = 50", "advanced.request-tracker.logs.max-values = 50", "advanced.request-tracker.logs.show-stack-traces = true", + "advanced.request-tracker.logs.node-level = false", + "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", + "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", + "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", + "profiles.no-logs.advanced.request-tracker.logs.error.enabled = false", + "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") + .build(); + + @Rule + public SessionRule sessionRuleNode = + SessionRule.builder(simulacronRule) + .withOptions( + "basic.request.consistency = ONE", + "advanced.request-tracker.class = com.datastax.oss.driver.api.core.tracker.RequestNodeLoggerExample", + "advanced.request-tracker.logs.success.enabled = true", + "advanced.request-tracker.logs.slow.enabled = true", + "advanced.request-tracker.logs.error.enabled = true", + "advanced.request-tracker.logs.max-query-length = 500", + "advanced.request-tracker.logs.show-values = true", + "advanced.request-tracker.logs.max-value-length = 50", + "advanced.request-tracker.logs.max-values = 50", + "advanced.request-tracker.logs.show-stack-traces = true", "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", @@ -105,7 +130,7 @@ public void should_log_successful_request() { simulacronRule.cluster().prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); // When - sessionRule.session().execute(QUERY); + sessionRuleRequest.session().execute(QUERY); // Then Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -120,7 +145,7 @@ public void should_log_failed_request_with_stack_trace() { // When try { - sessionRule.session().execute(QUERY); + sessionRuleRequest.session().execute(QUERY); fail("Expected a ServerError"); } catch (ServerError error) { // expected @@ -142,7 +167,7 @@ public void should_log_failed_request_without_stack_trace() { // When try { - sessionRule + sessionRuleRequest .session() .execute(SimpleStatement.builder(QUERY).withConfigProfileName("no-traces").build()); fail("Expected a ServerError"); @@ -164,7 +189,7 @@ public void should_log_slow_request() { simulacronRule.cluster().prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); // When - sessionRule + sessionRuleRequest .session() .execute(SimpleStatement.builder(QUERY).withConfigProfileName("low-threshold").build()); @@ -180,7 +205,7 @@ public void should_not_log_when_disabled() throws InterruptedException { simulacronRule.cluster().prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); // When - sessionRule + sessionRuleRequest .session() .execute(SimpleStatement.builder(QUERY).withConfigProfileName("no-logs").build()); @@ -189,4 +214,58 @@ public void should_not_log_when_disabled() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(500); Mockito.verify(appender, never()).doAppend(any(LoggingEvent.class)); } + + @Test + public void should_log_failed_nodes_on_successful_request() { + // Given + simulacronRule + .cluster() + .node(0) + .prime(when(QUERY).then(unavailable(ConsistencyLevel.ONE, 1, 3))); + simulacronRule + .cluster() + .node(1) + .prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + simulacronRule + .cluster() + .node(2) + .prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + + // When + ResultSet set = sessionRuleNode.session().execute(QUERY); + + // Then + Mockito.verify(appender, new Timeout(500, VerificationModeFactory.times(3))) + .doAppend(loggingEventCaptor.capture()); + List events = loggingEventCaptor.getAllValues(); + assertThat(events.get(0).getFormattedMessage()).contains("Error", "[0 values]", QUERY); + assertThat(events.get(1).getFormattedMessage()).contains("Success", "[0 values]", QUERY); + assertThat(events.get(2).getFormattedMessage()).contains("Success", "[0 values]", QUERY); + } + + @Test + public void should_log_successful_nodes_on_successful_request() { + simulacronRule + .cluster() + .node(0) + .prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + simulacronRule + .cluster() + .node(1) + .prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + simulacronRule + .cluster() + .node(2) + .prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + + // When + ResultSet set = sessionRuleNode.session().execute(QUERY); + + // Then + Mockito.verify(appender, new Timeout(500, VerificationModeFactory.times(2))) + .doAppend(loggingEventCaptor.capture()); + List events = loggingEventCaptor.getAllValues(); + assertThat(events.get(0).getFormattedMessage()).contains("Success", "[0 values]", QUERY); + assertThat(events.get(1).getFormattedMessage()).contains("Success", "[0 values]", QUERY); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java new file mode 100644 index 00000000000..24b30a7e8c4 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java @@ -0,0 +1,97 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.tracker; + +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.tracker.RequestLogFormatter; +import com.datastax.oss.driver.internal.core.tracker.RequestLogger; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class RequestNodeLoggerExample extends RequestLogger { + + public RequestNodeLoggerExample(DriverContext context) { + super(context.sessionName(), new RequestLogFormatter(context)); + } + + @Override + public void onNodeError( + @NonNull Request request, + @NonNull Throwable error, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + if (!configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED)) { + return; + } + + int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); + boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); + int maxValues = + showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + int maxValueLength = + showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + boolean showStackTraces = + configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES); + + logError( + request, + error, + latencyNanos, + node, + maxQueryLength, + showValues, + maxValues, + maxValueLength, + showStackTraces); + } + + @Override + public void onNodeSuccess( + @NonNull Request request, + long latencyNanos, + @NonNull DriverConfigProfile configProfile, + @NonNull Node node) { + boolean successEnabled = + configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED); + boolean slowEnabled = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED); + if (!successEnabled && !slowEnabled) { + return; + } + + long slowThresholdNanos = + configProfile.isDefined(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD) + ? configProfile.getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD).toNanos() + : Long.MAX_VALUE; + boolean isSlow = latencyNanos > slowThresholdNanos; + if ((isSlow && !slowEnabled) || (!isSlow && !successEnabled)) { + return; + } + + int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); + boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); + int maxValues = + showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + int maxValueLength = + showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + + logSuccess( + request, latencyNanos, isSlow, node, maxQueryLength, showValues, maxValues, maxValueLength); + } +} From f6e49c8ed36f80ce430b15d5efa882c7600d297a Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 26 Jun 2018 15:02:06 -0500 Subject: [PATCH 498/742] Make DataTypeClassNameParser public These are part of the internal package structure so can be made public. --- .../schema/parsing/DataTypeClassNameCompositeParser.java | 4 ++-- .../core/metadata/schema/parsing/DataTypeClassNameParser.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java index fed69609301..155785e96d0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java @@ -27,9 +27,9 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class DataTypeClassNameCompositeParser extends DataTypeClassNameParser { +public class DataTypeClassNameCompositeParser extends DataTypeClassNameParser { - ParseResult parseWithComposite( + public ParseResult parseWithComposite( String className, CqlIdentifier keyspaceId, Map userTypes, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java index 23dc5b89787..95696ea967a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java @@ -50,7 +50,7 @@ * never in a critical path. */ @ThreadSafe -class DataTypeClassNameParser implements DataTypeParser { +public class DataTypeClassNameParser implements DataTypeParser { private static final Logger LOG = LoggerFactory.getLogger(DataTypeClassNameParser.class); From 416556023365a3fd679bc938a86b8785b66175a5 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 26 Jun 2018 15:04:08 -0500 Subject: [PATCH 499/742] Use openjdk8 instead of oraclejdk8 --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index caecf866b49..84b090fd579 100644 --- a/build.yaml +++ b/build.yaml @@ -1,5 +1,5 @@ java: - - oraclejdk8 + - openjdk8 os: - ubuntu/trusty64/m3.large cassandra: From 88feb904ace9068beeac2d392f0a91562fa8b81d Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 26 Jun 2018 13:47:43 -0700 Subject: [PATCH 500/742] Rename current version to 4.0.0-beta1 --- changelog/README.md | 2 +- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 2 +- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 1e2622e1ac2..e74d52b69bd 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-alpha4 (in progress) +### 4.0.0-beta1 (in progress) - [improvement] JAVA-1884: Add additional methods from TypeToken to GenericType - [improvement] JAVA-1883: Use custom queue implementation for LBP's query plan diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 1ef383ac677..688dc24a1b3 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -24,7 +24,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 4a43832f6d9..118876848ff 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -23,7 +23,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT java-driver-core diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 646a834b80d..82d75ac9c7a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index b78ae4a9d74..550f070762d 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT pom DataStax Java driver for Apache Cassandra(R) diff --git a/query-builder/pom.xml b/query-builder/pom.xml index ddc07ca6d97..ae1c6890cfd 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -23,7 +23,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 2929a3d8364..19b9ac19bcc 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-alpha4-SNAPSHOT + 4.0.0-beta1-SNAPSHOT java-driver-test-infra From 4429a8a4f46d0ab626e1f807000cca722a66b24e Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Jun 2018 10:21:00 -0700 Subject: [PATCH 501/742] Exclude internal packages from generated API docs --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 550f070762d..4c4b000e525 100644 --- a/pom.xml +++ b/pom.xml @@ -430,6 +430,7 @@ limitations under the License.]]> false true all,-missing + com.datastax.oss.driver.internal -preventleak From 63706fa87a2342867c78076db9d328130cd53f89 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Jun 2018 15:47:03 -0700 Subject: [PATCH 502/742] JAVA-1763: Generate a binary tarball as part of the build process --- LICENSE | 202 +++++++++++++++++++ changelog/README.md | 1 + distribution/pom.xml | 160 +++++++++++++++ distribution/src/assembly/binary-tarball.xml | 133 ++++++++++++ pom.xml | 7 +- 5 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 distribution/pom.xml create mode 100644 distribution/src/assembly/binary-tarball.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/changelog/README.md b/changelog/README.md index e74d52b69bd..814be4c884c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1763: Generate a binary tarball as part of the build process - [improvement] JAVA-1884: Add additional methods from TypeToken to GenericType - [improvement] JAVA-1883: Use custom queue implementation for LBP's query plan - [improvement] JAVA-1890: Add more configuration options to DefaultSslEngineFactory diff --git a/distribution/pom.xml b/distribution/pom.xml new file mode 100644 index 00000000000..8b3af2f6bac --- /dev/null +++ b/distribution/pom.xml @@ -0,0 +1,160 @@ + + + 4.0.0 + + + com.datastax.oss + java-driver-parent + 4.0.0-beta1-SNAPSHOT + + + java-driver-distribution + + jar + + DataStax Java driver for Apache Cassandra(R) - binary distribution + + + + + ${project.groupId} + java-driver-core + ${project.version} + + + ${project.groupId} + java-driver-query-builder + ${project.version} + + + + com.github.stephenc.jcip + jcip-annotations + + + com.github.spotbugs + spotbugs-annotations + + + + + datastax-java-driver-${project.version} + + + maven-jar-plugin + + + + default-jar + none + + + + + maven-source-plugin + + true + + + + maven-install-plugin + + true + + + + maven-deploy-plugin + + true + + + + + + + + release + + + + maven-javadoc-plugin + + + dependencies-javadoc + + process-classes + + jar + + + true + + com.github.stephenc.jcip:jcip-annotations + com.github.spotbugs:spotbugs-annotations + + DataStax Java driver for Apache Cassandra® ${project.version} API + + DataStax Java driver for Apache Cassandra(R) ${project.version} API + + + + + + + maven-assembly-plugin + + + assemble-binary-tarball + package + + single + + + + + false + + src/assembly/binary-tarball.xml + + posix + + + + org.sonatype.plugins + nexus-staging-maven-plugin + + true + + + + + + + \ No newline at end of file diff --git a/distribution/src/assembly/binary-tarball.xml b/distribution/src/assembly/binary-tarball.xml new file mode 100644 index 00000000000..982b38a9850 --- /dev/null +++ b/distribution/src/assembly/binary-tarball.xml @@ -0,0 +1,133 @@ + + + binary-tarball + + tar.gz + + true + + + + + + true + + com.datastax.oss:java-driver-core + + + lib/core + false + + + lib/core + + + com.datastax.oss:java-driver-query-builder + + true + + + + + + + + true + + com.datastax.oss:java-driver-query-builder + + + lib/query-builder + false + + + + com.datastax.oss:java-driver-core + + com.datastax.oss:java-driver-shaded-guava + com.github.stephenc.jcip:jcip-annotations + + + true + + + + + + + + true + + com.datastax.oss:java-driver-core + com.datastax.oss:java-driver-query-builder + + + false + sources + ${module.artifactId}-${module.version}-src.zip + + src + + * + + + + + + + + + + target/apidocs + apidocs + + + + .. + . + + README* + LICENSE* + + + + + ../changelog + + + + ../faq + + + + ../manual + + + + ../upgrade_guide + + + + + diff --git a/pom.xml b/pom.xml index 4c4b000e525..b79c12cfe29 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ query-builder test-infra integration-tests + distribution @@ -230,6 +231,10 @@ maven-shade-plugin 3.0.0 + + maven-assembly-plugin + 3.1.0 + net.alchim31.maven @@ -249,7 +254,7 @@ maven-javadoc-plugin - 3.0.0 + 3.0.1 maven-jar-plugin From 56db54b553249bd3643118da6a1d246302a37a7a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 26 Jun 2018 16:59:35 -0700 Subject: [PATCH 503/742] JAVA-1877: Use a separate reconnection schedule for the control connection --- changelog/README.md | 1 + .../core/connection/ReconnectionPolicy.java | 36 ++++++++++++++----- .../ConstantReconnectionPolicy.java | 17 ++++++++- .../ExponentialReconnectionPolicy.java | 30 +++++++++++----- .../core/control/ControlConnection.java | 8 ++++- .../internal/core/pool/ChannelPool.java | 4 ++- .../core/util/concurrent/Reconnection.java | 19 +++++----- .../core/control/ControlConnectionTest.java | 6 ++-- .../control/ControlConnectionTestBase.java | 3 +- .../core/pool/ChannelPoolTestBase.java | 4 ++- .../util/concurrent/ReconnectionTest.java | 6 +--- 11 files changed, 96 insertions(+), 38 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 814be4c884c..833dce268e6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1877: Use a separate reconnection schedule for the control connection - [improvement] JAVA-1763: Generate a binary tarball as part of the build process - [improvement] JAVA-1884: Add additional methods from TypeToken to GenericType - [improvement] JAVA-1883: Use custom queue implementation for LBP's query plan diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java index b2255100e5a..fedb0ed1ebc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java @@ -15,23 +15,43 @@ */ package com.datastax.oss.driver.api.core.connection; +import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; /** - * Decides how often the driver tries to re-establish lost connections to a node. + * Decides how often the driver tries to re-establish lost connections. * - *

          Each time a connection to a node is lost, a {@link #newSchedule() new schedule} instance gets - * created. Then {@link ReconnectionSchedule#nextDelay()} will be called each time the driver needs - * to schedule the next connection attempt. When the node is back to its required number of - * connections, the schedule will be reset (that is, the next failure will create a fresh schedule - * instance). + *

          When a reconnection starts, the driver invokes this policy to create a {@link + * ReconnectionSchedule ReconnectionSchedule} instance. That schedule's {@link + * ReconnectionSchedule#nextDelay() nextDelay()} method will get called each time the driver needs + * to program the next connection attempt. When the reconnection succeeds, the schedule is + * discarded; if the connection is lost again later, the next reconnection attempt will query the + * policy again to obtain a new schedule. + * + *

          There are two types of reconnection: + * + *

            + *
          • {@linkplain #newNodeSchedule(Node) for regular node connections}: when the connection pool + * for a node does not have its configured number of connections (see {@code + * advanced.connection.pool.*.size} in the configuration), a reconnection starts for that + * pool. + *
          • {@linkplain #newControlConnectionSchedule() for the control connection}: when the control + * node goes down, a reconnection starts to find another node to replace it. + *
          + * + * This interface defines separate methods for those two cases, but implementations are free to + * delegate to the same method internally if the same type of schedule can be used. */ public interface ReconnectionPolicy extends AutoCloseable { - /** Creates a new {@linkplain ReconnectionSchedule schedule}. */ + /** Creates a new schedule for the given node. */ + @NonNull + ReconnectionSchedule newNodeSchedule(@NonNull Node node); + + /** Creates a new schedule for the control connection. */ @NonNull - ReconnectionSchedule newSchedule(); + ReconnectionSchedule newControlConnectionSchedule(); /** Called when the cluster that this policy is associated with closes. */ @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java index e2a181fa839..c3f954a897e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java @@ -19,16 +19,23 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** A reconnection policy that waits a constant time between each reconnection attempt. */ public class ConstantReconnectionPolicy implements ReconnectionPolicy { + private static final Logger LOG = LoggerFactory.getLogger(ConstantReconnectionPolicy.class); + + private final String logPrefix; private final ReconnectionSchedule schedule; /** Builds a new instance. */ public ConstantReconnectionPolicy(DriverContext context) { + this.logPrefix = context.sessionName(); DriverConfigProfile config = context.config().getDefaultProfile(); Duration delay = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY); if (delay.isNegative()) { @@ -44,7 +51,15 @@ public ConstantReconnectionPolicy(DriverContext context) { @NonNull @Override - public ReconnectionSchedule newSchedule() { + public ReconnectionSchedule newNodeSchedule(@NonNull Node node) { + LOG.debug("[{}] Creating new schedule for {}", logPrefix, node); + return schedule; + } + + @NonNull + @Override + public ReconnectionSchedule newControlConnectionSchedule() { + LOG.debug("[{}] Creating new schedule for the control connection", logPrefix); return schedule; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index 61969e5e119..089f74dbd45 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -19,24 +19,36 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import net.jcip.annotations.ThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A reconnection policy that waits exponentially longer between each reconnection attempt (but * keeps a constant delay once a maximum delay is reached). + * + *

          It uses the same schedule implementation for individual nodes or the control connection: + * reconnection attempt {@code i} will be tried {@code Math.min(2^(i-1) * getBaseDelayMs(), + * getMaxDelayMs())} milliseconds after the previous one. */ @ThreadSafe public class ExponentialReconnectionPolicy implements ReconnectionPolicy { + private static final Logger LOG = LoggerFactory.getLogger(ExponentialReconnectionPolicy.class); + + private final String logPrefix; private final long baseDelayMs; private final long maxDelayMs; private final long maxAttempts; /** Builds a new instance. */ public ExponentialReconnectionPolicy(DriverContext context) { + this.logPrefix = context.sessionName(); + DriverConfigProfile config = context.config().getDefaultProfile(); this.baseDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY).toMillis(); @@ -84,17 +96,17 @@ public long getMaxDelayMs() { return maxDelayMs; } - /** - * A new schedule that used an exponentially growing delay between reconnection attempts. - * - *

          For this schedule, reconnection attempt {@code i} will be tried {@code Math.min(2^(i-1) * - * getBaseDelayMs(), getMaxDelayMs())} milliseconds after the previous one. - * - * @return the newly created schedule. - */ @NonNull @Override - public ReconnectionSchedule newSchedule() { + public ReconnectionSchedule newNodeSchedule(@NonNull Node node) { + LOG.debug("[{}] Creating new schedule for {}", logPrefix, node); + return new ExponentialSchedule(); + } + + @NonNull + @Override + public ReconnectionSchedule newControlConnectionSchedule() { + LOG.debug("[{}] Creating new schedule for the control connection", logPrefix); return new ExponentialSchedule(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index fd3431e35bd..ec97c3d9040 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -224,8 +225,13 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; + ReconnectionPolicy reconnectionPolicy = context.reconnectionPolicy(); this.reconnection = - new Reconnection(logPrefix, adminExecutor, context.reconnectionPolicy(), this::reconnect); + new Reconnection( + logPrefix, + adminExecutor, + reconnectionPolicy::newControlConnectionSchedule, + this::reconnect); // In "reconnect-on-init" mode, handle cancellation of the initFuture by user code CompletableFutures.whenCancelled( this.initFuture, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index a1bd29913af..bc1423b45d8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; @@ -234,11 +235,12 @@ private SingleThreaded( this.wantedCount = getConfiguredSize(distance); this.channelFactory = context.channelFactory(); this.eventBus = context.eventBus(); + ReconnectionPolicy reconnectionPolicy = context.reconnectionPolicy(); this.reconnection = new Reconnection( logPrefix, adminExecutor, - context.reconnectionPolicy(), + () -> reconnectionPolicy.newNodeSchedule(node), this::addMissingChannels, () -> eventBus.fire(ChannelEvent.reconnectionStarted(node)), () -> eventBus.fire(ChannelEvent.reconnectionStopped(node))); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index 9db8355966a..eaf2a0eba75 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util.concurrent; -import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; import com.datastax.oss.driver.internal.core.util.Loggers; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; @@ -25,6 +25,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import net.jcip.annotations.NotThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,13 +51,13 @@ private enum State { private final String logPrefix; private final EventExecutor executor; - private final ReconnectionPolicy reconnectionPolicy; + private final Supplier scheduleSupplier; private final Callable> reconnectionTask; private final Runnable onStart; private final Runnable onStop; private State state = State.STOPPED; - private ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; + private ReconnectionSchedule reconnectionSchedule; private ScheduledFuture> nextAttempt; /** @@ -66,13 +67,13 @@ private enum State { public Reconnection( String logPrefix, EventExecutor executor, - ReconnectionPolicy reconnectionPolicy, + Supplier scheduleSupplier, Callable> reconnectionTask, Runnable onStart, Runnable onStop) { this.logPrefix = logPrefix; this.executor = executor; - this.reconnectionPolicy = reconnectionPolicy; + this.scheduleSupplier = scheduleSupplier; this.reconnectionTask = reconnectionTask; this.onStart = onStart; this.onStop = onStop; @@ -81,9 +82,9 @@ public Reconnection( public Reconnection( String logPrefix, EventExecutor executor, - ReconnectionPolicy reconnectionPolicy, + Supplier scheduleSupplier, Callable> reconnectionTask) { - this(logPrefix, executor, reconnectionPolicy, reconnectionTask, () -> {}, () -> {}); + this(logPrefix, executor, scheduleSupplier, reconnectionTask, () -> {}, () -> {}); } /** @@ -108,7 +109,7 @@ public void start() { state = State.ATTEMPT_IN_PROGRESS; break; case STOPPED: - reconnectionSchedule = reconnectionPolicy.newSchedule(); + reconnectionSchedule = scheduleSupplier.get(); onStart.run(); scheduleNextAttempt(); break; @@ -180,7 +181,7 @@ private void scheduleNextAttempt() { assert executor.inEventLoop(); state = State.SCHEDULED; if (reconnectionSchedule == null) { // happens if reconnectNow() while we were stopped - reconnectionSchedule = reconnectionPolicy.newSchedule(); + reconnectionSchedule = scheduleSupplier.get(); } Duration nextInterval = reconnectionSchedule.nextDelay(); LOG.debug("[{}] Scheduling next reconnection in {}", logPrefix, nextInterval); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 35aa42a3ee8..c47ebc36e11 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -16,9 +16,11 @@ package com.datastax.oss.driver.internal.core.control; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -105,7 +107,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); // each attempt tries all nodes, so there is no reconnection - Mockito.verify(reconnectionPolicy, never()).newSchedule(); + Mockito.verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); factoryHelper.verifyNoMoreCalls(); } @@ -130,7 +132,7 @@ public void should_fail_to_init_if_all_contact_points_fail() { Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node2)); // no reconnections at init - Mockito.verify(reconnectionPolicy, never()).newSchedule(); + Mockito.verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index c21443f9d28..a5b4711cdc0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -95,7 +95,8 @@ public void setup() { }); Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + Mockito.when(reconnectionPolicy.newControlConnectionSchedule()) + .thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 2ad941117e0..1aaa3e9e32f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -76,7 +77,8 @@ public void setup() { Mockito.when(context.channelFactory()).thenReturn(channelFactory); Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); + Mockito.when(reconnectionPolicy.newNodeSchedule(any(Node.class))) + .thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index 687bacf6f07..19ff1da3f4b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -19,7 +19,6 @@ import static org.mockito.Mockito.times; import com.datastax.oss.driver.TestDataProviders; -import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -40,7 +39,6 @@ @RunWith(DataProviderRunner.class) public class ReconnectionTest { - @Mock private ReconnectionPolicy reconnectionPolicy; @Mock private ReconnectionSchedule reconnectionSchedule; @Mock private Runnable onStartCallback; @Mock private Runnable onStopCallback; @@ -53,8 +51,6 @@ public class ReconnectionTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(reconnectionPolicy.newSchedule()).thenReturn(reconnectionSchedule); - // Unfortunately Netty does not expose EmbeddedEventLoop, so we have to go through a channel channel = new EmbeddedChannel(); EventExecutor eventExecutor = channel.eventLoop(); @@ -64,7 +60,7 @@ public void setup() { new Reconnection( "test", eventExecutor, - reconnectionPolicy, + () -> reconnectionSchedule, reconnectionTask, onStartCallback, onStopCallback); From 441d9c587ccc0c6cfc1a7c7038c9f636141fa720 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 5 Jul 2018 11:05:52 -0700 Subject: [PATCH 504/742] Improve policy javadocs Include a sample configuration snippet for each implementation. --- .../api/core/tracker/RequestTracker.java | 9 +++++- .../PassThroughAddressTranslator.java | 17 +++++++++- .../core/auth/PlainTextAuthProvider.java | 4 +-- .../ConstantReconnectionPolicy.java | 18 ++++++++++- .../ExponentialReconnectionPolicy.java | 15 +++++++++ .../DefaultLoadBalancingPolicy.java | 17 ++++++++++ .../core/metadata/NoopNodeStateListener.java | 22 ++++++++++++- .../schema/NoopSchemaChangeListener.java | 22 ++++++++++++- .../core/retry/DefaultRetryPolicy.java | 21 +++++++++++-- .../ConcurrencyLimitingRequestThrottler.java | 19 +++++++++++- .../PassThroughRequestThrottler.java | 13 ++++++++ .../RateLimitingRequestThrottler.java | 20 +++++++++++- .../ConstantSpeculativeExecutionPolicy.java | 16 ++++++++-- .../specex/NoSpeculativeExecutionPolicy.java | 17 +++++++++- .../core/ssl/DefaultSslEngineFactory.java | 6 ++-- .../core/time/AtomicTimestampGenerator.java | 18 +++++++++++ .../time/ServerSideTimestampGenerator.java | 13 ++++++++ .../time/ThreadLocalTimestampGenerator.java | 18 +++++++++++ .../core/tracker/NoopRequestTracker.java | 20 ++++++++++++ .../internal/core/tracker/RequestLogger.java | 31 +++++++++++++++++++ 20 files changed, 318 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java index 72f7e763013..d5369325a09 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java @@ -19,11 +19,18 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -/** Tracks request execution for a session. */ +/** + * Tracks request execution for a session. + * + *

          There is exactly one tracker per {@link Session}. It can be provided either via the + * configuration (see {@code reference.conf} in the manual or core driver JAR), or programmatically + * via {@link SessionBuilder#withRequestTracker(RequestTracker)}. + */ public interface RequestTracker extends AutoCloseable { /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/PassThroughAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/PassThroughAddressTranslator.java index 983c63e9492..7628d6a0eda 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/PassThroughAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/PassThroughAddressTranslator.java @@ -21,7 +21,22 @@ import java.net.InetSocketAddress; import net.jcip.annotations.ThreadSafe; -/** An address translator that always returns the same address unchanged. */ +/** + * An address translator that always returns the same address unchanged. + * + *

          To activate this translator, modify the {@code advanced.address-translator} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.address-translator {
          + *     class = PassThroughAddressTranslator
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ @ThreadSafe public class PassThroughAddressTranslator implements AddressTranslator { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java index 1b7d66cd016..aaffba3b31a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java @@ -33,7 +33,7 @@ * A simple authentication provider that supports SASL authentication using the PLAIN mechanism for * version 3 (or above) of the CQL native protocol. * - *

          To activate this provider, an {@code auth-provider} section must be included in the driver + *

          To activate this provider, add an {@code advanced.auth-provider} section in the driver * configuration, for example: * *

          @@ -46,7 +46,7 @@
            * }
            * 
          * - * See the {@code reference.conf} file included with the driver for more information. + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class PlainTextAuthProvider implements AuthProvider { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java index c3f954a897e..93dde13aa0a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java @@ -25,7 +25,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** A reconnection policy that waits a constant time between each reconnection attempt. */ +/** + * A reconnection policy that waits a constant time between each reconnection attempt. + * + *

          To activate this policy, modify the {@code advanced.reconnection-policy} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.reconnection-policy {
          + *     class = ConstantReconnectionPolicy
          + *     base-delay = 1 second
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ public class ConstantReconnectionPolicy implements ReconnectionPolicy { private static final Logger LOG = LoggerFactory.getLogger(ConstantReconnectionPolicy.class); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index 089f74dbd45..43a2611c072 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -34,6 +34,21 @@ *

          It uses the same schedule implementation for individual nodes or the control connection: * reconnection attempt {@code i} will be tried {@code Math.min(2^(i-1) * getBaseDelayMs(), * getMaxDelayMs())} milliseconds after the previous one. + * + *

          To activate this policy, modify the {@code advanced.reconnection-policy} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.reconnection-policy {
          + *     class = ExponentialReconnectionPolicy
          + *     base-delay = 1 second
          + *     max-delay = 60 seconds
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class ExponentialReconnectionPolicy implements ReconnectionPolicy { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 1aa163a54bd..8ea0ee21a12 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -51,6 +51,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * The default load balancing policy implementation. + * + *

          To activate this policy, modify the {@code basic.load-balancing-policy} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   basic.load-balancing-policy {
          + *     class = DefaultLoadBalancingPolicy
          + *     local-datacenter = datacenter1
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ @ThreadSafe public class DefaultLoadBalancingPolicy implements LoadBalancingPolicy { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NoopNodeStateListener.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NoopNodeStateListener.java index ed3c4376c34..2e70d8efb6a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NoopNodeStateListener.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NoopNodeStateListener.java @@ -16,10 +16,30 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.NodeStateListenerBase; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import net.jcip.annotations.ThreadSafe; -/** Default node state listener implementation with empty methods. */ +/** + * Default node state listener implementation with empty methods. + * + *

          To activate this listener, modify the {@code advanced.node-state-listener} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.node-state-listener {
          + *     class = NoopNodeStateListener
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + * + *

          Note that if a listener is specified programmatically with {@link + * SessionBuilder#withNodeStateListener(NodeStateListener)}, the configuration is ignored. + */ @ThreadSafe public class NoopNodeStateListener extends NodeStateListenerBase { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/NoopSchemaChangeListener.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/NoopSchemaChangeListener.java index bd671807afa..2df3935a80f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/NoopSchemaChangeListener.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/NoopSchemaChangeListener.java @@ -16,10 +16,30 @@ package com.datastax.oss.driver.internal.core.metadata.schema; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListenerBase; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import net.jcip.annotations.ThreadSafe; -/** Default schema change listener implementation with empty methods. */ +/** + * Default schema change listener implementation with empty methods. + * + *

          To activate this listener, modify the {@code advanced.schema-change-listener} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.schema-change-listener {
          + *     class = NoopSchemaChangeListener
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + * + *

          Note that if a listener is specified programmatically with {@link + * SessionBuilder#withSchemaChangeListener(SchemaChangeListener)}, the configuration is ignored. + */ @ThreadSafe public class NoopSchemaChangeListener extends SchemaChangeListenerBase { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java index 7f8ffc03a60..f2242676d7a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java @@ -34,9 +34,24 @@ import org.slf4j.LoggerFactory; /** - * The default retry policy. This is a very conservative implementation: it triggers a maximum of - * one retry per request, and only in cases that have a high chance of success (see the method - * javadocs for detailed explanations of each case). + * The default retry policy. + * + *

          This is a very conservative implementation: it triggers a maximum of one retry per request, + * and only in cases that have a high chance of success (see the method javadocs for detailed + * explanations of each case). + * + *

          To activate this policy, modify the {@code advanced.retry-policy} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.retry-policy {
          + *     class = DefaultRetryPolicy
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class DefaultRetryPolicy implements RetryPolicy { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java index 805bc4a02dc..f35730d6af4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -31,7 +31,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** A request throttler that limits the number of concurrent requests. */ +/** + * A request throttler that limits the number of concurrent requests. + * + *

          To activate this throttler, modify the {@code advanced.throttler} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.throttler {
          + *     class = ConcurrencyLimitingRequestThrottler
          + *     max-concurrent-requests = 10000
          + *     max-queue-size = 10000
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ @ThreadSafe public class ConcurrencyLimitingRequestThrottler implements RequestThrottler { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java index e0efc4f12d1..11fa2632a34 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/PassThroughRequestThrottler.java @@ -25,6 +25,19 @@ /** * A request throttler that does not enforce any kind of limitation: requests are always executed * immediately. + * + *

          To activate this throttler, modify the {@code advanced.throttler} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.throttler {
          + *     class = PassThroughRequestThrottler
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class PassThroughRequestThrottler implements RequestThrottler { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java index 10c76d6bd7e..d154d90a17f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java @@ -35,7 +35,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** A request throttler that limits the rate of requests per second. */ +/** + * A request throttler that limits the rate of requests per second. + * + *

          To activate this throttler, modify the {@code advanced.throttler} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.throttler {
          + *     class = RateLimitingRequestThrottler
          + *     max-requests-per-second = 10000
          + *     max-queue-size = 10000
          + *     drain-interval = 10 milliseconds
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ @ThreadSafe public class RateLimitingRequestThrottler implements RequestThrottler { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java index 86088384960..829e7c552e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -30,8 +30,20 @@ * A policy that schedules a configurable number of speculative executions, separated by a fixed * delay. * - *

          See the (commented) sample configuration in {@code reference.conf} for detailed explanations - * about each option. + *

          To activate this policy, modify the {@code advanced.speculative-execution-policy} section in + * the driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.speculative-execution-policy {
          + *     class = ConstantSpeculativeExecutionPolicy
          + *     max-executions = 3
          + *     delay = 100 milliseconds
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/NoSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/NoSpeculativeExecutionPolicy.java index 09897945f97..b758e206c3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/NoSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/NoSpeculativeExecutionPolicy.java @@ -24,7 +24,22 @@ import edu.umd.cs.findbugs.annotations.Nullable; import net.jcip.annotations.ThreadSafe; -/** A policy that never triggers speculative executions. */ +/** + * A policy that never triggers speculative executions. + * + *

          To activate this policy, modify the {@code advanced.speculative-execution-policy} section in + * the driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.speculative-execution-policy {
          + *     class = NoSpeculativeExecutionPolicy
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + */ @ThreadSafe public class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 9ab6fbf4d42..271dd158bbc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -38,7 +38,7 @@ /** * Default SSL implementation. * - *

          To activate this class, an {@code ssl-engine-factory} section must be included in the driver + *

          To activate this class, add an {@code advanced.ssl-engine-factory} section in the driver * configuration, for example: * *

          @@ -48,14 +48,14 @@
            *     cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
            *     hostname-validation = false
            *     truststore-path = /path/to/client.truststore
          - *     truststore-psasword = password123
          + *     truststore-password = password123
            *     keystore-path = /path/to/client.keystore
            *     keystore-password = password123
            *   }
            * }
            * 
          * - * See the {@code reference.conf} file included with the driver for more information. + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class DefaultSslEngineFactory implements SslEngineFactory { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGenerator.java index 72b41aee619..28bd5fdf2e4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGenerator.java @@ -24,6 +24,24 @@ /** * A timestamp generator that guarantees monotonically increasing timestamps across all client * threads, and logs warnings when timestamps drift in the future. + * + *

          To activate this generator, modify the {@code advanced.timestamp-generator} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.timestamp-generator {
          + *     class = AtomicTimestampGenerator
          + *     drift-warning {
          + *       threshold = 1 second
          + *       interval = 10 seconds
          + *     }
          + *     force-java-clock = false
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class AtomicTimestampGenerator extends MonotonicTimestampGenerator { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/ServerSideTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/ServerSideTimestampGenerator.java index 3890a0253a5..1e9f6c52eeb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/ServerSideTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/ServerSideTimestampGenerator.java @@ -22,6 +22,19 @@ /** * A timestamp generator that never sends a timestamp with any query, therefore letting Cassandra * assign a server-side timestamp. + * + *

          To activate this generator, modify the {@code advanced.timestamp-generator} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.timestamp-generator {
          + *     class = ServerSideTimestampGenerator
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class ServerSideTimestampGenerator implements TimestampGenerator { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGenerator.java index d1aee15b61c..511a3a2e395 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGenerator.java @@ -26,6 +26,24 @@ *

          Beware that there is a risk of timestamp collision with this generator when accessed by more * than one thread at a time; only use it when threads are not in direct competition for timestamp * ties (i.e., they are executing independent statements). + * + *

          To activate this generator, modify the {@code advanced.timestamp-generator} section in the + * driver configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.timestamp-generator {
          + *     class = ThreadLocalTimestampGenerator
          + *     drift-warning {
          + *       threshold = 1 second
          + *       interval = 10 seconds
          + *     }
          + *     force-java-clock = false
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. */ @ThreadSafe public class ThreadLocalTimestampGenerator extends MonotonicTimestampGenerator { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java index bdeed5c4e43..faccd45a0eb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java @@ -19,10 +19,30 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.ThreadSafe; +/** + * A no-op request tracker. + * + *

          To activate this tracker, modify the {@code advanced.request-tracker} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.request-tracker {
          + *     class = NoopRequestTracker
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + * + *

          Note that if a tracker is specified programmatically with {@link + * SessionBuilder#withRequestTracker(RequestTracker)}, the configuration is ignored. + */ @ThreadSafe public class NoopRequestTracker implements RequestTracker { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index 8752acb3e41..e9e42f100c2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -20,12 +20,43 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * A request tracker that logs the requests executed through the session, according to a set of + * configurable options. + * + *

          To activate this tracker, modify the {@code advanced.request-tracker} section in the driver + * configuration, for example: + * + *

          + * datastax-java-driver {
          + *   advanced.request-tracker {
          + *     class = RequestLogger
          + *     logs {
          + *       success { enabled = true }
          + *       slow { enabled = true, threshold = 1 second }
          + *       error { enabled = true }
          + *       max-query-length = 500
          + *       show-values = true
          + *       max-value-length = 50
          + *       max-values = 50
          + *       show-stack-traces = true
          + *     }
          + *   }
          + * }
          + * 
          + * + * See {@code reference.conf} (in the manual or core driver JAR) for more details. + * + *

          Note that if a tracker is specified programmatically with {@link + * SessionBuilder#withRequestTracker(RequestTracker)}, the configuration is ignored. + */ @ThreadSafe public class RequestLogger implements RequestTracker { From 99ed6ebc7d57cfa953bd277efa5460467f38ff2f Mon Sep 17 00:00:00 2001 From: GregBestland Date: Mon, 18 Jun 2018 15:31:45 -0500 Subject: [PATCH 505/742] JAVA-1870: Use sensible defaults in RequestLogger if config options are missing --- changelog/README.md | 1 + .../api/core/config/DriverConfigProfile.java | 78 ++++++++++++++++++- .../internal/core/tracker/RequestLogger.java | 37 +++++---- .../api/core/tracker/RequestLoggerIT.java | 48 ++++++++++++ 4 files changed, 146 insertions(+), 18 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 833dce268e6..d64c232e9f1 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1870: Use sensible defaults in RequestLogger if config options are missing - [improvement] JAVA-1877: Use a separate reconnection schedule for the control connection - [improvement] JAVA-1763: Generate a binary tarball as part of the build process - [improvement] JAVA-1884: Add additional methods from TypeToken to GenericType diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java index 3471b69ab53..10962b6b457 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.config; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; import java.util.List; import java.util.Map; @@ -58,63 +59,118 @@ public interface DriverConfigProfile { boolean getBoolean(@NonNull DriverOption option); + default boolean getBoolean(@NonNull DriverOption option, boolean defaultValue) { + return isDefined(option) ? getBoolean(option) : defaultValue; + } + @NonNull DriverConfigProfile withBoolean(@NonNull DriverOption option, boolean value); @NonNull List getBooleanList(@NonNull DriverOption option); + @Nullable + default List getBooleanList( + @NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getBooleanList(option) : defaultValue; + } + @NonNull DriverConfigProfile withBooleanList(@NonNull DriverOption option, @NonNull List value); int getInt(@NonNull DriverOption option); + default int getInt(@NonNull DriverOption option, int defaultValue) { + return isDefined(option) ? getInt(option) : defaultValue; + } + @NonNull DriverConfigProfile withInt(@NonNull DriverOption option, int value); @NonNull List getIntList(@NonNull DriverOption option); + @Nullable + default List getIntList( + @NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getIntList(option) : defaultValue; + } + @NonNull DriverConfigProfile withIntList(@NonNull DriverOption option, @NonNull List value); long getLong(@NonNull DriverOption option); - @NonNull + default long getLong(@NonNull DriverOption option, long defaultValue) { + return isDefined(option) ? getLong(option) : defaultValue; + } + DriverConfigProfile withLong(@NonNull DriverOption option, long value); @NonNull List getLongList(@NonNull DriverOption option); + @Nullable + default List getLongList(@NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getLongList(option) : defaultValue; + } + @NonNull DriverConfigProfile withLongList(@NonNull DriverOption option, @NonNull List value); double getDouble(@NonNull DriverOption option); + default double getDouble(@NonNull DriverOption option, double defaultValue) { + return isDefined(option) ? getDouble(option) : defaultValue; + } + @NonNull DriverConfigProfile withDouble(@NonNull DriverOption option, double value); @NonNull List getDoubleList(@NonNull DriverOption option); + @Nullable + default List getDoubleList( + @NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getDoubleList(option) : defaultValue; + } + @NonNull DriverConfigProfile withDoubleList(@NonNull DriverOption option, @NonNull List value); @NonNull String getString(@NonNull DriverOption option); + @Nullable + default String getString(@NonNull DriverOption option, @Nullable String defaultValue) { + return isDefined(option) ? getString(option) : defaultValue; + } + @NonNull DriverConfigProfile withString(@NonNull DriverOption option, @NonNull String value); @NonNull List getStringList(@NonNull DriverOption option); + @Nullable + default List getStringList( + @NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getStringList(option) : defaultValue; + } + @NonNull DriverConfigProfile withStringList(@NonNull DriverOption option, @NonNull List value); @NonNull Map getStringMap(@NonNull DriverOption option); + @Nullable + default Map getStringMap( + @NonNull DriverOption option, @Nullable Map defaultValue) { + return isDefined(option) ? getStringMap(option) : defaultValue; + } + @NonNull DriverConfigProfile withStringMap( @NonNull DriverOption option, @NonNull Map value); @@ -126,6 +182,10 @@ DriverConfigProfile withStringMap( */ long getBytes(@NonNull DriverOption option); + default long getBytes(@NonNull DriverOption option, long defaultValue) { + return isDefined(option) ? getBytes(option) : defaultValue; + } + /** @see #getBytes(DriverOption) */ @NonNull DriverConfigProfile withBytes(@NonNull DriverOption option, long value); @@ -134,6 +194,11 @@ DriverConfigProfile withStringMap( @NonNull List getBytesList(DriverOption option); + @Nullable + default List getBytesList(DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getBytesList(option) : defaultValue; + } + /** @see #getBytes(DriverOption) */ @NonNull DriverConfigProfile withBytesList(@NonNull DriverOption option, @NonNull List value); @@ -141,12 +206,23 @@ DriverConfigProfile withStringMap( @NonNull Duration getDuration(@NonNull DriverOption option); + @Nullable + default Duration getDuration(@NonNull DriverOption option, @Nullable Duration defaultValue) { + return isDefined(option) ? getDuration(option) : defaultValue; + } + @NonNull DriverConfigProfile withDuration(@NonNull DriverOption option, @NonNull Duration value); @NonNull List getDurationList(@NonNull DriverOption option); + @Nullable + default List getDurationList( + @NonNull DriverOption option, @Nullable List defaultValue) { + return isDefined(option) ? getDurationList(option) : defaultValue; + } + @NonNull DriverConfigProfile withDurationList(@NonNull DriverOption option, @NonNull List value); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index e9e42f100c2..f5009bb161f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,27 +83,28 @@ public void onSuccess( @NonNull Node node) { boolean successEnabled = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED); - boolean slowEnabled = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED); + configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, false); + boolean slowEnabled = + configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, false); if (!successEnabled && !slowEnabled) { return; } long slowThresholdNanos = - configProfile.isDefined(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD) - ? configProfile.getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD).toNanos() - : Long.MAX_VALUE; + configProfile + .getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD, Duration.ofSeconds(1)) + .toNanos(); boolean isSlow = latencyNanos > slowThresholdNanos; if ((isSlow && !slowEnabled) || (!isSlow && !successEnabled)) { return; } - int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); - int maxValues = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + int maxQueryLength = + configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); + int maxValues = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); int maxValueLength = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); logSuccess( request, latencyNanos, isSlow, node, maxQueryLength, showValues, maxValues, maxValueLength); @@ -116,18 +118,19 @@ public void onError( @NonNull DriverConfigProfile configProfile, Node node) { - if (!configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED)) { + if (!configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, false)) { return; } - int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); - int maxValues = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + int maxQueryLength = + configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); + int maxValues = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); + int maxValueLength = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); boolean showStackTraces = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES); + configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, false); logError( request, diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 4ba8ac65f33..0bfed655b62 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -105,6 +105,20 @@ public class RequestLoggerIT { "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") .build(); + @Rule + public SessionRule sessionRuleDefaults = + SessionRule.builder(simulacronRule) + .withOptions( + "advanced.request-tracker.class = com.datastax.oss.driver.internal.core.tracker.RequestLogger", + "advanced.request-tracker.logs.success.enabled = true", + "advanced.request-tracker.logs.error.enabled = true", + "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", + "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", + "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", + "profiles.no-logs.advanced.request-tracker.logs.error.enabled = false", + "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") + .build(); + @Captor private ArgumentCaptor loggingEventCaptor; @Mock private Appender appender; private Logger logger; @@ -138,6 +152,20 @@ public void should_log_successful_request() { .contains("Success", "[0 values]", QUERY); } + @Test + public void should_log_successful_request_with_defaults() { + // Given + simulacronRule.cluster().prime(when(QUERY).then(rows().row("release_version", "3.0.0"))); + + // When + sessionRuleDefaults.session().execute(QUERY); + + // Then + Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + assertThat(loggingEventCaptor.getValue().getFormattedMessage()) + .contains("Success", "[0 values]", QUERY); + } + @Test public void should_log_failed_request_with_stack_trace() { // Given @@ -160,6 +188,26 @@ public void should_log_failed_request_with_stack_trace() { assertThat(log.getThrowableProxy().getClassName()).isEqualTo(ServerError.class.getName()); } + @Test + public void should_log_failed_request_with_stack_trace_with_defaults() { + // Given + simulacronRule.cluster().prime(when(QUERY).then(serverError("test"))); + + // When + try { + sessionRuleDefaults.session().execute(QUERY); + fail("Expected a ServerError"); + } catch (ServerError error) { + // expected + } + + // Then + Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + ILoggingEvent log = loggingEventCaptor.getValue(); + assertThat(log.getFormattedMessage()) + .contains("Error", "[0 values]", QUERY, ServerError.class.getName()); + } + @Test public void should_log_failed_request_without_stack_trace() { // Given From 250f88cb8f798c0d34d556f0e8c5efcce5e2c49e Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 5 Jul 2018 16:44:28 -0700 Subject: [PATCH 506/742] Use new config "getters with default" where relevant --- .../driver/api/core/session/SessionBuilder.java | 4 +--- .../loadbalancing/DefaultLoadBalancingPolicy.java | 4 +--- .../internal/core/metadata/MetadataManager.java | 10 ++++------ .../metadata/schema/queries/SchemaQueries.java | 5 ++--- .../core/ssl/DefaultSslEngineFactory.java | 8 ++------ .../core/time/MonotonicTimestampGenerator.java | 15 +++++++-------- .../internal/core/cql/StatementSizeTest.java | 3 +-- .../queries/Cassandra21SchemaQueriesTest.java | 7 +++++-- .../queries/Cassandra22SchemaQueriesTest.java | 7 +++++-- .../queries/Cassandra3SchemaQueriesTest.java | 13 ++++++++----- .../time/MonotonicTimestampGeneratorTestBase.java | 10 +++------- 11 files changed, 39 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index af2fda87630..24f9177d574 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -291,9 +291,7 @@ protected final CompletionStage buildDefaultSessionAsync() { DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = - defaultConfig.isDefined(DefaultDriverOption.CONTACT_POINTS) - ? defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS) - : Collections.emptyList(); + defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 8ea0ee21a12..b1d5abfb885 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -306,9 +306,7 @@ public void close() { private static String getLocalDcFromConfig(DriverContext context, String profileName) { DriverConfigProfile config = context.config().getProfile(profileName); - return (config.isDefined(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) - ? config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) - : null; + return config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index f357a95fc3d..997f146433a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -78,9 +78,8 @@ public MetadataManager(InternalDriverContext context) { this.metadata = DefaultMetadata.EMPTY; this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = - config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - : Collections.emptyList(); + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED); context.eventBus().register(ConfigChangeEvent.class, this::onConfigChanged); @@ -93,9 +92,8 @@ private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = - config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - : Collections.emptyList(); + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED); if ((!schemaEnabledBefore diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java index 02ba0133b0f..3acd0f70e46 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java @@ -82,9 +82,8 @@ protected SchemaQueries( this.pageSize = config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); List refreshedKeyspaces = - config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - ? config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES) - : Collections.emptyList(); + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); this.whereClause = buildWhereClause(refreshedKeyspaces); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 271dd158bbc..1516dfe79bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -79,12 +79,8 @@ public DefaultSslEngineFactory(DriverContext driverContext) { } else { this.cipherSuites = null; } - if (config.isDefined(DefaultDriverOption.SSL_HOSTNAME_VALIDATION)) { - this.requireHostnameValidation = - config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION); - } else { - this.requireHostnameValidation = false; - } + this.requireHostnameValidation = + config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false); } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java index ed090f13c39..f40a6326512 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import java.time.Duration; import java.util.concurrent.atomic.AtomicLong; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; @@ -47,12 +48,11 @@ protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); this.warningThresholdMicros = - (config.isDefined(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) - ? config - .getDuration(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD) - .toNanos() - / 1000 - : 0; + config + .getDuration( + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO) + .toNanos() + / 1000; if (this.warningThresholdMicros == 0) { this.warningIntervalMillis = 0; @@ -105,8 +105,7 @@ private void maybeLog(long currentTick, long last) { private static Clock buildClock(DriverContext context) { DriverConfigProfile config = context.config().getDefaultProfile(); boolean forceJavaClock = - config.isDefined(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK) - && config.getBoolean(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK); + config.getBoolean(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK, false); return Clock.getInstance(forceJavaClock); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java index 56e8e7af63f..b0715f4f01b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java @@ -33,7 +33,6 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.time.AtomicTimestampGenerator; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -63,6 +62,7 @@ public class StatementSizeTest { @Mock InternalDriverContext driverContext; @Mock DriverConfig config; @Mock DriverConfigProfile defaultConfigProfile; + @Mock TimestampGenerator timestampGenerator; @Before public void setup() { @@ -87,7 +87,6 @@ public void setup() { .thenReturn(new CassandraProtocolVersionRegistry(null)); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); Mockito.when(driverContext.config()).thenReturn(config); - TimestampGenerator timestampGenerator = new AtomicTimestampGenerator(driverContext); Mockito.when(driverContext.timestampGenerator()).thenReturn(timestampGenerator); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index 1f0be9f04ce..cf1da01203b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Collections; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -35,8 +36,10 @@ public class Cassandra21SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) - .thenReturn(false); + Mockito.when( + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + .thenReturn(Collections.emptyList()); SchemaQueriesWithMockedChannel queries = new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index af7232c8657..d1aa6a677ad 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Collections; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -35,8 +36,10 @@ public class Cassandra22SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) - .thenReturn(false); + Mockito.when( + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + .thenReturn(Collections.emptyList()); SchemaQueriesWithMockedChannel queries = new SchemaQueriesWithMockedChannel(driverChannel, null, config, "test"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index 0b4a75d22dc..d8c974de964 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import java.util.Collections; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -39,8 +40,10 @@ public void setup() { super.setup(); // By default, no keyspace filter - Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) - .thenReturn(false); + Mockito.when( + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + .thenReturn(Collections.emptyList()); } @Test @@ -50,9 +53,9 @@ public void should_query_without_keyspace_filter() { @Test public void should_query_with_keyspace_filter() { - Mockito.when(config.isDefined(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) - .thenReturn(true); - Mockito.when(config.getStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES)) + Mockito.when( + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) .thenReturn(ImmutableList.of("ks1", "ks2")); should_query_with_where_clause(" WHERE keyspace_name in ('ks1','ks2')"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java index 2faee452a1e..0d8ff547ead 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java @@ -57,14 +57,10 @@ public void setup() { Mockito.when(context.config()).thenReturn(config); // Disable warnings by default - Mockito.when( - defaultConfigProfile.isDefined( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) - .thenReturn(true); Mockito.when( defaultConfigProfile.getDuration( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) - .thenReturn(Duration.ofNanos(0)); + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) + .thenReturn(Duration.ZERO); // Actual value doesn't really matter since we only test the first warning Mockito.when( defaultConfigProfile.getDuration( @@ -112,7 +108,7 @@ public void should_increment_if_clock_does_not_increase() { public void should_warn_if_timestamps_drift() { Mockito.when( defaultConfigProfile.getDuration( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD)) + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) .thenReturn(Duration.ofNanos(2 * 1000)); Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); From 2b9d189f3a0129dfe4436135ae4f150cc3e28027 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 9 Jul 2018 15:56:35 -0500 Subject: [PATCH 507/742] Fix quiet-period configuration in TestConfigLoader --- .../driver/internal/testinfra/session/TestConfigLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java index 5a52c58ca93..5b286ef2f24 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java @@ -33,8 +33,8 @@ private static Config buildConfig(String... customOptions) { String.join( "\n", customConfig, - "netty.io-group.shutdown.quiet-period = 0", - "netty.admin-group.shutdown.quiet-period = 0"); + "advanced.netty.io-group.shutdown.quiet-period = 0", + "advanced.netty.admin-group.shutdown.quiet-period = 0"); return ConfigFactory.parseString(additionalCustomConfig).withFallback(getConfig()); } From 6fe9585af82ce91d4bdf4311d092737163ac3159 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 9 Jul 2018 14:18:00 -0500 Subject: [PATCH 508/742] JAVA-1879: Duplicate basic.request options as Statement attributes --- changelog/README.md | 1 + .../driver/api/core/cql/BatchStatement.java | 8 + .../api/core/cql/BatchStatementBuilder.java | 6 +- .../api/core/cql/BoundStatementBuilder.java | 4 + .../driver/api/core/cql/PrepareRequest.java | 21 ++ .../driver/api/core/cql/SimpleStatement.java | 12 + .../api/core/cql/SimpleStatementBuilder.java | 6 +- .../oss/driver/api/core/cql/Statement.java | 73 +++++ .../driver/api/core/cql/StatementBuilder.java | 38 +++ .../oss/driver/api/core/session/Request.java | 12 + .../driver/internal/core/cql/Conversions.java | 46 +-- .../core/cql/CqlPrepareHandlerBase.java | 10 +- .../core/cql/CqlRequestHandlerBase.java | 5 +- .../core/cql/DefaultBatchStatement.java | 221 ++++++++++++- .../core/cql/DefaultBoundStatement.java | 181 +++++++++++ .../core/cql/DefaultPrepareRequest.java | 30 ++ .../core/cql/DefaultPreparedStatement.java | 20 +- .../core/cql/DefaultSimpleStatement.java | 219 ++++++++++++- .../internal/core/cql/StatementSizeTest.java | 4 + .../core/tracker/RequestLogFormatterTest.java | 6 +- .../driver/api/core/cql/BoundStatementIT.java | 305 +++++++++++++++--- .../api/core/cql/SimpleStatementIT.java | 128 ++++++-- .../example/guava/internal/KeyRequest.java | 8 + manual/core/logging/README.md | 5 +- manual/core/statements/README.md | 35 +- 25 files changed, 1255 insertions(+), 149 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index d64c232e9f1..324b0120376 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1879: Duplicate basic.request options as Request/Statement attributes - [improvement] JAVA-1870: Use sensible defaults in RequestLogger if config options are missing - [improvement] JAVA-1877: Use a separate reconnection schedule for the control connection - [improvement] JAVA-1763: Generate a binary tarball as part of the build process diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index cfb5d165c05..7c8de2bfde3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -56,6 +56,10 @@ static BatchStatement newInstance(@NonNull BatchType batchType) { false, false, Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, null); } @@ -79,6 +83,10 @@ static BatchStatement newInstance( false, false, Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, null); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index 626220258b5..5997c8298f5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -142,6 +142,10 @@ public BatchStatement build() { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index 6286ad67d38..9987139704e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -130,6 +130,10 @@ public BoundStatement build() { tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 015389b7d44..4abac7f3131 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.retry.RetryPolicy; @@ -110,4 +111,24 @@ default boolean isTracing() { */ @Nullable Boolean areBoundStatementsIdempotent(); + + /** + * The page size to use for the bound statements that will be created from the prepared statement. + * If the value is 0 or negative, the default value will be used from the configuration. + */ + int getPageSizeForBoundStatements(); + + /** + * The consistency level to use for the bound statements that will be created from the prepared + * statement or {@link null} to use the default value from the configuration. + */ + @Nullable + ConsistencyLevel getConsistencyLevelForBoundStatements(); + + /** + * The serial consistency level to use for the bound statements that will be created from the + * prepared statement or {@code null} to use the default value from the configuration. + */ + @Nullable + ConsistencyLevel getSerialConsistencyLevelForBoundStatements(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 3fa3d9450bc..f12cff6934e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -73,6 +73,10 @@ static SimpleStatement newInstance(@NonNull String cqlQuery) { null, false, Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, null); } @@ -99,6 +103,10 @@ static SimpleStatement newInstance( null, false, Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, null); } @@ -122,6 +130,10 @@ static SimpleStatement newInstance( null, false, Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, null); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 87a6dd1bd5f..f5b7072499f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -172,6 +172,10 @@ public SimpleStatement build() { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 91439da368b..d087227ae26 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -15,8 +15,10 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -28,6 +30,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -195,6 +198,17 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { @NonNull T setTimestamp(long newTimestamp); + /** + * Sets how long to wait for this request to complete. This is a global limit on the duration of a + * session.execute() call, including any retries the driver might do. + * + * @param newTimeout the timeout to use, or {@code null} to use the default value defined in the + * configuration. + * @see DefaultDriverOption#REQUEST_TIMEOUT + */ + @NonNull + T setTimeout(@Nullable Duration newTimeout); + /** * Returns the paging state to send with the statement, or {@code null} if this statement has no * paging state. @@ -225,6 +239,65 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { @NonNull T setPagingState(@Nullable ByteBuffer newPagingState); + /** + * Returns the page size to use for the statement. + * + * @return the set page size, otherwise 0 or a negative value to use the default value defined in + * the configuration. + * @see DefaultDriverOption#REQUEST_PAGE_SIZE + */ + int getPageSize(); + + /** + * Configures how many rows will be retrieved simultaneously in a single network roundtrip (the + * goal being to avoid loading too many results in memory at the same time). + * + * @param newPageSize the page size to use, set to 0 or a negative value to use the default value + * defined in the configuration. + * @see DefaultDriverOption#REQUEST_PAGE_SIZE + */ + @NonNull + T setPageSize(int newPageSize); + + /** + * Returns the {@link ConsistencyLevel} to use for the statement. + * + * @return the set consistency, or {@code null} to use the default value defined in the + * configuration. + * @see DefaultDriverOption#REQUEST_CONSISTENCY + */ + @Nullable + ConsistencyLevel getConsistencyLevel(); + + /** + * Sets the {@link ConsistencyLevel} to use for this statement. + * + * @param newConsistencyLevel the consistency level to use, or null to use the default value + * defined in the configuration. + * @see DefaultDriverOption#REQUEST_CONSISTENCY + */ + T setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel); + + /** + * Returns the serial {@link ConsistencyLevel} to use for the statement. + * + * @return the set serial consistency, or {@code null} to use the default value defined in the + * configuration. + * @see DefaultDriverOption#REQUEST_SERIAL_CONSISTENCY + */ + @Nullable + ConsistencyLevel getSerialConsistencyLevel(); + + /** + * Sets the serial {@link ConsistencyLevel} to use for this statement. + * + * @param newSerialConsistencyLevel the serial consistency level to use, or null to use the + * default value defined in the configuration. + * @see DefaultDriverOption#REQUEST_SERIAL_CONSISTENCY + */ + @NonNull + T setSerialConsistencyLevel(@Nullable ConsistencyLevel newSerialConsistencyLevel); + /** * Calculates the approximate size in bytes that the statement will have when encoded. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index c113d989ad5..356623382ee 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -22,6 +23,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; import net.jcip.annotations.NotThreadSafe; @@ -48,6 +50,10 @@ public abstract class StatementBuilder, S exten protected boolean tracing; protected long timestamp = Long.MIN_VALUE; @Nullable protected ByteBuffer pagingState; + protected int pageSize = Integer.MIN_VALUE; + @Nullable protected ConsistencyLevel consistencyLevel; + @Nullable protected ConsistencyLevel serialConsistencyLevel; + @Nullable protected Duration timeout; protected StatementBuilder() { // nothing to do @@ -62,6 +68,10 @@ protected StatementBuilder(S template) { this.tracing = template.isTracing(); this.timestamp = template.getTimestamp(); this.pagingState = template.getPagingState(); + this.pageSize = template.getPageSize(); + this.consistencyLevel = template.getConsistencyLevel(); + this.serialConsistencyLevel = template.getSerialConsistencyLevel(); + this.timeout = template.getTimeout(); } /** @see Statement#setConfigProfileName(String) */ @@ -155,6 +165,34 @@ public T withPagingState(@Nullable ByteBuffer pagingState) { return self; } + /** @see Statement#setPageSize(int) */ + @NonNull + public T withPageSize(int pageSize) { + this.pageSize = pageSize; + return self; + } + + /** @see Statement#setConsistencyLevel(ConsistencyLevel) */ + @NonNull + public T withConsistencyLevel(@Nullable ConsistencyLevel consistencyLevel) { + this.consistencyLevel = consistencyLevel; + return self; + } + + /** @see Statement#setSerialConsistencyLevel(ConsistencyLevel) */ + @NonNull + public T withSerialConsistencyLevel(@Nullable ConsistencyLevel serialConsistencyLevel) { + this.serialConsistencyLevel = serialConsistencyLevel; + return self; + } + + /** @see Statement#setTimeout(Duration) */ + @NonNull + public T withTimeout(@Nullable Duration timeout) { + this.timeout = timeout; + return self; + } + @NonNull protected Map buildCustomPayload() { return (customPayloadBuilder == null) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 05e0154fa48..e743b6aea10 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -24,6 +24,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; /** @@ -149,6 +150,17 @@ public interface Request { @Nullable Boolean isIdempotent(); + /** + * How long to wait for this request to complete. This is a global limit on the duration of a + * session.execute() call, including any retries the driver might do. + * + * @return the set duration, or {@code null} to use the default value defined in the + * configuration. + * @see DefaultDriverOption#REQUEST_TIMEOUT + */ + @Nullable + Duration getTimeout(); + /** * Whether tracing information should be recorded for this request. * diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 3ef789f63ce..563c2247bc1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -95,17 +96,22 @@ public class Conversions { public static Message toMessage( Statement statement, DriverConfigProfile config, InternalDriverContext context) { - int consistency = - context - .consistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) - .getProtocolCode(); - int pageSize = config.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE); - int serialConsistency = - context - .consistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) - .getProtocolCode(); + ConsistencyLevel consistency = + statement.getConsistencyLevel() != null + ? statement.getConsistencyLevel() + : context + .consistencyLevelRegistry() + .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); + int pageSize = + statement.getPageSize() > 0 + ? statement.getPageSize() + : config.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE); + ConsistencyLevel serialConsistency = + statement.getSerialConsistencyLevel() != null + ? statement.getSerialConsistencyLevel() + : context + .consistencyLevelRegistry() + .fromName(config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)); long timestamp = statement.getTimestamp(); if (timestamp == Long.MIN_VALUE) { timestamp = context.timestampGenerator().next(); @@ -128,13 +134,13 @@ public static Message toMessage( } QueryOptions queryOptions = new QueryOptions( - consistency, + consistency.getProtocolCode(), encode(simpleStatement.getPositionalValues(), codecRegistry, protocolVersion), encode(simpleStatement.getNamedValues(), codecRegistry, protocolVersion), false, pageSize, statement.getPagingState(), - serialConsistency, + serialConsistency.getProtocolCode(), timestamp, (keyspace == null) ? null : keyspace.asInternal()); return new Query(simpleStatement.getQuery(), queryOptions); @@ -147,13 +153,13 @@ public static Message toMessage( boundStatement.getPreparedStatement().getResultSetDefinitions().size() > 0; QueryOptions queryOptions = new QueryOptions( - consistency, + consistency.getProtocolCode(), boundStatement.getValues(), Collections.emptyMap(), skipMetadata, pageSize, statement.getPagingState(), - serialConsistency, + serialConsistency.getProtocolCode(), timestamp, null); PreparedStatement preparedStatement = boundStatement.getPreparedStatement(); @@ -200,8 +206,8 @@ public static Message toMessage( batchStatement.getBatchType().getProtocolCode(), queriesOrIds, values, - consistency, - serialConsistency, + consistency.getProtocolCode(), + serialConsistency.getProtocolCode(), timestamp, (keyspace == null) ? null : keyspace.asInternal()); } else { @@ -348,7 +354,11 @@ public static DefaultPreparedStatement toPreparedStatement( request.areBoundStatementsIdempotent(), context.codecRegistry(), context.protocolVersion(), - NullAllowingImmutableMap.copyOf(request.getCustomPayload())); + NullAllowingImmutableMap.copyOf(request.getCustomPayload()), + request.getPageSizeForBoundStatements(), + request.getConsistencyLevelForBoundStatements(), + request.getSerialConsistencyLevelForBoundStatements(), + request.getTimeout()); } public static ColumnDefinitions toColumnDefinitions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index a9c24bd4f52..92dcb10540d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -157,7 +157,10 @@ protected CqlPrepareHandlerBase( new Prepare(request.getQuery(), (keyspace == null) ? null : keyspace.asInternal()); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - this.timeout = configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); + this.timeout = + request.getTimeout() != null + ? request.getTimeout() + : configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.prepareOnAllNodes = configProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); @@ -284,7 +287,10 @@ private DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatemen LOG.warn( "Re-preparing already prepared query. " + "This is generally an anti-pattern and will likely affect performance. " - + "Consider preparing the statement only once. Query='{}'", + + "The cached version of the PreparedStatement will be returned, which may use " + + "different bound statement execution parameters (CL, timeout, etc.) from the " + + "current session.prepare call. Consider preparing the statement only once. " + + "Query='{}'", preparedStatement.getQuery()); // The one object in the cache will get GCed once it's not referenced by the client anymore diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index fba38cdf7cb..9cdcb76f4ae 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -179,7 +179,10 @@ protected CqlRequestHandlerBase( this.message = Conversions.toMessage(statement, configProfile, context); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); - this.timeout = configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); + this.timeout = + statement.getTimeout() != null + ? statement.getTimeout() + : configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.activeExecutionsCount = new AtomicInteger(1); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index aa765bea9f0..f2f530e25f5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.BatchStatement; @@ -27,6 +28,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -48,6 +50,10 @@ public class DefaultBatchStatement implements BatchStatement { private final boolean tracing; private final long timestamp; private final ByteBuffer pagingState; + private final int pageSize; + private final ConsistencyLevel consistencyLevel; + private final ConsistencyLevel serialConsistencyLevel; + private final Duration timeout; public DefaultBatchStatement( BatchType batchType, @@ -62,7 +68,11 @@ public DefaultBatchStatement( Boolean idempotent, boolean tracing, long timestamp, - ByteBuffer pagingState) { + ByteBuffer pagingState, + int pageSize, + ConsistencyLevel consistencyLevel, + ConsistencyLevel serialConsistencyLevel, + Duration timeout) { this.batchType = batchType; this.statements = ImmutableList.copyOf(statements); this.configProfileName = configProfileName; @@ -76,6 +86,10 @@ public DefaultBatchStatement( this.tracing = tracing; this.timestamp = timestamp; this.pagingState = pagingState; + this.pageSize = pageSize; + this.consistencyLevel = consistencyLevel; + this.serialConsistencyLevel = serialConsistencyLevel; + this.timeout = timeout; } @NonNull @@ -100,7 +114,11 @@ public BatchStatement setBatchType(@NonNull BatchType newBatchType) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -119,7 +137,11 @@ public BatchStatement setKeyspace(@Nullable CqlIdentifier newKeyspace) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -142,7 +164,11 @@ public BatchStatement add(@NonNull BatchableStatement statement) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } } @@ -169,7 +195,11 @@ public BatchStatement addAll(@NonNull Iterable> idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } } @@ -194,7 +224,11 @@ public BatchStatement clear() { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -224,7 +258,97 @@ public BatchStatement setPagingState(ByteBuffer newPagingState) { idempotent, tracing, timestamp, - newPagingState); + newPagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Override + public int getPageSize() { + return pageSize; + } + + @NonNull + @Override + public BatchStatement setPageSize(int newPageSize) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + newPageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Nullable + @Override + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + @Override + public BatchStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + newConsistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Nullable + @Override + public ConsistencyLevel getSerialConsistencyLevel() { + return serialConsistencyLevel; + } + + @NonNull + @Override + public BatchStatement setSerialConsistencyLevel( + @Nullable ConsistencyLevel newSerialConsistencyLevel) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + newSerialConsistencyLevel, + timeout); } @Override @@ -248,7 +372,11 @@ public BatchStatement setConfigProfileName(@Nullable String newConfigProfileName idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -272,7 +400,11 @@ public DefaultBatchStatement setConfigProfile(@Nullable DriverConfigProfile newP idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -320,7 +452,11 @@ public BatchStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -354,7 +490,11 @@ public BatchStatement setRoutingKey(ByteBuffer newRoutingKey) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -388,7 +528,11 @@ public BatchStatement setRoutingToken(Token newRoutingToken) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -413,7 +557,11 @@ public DefaultBatchStatement setCustomPayload(@NonNull Map n idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -421,6 +569,12 @@ public Boolean isIdempotent() { return idempotent; } + @Nullable + @Override + public Duration getTimeout() { + return null; + } + @NonNull @Override public DefaultBatchStatement setIdempotent(Boolean newIdempotence) { @@ -437,7 +591,11 @@ public DefaultBatchStatement setIdempotent(Boolean newIdempotence) { newIdempotence, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -461,7 +619,11 @@ public BatchStatement setTracing(boolean newTracing) { idempotent, newTracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -485,6 +647,33 @@ public BatchStatement setTimestamp(long newTimestamp) { idempotent, tracing, newTimestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @NonNull + @Override + public BatchStatement setTimeout(@Nullable Duration newTimeout) { + return new DefaultBatchStatement( + batchType, + statements, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + newTimeout); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 34c63e2cb1f..3dc902791b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -28,6 +29,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -49,6 +51,10 @@ public class DefaultBoundStatement implements BoundStatement { private final boolean tracing; private final long timestamp; private final ByteBuffer pagingState; + private final int pageSize; + private final ConsistencyLevel consistencyLevel; + private final ConsistencyLevel serialConsistencyLevel; + private final Duration timeout; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; @@ -66,6 +72,10 @@ public DefaultBoundStatement( boolean tracing, long timestamp, ByteBuffer pagingState, + int pageSize, + ConsistencyLevel consistencyLevel, + ConsistencyLevel serialConsistencyLevel, + Duration timeout, CodecRegistry codecRegistry, ProtocolVersion protocolVersion) { this.preparedStatement = preparedStatement; @@ -81,6 +91,10 @@ public DefaultBoundStatement( this.tracing = tracing; this.timestamp = timestamp; this.pagingState = pagingState; + this.pageSize = pageSize; + this.consistencyLevel = consistencyLevel; + this.serialConsistencyLevel = serialConsistencyLevel; + this.timeout = timeout; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; } @@ -150,6 +164,10 @@ public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -188,6 +206,10 @@ public BoundStatement setConfigProfileName(@Nullable String newConfigProfileName tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -214,6 +236,10 @@ public BoundStatement setConfigProfile(@Nullable DriverConfigProfile newConfigPr tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -247,6 +273,10 @@ public BoundStatement setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeysp tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -293,6 +323,10 @@ public BoundStatement setRoutingKey(@Nullable ByteBuffer newRoutingKey) { tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -319,6 +353,10 @@ public BoundStatement setRoutingToken(@Nullable Token newRoutingToken) { tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -346,6 +384,10 @@ public BoundStatement setCustomPayload(@NonNull Map newCusto tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -372,6 +414,10 @@ public BoundStatement setIdempotent(@Nullable Boolean newIdempotence) { tracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -398,6 +444,10 @@ public BoundStatement setTracing(boolean newTracing) { newTracing, timestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } @@ -424,6 +474,41 @@ public BoundStatement setTimestamp(long newTimestamp) { tracing, newTimestamp, pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, + codecRegistry, + protocolVersion); + } + + @Nullable + @Override + public Duration getTimeout() { + return timeout; + } + + @NonNull + @Override + public BoundStatement setTimeout(@Nullable Duration newTimeout) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + newTimeout, codecRegistry, protocolVersion); } @@ -450,6 +535,102 @@ public BoundStatement setPagingState(@Nullable ByteBuffer newPagingState) { tracing, timestamp, newPagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, + codecRegistry, + protocolVersion); + } + + @Override + public int getPageSize() { + return pageSize; + } + + @NonNull + @Override + public BoundStatement setPageSize(int newPageSize) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + newPageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, + codecRegistry, + protocolVersion); + } + + @Nullable + @Override + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + @Override + public BoundStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + newConsistencyLevel, + serialConsistencyLevel, + timeout, + codecRegistry, + protocolVersion); + } + + @Nullable + @Override + public ConsistencyLevel getSerialConsistencyLevel() { + return serialConsistencyLevel; + } + + @NonNull + @Override + public BoundStatement setSerialConsistencyLevel( + @Nullable ConsistencyLevel newSerialConsistencyLevel) { + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + values, + configProfileName, + configProfile, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + newSerialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index f8aab506a55..fc9f6062fa2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -24,6 +25,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; import net.jcip.annotations.Immutable; @@ -40,12 +42,17 @@ *

        • will use the same configuration profile (or configuration profile name) as the {@code * SimpleStatement}; *
        • will use the same custom payload as the {@code SimpleStatement}; + *
        • will use the same timeout as the {@code SimpleStatement}. * *
        • any bound statement created from the prepared statement: *
            *
          • will use the same configuration profile (or configuration profile name) as the {@code * SimpleStatement}; *
          • will use the same custom payload as the {@code SimpleStatement}; + *
          • will use the same page size as the {@code SimpleStatement}; + *
          • will use the same consistency level as the {@code SimpleStatement}; + *
          • will use the same serial consistency level as the {@code SimpleStatement}; + *
          • will use the same timeout as the {@code SimpleStatement}; *
          • will be idempotent if and only if the {@code SimpleStatement} was idempotent. *
          * @@ -117,6 +124,12 @@ public Map getCustomPayload() { return statement.getCustomPayload(); } + @Nullable + @Override + public Duration getTimeout() { + return statement.getTimeout(); + } + @Nullable @Override public String getConfigProfileNameForBoundStatements() { @@ -140,4 +153,21 @@ public Map getCustomPayloadForBoundStatements() { public Boolean areBoundStatementsIdempotent() { return statement.isIdempotent(); } + + @Override + public int getPageSizeForBoundStatements() { + return statement.getPageSize(); + } + + @Nullable + @Override + public ConsistencyLevel getConsistencyLevelForBoundStatements() { + return statement.getConsistencyLevel(); + } + + @Nullable + @Override + public ConsistencyLevel getSerialConsistencyLevelForBoundStatements() { + return statement.getSerialConsistencyLevel(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 75eba4ec2ea..14a449648d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -27,6 +28,7 @@ import com.datastax.oss.driver.internal.core.session.RepreparePayload; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.List; import java.util.Map; import net.jcip.annotations.ThreadSafe; @@ -46,6 +48,10 @@ public class DefaultPreparedStatement implements PreparedStatement { private final DriverConfigProfile configProfile; private final Map customPayloadForBoundStatements; private final Boolean idempotent; + private final int pageSize; + private final ConsistencyLevel consistencyLevel; + private final ConsistencyLevel serialConsistencyLevel; + private final Duration timeout; public DefaultPreparedStatement( ByteBuffer id, @@ -61,7 +67,11 @@ public DefaultPreparedStatement( Boolean idempotent, CodecRegistry codecRegistry, ProtocolVersion protocolVersion, - Map customPayloadForPrepare) { + Map customPayloadForPrepare, + int pageSize, + ConsistencyLevel consistencyLevel, + ConsistencyLevel serialConsistencyLevel, + Duration timeout) { this.id = id; this.partitionKeyIndices = partitionKeyIndices; // It's important that we keep a reference to this object, so that it only gets evicted from @@ -75,6 +85,10 @@ public DefaultPreparedStatement( this.idempotent = idempotent; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; + this.pageSize = pageSize; + this.consistencyLevel = consistencyLevel; + this.serialConsistencyLevel = serialConsistencyLevel; + this.timeout = timeout; } @NonNull @@ -138,6 +152,10 @@ public BoundStatement bind(@NonNull Object... values) { false, Long.MIN_VALUE, null, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, codecRegistry, protocolVersion); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 0949845465f..c23e4da7547 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -24,6 +25,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.List; import java.util.Map; import net.jcip.annotations.Immutable; @@ -46,6 +48,10 @@ public class DefaultSimpleStatement implements SimpleStatement { private final boolean tracing; private final long timestamp; private final ByteBuffer pagingState; + private final int pageSize; + private final ConsistencyLevel consistencyLevel; + private final ConsistencyLevel serialConsistencyLevel; + private final Duration timeout; /** @see SimpleStatement#builder(String) */ public DefaultSimpleStatement( @@ -62,7 +68,11 @@ public DefaultSimpleStatement( Boolean idempotent, boolean tracing, long timestamp, - ByteBuffer pagingState) { + ByteBuffer pagingState, + int pageSize, + ConsistencyLevel consistencyLevel, + ConsistencyLevel serialConsistencyLevel, + Duration timeout) { if (!positionalValues.isEmpty() && !namedValues.isEmpty()) { throw new IllegalArgumentException("Can't have both positional and named values"); } @@ -80,6 +90,10 @@ public DefaultSimpleStatement( this.tracing = tracing; this.timestamp = timestamp; this.pagingState = pagingState; + this.pageSize = pageSize; + this.consistencyLevel = consistencyLevel; + this.serialConsistencyLevel = serialConsistencyLevel; + this.timeout = timeout; } @NonNull @@ -105,7 +119,11 @@ public SimpleStatement setQuery(@NonNull String newQuery) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -131,7 +149,11 @@ public SimpleStatement setPositionalValues(@NonNull List newPositionalVa idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -157,7 +179,11 @@ public SimpleStatement setNamedValuesWithIds(@NonNull Map idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -183,7 +209,11 @@ public SimpleStatement setConfigProfileName(@Nullable String newConfigProfileNam idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -209,7 +239,11 @@ public SimpleStatement setConfigProfile(@Nullable DriverConfigProfile newProfile idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -235,7 +269,11 @@ public SimpleStatement setKeyspace(@Nullable CqlIdentifier newKeyspace) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -261,7 +299,11 @@ public SimpleStatement setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeys idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -287,7 +329,11 @@ public SimpleStatement setRoutingKey(@Nullable ByteBuffer newRoutingKey) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -313,7 +359,11 @@ public SimpleStatement setRoutingToken(@Nullable Token newRoutingToken) { idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @NonNull @@ -339,7 +389,11 @@ public SimpleStatement setCustomPayload(@NonNull Map newCust idempotent, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Nullable @@ -365,7 +419,11 @@ public SimpleStatement setIdempotent(@Nullable Boolean newIdempotence) { newIdempotence, tracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -390,7 +448,11 @@ public SimpleStatement setTracing(boolean newTracing) { idempotent, newTracing, timestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); } @Override @@ -415,7 +477,41 @@ public SimpleStatement setTimestamp(long newTimestamp) { idempotent, tracing, newTimestamp, - pagingState); + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Nullable + @Override + public Duration getTimeout() { + return timeout; + } + + @NonNull + @Override + public SimpleStatement setTimeout(@Nullable Duration newTimeout) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + newTimeout); } @Nullable @@ -441,7 +537,100 @@ public SimpleStatement setPagingState(@Nullable ByteBuffer newPagingState) { idempotent, tracing, timestamp, - newPagingState); + newPagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Override + public int getPageSize() { + return pageSize; + } + + @NonNull + @Override + public SimpleStatement setPageSize(int newPageSize) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + newPageSize, + consistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Nullable + @Override + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + @Override + public SimpleStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + newConsistencyLevel, + serialConsistencyLevel, + timeout); + } + + @Nullable + @Override + public ConsistencyLevel getSerialConsistencyLevel() { + return serialConsistencyLevel; + } + + @NonNull + @Override + public SimpleStatement setSerialConsistencyLevel( + @Nullable ConsistencyLevel newSerialConsistencyLevel) { + return new DefaultSimpleStatement( + query, + positionalValues, + namedValues, + configProfileName, + configProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + newSerialConsistencyLevel, + timeout); } public static Map wrapKeys(Map namedValues) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java index b0715f4f01b..d1cbc0a5a7a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java @@ -278,6 +278,10 @@ private BoundStatement newBoundStatement( false, -1, null, + Integer.MIN_VALUE, + null, + null, + null, CodecRegistry.DEFAULT, DefaultProtocolVersion.V5); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java index 89ffb494052..276812220df 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java @@ -275,6 +275,10 @@ private PreparedStatement mockPreparedStatement(String query, Map sessionRule = + @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + + @ClassRule + public static SessionRule sessionRule = new SessionRule<>(ccm, "basic.request.page-size = 20"); @Rule public TestName name = new TestName(); + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String KEY = "test"; + private static final int VALUE = 7; - @Before - public void setupSchema() { + @BeforeClass + public static void setupSchema() { + // table where every column forms the primary key. + sessionRule + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") + .withConfigProfile(sessionRule.slowProfile()) + .build()); + for (int i = 0; i < 100; i++) { + sessionRule + .session() + .execute( + SimpleStatement.builder("INSERT INTO test (k, v) VALUES (?, ?)") + .addPositionalValues(KEY, i) + .build()); + } + // table with simple primary key, single cell. sessionRule .session() @@ -56,6 +99,12 @@ public void setupSchema() { .build()); } + @Before + public void clearPrimes() { + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + } + @Test(expected = IllegalStateException.class) public void should_not_allow_unset_value_when_protocol_less_than_v4() { try (CqlSession v3Session = @@ -72,71 +121,76 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { @Test @CassandraRequirement(min = "2.2") public void should_not_write_tombstone_if_value_is_implicitly_unset() { - PreparedStatement prepared = - sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); + session.execute(prepared.bind(name.getMethodName(), VALUE)); - BoundStatement boundStatement = - prepared.boundStatementBuilder().setString(0, name.getMethodName()).build(); + BoundStatement boundStatement = + prepared.boundStatementBuilder().setString(0, name.getMethodName()).build(); - verifyUnset(boundStatement); + verifyUnset(session, boundStatement, name.getMethodName()); + } } @Test @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset() { - PreparedStatement prepared = - sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); + session.execute(prepared.bind(name.getMethodName(), VALUE)); - BoundStatement boundStatement = - prepared - .boundStatementBuilder() - .setString(0, name.getMethodName()) - .setInt(1, VALUE + 1) // set initially, will be unset later - .build(); + BoundStatement boundStatement = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, VALUE + 1) // set initially, will be unset later + .build(); - verifyUnset(boundStatement.unset(1)); + verifyUnset(session, boundStatement.unset(1), name.getMethodName()); + } } @Test @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { - PreparedStatement prepared = - sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - - sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - BoundStatement boundStatement = - prepared - .boundStatementBuilder() - .setString(0, name.getMethodName()) - .setInt(1, VALUE + 1) // set initially, will be unset later - .unset(1) - .build(); + session.execute(prepared.bind(name.getMethodName(), VALUE)); - verifyUnset(boundStatement); + BoundStatement boundStatement = + prepared + .boundStatementBuilder() + .setString(0, name.getMethodName()) + .setInt(1, VALUE + 1) // set initially, will be unset later + .unset(1) + .build(); + + verifyUnset(session, boundStatement, name.getMethodName()); + } } @Test public void should_have_empty_result_definitions_for_update_query() { - PreparedStatement prepared = - sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - assertThat(prepared.getResultSetDefinitions()).hasSize(0); + assertThat(prepared.getResultSetDefinitions()).hasSize(0); - ResultSet rs = sessionRule.session().execute(prepared.bind(name.getMethodName(), VALUE)); - assertThat(rs.getColumnDefinitions()).hasSize(0); + ResultSet rs = session.execute(prepared.bind(name.getMethodName(), VALUE)); + assertThat(rs.getColumnDefinitions()).hasSize(0); + } } @Test public void should_bind_null_value_when_setting_values_in_bulk() { - PreparedStatement prepared = - sessionRule.session().prepare("INSERT INTO test2 (k, v0) values (?, ?)"); - BoundStatement boundStatement = prepared.bind(name.getMethodName(), null); - assertThat(boundStatement.get(1, TypeCodecs.INT)).isNull(); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); + BoundStatement boundStatement = prepared.bind(name.getMethodName(), null); + assertThat(boundStatement.get(1, TypeCodecs.INT)).isNull(); + } } @Test @@ -160,18 +214,169 @@ public void should_allow_custom_codecs_when_setting_values_in_bulk() { } } - private void verifyUnset(BoundStatement boundStatement) { - sessionRule.session().execute(boundStatement.unset(1)); + @Test + public void should_use_page_size_from_simple_statement() { + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + PreparedStatement prepared = session.prepare(st); + ResultSet result = session.execute(prepared.bind()); + + // Should have only fetched 10 (page size) rows. + assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + } + } + + @Test + public void should_use_page_size() { + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + // set page size on simple statement, but will be unused since + // overridden by bound statement. + SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + PreparedStatement prepared = session.prepare(st); + ResultSet result = session.execute(prepared.bind().setPageSize(12)); + + // Should have only fetched 10 (page size) rows. + assertThat(result.getAvailableWithoutFetching()).isEqualTo(12); + } + } + + @Test + public void should_use_consistencies_from_simple_statement() { + try (CqlSession session = SessionUtils.newSession(simulacron)) { + SimpleStatement st = + SimpleStatement.builder("SELECT * FROM test where k = ?") + .withConsistencyLevel(DefaultConsistencyLevel.TWO) + .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + PreparedStatement prepared = session.prepare(st); + simulacron.cluster().clearLogs(); + // since query is unprimed, we use a text value for bind parameter as this is + // what simulacron expects for unprimed statements. + session.execute(prepared.bind("0")); + + List logs = simulacron.cluster().getLogs().getQueryLogs(); + assertThat(logs).hasSize(1); + + QueryLog log = logs.get(0); + + Message message = log.getFrame().message; + assertThat(message).isInstanceOf(Execute.class); + Execute execute = (Execute) message; + assertThat(execute.options.consistency) + .isEqualTo(DefaultConsistencyLevel.TWO.getProtocolCode()); + assertThat(execute.options.serialConsistency) + .isEqualTo(DefaultConsistencyLevel.LOCAL_SERIAL.getProtocolCode()); + } + } + + @Test + public void should_use_consistencies() { + try (CqlSession session = SessionUtils.newSession(simulacron)) { + // set consistencies on simple statement, but they will be unused since + // overridden by bound statement. + SimpleStatement st = + SimpleStatement.builder("SELECT * FROM test where k = ?") + .withConsistencyLevel(DefaultConsistencyLevel.TWO) + .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + PreparedStatement prepared = session.prepare(st); + simulacron.cluster().clearLogs(); + // since query is unprimed, we use a text value for bind parameter as this is + // what simulacron expects for unprimed statements. + session.execute( + prepared + .boundStatementBuilder("0") + .withConsistencyLevel(DefaultConsistencyLevel.THREE) + .withSerialConsistencyLevel(DefaultConsistencyLevel.SERIAL) + .build()); + + List logs = simulacron.cluster().getLogs().getQueryLogs(); + assertThat(logs).hasSize(1); + + QueryLog log = logs.get(0); + + Message message = log.getFrame().message; + assertThat(message).isInstanceOf(Execute.class); + Execute execute = (Execute) message; + assertThat(execute.options.consistency) + .isEqualTo(DefaultConsistencyLevel.THREE.getProtocolCode()); + assertThat(execute.options.serialConsistency) + .isEqualTo(DefaultConsistencyLevel.SERIAL.getProtocolCode()); + } + } + + @Test + public void should_use_timeout_from_simple_statement() { + try (CqlSession session = SessionUtils.newSession(simulacron)) { + Map params = ImmutableMap.of("k", 0); + Map paramTypes = ImmutableMap.of("k", "int"); + simulacron + .cluster() + .prime( + when(query( + "mock query", + Lists.newArrayList( + com.datastax.oss.simulacron.common.codec.ConsistencyLevel.ONE), + params, + paramTypes)) + .then(noRows()) + .delay(1500, TimeUnit.MILLISECONDS)); + SimpleStatement st = + SimpleStatement.builder("mock query") + .withTimeout(Duration.ofSeconds(1)) + .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + PreparedStatement prepared = session.prepare(st); + + thrown.expect(DriverTimeoutException.class); + thrown.expectMessage("Query timed out after PT1S"); + + session.execute(prepared.bind(0)); + } + } + + @Test + public void should_use_timeout() { + try (CqlSession session = SessionUtils.newSession(simulacron)) { + Map params = ImmutableMap.of("k", 0); + Map paramTypes = ImmutableMap.of("k", "int"); + // set timeout on simple statement, but will be unused since overridden by bound statement. + simulacron + .cluster() + .prime( + when(query( + "mock query", + Lists.newArrayList( + com.datastax.oss.simulacron.common.codec.ConsistencyLevel.ONE), + params, + paramTypes)) + .then(noRows()) + .delay(1500, TimeUnit.MILLISECONDS)); + SimpleStatement st = + SimpleStatement.builder("mock query") + .withTimeout(Duration.ofSeconds(1)) + .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + PreparedStatement prepared = session.prepare(st); + + thrown.expect(DriverTimeoutException.class); + thrown.expectMessage("Query timed out after PT0.15S"); + + session.execute(prepared.bind(0).setTimeout(Duration.ofMillis(150))); + } + } + + private static void verifyUnset( + CqlSession session, BoundStatement boundStatement, String valueName) { + session.execute(boundStatement.unset(1)); // Verify that no tombstone was written by reading data back and ensuring initial value is // retained. ResultSet result = - sessionRule - .session() - .execute( - SimpleStatement.builder("SELECT v0 from test2 where k = ?") - .addPositionalValue(name.getMethodName()) - .build()); + session.execute( + SimpleStatement.builder("SELECT v0 from test2 where k = ?") + .addPositionalValue(valueName) + .build()); Row row = result.iterator().next(); assertThat(row.getInt(0)).isEqualTo(VALUE); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 5ac3417dc8a..51dd0487157 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -15,21 +15,34 @@ */ package com.datastax.oss.driver.api.core.cql; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.request.Query; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.common.cluster.QueryLog; +import java.time.Duration; +import java.util.List; import java.util.concurrent.TimeUnit; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; import org.junit.rules.TestName; @Category(ParallelizableTests.class) @@ -38,25 +51,34 @@ public class SimpleStatementIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); @ClassRule - public static SessionRule cluster = + public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + + @ClassRule + public static SessionRule sessionRule = new SessionRule<>(ccm, "basic.request.page-size = 20"); + @ClassRule + public static SessionRule simulacronSessionRule = + SessionRule.builder(simulacron).build(); + @Rule public TestName name = new TestName(); + @Rule public ExpectedException thrown = ExpectedException.none(); + private static final String KEY = "test"; @BeforeClass public static void setupSchema() { // table where every column forms the primary key. - cluster + sessionRule .session() .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { - cluster + sessionRule .session() .execute( SimpleStatement.builder("INSERT INTO test (k, v) VALUES (?, ?)") @@ -65,26 +87,32 @@ public static void setupSchema() { } // table with simple primary key, single cell. - cluster + sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v int)") - .withConfigProfile(cluster.slowProfile()) + .withConfigProfile(sessionRule.slowProfile()) .build()); } + @Before + public void clearPrimes() { + simulacron.cluster().clearLogs(); + simulacron.cluster().clearPrimes(true); + } + @Test public void should_use_paging_state_when_copied() { Statement st = SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); - ResultSet result = cluster.session().execute(st); + ResultSet result = sessionRule.session().execute(st); // given a query created from a copy of a previous query with paging state from previous queries // response. st = st.copy(result.getExecutionInfo().getPagingState()); // when executing that query. - result = cluster.session().execute(st); + result = sessionRule.session().execute(st); // then the response should start on the page boundary. assertThat(result.iterator().next().getInt("v")).isEqualTo(20); @@ -94,7 +122,7 @@ public void should_use_paging_state_when_copied() { public void should_use_paging_state_when_provided_to_new_statement() { Statement st = SimpleStatement.builder(String.format("SELECT v FROM test WHERE k='%s'", KEY)).build(); - ResultSet result = cluster.session().execute(st); + ResultSet result = sessionRule.session().execute(st); // given a query created from a copy of a previous query with paging state from previous queries // response. @@ -104,7 +132,7 @@ public void should_use_paging_state_when_provided_to_new_statement() { .build(); // when executing that query. - result = cluster.session().execute(st); + result = sessionRule.session().execute(st); // then the response should start on the page boundary. assertThat(result.iterator().next().getInt("v")).isEqualTo(20); @@ -115,7 +143,7 @@ public void should_use_paging_state_when_provided_to_new_statement() { public void should_fail_if_using_paging_state_from_different_query() { Statement st = SimpleStatement.builder("SELECT v FROM test WHERE k=:k").addNamedValue("k", KEY).build(); - ResultSet result = cluster.session().execute(st); + ResultSet result = sessionRule.session().execute(st); // TODO Expect PagingStateException @@ -136,7 +164,7 @@ public void should_use_timestamp_when_set() { .withTimestamp(timestamp) .build(); - cluster.session().execute(insert); + sessionRule.session().execute(insert); // when retrieving writetime of cell from that insert. SimpleStatement select = @@ -144,7 +172,7 @@ public void should_use_timestamp_when_set() { .addPositionalValue(name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); // then the writetime should equal the timestamp provided. @@ -158,7 +186,7 @@ public void should_use_tracing_when_set() { // TODO currently there's no way to validate tracing was set since trace id is not set // also write test to verify it is not set. ResultSet result = - cluster + sessionRule .session() .execute(SimpleStatement.builder("select * from test").withTracing().build()); } @@ -173,7 +201,7 @@ public void should_use_positional_values() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then we should be able to retrieve the data as inserted. SimpleStatement select = @@ -181,7 +209,7 @@ public void should_use_positional_values() { .addPositionalValue(name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); Row row = result.iterator().next(); @@ -199,7 +227,7 @@ public void should_allow_nulls_in_positional_values() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then we should be able to retrieve the data as inserted. SimpleStatement select = @@ -207,7 +235,7 @@ public void should_allow_nulls_in_positional_values() { .addPositionalValue(name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); Row row = result.iterator().next(); @@ -224,7 +252,7 @@ public void should_fail_when_too_many_positional_values_provided() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then the server will throw an InvalidQueryException which is thrown up to the client. } @@ -238,7 +266,7 @@ public void should_fail_when_not_enough_positional_values_provided() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then the server will throw an InvalidQueryException which is thrown up to the client. } @@ -253,7 +281,7 @@ public void should_use_named_values() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then we should be able to retrieve the data as inserted. SimpleStatement select = @@ -261,7 +289,7 @@ public void should_use_named_values() { .addNamedValue("k", name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); Row row = result.iterator().next(); @@ -279,7 +307,7 @@ public void should_allow_nulls_in_named_values() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then we should be able to retrieve the data as inserted. SimpleStatement select = @@ -287,7 +315,7 @@ public void should_allow_nulls_in_named_values() { .addNamedValue("k", name.getMethodName()) .build(); - ResultSet result = cluster.session().execute(select); + ResultSet result = sessionRule.session().execute(select); assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); Row row = result.iterator().next(); @@ -304,7 +332,7 @@ public void should_fail_when_named_value_missing() { .build(); // when executing that statement - cluster.session().execute(insert); + sessionRule.session().execute(insert); // then the server will throw an InvalidQueryException which is thrown up to the client. } @@ -331,7 +359,55 @@ public void should_use_positional_value_with_case_sensitive_id() { SimpleStatement.builder("SELECT count(*) FROM test2 WHERE k=:\"theKey\"") .addNamedValue(CqlIdentifier.fromCql("\"theKey\""), 0) .build(); - Row row = cluster.session().execute(statement).one(); + Row row = sessionRule.session().execute(statement).one(); assertThat(row.getLong(0)).isEqualTo(0); } + + @Test + public void should_use_page_size() { + Statement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + ResultSet result = sessionRule.session().execute(st); + + // Should have only fetched 10 (page size) rows. + assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + } + + @Test + public void should_use_consistencies() { + SimpleStatement st = + SimpleStatement.builder("SELECT * FROM test where k = ?") + .withConsistencyLevel(DefaultConsistencyLevel.TWO) + .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + simulacronSessionRule.session().execute(st); + + List logs = simulacron.cluster().getLogs().getQueryLogs(); + assertThat(logs).hasSize(1); + + QueryLog log = logs.get(0); + + Message message = log.getFrame().message; + assertThat(message).isInstanceOf(Query.class); + Query query = (Query) message; + assertThat(query.options.consistency).isEqualTo(DefaultConsistencyLevel.TWO.getProtocolCode()); + assertThat(query.options.serialConsistency) + .isEqualTo(DefaultConsistencyLevel.LOCAL_SERIAL.getProtocolCode()); + } + + @Test + public void should_use_timeout() { + simulacron + .cluster() + .prime(when("mock query").then(noRows()).delay(1500, TimeUnit.MILLISECONDS)); + SimpleStatement st = + SimpleStatement.builder("mock query") + .withTimeout(Duration.ofSeconds(1)) + .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + + thrown.expect(DriverTimeoutException.class); + thrown.expectMessage("Query timed out after PT1S"); + + simulacronSessionRule.session().execute(st); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java index 1842dd2e5d5..b7ab093cac8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java @@ -21,7 +21,9 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; /** A custom request that simply wraps an integer key and uses it as a parameter for a query. */ @@ -78,6 +80,12 @@ public Boolean isIdempotent() { return true; } + @Nullable + @Override + public Duration getTimeout() { + return null; + } + @Override public boolean isTracing() { return false; diff --git a/manual/core/logging/README.md b/manual/core/logging/README.md index 4537cc9382b..5d190373ec5 100644 --- a/manual/core/logging/README.md +++ b/manual/core/logging/README.md @@ -73,8 +73,9 @@ test). This is an anti-pattern that should be avoided in production (see 'request.warn-if-set-keyspace' in the configuration). WARN c.d.o.d.i.c.c.CqlPrepareHandlerBase - Re-preparing already prepared query. This is generally -an anti-pattern and will likely affect performance. Consider preparing the statement only once. -Query='...' +an anti-pattern and will likely affect performance. The cached version of the PreparedStatement +will be returned, which may use different bound statement execution parameters (CL, timeout, etc.) +from the current session.prepare call. Consider preparing the statement only once. Query='...' ``` #### INFO diff --git a/manual/core/statements/README.md b/manual/core/statements/README.md index 475fb9b7a10..21d7b54de25 100644 --- a/manual/core/statements/README.md +++ b/manual/core/statements/README.md @@ -10,23 +10,23 @@ implementations: query. Typically used for queries that are executed often, with different values. * [BatchStatement](batch/): a statement that groups multiple statements to be executed as a batch. -All statement types share a [common set of options][StatementBuilder], that can be set through -either setters or a builder: - - - -* [configuration profile](../configuration/) name, or the configuration profile itself if it's been - built dynamically. -* custom payload to send arbitrary key/value pairs with the request (you only use this if you have - a custom query handler on the server). -* idempotent flag. -* tracing flag. -* query timestamp. -* paging state. +All statement types share a [common set of execution attributes][StatementBuilder], that can be set +through either setters or a builder: + +* [execution profile](../configuration/) name, or the profile itself if it's been built dynamically. +* [idempotent flag](../idempotence/). +* [tracing flag](../tracing/). +* [query timestamp](../query_timestamps/). +* [page size and paging state](../paging/). * [per-query keyspace](per_query_keyspace/) (Cassandra 4 or above). +* [token-aware routing](../load_balancing/#token-aware) information (keyspace and key/token). +* normal and serial consistency level. +* query timeout. +* custom payload to send arbitrary key/value pairs with the request (you should only need this if + you have a custom query handler on the server). -When setting these options, keep in mind that statements are immutable, and every method returns a -different instance: +When setting these attributes, keep in mind that statements are immutable, and every method returns +a different instance: ```java SimpleStatement statement = @@ -40,6 +40,11 @@ statement.setIdempotent(true); statement = statement.setConfigProfileName("oltp").setIdempotent(true); ``` +Note that some attributes can either be set programmatically, or inherit a default value defined in +the [configuration](../configuration/). Namely, these are: idempotent flag, query timeout, +consistency levels and page size. We recommended the configuration approach whenever possible (you +can create execution profiles to capture common combinations of those options). + [Statement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Statement.html [StatementBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/StatementBuilder.html [execute]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#execute-com.datastax.oss.driver.api.core.cql.Statement- From a072951c29e4b19d366ad89d3516460e1e1f1692 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Tue, 10 Jul 2018 19:26:21 -0500 Subject: [PATCH 509/742] JAVA-1437: Enable SSL hostname validation by default (#1053) Motivation: When SSL is enabled, the driver should also enable validation of certificate common names against server hostname by default. The reasoning is that it seems logical to perform more validation than less by default when the user enables SSL. Modifications: Change advanced.ssl-engine-factory.hostname-validation default to true. Update manual and tests to reflect this change. Result: When DefaultSslEngineFactory is configured, hostname validation is enabled by default. The user may adjust the configuration to explicitly disable hostname validation. --- changelog/README.md | 1 + .../driver/internal/core/ssl/DefaultSslEngineFactory.java | 2 +- core/src/main/resources/reference.conf | 4 ++-- .../oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java | 6 ++++-- .../core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java | 2 +- ...efaultSslEngineFactoryPropertyBasedWithClientAuthIT.java | 4 +++- .../core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java | 2 ++ manual/core/ssl/README.md | 4 ++-- 8 files changed, 16 insertions(+), 9 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 324b0120376..dff9824f47b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1437: Enable SSL hostname validation by default - [improvement] JAVA-1879: Duplicate basic.request options as Request/Statement attributes - [improvement] JAVA-1870: Use sensible defaults in RequestLogger if config options are missing - [improvement] JAVA-1877: Use a separate reconnection schedule for the control connection diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 1516dfe79bd..312a7b0822a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -80,7 +80,7 @@ public DefaultSslEngineFactory(DriverContext driverContext) { this.cipherSuites = null; } this.requireHostnameValidation = - config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false); + config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, true); } @NonNull diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 8ef420b6f2d..b7e529a1227 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -407,8 +407,8 @@ datastax-java-driver { // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] # Whether or not to require validation that the hostname of the server certificate's common - # name matches the hostname of the server being connected to. - hostname-validation = false + # name matches the hostname of the server being connected to. If not set, defaults to true. + // hostname-validation = true # The locations and passwords used to access truststore and keystore contents. # These properties are optional. If either truststore-path or keystore-path are specified, diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index dd3e66b9691..a6dc4bbc15b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -33,6 +33,7 @@ public void should_connect_with_ssl() { SessionUtils.newSession( ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = false", "advanced.ssl-engine-factory.truststore-path = " + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), "advanced.ssl-engine-factory.truststore-password = " @@ -49,7 +50,6 @@ public void should_not_connect_if_hostname_validation_enabled_and_hostname_does_ SessionUtils.newSession( ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = true", "advanced.ssl-engine-factory.truststore-path = " + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), "advanced.ssl-engine-factory.truststore-password = " @@ -62,7 +62,9 @@ public void should_not_connect_if_hostname_validation_enabled_and_hostname_does_ public void should_not_connect_if_truststore_not_provided() { try (CqlSession session = SessionUtils.newSession( - ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = false")) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java index 2bf3839fc6d..4670abe5992 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java @@ -27,7 +27,7 @@ @Category(IsolatedTests.class) public class DefaultSslEngineFactoryPropertyBasedIT { - @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSsl().build(); + @ClassRule public static CustomCcmRule ccm = CustomCcmRule.builder().withSslLocalhostCn().build(); @Test public void should_connect_with_ssl() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java index 522c55ca980..ca98f0071cb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java @@ -41,7 +41,9 @@ public void should_connect_with_ssl_using_client_auth() { "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); try (CqlSession session = SessionUtils.newSession( - ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { + ccm, + "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = false")) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index f51c56f2d9c..1c8c3dd7c39 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -33,6 +33,7 @@ public void should_connect_with_ssl_using_client_auth() { SessionUtils.newSession( ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = false", "advanced.ssl-engine-factory.keystore-path = " + CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath(), "advanced.ssl-engine-factory.keystore-password = " @@ -51,6 +52,7 @@ public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() SessionUtils.newSession( ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", + "advanced.ssl-engine-factory.hostname-validation = false", "advanced.ssl-engine-factory.truststore-path = " + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), "advanced.ssl-engine-factory.truststore-password = " diff --git a/manual/core/ssl/README.md b/manual/core/ssl/README.md index 73182eb5488..9d28cf315e1 100644 --- a/manual/core/ssl/README.md +++ b/manual/core/ssl/README.md @@ -84,8 +84,8 @@ datastax-java-driver { // cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ] # Whether or not to require validation that the hostname of the server certificate's common - # name matches the hostname of the server being connected to. - hostname-validation = false + # name matches the hostname of the server being connected to. If not set, defaults to true. + // hostname-validation = true # The locations and passwords used to access truststore and keystore contents. # These properties are optional. If either truststore-path or keystore-path are specified, From e77aca05a19ec626241ad30afaa2bb86d287947f Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 18 Jun 2018 10:24:25 -0700 Subject: [PATCH 510/742] JAVA-1852: Document driver integration --- manual/core/README.md | 2 + manual/core/integration/README.md | 418 ++++++++++++++++++++++++++++++ 2 files changed, 420 insertions(+) create mode 100644 manual/core/integration/README.md diff --git a/manual/core/README.md b/manual/core/README.md index 496fc1c2aa4..2c85269d6e8 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -11,6 +11,8 @@ following coordinates: ``` +(For more details on setting up your build tool, see the [integration](integration/) page.) + ### Quick start Here's a short program that connects to Cassandra and executes a query: diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md new file mode 100644 index 00000000000..06dcdbc620e --- /dev/null +++ b/manual/core/integration/README.md @@ -0,0 +1,418 @@ +## Integration + +This page contains various information on how to integrate the driver in your application. + +### Minimal project structure + +We publish the driver to [Maven central][central_oss]. Most modern build tools can download the +dependency automatically. + +#### Maven + +Create the following 4 files: + +``` +$ find . -type f +./pom.xml +./src/main/resources/application.conf +./src/main/resources/logback.xml +./src/main/java/Main.java +``` + +##### Project descriptor + +`pom.xml` is the [Project Object Model][maven_pom] that describes your application. We declare the +dependencies, and tell Maven that we're going to use Java 8: + +```xml + + + 4.0.0 + + com.example.yourcompany + yourapp + 1.0.0-SNAPSHOT + + + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + ch.qos.logback + logback-classic + 1.2.3 + + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + +``` + +##### Application configuration + +`application.conf` is not stricly necessary, but it illustrates an important point about the +driver's [configuration](../configuration/): you override any of the driver's default options here. + +``` +datastax-java-driver { + basic.session-name = poc +} +``` + +In this case, we just specify a custom name for our session, it will appear in the logs. + +##### Logging configuration + +For this example, we choose Logback as our [logging framework](../logging/) (we added the dependency +in `pom.xml`). `logback.xml` configures it to send the driver's `INFO` logs to the console. + +```xml + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + +``` + +Again, this is not strictly necessary: a truly minimal example could run without the Logback +dependency, or this file; but the default behavior is a bit verbose. + +##### Main class + +`Main.java` is the canonical example introduced in our [quick start](../#quick-start); it connects +to Cassandra, queries the server version and prints it: + +```java +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; + +public class Main { + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().build()) { + ResultSet rs = session.execute("SELECT release_version FROM system.local"); + System.out.println(rs.one().getString(0)); + } + } +} +``` + +Make sure you have a Cassandra instance running on 127.0.0.1:9042 (otherwise, you use +[CqlSession.builder().addContactPoint()][SessionBuilder.addContactPoint] to use a different +address). + +##### Running + +To launch the program from the command line, use: + +``` +$ mvn compile exec:java -Dexec.mainClass=Main +``` + +You should see output similar to: + +``` +... +[INFO] ------------------------------------------------------------------------ +[INFO] Building yourapp 1.0.0-SNAPSHOT +[INFO] ------------------------------------------------------------------------ +... (at this point, Maven will download the dependencies the first time) +[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ yourapp --- +[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! +[INFO] Copying 1 resource +[INFO] +[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ yourapp --- +[INFO] Nothing to compile - all classes are up to date +[INFO] +[INFO] --- exec-maven-plugin:1.3.1:java (default-cli) @ yourapp --- +11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta1 +11:39:45.648 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision +11:39:45.649 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 +3.11.2 +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 11.777 s +[INFO] Finished at: 2018-06-18T11:32:49-08:00 +[INFO] Final Memory: 16M/277M +[INFO] ------------------------------------------------------------------------ +``` + +#### Gradle + +[Initialize a new project][gradle_init] with Gradle. + +Modify `build.gradle` to add the dependencies: + +```groovy +group 'com.example.yourcompany' +version '1.0.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-alpha4-SNAPSHOT' + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' +} +``` + +Then place [application.conf](#application-configuration), [logback.xml](#logging-configuration) and +[Main.java](#main-class) in the same locations, and with the same contents, as in the Maven example: + +``` +./src/main/resources/application.conf +./src/main/resources/logback.xml +./src/main/java/Main.java +``` + +Optionally, if you want to run from the command line, add the following at the end of +`build.gradle`: + +```groovy +task execute(type:JavaExec) { + main = 'Main' + classpath = sourceSets.main.runtimeClasspath +} +``` + +Then launch with: + +``` +$ ./gradlew execute +``` + +You should see output similar to: + +``` +$ ./gradlew execute +:compileJava +:processResources +:classes +:execute +13:32:25.339 [main] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-alpha4-SNAPSHOT +13:32:25.682 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision +13:32:25.683 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 +3.11.2 + +BUILD SUCCESSFUL +``` + +#### Manually (from the binary tarball) + +If your build tool can't fetch dependencies from Maven central, we publish a binary tarball on the +[DataStax download server][downloads]. + +The driver and its dependencies must be in the compile-time classpath. Application resources, such +as `application.conf` and `logback.xml` in our previous examples, must be in the runtime classpath. + +### Driver dependencies + +The driver depends on a number of third-party libraries; some of those dependencies are opt-in, +while others are present by default, but may be excluded under specific circumstances. + +Here's a rundown of what you can customize: + +#### Netty + +[Netty](https://netty.io/) is the NIO framework that powers the driver's networking layer. + +It is a required dependency, but we provide a a [shaded JAR](../shaded_jar/) that relocates it to a +different Java package; this is useful to avoid dependency hell if you already use Netty in another +part of your application. + +### Typesafe config + +[Typesafe config](https://lightbend.github.io/config/) is used for our file-based +[configuration](../configuration/). + +It is a required dependency if you use the driver's built-in configuration loader, but this can be +[completely overridden](../configuration/#bypassing-typesafe-config) with your own implementation, +that could use a different framework or an ad-hoc solution. + +In that case, you can exclude the dependency: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + com.typesafe + config + + + +``` + +#### Native libraries + +The driver performs native calls with [JNR](https://github.com/jnr). This is used in two cases: + +* to access a microsecond-precision clock in [timestamp generators](../query_timestamps/); +* to get the process ID when generating [UUIDs][Uuids]. + +In both cases, this is completely optional; if system calls are not available on the current +platform, or the libraries fail to load for any reason, the driver falls back to pure Java +workarounds. + +If you don't want to use system calls, or already know (from looking at the driver's logs) that they +are not available on your platform, you can exclude the following dependencies: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + com.github.jnr + jnr-ffi + + + com.github.jnr + jnr-posix + + + +``` + +#### Compression libraries + +The driver supports compression with either [LZ4](https://github.com/jpountz/lz4-java) or +[Snappy](http://google.github.io/snappy/). + +These dependencies are optional; you have to add them explicitly in your application in order to +enable compression. See the [Compression](../compression/) page for more details. + +#### Metrics + +The driver exposes [metrics](../metrics/) through the +[Dropwizard](http://metrics.dropwizard.io/4.0.0/manual/index.html) library. + +The dependency is declared as required, but metrics are optional. If you've disabled all metrics, +and never call [Session.getMetrics] anywhere in your application, you can remove the dependency: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + io.dropwizard.metrics + metrics-core + + + +``` + +In addition, "timer" metrics use [HdrHistogram](http://hdrhistogram.github.io/HdrHistogram/) to +record latency percentiles. At the time of writing, these metrics are: `cql-requests`, +`throttling.delay` and `cql-messages`; you can also identify them by reading the comments in the +[configuration reference](../configuration/reference/) (look for "exposed as a Timer"). + +If all of these metrics are disabled, you can remove the dependency: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + org.hdrhistogram + HdrHistogram + + + +``` + +#### Documenting annotations + +The driver team uses annotations to document certain aspects of the code: + +* thread safety with [Java Concurrency in Practice](http://jcip.net/annotations/doc/index.html) + annotations `@Immutable`, `@ThreadSafe`, `@NotThreadSafe` and `@GuardedBy`; +* nullability with [SpotBugs](https://spotbugs.github.io/) annotations `@Nullable` and `@NonNull`. + +This is mostly used during development; while these annotations are retained in class files, they +serve no purpose at runtime. Whether to make them optional dependencies is up for debate: + +* if they are required, it's two additional JARs that every client has to pull in (admittedly they + are quite small); +* if they are optional, the bytecode will reference missing classes. This is not a blocker, but it + can manifest to the end user in a few ways: + + * if you navigate to the driver's sources in your IDE, the missing annotations will be + highlighted as errors (note however that modern IDEs such as IntelliJ IDEA can analyze + nullability annotations even if they are missing from the classpath); + * this produces compiler warnings (see [this discussion][guava] of a similar issue for Google's + Guava library). + +The Java driver team has decided to make the dependencies optional. If that creates any problem for +you, the workaround is to redeclare them explicitly in your application: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-beta1 + + + com.github.stephenc.jcip + jcip-annotations + 1.0-1 + + + com.github.spotbugs + spotbugs-annotations + 3.1.3 + +``` + +#### Mandatory dependencies + +The remaining core driver dependencies are the only ones that are truly mandatory: + +* the [native protocol](https://github.com/datastax/native-protocol) layer. This is essentially part + of the driver code, but was externalized for reuse in other projects; +* `java-driver-shaded-guava`, a shaded version of [Guava](https://github.com/google/guava). It is + relocated to a different package, and only used by internal driver code, so it should be + completely transparent to third-party code; +* the [SLF4J](https://www.slf4j.org/) API for [logging](../logging/). + +[central_oss]: https://search.maven.org/#search%7Cga%7C1%7Ccom.datastax.oss +[maven_pom]: https://maven.apache.org/guides/introduction/introduction-to-the-pom.html +[gradle_init]: https://guides.gradle.org/creating-new-gradle-builds/ +[downloads]: http://downloads.datastax.com/java-driver/ +[guava]: https://github.com/google/guava/issues/2721 + +[Session.getMetrics]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#getMetrics-- +[SessionBuilder.addContactPoint]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/SessionBuilder.html#addContactPoint-java.net.InetSocketAddress- +[Uuids]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/uuid/Uuids.html From de439f49ba594ad526ce4eca34d47adb55778118 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 27 Jun 2018 07:49:22 -0700 Subject: [PATCH 511/742] JAVA-1897: Improve extensibility of schema metadata classes Motivation: Custom extensions of the server might add new fields to schema system tables, calling for dedicated classes, e.g. MyCustomKeyspaceMetadata. If Metadata methods declare exact types, converting to those custom types will require a cast. Modifications: Use bounded types for all Metadata methods, e.g. Map getKeyspaces(); Adapt internal classes so that custom schema parsing logic can be plugged in more easily. Result: Custom extensions can now extend the interfaces to specialize the types: interface MyCustomSession extends Session { MyCustomMetadata getMetadata(); } interface MyCustomMetadata extends Metadata { Map getKeyspaces(); } The client doesn't need to cast anymore. --- changelog/README.md | 1 + .../driver/api/core/metadata/Metadata.java | 8 +- .../metadata/schema/KeyspaceMetadata.java | 47 ++-- .../metadata/schema/RelationMetadata.java | 12 +- .../core/metadata/schema/TableMetadata.java | 5 +- .../oss/driver/api/core/session/Session.java | 4 +- .../driver/internal/core/CqlIdentifiers.java | 3 +- .../DefaultLoadBalancingPolicy.java | 2 +- .../core/metadata/DefaultMetadata.java | 23 +- .../metadata/InitContactPointsRefresh.java | 12 +- .../core/metadata/MetadataManager.java | 8 +- .../schema/parsing/AggregateParser.java | 6 +- .../schema/parsing/CassandraSchemaParser.java | 182 ++++++++++++ .../DataTypeClassNameCompositeParser.java | 10 +- .../schema/parsing/DataTypeParser.java | 2 +- .../parsing/DefaultSchemaParserFactory.java | 2 +- .../schema/parsing/FunctionParser.java | 6 +- .../metadata/schema/parsing/RawColumn.java | 151 ++++++---- .../schema/parsing/RelationParser.java | 5 +- .../metadata/schema/parsing/SchemaParser.java | 150 +--------- .../schema/parsing/SimpleJsonParser.java | 6 +- .../metadata/schema/parsing/TableParser.java | 75 +---- .../schema/parsing/UserDefinedTypeParser.java | 6 +- .../metadata/schema/parsing/ViewParser.java | 18 +- .../queries/Cassandra21SchemaQueries.java | 2 +- .../queries/Cassandra22SchemaQueries.java | 2 +- .../queries/Cassandra3SchemaQueries.java | 2 +- .../queries/CassandraSchemaQueries.java | 188 +++++++++++++ .../schema/queries/CassandraSchemaRows.java | 260 ++++++++++++++++++ .../queries/DefaultSchemaQueriesFactory.java | 11 +- .../schema/queries/SchemaQueries.java | 180 +----------- .../metadata/schema/queries/SchemaRows.java | 192 ++----------- .../schema/refresh/SchemaRefresh.java | 8 +- .../core/metadata/token/DefaultTokenMap.java | 6 +- .../internal/core/session/SessionWrapper.java | 4 +- ...faultLoadBalancingPolicyQueryPlanTest.java | 2 +- .../core/metadata/AddNodeRefreshTest.java | 7 +- .../metadata/DefaultMetadataTokenMapTest.java | 19 +- .../metadata/FullNodeListRefreshTest.java | 7 +- .../core/metadata/RemoveNodeRefreshTest.java | 7 +- .../schema/parsing/SchemaParserTest.java | 7 +- .../schema/parsing/SchemaParserTestBase.java | 3 + .../schema/parsing/TableParserTest.java | 13 +- .../schema/parsing/ViewParserTest.java | 9 +- .../queries/Cassandra21SchemaQueriesTest.java | 30 +- .../queries/Cassandra22SchemaQueriesTest.java | 39 +-- .../queries/Cassandra3SchemaQueriesTest.java | 67 ++--- .../driver/api/core/metadata/DescribeIT.java | 6 +- .../api/core/metadata/SchemaChangesIT.java | 13 +- .../driver/api/core/metadata/SchemaIT.java | 2 +- 50 files changed, 1034 insertions(+), 796 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java diff --git a/changelog/README.md b/changelog/README.md index dff9824f47b..36e199b5ca4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1897: Improve extensibility of schema metadata classes - [improvement] JAVA-1437: Enable SSL hostname validation by default - [improvement] JAVA-1879: Duplicate basic.request options as Request/Statement attributes - [improvement] JAVA-1870: Use sensible defaults in RequestLogger if config options are missing diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index f2bd0433a9c..449893e4d80 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -53,10 +53,10 @@ public interface Metadata { * @see DefaultDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ @NonNull - Map getKeyspaces(); + Map getKeyspaces(); @NonNull - default Optional getKeyspace(@NonNull CqlIdentifier keyspaceId) { + default Optional getKeyspace(@NonNull CqlIdentifier keyspaceId) { return Optional.ofNullable(getKeyspaces().get(keyspaceId)); } @@ -65,7 +65,7 @@ default Optional getKeyspace(@NonNull CqlIdentifier keyspaceId * getKeyspace(CqlIdentifier.fromCql(keyspaceName))}. */ @NonNull - default Optional getKeyspace(@NonNull String keyspaceName) { + default Optional getKeyspace(@NonNull String keyspaceName) { return getKeyspace(CqlIdentifier.fromCql(keyspaceName)); } @@ -78,5 +78,5 @@ default Optional getKeyspace(@NonNull String keyspaceName) { * @see DefaultDriverOption#METADATA_TOKEN_MAP_ENABLED */ @NonNull - Optional getTokenMap(); + Optional getTokenMap(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java index 39a68f342cc..09ed16037bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java @@ -39,25 +39,26 @@ public interface KeyspaceMetadata extends Describable { Map getReplication(); @NonNull - Map getTables(); + Map getTables(); @NonNull - default Optional getTable(@NonNull CqlIdentifier tableId) { + default Optional getTable(@NonNull CqlIdentifier tableId) { return Optional.ofNullable(getTables().get(tableId)); } /** Shortcut for {@link #getTable(CqlIdentifier) getTable(CqlIdentifier.fromCql(tableName))}. */ @NonNull - default Optional getTable(@NonNull String tableName) { + default Optional getTable(@NonNull String tableName) { return getTable(CqlIdentifier.fromCql(tableName)); } @NonNull - Map getViews(); + Map getViews(); /** Gets the views based on a given table. */ @NonNull - default Map getViewsOnTable(@NonNull CqlIdentifier tableId) { + default Map getViewsOnTable( + @NonNull CqlIdentifier tableId) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (ViewMetadata view : getViews().values()) { if (view.getBaseTable().equals(tableId)) { @@ -68,21 +69,21 @@ default Map getViewsOnTable(@NonNull CqlIdentifier } @NonNull - default Optional getView(@NonNull CqlIdentifier viewId) { + default Optional getView(@NonNull CqlIdentifier viewId) { return Optional.ofNullable(getViews().get(viewId)); } /** Shortcut for {@link #getView(CqlIdentifier) getView(CqlIdentifier.fromCql(viewName))}. */ @NonNull - default Optional getView(@NonNull String viewName) { + default Optional getView(@NonNull String viewName) { return getView(CqlIdentifier.fromCql(viewName)); } @NonNull - Map getUserDefinedTypes(); + Map getUserDefinedTypes(); @NonNull - default Optional getUserDefinedType(@NonNull CqlIdentifier typeId) { + default Optional getUserDefinedType(@NonNull CqlIdentifier typeId) { return Optional.ofNullable(getUserDefinedTypes().get(typeId)); } @@ -91,20 +92,21 @@ default Optional getUserDefinedType(@NonNull CqlIdentifier type * getUserDefinedType(CqlIdentifier.fromCql(typeName))}. */ @NonNull - default Optional getUserDefinedType(@NonNull String typeName) { + default Optional getUserDefinedType(@NonNull String typeName) { return getUserDefinedType(CqlIdentifier.fromCql(typeName)); } @NonNull - Map getFunctions(); + Map getFunctions(); @NonNull - default Optional getFunction(@NonNull FunctionSignature functionSignature) { + default Optional getFunction( + @NonNull FunctionSignature functionSignature) { return Optional.ofNullable(getFunctions().get(functionSignature)); } @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull CqlIdentifier functionId, @NonNull Iterable parameterTypes) { return Optional.ofNullable( getFunctions().get(new FunctionSignature(functionId, parameterTypes))); @@ -115,7 +117,7 @@ default Optional getFunction( * getFunction(CqlIdentifier.fromCql(functionName), parameterTypes)}. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull String functionName, @NonNull Iterable parameterTypes) { return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); } @@ -124,7 +126,7 @@ default Optional getFunction( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull CqlIdentifier functionId, @NonNull DataType... parameterTypes) { return Optional.ofNullable( getFunctions().get(new FunctionSignature(functionId, parameterTypes))); @@ -137,21 +139,22 @@ default Optional getFunction( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull String functionName, @NonNull DataType... parameterTypes) { return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); } @NonNull - Map getAggregates(); + Map getAggregates(); @NonNull - default Optional getAggregate(@NonNull FunctionSignature aggregateSignature) { + default Optional getAggregate( + @NonNull FunctionSignature aggregateSignature) { return Optional.ofNullable(getAggregates().get(aggregateSignature)); } @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull CqlIdentifier aggregateId, @NonNull Iterable parameterTypes) { return Optional.ofNullable( getAggregates().get(new FunctionSignature(aggregateId, parameterTypes))); @@ -162,7 +165,7 @@ default Optional getAggregate( * getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes)}. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull String aggregateName, @NonNull Iterable parameterTypes) { return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); } @@ -171,7 +174,7 @@ default Optional getAggregate( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull CqlIdentifier aggregateId, @NonNull DataType... parameterTypes) { return Optional.ofNullable( getAggregates().get(new FunctionSignature(aggregateId, parameterTypes))); @@ -184,7 +187,7 @@ default Optional getAggregate( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull String aggregateName, @NonNull DataType... parameterTypes) { return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java index ed7fa231998..f1c30c59759 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java @@ -46,7 +46,7 @@ public interface RelationMetadata extends Describable { * @see #getClusteringColumns() */ @NonNull - default List getPrimaryKey() { + default List getPrimaryKey() { return ImmutableList.builder() .addAll(getPartitionKey()) .addAll(getClusteringColumns().keySet()) @@ -54,16 +54,16 @@ default List getPrimaryKey() { } @NonNull - List getPartitionKey(); + List getPartitionKey(); @NonNull - Map getClusteringColumns(); + Map getClusteringColumns(); @NonNull - Map getColumns(); + Map getColumns(); @NonNull - default Optional getColumn(@NonNull CqlIdentifier columnId) { + default Optional getColumn(@NonNull CqlIdentifier columnId) { return Optional.ofNullable(getColumns().get(columnId)); } @@ -71,7 +71,7 @@ default Optional getColumn(@NonNull CqlIdentifier columnId) { * Shortcut for {@link #getColumn(CqlIdentifier) getColumn(CqlIdentifier.fromCql(columnName))}. */ @NonNull - default Optional getColumn(@NonNull String columnName) { + default Optional getColumn(@NonNull String columnName) { return getColumn(CqlIdentifier.fromCql(columnName)); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java index feb2d95fcd3..8caf3baa2f3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java @@ -27,7 +27,7 @@ public interface TableMetadata extends RelationMetadata { boolean isCompactStorage(); @NonNull - Map getIndexes(); + Map getIndexes(); @NonNull @Override @@ -82,7 +82,8 @@ default String describe(boolean pretty) { if (getClusteringColumns().containsValue(ClusteringOrder.DESC)) { builder.andWith().append("CLUSTERING ORDER BY ("); boolean first = true; - for (Map.Entry entry : getClusteringColumns().entrySet()) { + for (Map.Entry entry : + getClusteringColumns().entrySet()) { if (first) { first = false; } else { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 2e1b0ed9bc0..482a14778c8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -113,7 +113,7 @@ public interface Session extends AsyncAutoCloseable { * complete. Otherwise, a completed future with the current metadata. */ @NonNull - CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue); + CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue); /** * Force an immediate refresh of the schema metadata, even if it is currently disabled (either in @@ -123,7 +123,7 @@ public interface Session extends AsyncAutoCloseable { * #getMetadata()} when that future completes). */ @NonNull - CompletionStage refreshSchemaAsync(); + CompletionStage refreshSchemaAsync(); /** * Convenience method to call {@link #refreshSchemaAsync()} and block for the result. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CqlIdentifiers.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CqlIdentifiers.java index 3e20fe96cb9..43b2b2fe249 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CqlIdentifiers.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CqlIdentifiers.java @@ -18,11 +18,12 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import java.util.List; import java.util.Map; public class CqlIdentifiers { - public static Iterable wrap(Iterable in) { + public static List wrap(Iterable in) { ImmutableList.Builder out = ImmutableList.builder(); for (String name : in) { out.add(CqlIdentifier.fromCql(name)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index b1d5abfb885..54c61b4365a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -243,7 +243,7 @@ private Set getReplicas(Request request, Session session) { return Collections.emptySet(); } - Optional maybeTokenMap = metadataManager.getMetadata().getTokenMap(); + Optional maybeTokenMap = metadataManager.getMetadata().getTokenMap(); if (maybeTokenMap.isPresent()) { TokenMap tokenMap = maybeTokenMap.get(); return (token != null) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index a083a029cb6..469dc829631 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -45,19 +45,16 @@ @Immutable public class DefaultMetadata implements Metadata { private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadata.class); - public static DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap()); + public static DefaultMetadata EMPTY = + new DefaultMetadata(Collections.emptyMap(), Collections.emptyMap(), null); - private final Map nodes; - private final Map keyspaces; - private final TokenMap tokenMap; + protected final Map nodes; + protected final Map keyspaces; + protected final TokenMap tokenMap; - public DefaultMetadata(Map nodes) { - this(ImmutableMap.copyOf(nodes), Collections.emptyMap(), null); - } - - private DefaultMetadata( + protected DefaultMetadata( Map nodes, - Map keyspaces, + Map keyspaces, TokenMap tokenMap) { this.nodes = nodes; this.keyspaces = keyspaces; @@ -72,7 +69,7 @@ public Map getNodes() { @NonNull @Override - public Map getKeyspaces() { + public Map getKeyspaces() { return keyspaces; } @@ -121,9 +118,9 @@ public DefaultMetadata withSchema( } @Nullable - private TokenMap rebuildTokenMap( + protected TokenMap rebuildTokenMap( Map newNodes, - Map newKeyspaces, + Map newKeyspaces, boolean tokenMapEnabled, boolean forceFullRebuild, TokenFactory tokenFactory, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index 51a5873de27..8d806562ab8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -39,7 +39,7 @@ class InitContactPointsRefresh implements MetadataRefresh { @Override public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - assert oldMetadata == DefaultMetadata.EMPTY; + String logPrefix = context.sessionName(); LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); @@ -47,7 +47,13 @@ public Result compute( for (InetSocketAddress address : contactPoints) { newNodes.put(address, new DefaultNode(address, context)); } - return new Result(new DefaultMetadata(newNodes.build())); - // No token map refresh, because we don't have enough information yet + return new Result( + oldMetadata.withNodes( + newNodes.build(), + // At this stage there is no token map and we don't have the info to refresh it yet + false, + false, + null, + context)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 997f146433a..de9c03d8ab4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -69,13 +69,17 @@ public class MetadataManager implements AsyncAutoCloseable { private volatile Set providedContactPoints; public MetadataManager(InternalDriverContext context) { + this(context, DefaultMetadata.EMPTY); + } + + protected MetadataManager(InternalDriverContext context, DefaultMetadata initialMetadata) { this.context = context; + this.metadata = initialMetadata; this.logPrefix = context.sessionName(); this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); this.config = context.config().getDefaultProfile(); this.singleThreaded = new SingleThreaded(context, config); this.controlConnection = context.controlConnection(); - this.metadata = DefaultMetadata.EMPTY; this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = config.getStringList( @@ -417,7 +421,7 @@ private CompletionStage maybeInitControlConnection() { private Void parseAndApplySchemaRows(SchemaRows schemaRows) { assert adminExecutor.inEventLoop(); - assert schemaRows.refreshFuture == currentSchemaRefresh; + assert schemaRows.refreshFuture() == currentSchemaRefresh; try { SchemaRefresh schemaRefresh = schemaParserFactory.newInstance(schemaRows).parse(); long start = System.nanoTime(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java index 2d4b1a5e70b..d31460e34ff 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java @@ -31,16 +31,16 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class AggregateParser { +public class AggregateParser { private final DataTypeParser dataTypeParser; private final InternalDriverContext context; - AggregateParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + public AggregateParser(DataTypeParser dataTypeParser, InternalDriverContext context) { this.dataTypeParser = dataTypeParser; this.context = context; } - AggregateMetadata parseAggregate( + public AggregateMetadata parseAggregate( AdminRow row, CqlIdentifier keyspaceId, Map userDefinedTypes) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java new file mode 100644 index 00000000000..cd722ed09aa --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java @@ -0,0 +1,182 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.parsing; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.schema.DefaultKeyspaceMetadata; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; +import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.datastax.oss.driver.shaded.guava.common.base.MoreObjects; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import java.util.Map; +import net.jcip.annotations.ThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default parser implementation for Cassandra. + * + *

          For modularity, the code for each element row is split into separate classes (schema stuff is + * not on the hot path, so creating a few extra objects doesn't matter). + */ +@ThreadSafe +public class CassandraSchemaParser implements SchemaParser { + + private static final Logger LOG = LoggerFactory.getLogger(CassandraSchemaParser.class); + + private final SchemaRows rows; + private final UserDefinedTypeParser userDefinedTypeParser; + private final TableParser tableParser; + private final ViewParser viewParser; + private final FunctionParser functionParser; + private final AggregateParser aggregateParser; + private final String logPrefix; + private final long startTimeNs = System.nanoTime(); + + public CassandraSchemaParser(SchemaRows rows, InternalDriverContext context) { + this.rows = rows; + this.logPrefix = context.sessionName(); + + this.userDefinedTypeParser = new UserDefinedTypeParser(rows.dataTypeParser(), context); + this.tableParser = new TableParser(rows, context); + this.viewParser = new ViewParser(rows, context); + this.functionParser = new FunctionParser(rows.dataTypeParser(), context); + this.aggregateParser = new AggregateParser(rows.dataTypeParser(), context); + } + + @Override + public SchemaRefresh parse() { + ImmutableMap.Builder keyspacesBuilder = ImmutableMap.builder(); + for (AdminRow row : rows.keyspaces()) { + KeyspaceMetadata keyspace = parseKeyspace(row); + keyspacesBuilder.put(keyspace.getName(), keyspace); + } + SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build()); + LOG.debug("[{}] Schema parsing took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); + return refresh; + } + + private KeyspaceMetadata parseKeyspace(AdminRow keyspaceRow) { + + // Cassandra <= 2.2 + // CREATE TABLE system.schema_keyspaces ( + // keyspace_name text PRIMARY KEY, + // durable_writes boolean, + // strategy_class text, + // strategy_options text + // ) + // + // Cassandra >= 3.0: + // CREATE TABLE system_schema.keyspaces ( + // keyspace_name text PRIMARY KEY, + // durable_writes boolean, + // replication frozen> + // ) + CqlIdentifier keyspaceId = CqlIdentifier.fromInternal(keyspaceRow.getString("keyspace_name")); + boolean durableWrites = + MoreObjects.firstNonNull(keyspaceRow.getBoolean("durable_writes"), false); + + Map replicationOptions; + if (keyspaceRow.contains("strategy_class")) { + String strategyClass = keyspaceRow.getString("strategy_class"); + Map strategyOptions = + SimpleJsonParser.parseStringMap(keyspaceRow.getString("strategy_options")); + replicationOptions = + ImmutableMap.builder() + .putAll(strategyOptions) + .put("class", strategyClass) + .build(); + } else { + replicationOptions = keyspaceRow.getMapOfStringToString("replication"); + } + + Map types = parseTypes(keyspaceId); + + return new DefaultKeyspaceMetadata( + keyspaceId, + durableWrites, + replicationOptions, + types, + parseTables(keyspaceId, types), + parseViews(keyspaceId, types), + parseFunctions(keyspaceId, types), + parseAggregates(keyspaceId, types)); + } + + private Map parseTypes(CqlIdentifier keyspaceId) { + return userDefinedTypeParser.parse(rows.types().get(keyspaceId), keyspaceId); + } + + private Map parseTables( + CqlIdentifier keyspaceId, Map types) { + ImmutableMap.Builder tablesBuilder = ImmutableMap.builder(); + for (AdminRow tableRow : rows.tables().get(keyspaceId)) { + TableMetadata table = tableParser.parseTable(tableRow, keyspaceId, types); + if (table != null) { + tablesBuilder.put(table.getName(), table); + } + } + return tablesBuilder.build(); + } + + private Map parseViews( + CqlIdentifier keyspaceId, Map types) { + ImmutableMap.Builder viewsBuilder = ImmutableMap.builder(); + for (AdminRow viewRow : rows.views().get(keyspaceId)) { + ViewMetadata view = viewParser.parseView(viewRow, keyspaceId, types); + if (view != null) { + viewsBuilder.put(view.getName(), view); + } + } + return viewsBuilder.build(); + } + + private Map parseFunctions( + CqlIdentifier keyspaceId, Map types) { + ImmutableMap.Builder functionsBuilder = + ImmutableMap.builder(); + for (AdminRow functionRow : rows.functions().get(keyspaceId)) { + FunctionMetadata function = functionParser.parseFunction(functionRow, keyspaceId, types); + if (function != null) { + functionsBuilder.put(function.getSignature(), function); + } + } + return functionsBuilder.build(); + } + + private Map parseAggregates( + CqlIdentifier keyspaceId, Map types) { + ImmutableMap.Builder aggregatesBuilder = + ImmutableMap.builder(); + for (AdminRow aggregateRow : rows.aggregates().get(keyspaceId)) { + AggregateMetadata aggregate = aggregateParser.parseAggregate(aggregateRow, keyspaceId, types); + if (aggregate != null) { + aggregatesBuilder.put(aggregate.getSignature(), aggregate); + } + } + return aggregatesBuilder.build(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java index 155785e96d0..225004ca9ab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameCompositeParser.java @@ -65,11 +65,11 @@ public ParseResult parseWithComposite( return new ParseResult(true, types, reversed, collections); } - static class ParseResult { - final boolean isComposite; - final List types; - final List reversed; - final Map collections; + public static class ParseResult { + public final boolean isComposite; + public final List types; + public final List reversed; + public final Map collections; private ParseResult(DataType type, boolean reversed) { this( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java index 6c14d65a453..42ee4c37b05 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeParser.java @@ -26,7 +26,7 @@ import java.util.Map; /** Parses data types from their string representation in schema tables. */ -interface DataTypeParser { +public interface DataTypeParser { /** * @param userTypes the UDTs in the current keyspace, if we know them already. This is used to diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java index 94a5047650d..9a4a5bf148a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DefaultSchemaParserFactory.java @@ -30,6 +30,6 @@ public DefaultSchemaParserFactory(InternalDriverContext context) { @Override public SchemaParser newInstance(SchemaRows rows) { - return new SchemaParser(rows, context); + return new CassandraSchemaParser(rows, context); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java index c212190f993..6da4fa2637e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java @@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory; @ThreadSafe -class FunctionParser { +public class FunctionParser { private static final Logger LOG = LoggerFactory.getLogger(FunctionParser.class); @@ -40,13 +40,13 @@ class FunctionParser { private final InternalDriverContext context; private final String logPrefix; - FunctionParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + public FunctionParser(DataTypeParser dataTypeParser, InternalDriverContext context) { this.dataTypeParser = dataTypeParser; this.context = context; this.logPrefix = context.sessionName(); } - FunctionMetadata parseFunction( + public FunctionMetadata parseFunction( AdminRow row, CqlIdentifier keyspaceId, Map userDefinedTypes) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java index 3d02c907cbf..fc79a830dc8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.ListIterator; import java.util.Map; import net.jcip.annotations.NotThreadSafe; @@ -32,56 +33,22 @@ * instances. */ @NotThreadSafe -class RawColumn implements Comparable { +public class RawColumn implements Comparable { - static List toRawColumns( - Collection rows, - CqlIdentifier keyspaceId, - Map userTypes) { - if (rows.isEmpty()) { - return Collections.emptyList(); - } else { - // Use a mutable list, we might remove some elements later - List result = Lists.newArrayListWithExpectedSize(rows.size()); - for (AdminRow row : rows) { - result.add(new RawColumn(row, keyspaceId, userTypes)); - } - return result; - } - } - - enum Kind { - PARTITION_KEY, - CLUSTERING_COLUMN, - REGULAR, - COMPACT_VALUE, - STATIC, - ; - - static Kind from(String s) { - if ("partition_key".equalsIgnoreCase(s)) { - return PARTITION_KEY; - } else if ("clustering_key".equalsIgnoreCase(s) || "clustering".equalsIgnoreCase(s)) { - return CLUSTERING_COLUMN; - } else if ("regular".equalsIgnoreCase(s)) { - return REGULAR; - } else if ("compact_value".equalsIgnoreCase(s)) { - return COMPACT_VALUE; - } else if ("static".equalsIgnoreCase(s)) { - return STATIC; - } - throw new IllegalArgumentException("Unknown column kind " + s); - } - } + public static final String KIND_PARTITION_KEY = "partition_key"; + public static final String KIND_CLUSTERING_COLUMN = "clustering"; + public static final String KIND_REGULAR = "regular"; + public static final String KIND_COMPACT_VALUE = "compact_value"; + public static final String KIND_STATIC = "static"; - final CqlIdentifier name; - Kind kind; - final int position; - final String dataType; - final boolean reversed; - final String indexName; - final String indexType; - final Map indexOptions; + public final CqlIdentifier name; + public String kind; + public final int position; + public final String dataType; + public final boolean reversed; + public final String indexName; + public final String indexType; + public final Map indexOptions; private RawColumn( AdminRow row, CqlIdentifier keyspaceId, Map userTypes) { @@ -112,7 +79,15 @@ private RawColumn( // PRIMARY KEY (keyspace_name, table_name, column_name) // ) WITH CLUSTERING ORDER BY (table_name ASC, column_name ASC) this.name = CqlIdentifier.fromInternal(row.getString("column_name")); - this.kind = Kind.from((row.contains("kind") ? row.getString("kind") : row.getString("type"))); + if (row.contains("kind")) { + this.kind = row.getString("kind"); + } else { + this.kind = row.getString("type"); + // remap clustering_key to KIND_CLUSTERING_COLUMN so code doesn't have to check for both. + if (this.kind.equals("clustering_key")) { + this.kind = KIND_CLUSTERING_COLUMN; + } + } Integer rawPosition = row.contains("position") ? row.getInteger("position") : row.getInteger("component_index"); @@ -137,12 +112,86 @@ private RawColumn( public int compareTo(@NonNull RawColumn that) { // First, order by kind. Then order partition key and clustering columns by position. For // other kinds, order by column name. - if (this.kind != that.kind) { + if (!this.kind.equals(that.kind)) { return this.kind.compareTo(that.kind); - } else if (kind == Kind.PARTITION_KEY || kind == Kind.CLUSTERING_COLUMN) { + } else if (kind.equals(KIND_PARTITION_KEY) || kind.equals(KIND_CLUSTERING_COLUMN)) { return Integer.compare(this.position, that.position); } else { return this.name.asInternal().compareTo(that.name.asInternal()); } } + + public static List toRawColumns( + Collection rows, + CqlIdentifier keyspaceId, + Map userTypes) { + if (rows.isEmpty()) { + return Collections.emptyList(); + } else { + // Use a mutable list, we might remove some elements later + List result = Lists.newArrayListWithExpectedSize(rows.size()); + for (AdminRow row : rows) { + result.add(new RawColumn(row, keyspaceId, userTypes)); + } + return result; + } + } + + /** + * Helper method to filter columns while parsing a table's metadata. + * + *

          Upon migration from thrift to CQL, we internally create a pair of surrogate + * clustering/regular columns for compact static tables. These columns shouldn't be exposed to the + * user but are currently returned by C*. We also need to remove the static keyword for all other + * columns in the table. + */ + public static void pruneStaticCompactTableColumns(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + switch (column.kind) { + case KIND_CLUSTERING_COLUMN: + case KIND_REGULAR: + iterator.remove(); + break; + case KIND_STATIC: + column.kind = KIND_REGULAR; + break; + default: + // nothing to do + } + } + } + + /** + * Helper method to filter columns while parsing a table's metadata. + * + *

          Upon migration from thrift to CQL, we internally create a surrogate column "value" of type + * EmptyType for dense tables. This column shouldn't be exposed to the user but is currently + * returned by C*. + */ + public static void pruneDenseTableColumnsV3(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + if (column.kind.equals(KIND_REGULAR) && "empty".equals(column.dataType)) { + iterator.remove(); + } + } + } + + /** + * Helper method to filter columns while parsing a table's metadata. + * + *

          This is similar to {@link #pruneDenseTableColumnsV3(List)}, but for legacy C* versions. + */ + public static void pruneDenseTableColumnsV2(List columns) { + ListIterator iterator = columns.listIterator(); + while (iterator.hasNext()) { + RawColumn column = iterator.next(); + if (column.kind.equals(KIND_COMPACT_VALUE) && column.name.asInternal().isEmpty()) { + iterator.remove(); + } + } + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java index f9be182cee4..f59251f5123 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -33,14 +33,11 @@ public abstract class RelationParser { protected final SchemaRows rows; - protected final DataTypeParser dataTypeParser; protected final InternalDriverContext context; protected final String logPrefix; - protected RelationParser( - SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { + protected RelationParser(SchemaRows rows, InternalDriverContext context) { this.rows = rows; - this.dataTypeParser = dataTypeParser; this.context = context; this.logPrefix = context.sessionName(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java index f978777be36..beb02894a0d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParser.java @@ -15,151 +15,21 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; -import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; -import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.metadata.schema.DefaultKeyspaceMetadata; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; -import com.datastax.oss.driver.internal.core.util.NanoTime; -import com.datastax.oss.driver.shaded.guava.common.base.MoreObjects; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import java.util.Map; -import net.jcip.annotations.ThreadSafe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The main entry point for system schema rows parsing. * - *

          For modularity, the code for each element row is split into separate classes (schema stuff is - * not on the hot path, so creating a few extra objects doesn't matter). + *

          Implementations must be thread-safe. */ -@ThreadSafe -public class SchemaParser { - - private static final Logger LOG = LoggerFactory.getLogger(SchemaParser.class); - - private final SchemaRows rows; - private final UserDefinedTypeParser userDefinedTypeParser; - private final TableParser tableParser; - private final ViewParser viewParser; - private final FunctionParser functionParser; - private final AggregateParser aggregateParser; - private final String logPrefix; - private final long startTimeNs = System.nanoTime(); - - public SchemaParser(SchemaRows rows, InternalDriverContext context) { - this.rows = rows; - this.logPrefix = context.sessionName(); - - DataTypeParser dataTypeParser = - rows.isCassandraV3 ? new DataTypeCqlNameParser() : new DataTypeClassNameParser(); - - this.userDefinedTypeParser = new UserDefinedTypeParser(dataTypeParser, context); - this.tableParser = new TableParser(rows, dataTypeParser, context); - this.viewParser = new ViewParser(rows, dataTypeParser, context); - this.functionParser = new FunctionParser(dataTypeParser, context); - this.aggregateParser = new AggregateParser(dataTypeParser, context); - } - - public SchemaRefresh parse() { - ImmutableMap.Builder keyspacesBuilder = ImmutableMap.builder(); - for (AdminRow row : rows.keyspaces) { - KeyspaceMetadata keyspace = parseKeyspace(row); - keyspacesBuilder.put(keyspace.getName(), keyspace); - } - SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build()); - LOG.debug("[{}] Schema parsing took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); - return refresh; - } - - protected KeyspaceMetadata parseKeyspace(AdminRow keyspaceRow) { - - // Cassandra <= 2.2 - // CREATE TABLE system.schema_keyspaces ( - // keyspace_name text PRIMARY KEY, - // durable_writes boolean, - // strategy_class text, - // strategy_options text - // ) - // - // Cassandra >= 3.0: - // CREATE TABLE system_schema.keyspaces ( - // keyspace_name text PRIMARY KEY, - // durable_writes boolean, - // replication frozen> - // ) - CqlIdentifier keyspaceId = CqlIdentifier.fromInternal(keyspaceRow.getString("keyspace_name")); - boolean durableWrites = - MoreObjects.firstNonNull(keyspaceRow.getBoolean("durable_writes"), false); - - Map replicationOptions; - if (keyspaceRow.contains("strategy_class")) { - String strategyClass = keyspaceRow.getString("strategy_class"); - Map strategyOptions = - SimpleJsonParser.parseStringMap(keyspaceRow.getString("strategy_options")); - replicationOptions = - ImmutableMap.builder() - .putAll(strategyOptions) - .put("class", strategyClass) - .build(); - } else { - replicationOptions = keyspaceRow.getMapOfStringToString("replication"); - } - - Map types = - userDefinedTypeParser.parse(rows.types.get(keyspaceId), keyspaceId); - - ImmutableMap.Builder tablesBuilder = ImmutableMap.builder(); - for (AdminRow tableRow : rows.tables.get(keyspaceId)) { - TableMetadata table = tableParser.parseTable(tableRow, keyspaceId, types); - if (table != null) { - tablesBuilder.put(table.getName(), table); - } - } - - ImmutableMap.Builder viewsBuilder = ImmutableMap.builder(); - for (AdminRow viewRow : rows.views.get(keyspaceId)) { - ViewMetadata view = viewParser.parseView(viewRow, keyspaceId, types); - if (view != null) { - viewsBuilder.put(view.getName(), view); - } - } - - ImmutableMap.Builder functionsBuilder = - ImmutableMap.builder(); - for (AdminRow functionRow : rows.functions.get(keyspaceId)) { - FunctionMetadata function = functionParser.parseFunction(functionRow, keyspaceId, types); - if (function != null) { - functionsBuilder.put(function.getSignature(), function); - } - } - - ImmutableMap.Builder aggregatesBuilder = - ImmutableMap.builder(); - for (AdminRow aggregateRow : rows.aggregates.get(keyspaceId)) { - AggregateMetadata aggregate = aggregateParser.parseAggregate(aggregateRow, keyspaceId, types); - if (aggregate != null) { - aggregatesBuilder.put(aggregate.getSignature(), aggregate); - } - } - - return new DefaultKeyspaceMetadata( - keyspaceId, - durableWrites, - replicationOptions, - types, - tablesBuilder.build(), - viewsBuilder.build(), - functionsBuilder.build(), - aggregatesBuilder.build()); - } +public interface SchemaParser { + + /** + * Process the rows that this parser was initialized with, and creates a refresh that will be + * applied to the metadata. + * + * @see SchemaParserFactory#newInstance(SchemaRows) + */ + SchemaRefresh parse(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java index f1a3a89cf27..8da63a9018c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SimpleJsonParser.java @@ -36,7 +36,7 @@ * obviously not expose this publicly. */ @NotThreadSafe -class SimpleJsonParser { +public class SimpleJsonParser { private final String input; private int idx; @@ -45,7 +45,7 @@ private SimpleJsonParser(String input) { this.input = input; } - static List parseStringList(String input) { + public static List parseStringList(String input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } @@ -73,7 +73,7 @@ static List parseStringList(String input) { } } - static Map parseStringMap(String input) { + public static Map parseStringMap(String input) { if (input == null || input.isEmpty()) { return Collections.emptyMap(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java index 127d6e19e69..675a1867386 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -39,7 +39,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -48,15 +47,15 @@ import org.slf4j.LoggerFactory; @ThreadSafe -class TableParser extends RelationParser { +public class TableParser extends RelationParser { private static final Logger LOG = LoggerFactory.getLogger(TableParser.class); - TableParser(SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { - super(rows, dataTypeParser, context); + public TableParser(SchemaRows rows, InternalDriverContext context) { + super(rows, context); } - TableMetadata parseTable( + public TableMetadata parseTable( AdminRow tableRow, CqlIdentifier keyspaceId, Map userTypes) { // Cassandra <= 2.2: // CREATE TABLE system.schema_columnfamilies ( @@ -119,13 +118,14 @@ TableMetadata parseTable( // ) WITH CLUSTERING ORDER BY (table_name ASC) CqlIdentifier tableId = CqlIdentifier.fromInternal( - tableRow.getString(rows.isCassandraV3 ? "table_name" : "columnfamily_name")); + tableRow.getString( + tableRow.contains("table_name") ? "table_name" : "columnfamily_name")); UUID uuid = (tableRow.contains("id")) ? tableRow.getUuid("id") : tableRow.getUuid("cf_id"); List rawColumns = RawColumn.toRawColumns( - rows.columns.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId), + rows.columns().getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId), keyspaceId, userTypes); if (rawColumns.isEmpty()) { @@ -146,14 +146,14 @@ TableMetadata parseTable( isCompactStorage = isSuper || isDense || !isCompound; boolean isStaticCompact = !isSuper && !isDense && !isCompound; if (isStaticCompact) { - pruneStaticCompactTableColumns(rawColumns); + RawColumn.pruneStaticCompactTableColumns(rawColumns); } else if (isDense) { - pruneDenseTableColumnsV3(rawColumns); + RawColumn.pruneDenseTableColumnsV3(rawColumns); } } else { boolean isDense = tableRow.getBoolean("is_dense"); if (isDense) { - pruneDenseTableColumnsV2(rawColumns); + RawColumn.pruneDenseTableColumnsV2(rawColumns); } DataTypeClassNameCompositeParser.ParseResult comparator = new DataTypeClassNameCompositeParser() @@ -169,15 +169,15 @@ TableMetadata parseTable( ImmutableMap.Builder indexesBuilder = ImmutableMap.builder(); for (RawColumn raw : rawColumns) { - DataType dataType = dataTypeParser.parse(keyspaceId, raw.dataType, userTypes, context); + DataType dataType = rows.dataTypeParser().parse(keyspaceId, raw.dataType, userTypes, context); ColumnMetadata column = new DefaultColumnMetadata( - keyspaceId, tableId, raw.name, dataType, raw.kind == RawColumn.Kind.STATIC); + keyspaceId, tableId, raw.name, dataType, raw.kind.equals(RawColumn.KIND_STATIC)); switch (raw.kind) { - case PARTITION_KEY: + case RawColumn.KIND_PARTITION_KEY: partitionKeyBuilder.add(column); break; - case CLUSTERING_COLUMN: + case RawColumn.KIND_CLUSTERING_COLUMN: clusteringColumnsBuilder.put( column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); break; @@ -208,7 +208,7 @@ TableMetadata parseTable( } Collection indexRows = - rows.indexes.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId); + rows.indexes().getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId); for (AdminRow indexRow : indexRows) { IndexMetadata index = buildModernIndex(keyspaceId, tableId, indexRow); indexesBuilder.put(index.getName(), index); @@ -226,51 +226,6 @@ TableMetadata parseTable( indexesBuilder.build()); } - // Upon migration from thrift to CQL, we internally create a pair of surrogate clustering/regular - // columns for compact static tables. These columns shouldn't be exposed to the user but are - // currently returned by C*. We also need to remove the static keyword for all other columns in - // the table. - private void pruneStaticCompactTableColumns(List columns) { - ListIterator iterator = columns.listIterator(); - while (iterator.hasNext()) { - RawColumn column = iterator.next(); - switch (column.kind) { - case CLUSTERING_COLUMN: - case REGULAR: - iterator.remove(); - break; - case STATIC: - column.kind = RawColumn.Kind.REGULAR; - break; - default: - // nothing to do - } - } - } - - // Upon migration from thrift to CQL, we internally create a surrogate column "value" of type - // EmptyType for dense tables. This column shouldn't be exposed to the user but is currently - // returned by C*. - private void pruneDenseTableColumnsV3(List columns) { - ListIterator iterator = columns.listIterator(); - while (iterator.hasNext()) { - RawColumn column = iterator.next(); - if (column.kind == RawColumn.Kind.REGULAR && "empty".equals(column.dataType)) { - iterator.remove(); - } - } - } - - private void pruneDenseTableColumnsV2(List columns) { - ListIterator iterator = columns.listIterator(); - while (iterator.hasNext()) { - RawColumn column = iterator.next(); - if (column.kind == RawColumn.Kind.COMPACT_VALUE && column.name.asInternal().isEmpty()) { - iterator.remove(); - } - } - } - // In C*<=2.2, index information is stored alongside the column. private IndexMetadata buildLegacyIndex(RawColumn raw, ColumnMetadata column) { if (raw.indexName == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java index b5aeccc24e8..f04b6c9a807 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/UserDefinedTypeParser.java @@ -39,11 +39,11 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class UserDefinedTypeParser { +public class UserDefinedTypeParser { private final DataTypeParser dataTypeParser; private final InternalDriverContext context; - UserDefinedTypeParser(DataTypeParser dataTypeParser, InternalDriverContext context) { + public UserDefinedTypeParser(DataTypeParser dataTypeParser, InternalDriverContext context) { this.dataTypeParser = dataTypeParser; this.context = context; } @@ -54,7 +54,7 @@ class UserDefinedTypeParser { * order to properly build the definitions, we need to do a topological sort of the rows first, so * that each type is parsed after its dependencies. */ - Map parse( + public Map parse( Collection typeRows, CqlIdentifier keyspaceId) { if (typeRows.isEmpty()) { return Collections.emptyMap(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java index dc77b07e8a5..48bdac0a07e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParser.java @@ -40,15 +40,15 @@ import org.slf4j.LoggerFactory; @ThreadSafe -class ViewParser extends RelationParser { +public class ViewParser extends RelationParser { private static final Logger LOG = LoggerFactory.getLogger(ViewParser.class); - ViewParser(SchemaRows rows, DataTypeParser dataTypeParser, InternalDriverContext context) { - super(rows, dataTypeParser, context); + public ViewParser(SchemaRows rows, InternalDriverContext context) { + super(rows, context); } - ViewMetadata parseView( + public ViewMetadata parseView( AdminRow viewRow, CqlIdentifier keyspaceId, Map userTypes) { // Cassandra 3.0 (no views in earlier versions): // CREATE TABLE system_schema.views ( @@ -87,7 +87,7 @@ ViewMetadata parseView( List rawColumns = RawColumn.toRawColumns( - rows.columns.getOrDefault(keyspaceId, ImmutableMultimap.of()).get(viewId), + rows.columns().getOrDefault(keyspaceId, ImmutableMultimap.of()).get(viewId), keyspaceId, userTypes); if (rawColumns.isEmpty()) { @@ -106,15 +106,15 @@ ViewMetadata parseView( ImmutableMap.builder(); for (RawColumn raw : rawColumns) { - DataType dataType = dataTypeParser.parse(keyspaceId, raw.dataType, userTypes, context); + DataType dataType = rows.dataTypeParser().parse(keyspaceId, raw.dataType, userTypes, context); ColumnMetadata column = new DefaultColumnMetadata( - keyspaceId, viewId, raw.name, dataType, raw.kind == RawColumn.Kind.STATIC); + keyspaceId, viewId, raw.name, dataType, raw.kind.equals(RawColumn.KIND_STATIC)); switch (raw.kind) { - case PARTITION_KEY: + case RawColumn.KIND_PARTITION_KEY: partitionKeyBuilder.add(column); break; - case CLUSTERING_COLUMN: + case RawColumn.KIND_CLUSTERING_COLUMN: clusteringColumnsBuilder.put( column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); break; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java index aeca7a1dbd5..b64c7eae1fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java @@ -23,7 +23,7 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra21SchemaQueries extends SchemaQueries { +class Cassandra21SchemaQueries extends CassandraSchemaQueries { Cassandra21SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java index c33fa599bfd..bbed924788d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java @@ -23,7 +23,7 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra22SchemaQueries extends SchemaQueries { +class Cassandra22SchemaQueries extends CassandraSchemaQueries { Cassandra22SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java index 5c3d91af6d5..286ba8bde45 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java @@ -23,7 +23,7 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra3SchemaQueries extends SchemaQueries { +class Cassandra3SchemaQueries extends CassandraSchemaQueries { Cassandra3SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java new file mode 100644 index 00000000000..128f90068e2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java @@ -0,0 +1,188 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; +import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.util.NanoTime; +import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; +import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; +import io.netty.util.concurrent.EventExecutor; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import net.jcip.annotations.ThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ThreadSafe +public abstract class CassandraSchemaQueries implements SchemaQueries { + + private static final Logger LOG = LoggerFactory.getLogger(CassandraSchemaQueries.class); + + private final DriverChannel channel; + private final EventExecutor adminExecutor; + private final boolean isCassandraV3; + private final String logPrefix; + private final Duration timeout; + private final int pageSize; + private final String whereClause; + // The future we return from execute, completes when all the queries are done. + private final CompletableFuture schemaRowsFuture = new CompletableFuture<>(); + // A future that completes later, when the whole refresh is done. We just store it here to pass it + // down to the next step. + public final CompletableFuture refreshFuture; + private final long startTimeNs = System.nanoTime(); + + // All non-final fields are accessed exclusively on adminExecutor + private CassandraSchemaRows.Builder schemaRowsBuilder; + private int pendingQueries; + + protected CassandraSchemaQueries( + DriverChannel channel, + boolean isCassandraV3, + CompletableFuture refreshFuture, + DriverConfigProfile config, + String logPrefix) { + this.channel = channel; + this.adminExecutor = channel.eventLoop(); + this.isCassandraV3 = isCassandraV3; + this.refreshFuture = refreshFuture; + this.logPrefix = logPrefix; + this.timeout = config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT); + this.pageSize = config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); + + List refreshedKeyspaces = + config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); + this.whereClause = buildWhereClause(refreshedKeyspaces); + } + + private static String buildWhereClause(List refreshedKeyspaces) { + if (refreshedKeyspaces.isEmpty()) { + return ""; + } else { + StringBuilder builder = new StringBuilder(" WHERE keyspace_name in ("); + boolean first = true; + for (String keyspace : refreshedKeyspaces) { + if (first) { + first = false; + } else { + builder.append(","); + } + builder.append('\'').append(keyspace).append('\''); + } + return builder.append(")").toString(); + } + } + + protected abstract String selectKeyspacesQuery(); + + protected abstract String selectTablesQuery(); + + protected abstract Optional selectViewsQuery(); + + protected abstract Optional selectIndexesQuery(); + + protected abstract String selectColumnsQuery(); + + protected abstract String selectTypesQuery(); + + protected abstract Optional selectFunctionsQuery(); + + protected abstract Optional selectAggregatesQuery(); + + @Override + public CompletionStage execute() { + RunOrSchedule.on(adminExecutor, this::executeOnAdminExecutor); + return schemaRowsFuture; + } + + private void executeOnAdminExecutor() { + assert adminExecutor.inEventLoop(); + + schemaRowsBuilder = new CassandraSchemaRows.Builder(isCassandraV3, refreshFuture, logPrefix); + + query(selectKeyspacesQuery() + whereClause, schemaRowsBuilder::withKeyspaces); + query(selectTypesQuery() + whereClause, schemaRowsBuilder::withTypes); + query(selectTablesQuery() + whereClause, schemaRowsBuilder::withTables); + query(selectColumnsQuery() + whereClause, schemaRowsBuilder::withColumns); + selectIndexesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withIndexes)); + selectViewsQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withViews)); + selectFunctionsQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions)); + selectAggregatesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates)); + } + + private void query( + String queryString, + Function, CassandraSchemaRows.Builder> builderUpdater) { + assert adminExecutor.inEventLoop(); + + pendingQueries += 1; + query(queryString) + .whenCompleteAsync( + (result, error) -> handleResult(result, error, builderUpdater), adminExecutor); + } + + @VisibleForTesting + protected CompletionStage query(String query) { + return AdminRequestHandler.query(channel, query, timeout, pageSize, logPrefix).start(); + } + + private void handleResult( + AdminResult result, + Throwable error, + Function, CassandraSchemaRows.Builder> builderUpdater) { + if (schemaRowsFuture.isDone()) { // Another query failed already, ignore + return; + } + if (error != null) { + // Any error fails the whole refresh + schemaRowsFuture.completeExceptionally(error); + } else { + // Store the rows of the current page in the builder + schemaRowsBuilder = builderUpdater.apply(result); + // Move to the next page, or complete if we're the last query + if (result.hasNextPage()) { + result + .nextPage() + .whenCompleteAsync( + (nextResult, nextError) -> handleResult(nextResult, nextError, builderUpdater), + adminExecutor); + } else { + pendingQueries -= 1; + if (pendingQueries == 0) { + LOG.debug( + "[{}] Schema queries took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); + schemaRowsFuture.complete(schemaRowsBuilder.build()); + } + } + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java new file mode 100644 index 00000000000..f50ee68c5e1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java @@ -0,0 +1,260 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.DataTypeClassNameParser; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.DataTypeCqlNameParser; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.DataTypeParser; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableListMultimap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMultimap; +import com.datastax.oss.driver.shaded.guava.common.collect.Multimap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import net.jcip.annotations.Immutable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Immutable +public class CassandraSchemaRows implements SchemaRows { + + private final DataTypeParser dataTypeParser; + private final CompletableFuture refreshFuture; + private final List keyspaces; + private final Multimap tables; + private final Multimap views; + private final Multimap types; + private final Multimap functions; + private final Multimap aggregates; + private final Map> columns; + private final Map> indexes; + + private CassandraSchemaRows( + boolean isCassandraV3, + CompletableFuture refreshFuture, + List keyspaces, + Multimap tables, + Multimap views, + Map> columns, + Map> indexes, + Multimap types, + Multimap functions, + Multimap aggregates) { + this.dataTypeParser = + isCassandraV3 ? new DataTypeCqlNameParser() : new DataTypeClassNameParser(); + this.refreshFuture = refreshFuture; + this.keyspaces = keyspaces; + this.tables = tables; + this.views = views; + this.columns = columns; + this.indexes = indexes; + this.types = types; + this.functions = functions; + this.aggregates = aggregates; + } + + @Override + public DataTypeParser dataTypeParser() { + return dataTypeParser; + } + + @Override + public CompletableFuture refreshFuture() { + return refreshFuture; + } + + @Override + public List keyspaces() { + return keyspaces; + } + + @Override + public Multimap tables() { + return tables; + } + + @Override + public Multimap views() { + return views; + } + + @Override + public Multimap types() { + return types; + } + + @Override + public Multimap functions() { + return functions; + } + + @Override + public Multimap aggregates() { + return aggregates; + } + + @Override + public Map> columns() { + return columns; + } + + @Override + public Map> indexes() { + return indexes; + } + + public static class Builder { + private static final Logger LOG = LoggerFactory.getLogger(Builder.class); + + private final boolean isCassandraV3; + private final CompletableFuture refreshFuture; + private final String tableNameColumn; + private final String logPrefix; + private final ImmutableList.Builder keyspacesBuilder = ImmutableList.builder(); + private final ImmutableMultimap.Builder tablesBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder viewsBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder typesBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder functionsBuilder = + ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder aggregatesBuilder = + ImmutableListMultimap.builder(); + private final Map> + columnsBuilders = new LinkedHashMap<>(); + private final Map> + indexesBuilders = new LinkedHashMap<>(); + + public Builder( + boolean isCassandraV3, CompletableFuture refreshFuture, String logPrefix) { + this.isCassandraV3 = isCassandraV3; + this.refreshFuture = refreshFuture; + this.logPrefix = logPrefix; + this.tableNameColumn = isCassandraV3 ? "table_name" : "columnfamily_name"; + } + + public Builder withKeyspaces(Iterable rows) { + keyspacesBuilder.addAll(rows); + return this; + } + + public Builder withTables(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, tablesBuilder); + } + return this; + } + + public Builder withViews(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, viewsBuilder); + } + return this; + } + + public Builder withTypes(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, typesBuilder); + } + return this; + } + + public Builder withFunctions(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, functionsBuilder); + } + return this; + } + + public Builder withAggregates(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, aggregatesBuilder); + } + return this; + } + + public Builder withColumns(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspaceAndTable(row, columnsBuilders); + } + return this; + } + + public Builder withIndexes(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspaceAndTable(row, indexesBuilders); + } + return this; + } + + private void putByKeyspace( + AdminRow row, ImmutableMultimap.Builder builder) { + String keyspace = row.getString("keyspace_name"); + if (keyspace == null) { + LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); + } else { + builder.put(CqlIdentifier.fromInternal(keyspace), row); + } + } + + private void putByKeyspaceAndTable( + AdminRow row, + Map> builders) { + String keyspace = row.getString("keyspace_name"); + String table = row.getString(tableNameColumn); + if (keyspace == null) { + LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); + } else if (table == null) { + LOG.warn("[{}] Skipping system row with missing table name", logPrefix); + } else { + ImmutableMultimap.Builder builder = + builders.computeIfAbsent( + CqlIdentifier.fromInternal(keyspace), s -> ImmutableListMultimap.builder()); + builder.put(CqlIdentifier.fromInternal(table), row); + } + } + + public CassandraSchemaRows build() { + return new CassandraSchemaRows( + isCassandraV3, + refreshFuture, + keyspacesBuilder.build(), + tablesBuilder.build(), + viewsBuilder.build(), + build(columnsBuilders), + build(indexesBuilders), + typesBuilder.build(), + functionsBuilder.build(), + aggregatesBuilder.build()); + } + + private static Map> build( + Map> builders) { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (Map.Entry> entry : builders.entrySet()) { + builder.put(entry.getKey(), entry.getValue().build()); + } + return builder.build(); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index 61167bbe1b0..0343fd732d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -31,16 +31,16 @@ public class DefaultSchemaQueriesFactory implements SchemaQueriesFactory { private static final Logger LOG = LoggerFactory.getLogger(DefaultSchemaQueriesFactory.class); - private final InternalDriverContext context; + protected final InternalDriverContext context; + protected final String logPrefix; public DefaultSchemaQueriesFactory(InternalDriverContext context) { this.context = context; + this.logPrefix = context.sessionName(); } @Override public SchemaQueries newInstance(CompletableFuture refreshFuture) { - String logPrefix = context.sessionName(); - DriverChannel channel = context.controlConnection().channel(); if (channel == null || channel.closeFuture().isDone()) { throw new IllegalStateException("Control channel not available, aborting schema refresh"); @@ -53,6 +53,11 @@ public SchemaQueries newInstance(CompletableFuture refreshFuture) { + channel.remoteAddress() + ", aborting schema refresh"); } + return newInstance(node, channel, refreshFuture); + } + + protected SchemaQueries newInstance( + Node node, DriverChannel channel, CompletableFuture refreshFuture) { Version version = node.getCassandraVersion(); if (version == null) { LOG.warn( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java index 3acd0f70e46..6ab89d190ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueries.java @@ -15,179 +15,21 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; -import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; -import com.datastax.oss.driver.api.core.metadata.Metadata; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; -import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; -import com.datastax.oss.driver.internal.core.channel.DriverChannel; -import com.datastax.oss.driver.internal.core.util.NanoTime; -import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; -import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; -import io.netty.util.concurrent.EventExecutor; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.function.Function; -import net.jcip.annotations.ThreadSafe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Handles the queries to system tables during a schema refresh. + * Manages the queries to system tables during a schema refresh. * - *

          Depending on the kind of refresh, there is a variable number of queries. They are all - * asynchronous, and possibly paged. This class abstracts all the details and exposes a common - * result type. + *

          They are all asynchronous, and possibly paged. This class abstracts all the details and + * exposes a common result type. + * + *

          Implementations must be thread-safe. */ -@ThreadSafe -public abstract class SchemaQueries { - - private static final Logger LOG = LoggerFactory.getLogger(SchemaQueries.class); - - private final DriverChannel channel; - private final EventExecutor adminExecutor; - private final boolean isCassandraV3; - private final String logPrefix; - private final Duration timeout; - private final int pageSize; - private final String whereClause; - // The future we return from execute, completes when all the queries are done. - private final CompletableFuture schemaRowsFuture = new CompletableFuture<>(); - // A future that completes later, when the whole refresh is done. We just store it here to pass it - // down to the next step. - public final CompletableFuture refreshFuture; - private final long startTimeNs = System.nanoTime(); - - // All non-final fields are accessed exclusively on adminExecutor - private SchemaRows.Builder schemaRowsBuilder; - private int pendingQueries; - - protected SchemaQueries( - DriverChannel channel, - boolean isCassandraV3, - CompletableFuture refreshFuture, - DriverConfigProfile config, - String logPrefix) { - this.channel = channel; - this.adminExecutor = channel.eventLoop(); - this.isCassandraV3 = isCassandraV3; - this.refreshFuture = refreshFuture; - this.logPrefix = logPrefix; - this.timeout = config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT); - this.pageSize = config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE); - - List refreshedKeyspaces = - config.getStringList( - DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); - this.whereClause = buildWhereClause(refreshedKeyspaces); - } - - private static String buildWhereClause(List refreshedKeyspaces) { - if (refreshedKeyspaces.isEmpty()) { - return ""; - } else { - StringBuilder builder = new StringBuilder(" WHERE keyspace_name in ("); - boolean first = true; - for (String keyspace : refreshedKeyspaces) { - if (first) { - first = false; - } else { - builder.append(","); - } - builder.append('\'').append(keyspace).append('\''); - } - return builder.append(")").toString(); - } - } - - protected abstract String selectKeyspacesQuery(); - - protected abstract String selectTablesQuery(); - - protected abstract Optional selectViewsQuery(); - - protected abstract Optional selectIndexesQuery(); - - protected abstract String selectColumnsQuery(); - - protected abstract String selectTypesQuery(); - - protected abstract Optional selectFunctionsQuery(); - - protected abstract Optional selectAggregatesQuery(); - - public CompletionStage execute() { - RunOrSchedule.on(adminExecutor, this::executeOnAdminExecutor); - return schemaRowsFuture; - } - - private void executeOnAdminExecutor() { - assert adminExecutor.inEventLoop(); - - schemaRowsBuilder = new SchemaRows.Builder(isCassandraV3, refreshFuture, logPrefix); - - query(selectKeyspacesQuery() + whereClause, schemaRowsBuilder::withKeyspaces); - query(selectTypesQuery() + whereClause, schemaRowsBuilder::withTypes); - query(selectTablesQuery() + whereClause, schemaRowsBuilder::withTables); - query(selectColumnsQuery() + whereClause, schemaRowsBuilder::withColumns); - selectIndexesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withIndexes)); - selectViewsQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withViews)); - selectFunctionsQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions)); - selectAggregatesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates)); - } - - private void query( - String queryString, Function, SchemaRows.Builder> builderUpdater) { - assert adminExecutor.inEventLoop(); - - pendingQueries += 1; - query(queryString) - .whenCompleteAsync( - (result, error) -> handleResult(result, error, builderUpdater), adminExecutor); - } - - @VisibleForTesting - protected CompletionStage query(String query) { - return AdminRequestHandler.query(channel, query, timeout, pageSize, logPrefix).start(); - } +public interface SchemaQueries { - private void handleResult( - AdminResult result, - Throwable error, - Function, SchemaRows.Builder> builderUpdater) { - if (schemaRowsFuture.isDone()) { // Another query failed already, ignore - return; - } - if (error != null) { - // Any error fails the whole refresh - schemaRowsFuture.completeExceptionally(error); - } else { - // Store the rows of the current page in the builder - schemaRowsBuilder = builderUpdater.apply(result); - // Move to the next page, or complete if we're the last query - if (result.hasNextPage()) { - result - .nextPage() - .whenCompleteAsync( - (nextResult, nextError) -> handleResult(nextResult, nextError, builderUpdater), - adminExecutor); - } else { - pendingQueries -= 1; - if (pendingQueries == 0) { - LOG.debug( - "[{}] Schema queries took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); - schemaRowsFuture.complete(schemaRowsBuilder.build()); - } - } - } - } + /** + * Launch the queries asynchronously, returning a future that will complete when they have all + * succeeded. + */ + CompletionStage execute(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java index 480e19714c5..18148f23948 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java @@ -18,193 +18,41 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableListMultimap; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMultimap; +import com.datastax.oss.driver.internal.core.metadata.schema.parsing.DataTypeParser; import com.datastax.oss.driver.shaded.guava.common.collect.Multimap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import net.jcip.annotations.Immutable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Gathers all the rows returned by the queries for a schema refresh, categorizing them by - * keyspace/table where relevant. + * The system rows returned by the queries for a schema refresh, categorized by keyspace/table where + * relevant. + * + *

          Implementations must be thread-safe. */ -@Immutable -public class SchemaRows { - - public final boolean isCassandraV3; - public final CompletableFuture refreshFuture; - public final List keyspaces; - public final Multimap tables; - public final Multimap views; - public final Multimap types; - public final Multimap functions; - public final Multimap aggregates; - public final Map> columns; - public final Map> indexes; - - private SchemaRows( - boolean isCassandraV3, - CompletableFuture refreshFuture, - List keyspaces, - Multimap tables, - Multimap views, - Map> columns, - Map> indexes, - Multimap types, - Multimap functions, - Multimap aggregates) { - this.isCassandraV3 = isCassandraV3; - this.refreshFuture = refreshFuture; - this.keyspaces = keyspaces; - this.tables = tables; - this.views = views; - this.columns = columns; - this.indexes = indexes; - this.types = types; - this.functions = functions; - this.aggregates = aggregates; - } - - public static class Builder { - private static final Logger LOG = LoggerFactory.getLogger(Builder.class); - - private final boolean isCassandraV3; - private final CompletableFuture refreshFuture; - private final String tableNameColumn; - private final String logPrefix; - private final ImmutableList.Builder keyspacesBuilder = ImmutableList.builder(); - private final ImmutableMultimap.Builder tablesBuilder = - ImmutableListMultimap.builder(); - private final ImmutableMultimap.Builder viewsBuilder = - ImmutableListMultimap.builder(); - private final ImmutableMultimap.Builder typesBuilder = - ImmutableListMultimap.builder(); - private final ImmutableMultimap.Builder functionsBuilder = - ImmutableListMultimap.builder(); - private final ImmutableMultimap.Builder aggregatesBuilder = - ImmutableListMultimap.builder(); - private final Map> - columnsBuilders = new LinkedHashMap<>(); - private final Map> - indexesBuilders = new LinkedHashMap<>(); - - public Builder( - boolean isCassandraV3, CompletableFuture refreshFuture, String logPrefix) { - this.isCassandraV3 = isCassandraV3; - this.refreshFuture = refreshFuture; - this.logPrefix = logPrefix; - this.tableNameColumn = isCassandraV3 ? "table_name" : "columnfamily_name"; - } - - public Builder withKeyspaces(Iterable rows) { - keyspacesBuilder.addAll(rows); - return this; - } - - public Builder withTables(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspace(row, tablesBuilder); - } - return this; - } +public interface SchemaRows { - public Builder withViews(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspace(row, viewsBuilder); - } - return this; - } + List keyspaces(); - public Builder withTypes(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspace(row, typesBuilder); - } - return this; - } + Multimap tables(); - public Builder withFunctions(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspace(row, functionsBuilder); - } - return this; - } + Multimap views(); - public Builder withAggregates(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspace(row, aggregatesBuilder); - } - return this; - } + Multimap types(); - public Builder withColumns(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspaceAndTable(row, columnsBuilders); - } - return this; - } + Multimap functions(); - public Builder withIndexes(Iterable rows) { - for (AdminRow row : rows) { - putByKeyspaceAndTable(row, indexesBuilders); - } - return this; - } + Multimap aggregates(); - private void putByKeyspace( - AdminRow row, ImmutableMultimap.Builder builder) { - String keyspace = row.getString("keyspace_name"); - if (keyspace == null) { - LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); - } else { - builder.put(CqlIdentifier.fromInternal(keyspace), row); - } - } + Map> columns(); - private void putByKeyspaceAndTable( - AdminRow row, - Map> builders) { - String keyspace = row.getString("keyspace_name"); - String table = row.getString(tableNameColumn); - if (keyspace == null) { - LOG.warn("[{}] Skipping system row with missing keyspace name", logPrefix); - } else if (table == null) { - LOG.warn("[{}] Skipping system row with missing table name", logPrefix); - } else { - ImmutableMultimap.Builder builder = - builders.computeIfAbsent( - CqlIdentifier.fromInternal(keyspace), s -> ImmutableListMultimap.builder()); - builder.put(CqlIdentifier.fromInternal(table), row); - } - } + Map> indexes(); - public SchemaRows build() { - return new SchemaRows( - isCassandraV3, - refreshFuture, - keyspacesBuilder.build(), - tablesBuilder.build(), - viewsBuilder.build(), - build(columnsBuilders), - build(indexesBuilders), - typesBuilder.build(), - functionsBuilder.build(), - aggregatesBuilder.build()); - } + DataTypeParser dataTypeParser(); - private static Map> build( - Map> builders) { - ImmutableMap.Builder> builder = ImmutableMap.builder(); - for (Map.Entry> entry : builders.entrySet()) { - builder.put(entry.getKey(), entry.getValue().build()); - } - return builder.build(); - } - } + /** + * The future to complete when the schema refresh is complete (here just to be propagated further + * down the chain). + */ + CompletableFuture refreshFuture(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java index 0838b26e728..afb9e56e7c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java @@ -49,7 +49,7 @@ public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { ImmutableList.Builder events = ImmutableList.builder(); - Map oldKeyspaces = oldMetadata.getKeyspaces(); + Map oldKeyspaces = oldMetadata.getKeyspaces(); for (CqlIdentifier removedKey : Sets.difference(oldKeyspaces.keySet(), newKeyspaces.keySet())) { events.add(KeyspaceChangeEvent.dropped(oldKeyspaces.get(removedKey))); } @@ -132,8 +132,8 @@ private void computeChildEvents( } private void computeChildEvents( - Map oldChildren, - Map newChildren, + Map oldChildren, + Map newChildren, Function newDroppedEvent, Function newCreatedEvent, BiFunction newUpdatedEvent, @@ -141,7 +141,7 @@ private void computeChildEvents( for (K removedKey : Sets.difference(oldChildren.keySet(), newChildren.keySet())) { events.add(newDroppedEvent.apply(oldChildren.get(removedKey))); } - for (Map.Entry entry : newChildren.entrySet()) { + for (Map.Entry entry : newChildren.entrySet()) { K key = entry.getKey(); V newChild = entry.getValue(); V oldChild = oldChildren.get(key); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java index 7a868b1c9a8..9814f3c2ae8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -49,7 +49,7 @@ public class DefaultTokenMap implements TokenMap { public static DefaultTokenMap build( @NonNull Collection nodes, - @NonNull Collection keyspaces, + @NonNull Collection keyspaces, @NonNull TokenFactory tokenFactory, @NonNull ReplicationStrategyFactory replicationStrategyFactory, @NonNull String logPrefix) { @@ -191,7 +191,7 @@ private KeyspaceTokenMap getKeyspaceMap(CqlIdentifier keyspace) { /** Called when only the schema has changed. */ public DefaultTokenMap refresh( @NonNull Collection nodes, - @NonNull Collection keyspaces, + @NonNull Collection keyspaces, @NonNull ReplicationStrategyFactory replicationStrategyFactory) { Map> newReplicationConfigs = @@ -266,7 +266,7 @@ private TokenToPrimaryAndRing(Map tokenToPrimary, List ring) } private static Map> buildReplicationConfigs( - Collection keyspaces, String logPrefix) { + Collection keyspaces, String logPrefix) { ImmutableMap.Builder> builder = ImmutableMap.builder(); for (KeyspaceMetadata keyspace : keyspaces) { builder.put(keyspace.getName(), keyspace.getReplication()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java index 4624d8e5fa1..9b29f50ef83 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -74,13 +74,13 @@ public boolean isSchemaMetadataEnabled() { @NonNull @Override - public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { + public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { return delegate.setSchemaMetadataEnabled(newValue); } @NonNull @Override - public CompletionStage refreshSchemaAsync() { + public CompletionStage refreshSchemaAsync() { return delegate.refreshSchemaAsync(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 5fc1f2fa9d2..d19dba6aa64 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -63,7 +63,7 @@ public void setup() { Mockito.when(context.metadataManager()).thenReturn(metadataManager); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(metadata.getTokenMap()).thenReturn(Optional.of(tokenMap)); + Mockito.when(metadata.getTokenMap()).thenAnswer(invocation -> Optional.of(this.tokenMap)); // Use a subclass to disable shuffling, we just spy to make sure that the shuffling method was // called (makes tests easier) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 037482332ef..3a2fbb22aa2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.Map; import java.util.UUID; import org.junit.Before; @@ -51,7 +52,8 @@ public void setup() { @Test public void should_add_new_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); UUID hostId = Uuids.random(); UUID schemaVersion = Uuids.random(); DefaultNodeInfo newNodeInfo = @@ -81,7 +83,8 @@ public void should_add_new_node() { @Test public void should_not_add_existing_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() .withConnectAddress(ADDRESS1) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index 9edfe42dcce..a4cdf18d4b3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -63,13 +64,15 @@ public void setup() { @Test public void should_not_build_token_map_when_initializing_with_contact_points() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); assertThat(contactPointsMetadata.getTokenMap()).isNotPresent(); } @Test public void should_build_minimal_token_map_on_first_refresh() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); @@ -78,7 +81,8 @@ public void should_build_minimal_token_map_on_first_refresh() { @Test public void should_not_build_token_map_when_disabled() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( ImmutableMap.of(ADDRESS1, NODE1), false, true, new Murmur3TokenFactory(), context); @@ -87,7 +91,8 @@ public void should_not_build_token_map_when_disabled() { @Test public void should_stay_empty_on_first_refresh_if_partitioner_missing() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( ImmutableMap.of(ADDRESS1, NODE1), true, true, null, context); @@ -96,7 +101,8 @@ public void should_stay_empty_on_first_refresh_if_partitioner_missing() { @Test public void should_update_minimal_token_map_if_new_node_and_still_no_schema() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); @@ -108,7 +114,8 @@ public void should_update_minimal_token_map_if_new_node_and_still_no_schema() { @Test public void should_update_token_map_when_schema_changes() { - DefaultMetadata contactPointsMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1)); + DefaultMetadata contactPointsMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 19bb56afd05..5f14175b26c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import java.util.Collections; import java.util.UUID; import org.junit.Before; import org.junit.Test; @@ -58,7 +59,8 @@ public void setup() { public void should_add_and_remove_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), @@ -78,7 +80,8 @@ public void should_add_and_remove_nodes() { public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); UUID hostId1 = Uuids.random(); UUID hostId2 = Uuids.random(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 47c3cc422a5..55bf0236bcd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import java.net.InetSocketAddress; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,7 +52,8 @@ public void setup() { public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2)); + new DefaultMetadata( + ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); // When @@ -65,7 +67,8 @@ public void should_remove_existing_node() { @Test public void should_not_remove_nonexistent_node() { // Given - DefaultMetadata oldMetadata = new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1)); + DefaultMetadata oldMetadata = + new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java index eb6cd8908a6..c01fd606a6e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.metadata.MetadataRefresh; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.CassandraSchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.refresh.SchemaRefresh; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; @@ -135,10 +136,10 @@ public void should_parse_multiple_keyspaces() { assertThat(ks2.getUserDefinedTypes()).hasSize(1).containsKey(CqlIdentifier.fromInternal("t2")); } - private MetadataRefresh parse(Consumer builderConfig) { - SchemaRows.Builder builder = new SchemaRows.Builder(true, null, "test"); + private MetadataRefresh parse(Consumer builderConfig) { + CassandraSchemaRows.Builder builder = new CassandraSchemaRows.Builder(true, null, "test"); builderConfig.accept(builder); SchemaRows rows = builder.build(); - return new SchemaParser(rows, context).parse(); + return new CassandraSchemaParser(rows, context).parse(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java index b98dbfbffa2..b2b9ab10ed4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java @@ -127,6 +127,8 @@ protected static AdminRow mockTypeRow( protected static AdminRow mockLegacyTableRow(String keyspace, String name, String comparator) { AdminRow row = Mockito.mock(AdminRow.class); + Mockito.when(row.contains("table_name")).thenReturn(false); + Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); Mockito.when(row.getString("columnfamily_name")).thenReturn(name); Mockito.when(row.getBoolean("is_dense")).thenReturn(false); @@ -184,6 +186,7 @@ protected static AdminRow mockModernTableRow(String keyspace, String name) { AdminRow row = Mockito.mock(AdminRow.class); Mockito.when(row.contains("flags")).thenReturn(true); + Mockito.when(row.contains("table_name")).thenReturn(true); Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); Mockito.when(row.getString("table_name")).thenReturn(name); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java index e27d89415b6..0732e1b2ad0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.CassandraSchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -81,7 +82,7 @@ public class TableParserTest extends SchemaParserTestBase { @Test public void should_skip_when_no_column_rows() { SchemaRows rows = legacyRows(TABLE_ROW_2_2, Collections.emptyList()); - TableParser parser = new TableParser(rows, new DataTypeClassNameParser(), context); + TableParser parser = new TableParser(rows, context); TableMetadata table = parser.parseTable(TABLE_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); assertThat(table).isNull(); @@ -90,7 +91,7 @@ public void should_skip_when_no_column_rows() { @Test public void should_parse_legacy_tables() { SchemaRows rows = legacyRows(TABLE_ROW_2_2, COLUMN_ROWS_2_2); - TableParser parser = new TableParser(rows, new DataTypeClassNameParser(), context); + TableParser parser = new TableParser(rows, context); TableMetadata table = parser.parseTable(TABLE_ROW_2_2, KEYSPACE_ID, Collections.emptyMap()); checkTable(table); @@ -102,7 +103,7 @@ public void should_parse_legacy_tables() { @Test public void should_parse_modern_tables() { SchemaRows rows = modernRows(TABLE_ROW_3_0, COLUMN_ROWS_3_0, INDEX_ROWS_3_0); - TableParser parser = new TableParser(rows, new DataTypeCqlNameParser(), context); + TableParser parser = new TableParser(rows, context); TableMetadata table = parser.parseTable(TABLE_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); checkTable(table); @@ -127,7 +128,7 @@ private void checkTable(TableMetadata table) { assertThat(pk1.getType()).isEqualTo(DataTypes.TEXT); assertThat(table.getClusteringColumns().entrySet()).hasSize(2); - Iterator clusteringColumnsIterator = + Iterator clusteringColumnsIterator = table.getClusteringColumns().keySet().iterator(); ColumnMetadata clusteringColumn1 = clusteringColumnsIterator.next(); assertThat(clusteringColumn1.getName().asInternal()).isEqualTo("cc1"); @@ -176,8 +177,8 @@ private SchemaRows rows( Iterable columnRows, Iterable indexesRows, boolean isCassandraV3) { - SchemaRows.Builder builder = - new SchemaRows.Builder(isCassandraV3, null, "test") + CassandraSchemaRows.Builder builder = + new CassandraSchemaRows.Builder(isCassandraV3, null, "test") .withTables(ImmutableList.of(tableRow)) .withColumns(columnRows); if (indexesRows != null) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java index 1808b0ca8d2..805834ad40a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.metadata.schema.queries.CassandraSchemaRows; import com.datastax.oss.driver.internal.core.metadata.schema.queries.SchemaRows; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import java.util.Collections; @@ -44,7 +45,7 @@ public class ViewParserTest extends SchemaParserTestBase { @Test public void should_skip_when_no_column_rows() { SchemaRows rows = rows(VIEW_ROW_3_0, Collections.emptyList()); - ViewParser parser = new ViewParser(rows, new DataTypeClassNameParser(), context); + ViewParser parser = new ViewParser(rows, context); ViewMetadata view = parser.parseView(VIEW_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); assertThat(view).isNull(); @@ -53,7 +54,7 @@ public void should_skip_when_no_column_rows() { @Test public void should_parse_view() { SchemaRows rows = rows(VIEW_ROW_3_0, COLUMN_ROWS_3_0); - ViewParser parser = new ViewParser(rows, new DataTypeCqlNameParser(), context); + ViewParser parser = new ViewParser(rows, context); ViewMetadata view = parser.parseView(VIEW_ROW_3_0, KEYSPACE_ID, Collections.emptyMap()); assertThat(view.getKeyspace().asInternal()).isEqualTo("ks"); @@ -66,7 +67,7 @@ public void should_parse_view() { assertThat(pk0.getType()).isEqualTo(DataTypes.TEXT); assertThat(view.getClusteringColumns().entrySet()).hasSize(5); - Iterator clusteringColumnsIterator = + Iterator clusteringColumnsIterator = view.getClusteringColumns().keySet().iterator(); assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("score"); assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("user"); @@ -85,7 +86,7 @@ public void should_parse_view() { } private SchemaRows rows(AdminRow viewRow, Iterable columnRows) { - return new SchemaRows.Builder(true, null, "test") + return new CassandraSchemaRows.Builder(true, null, "test") .withViews(ImmutableList.of(viewRow)) .withColumns(columnRows) .build(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index cf1da01203b..523f65212f0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -75,27 +75,27 @@ public void should_query() { .isSuccess( rows -> { // Keyspace - assertThat(rows.keyspaces).hasSize(2); - assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); - assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + assertThat(rows.keyspaces()).hasSize(2); + assertThat(rows.keyspaces().get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces().get(1).getString("keyspace_name")).isEqualTo("ks2"); // Types - assertThat(rows.types.keySet()).containsOnly(KS1_ID); - assertThat(rows.types.get(KS1_ID)).hasSize(1); - assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + assertThat(rows.types().keySet()).containsOnly(KS1_ID); + assertThat(rows.types().get(KS1_ID)).hasSize(1); + assertThat(rows.types().get(KS1_ID).iterator().next().getString("type_name")) .isEqualTo("type"); // Tables - assertThat(rows.tables.keySet()).containsOnly(KS1_ID); - assertThat(rows.tables.get(KS1_ID)).hasSize(1); - assertThat(rows.tables.get(KS1_ID).iterator().next().getString("columnfamily_name")) + assertThat(rows.tables().keySet()).containsOnly(KS1_ID); + assertThat(rows.tables().get(KS1_ID)).hasSize(1); + assertThat(rows.tables().get(KS1_ID).iterator().next().getString("columnfamily_name")) .isEqualTo("foo"); // Rows - assertThat(rows.columns.keySet()).containsOnly(KS1_ID); - assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns().keySet()).containsOnly(KS1_ID); + assertThat(rows.columns().get(KS1_ID).keySet()).containsOnly(FOO_ID); assertThat( - rows.columns + rows.columns() .get(KS1_ID) .get(FOO_ID) .iterator() @@ -104,9 +104,9 @@ public void should_query() { .isEqualTo("k"); // No views, functions or aggregates in this version - assertThat(rows.views.keySet()).isEmpty(); - assertThat(rows.functions.keySet()).isEmpty(); - assertThat(rows.aggregates.keySet()).isEmpty(); + assertThat(rows.views().keySet()).isEmpty(); + assertThat(rows.functions().keySet()).isEmpty(); + assertThat(rows.aggregates().keySet()).isEmpty(); }); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index d1aa6a677ad..6e4312b508b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -85,27 +85,27 @@ public void should_query() { .isSuccess( rows -> { // Keyspace - assertThat(rows.keyspaces).hasSize(2); - assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); - assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + assertThat(rows.keyspaces()).hasSize(2); + assertThat(rows.keyspaces().get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces().get(1).getString("keyspace_name")).isEqualTo("ks2"); // Types - assertThat(rows.types.keySet()).containsOnly(KS1_ID); - assertThat(rows.types.get(KS1_ID)).hasSize(1); - assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + assertThat(rows.types().keySet()).containsOnly(KS1_ID); + assertThat(rows.types().get(KS1_ID)).hasSize(1); + assertThat(rows.types().get(KS1_ID).iterator().next().getString("type_name")) .isEqualTo("type"); // Tables - assertThat(rows.tables.keySet()).containsOnly(KS1_ID); - assertThat(rows.tables.get(KS1_ID)).hasSize(1); - assertThat(rows.tables.get(KS1_ID).iterator().next().getString("columnfamily_name")) + assertThat(rows.tables().keySet()).containsOnly(KS1_ID); + assertThat(rows.tables().get(KS1_ID)).hasSize(1); + assertThat(rows.tables().get(KS1_ID).iterator().next().getString("columnfamily_name")) .isEqualTo("foo"); // Rows - assertThat(rows.columns.keySet()).containsOnly(KS1_ID); - assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns().keySet()).containsOnly(KS1_ID); + assertThat(rows.columns().get(KS1_ID).keySet()).containsOnly(FOO_ID); assertThat( - rows.columns + rows.columns() .get(KS1_ID) .get(FOO_ID) .iterator() @@ -114,19 +114,20 @@ public void should_query() { .isEqualTo("k"); // Functions - assertThat(rows.functions.keySet()).containsOnly(KS2_ID); - assertThat(rows.functions.get(KS2_ID)).hasSize(1); - assertThat(rows.functions.get(KS2_ID).iterator().next().getString("function_name")) + assertThat(rows.functions().keySet()).containsOnly(KS2_ID); + assertThat(rows.functions().get(KS2_ID)).hasSize(1); + assertThat(rows.functions().get(KS2_ID).iterator().next().getString("function_name")) .isEqualTo("add"); // Aggregates - assertThat(rows.aggregates.keySet()).containsOnly(KS2_ID); - assertThat(rows.aggregates.get(KS2_ID)).hasSize(1); - assertThat(rows.aggregates.get(KS2_ID).iterator().next().getString("aggregate_name")) + assertThat(rows.aggregates().keySet()).containsOnly(KS2_ID); + assertThat(rows.aggregates().get(KS2_ID)).hasSize(1); + assertThat( + rows.aggregates().get(KS2_ID).iterator().next().getString("aggregate_name")) .isEqualTo("add"); // No views in this version - assertThat(rows.views.keySet()).isEmpty(); + assertThat(rows.views().keySet()).isEmpty(); }); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index d8c974de964..4e84a48a69a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -115,27 +115,27 @@ private void should_query_with_where_clause(String whereClause) { .isSuccess( rows -> { // Keyspace - assertThat(rows.keyspaces).hasSize(2); - assertThat(rows.keyspaces.get(0).getString("keyspace_name")).isEqualTo("ks1"); - assertThat(rows.keyspaces.get(1).getString("keyspace_name")).isEqualTo("ks2"); + assertThat(rows.keyspaces()).hasSize(2); + assertThat(rows.keyspaces().get(0).getString("keyspace_name")).isEqualTo("ks1"); + assertThat(rows.keyspaces().get(1).getString("keyspace_name")).isEqualTo("ks2"); // Types - assertThat(rows.types.keySet()).containsOnly(KS1_ID); - assertThat(rows.types.get(KS1_ID)).hasSize(1); - assertThat(rows.types.get(KS1_ID).iterator().next().getString("type_name")) + assertThat(rows.types().keySet()).containsOnly(KS1_ID); + assertThat(rows.types().get(KS1_ID)).hasSize(1); + assertThat(rows.types().get(KS1_ID).iterator().next().getString("type_name")) .isEqualTo("type"); // Tables - assertThat(rows.tables.keySet()).containsOnly(KS1_ID); - assertThat(rows.tables.get(KS1_ID)).hasSize(1); - assertThat(rows.tables.get(KS1_ID).iterator().next().getString("table_name")) + assertThat(rows.tables().keySet()).containsOnly(KS1_ID); + assertThat(rows.tables().get(KS1_ID)).hasSize(1); + assertThat(rows.tables().get(KS1_ID).iterator().next().getString("table_name")) .isEqualTo("foo"); // Columns - assertThat(rows.columns.keySet()).containsOnly(KS1_ID); - assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns().keySet()).containsOnly(KS1_ID); + assertThat(rows.columns().get(KS1_ID).keySet()).containsOnly(FOO_ID); assertThat( - rows.columns + rows.columns() .get(KS1_ID) .get(FOO_ID) .iterator() @@ -144,10 +144,10 @@ private void should_query_with_where_clause(String whereClause) { .isEqualTo("k"); // Indexes - assertThat(rows.indexes.keySet()).containsOnly(KS1_ID); - assertThat(rows.indexes.get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.indexes().keySet()).containsOnly(KS1_ID); + assertThat(rows.indexes().get(KS1_ID).keySet()).containsOnly(FOO_ID); assertThat( - rows.indexes + rows.indexes() .get(KS1_ID) .get(FOO_ID) .iterator() @@ -156,21 +156,22 @@ private void should_query_with_where_clause(String whereClause) { .isEqualTo("index"); // Views - assertThat(rows.views.keySet()).containsOnly(KS2_ID); - assertThat(rows.views.get(KS2_ID)).hasSize(1); - assertThat(rows.views.get(KS2_ID).iterator().next().getString("view_name")) + assertThat(rows.views().keySet()).containsOnly(KS2_ID); + assertThat(rows.views().get(KS2_ID)).hasSize(1); + assertThat(rows.views().get(KS2_ID).iterator().next().getString("view_name")) .isEqualTo("foo"); // Functions - assertThat(rows.functions.keySet()).containsOnly(KS2_ID); - assertThat(rows.functions.get(KS2_ID)).hasSize(1); - assertThat(rows.functions.get(KS2_ID).iterator().next().getString("function_name")) + assertThat(rows.functions().keySet()).containsOnly(KS2_ID); + assertThat(rows.functions().get(KS2_ID)).hasSize(1); + assertThat(rows.functions().get(KS2_ID).iterator().next().getString("function_name")) .isEqualTo("add"); // Aggregates - assertThat(rows.aggregates.keySet()).containsOnly(KS2_ID); - assertThat(rows.aggregates.get(KS2_ID)).hasSize(1); - assertThat(rows.aggregates.get(KS2_ID).iterator().next().getString("aggregate_name")) + assertThat(rows.aggregates().keySet()).containsOnly(KS2_ID); + assertThat(rows.aggregates().get(KS2_ID)).hasSize(1); + assertThat( + rows.aggregates().get(KS2_ID).iterator().next().getString("aggregate_name")) .isEqualTo("add"); }); } @@ -231,9 +232,9 @@ public void should_query_with_paging() { assertThat(result) .isSuccess( rows -> { - assertThat(rows.columns.keySet()).containsOnly(KS1_ID); - assertThat(rows.columns.get(KS1_ID).keySet()).containsOnly(FOO_ID); - assertThat(rows.columns.get(KS1_ID).get(FOO_ID)) + assertThat(rows.columns().keySet()).containsOnly(KS1_ID); + assertThat(rows.columns().get(KS1_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns().get(KS1_ID).get(FOO_ID)) .extracting(r -> r.getString("column_name")) .containsExactly("k", "v"); }); @@ -305,15 +306,15 @@ public void should_ignore_malformed_rows() { assertThat(result) .isSuccess( rows -> { - assertThat(rows.tables.keySet()).containsOnly(KS_ID); - assertThat(rows.tables.get(KS_ID)).hasSize(1); - assertThat(rows.tables.get(KS_ID).iterator().next().getString("table_name")) + assertThat(rows.tables().keySet()).containsOnly(KS_ID); + assertThat(rows.tables().get(KS_ID)).hasSize(1); + assertThat(rows.tables().get(KS_ID).iterator().next().getString("table_name")) .isEqualTo("foo"); - assertThat(rows.columns.keySet()).containsOnly(KS_ID); - assertThat(rows.columns.get(KS_ID).keySet()).containsOnly(FOO_ID); + assertThat(rows.columns().keySet()).containsOnly(KS_ID); + assertThat(rows.columns().get(KS_ID).keySet()).containsOnly(FOO_ID); assertThat( - rows.columns + rows.columns() .get(KS_ID) .get(FOO_ID) .iterator() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 0d7356a03a4..4b16da1c188 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -88,7 +88,8 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { // connect session to this keyspace. session.execute(String.format("USE %s", keyspace.asCql(false))); - Optional originalKsMeta = session.getMetadata().getKeyspace(keyspace); + Optional originalKsMeta = + session.getMetadata().getKeyspace(keyspace); // Usertype 'ztype' with two columns. Given name to ensure that even though it has an // alphabetically later name, it shows up before other user types ('ctype') that depend on it. @@ -199,7 +200,8 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { assertThat(originalKsMeta.get().getUserDefinedTypes()).isEmpty(); // validate that the exported schema matches what was expected exactly. - Optional ks = sessionRule.session().getMetadata().getKeyspace(keyspace); + Optional ks = + sessionRule.session().getMetadata().getKeyspace(keyspace); assertThat(ks.get().describeWithChildren(true).trim()).isEqualTo(expectedCql); // Also validate that when you create a Session with schema already created that the exported diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 334a7aa8da8..56b7d4320f8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; @@ -34,6 +35,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -138,7 +140,8 @@ public void should_handle_table_creation() { .hasValueSatisfying( k -> { assertThat(k.getType()).isEqualTo(DataTypes.INT); - assertThat(table.getPartitionKey()).containsExactly(k); + Assertions.assertThat(table.getPartitionKey()) + .containsExactly(k); }); assertThat(table.getClusteringColumns()).isEmpty(); }, @@ -418,7 +421,7 @@ private String keyspaceFilterOption(CqlIdentifier... keyspaces) { private void should_handle_creation( String beforeStatement, String createStatement, - Function> extract, + Function> extract, Consumer verifyMetadata, BiConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -467,7 +470,7 @@ ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { private void should_handle_drop( Iterable beforeStatements, String dropStatement, - Function> extract, + Function> extract, BiConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -510,7 +513,7 @@ ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { private void should_handle_update( Iterable beforeStatements, String updateStatement, - Function> extract, + Function> extract, Consumer verifyNewMetadata, TriConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -559,7 +562,7 @@ private void should_handle_update_via_drop_and_recreate( Iterable beforeStatements, String dropStatement, String recreateStatement, - Function> extract, + Function> extract, Consumer verifyNewMetadata, TriConsumer verifyListener, CqlIdentifier... keyspaces) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 8c3ee44331b..08eee7e5808 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -41,7 +41,7 @@ public class SchemaIT { @Test public void should_expose_system_and_test_keyspace() { - Map keyspaces = + Map keyspaces = sessionRule.session().getMetadata().getKeyspaces(); assertThat(keyspaces) .containsKeys( From dead2818e0c6ccba99ee5d5c0ab2cf18d559ab60 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 14 Jun 2018 13:47:24 -0700 Subject: [PATCH 512/742] JAVA-1819: Propagate more attributes to bound statements --- changelog/README.md | 1 + .../oss/driver/api/core/CqlSession.java | 82 +++++++++++++- .../api/core/cql/BoundStatementBuilder.java | 36 +++++- .../driver/api/core/cql/PrepareRequest.java | 45 +++++++- .../api/core/cql/SimpleStatementBuilder.java | 6 +- .../driver/api/core/cql/StatementBuilder.java | 10 +- .../driver/internal/core/cql/Conversions.java | 15 ++- .../core/cql/DefaultPrepareRequest.java | 73 +++++++----- .../core/cql/DefaultPreparedStatement.java | 106 +++++++++++------- .../core/tracker/RequestLogFormatterTest.java | 13 ++- .../driver/api/core/cql/BoundStatementIT.java | 94 ++++++++++++++++ 11 files changed, 397 insertions(+), 84 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 36e199b5ca4..09d530ae769 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1819: Propagate more attributes to bound statements - [improvement] JAVA-1897: Improve extensibility of schema metadata classes - [improvement] JAVA-1437: Enable SSL hostname validation by default - [improvement] JAVA-1879: Duplicate basic.request options as Request/Statement attributes diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index b67b9831757..e2f663597ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest; import edu.umd.cs.findbugs.annotations.NonNull; @@ -77,6 +78,47 @@ default CompletionStage executeAsync(@NonNull String query) { /** * Prepares a CQL statement synchronously (the calling thread blocks until the statement is * prepared). + * + *

          Note that the bound statements created from the resulting prepared statement will inherit + * some of the attributes of the provided simple statement. That is, given: + * + *

          {@code
          +   * SimpleStatement simpleStatement = SimpleStatement.newInstance("...");
          +   * PreparedStatement preparedStatement = session.prepare(simpleStatement);
          +   * BoundStatement boundStatement = preparedStatement.bind();
          +   * }
          + * + * Then: + * + *
            + *
          • the following methods return the same value as their counterpart on {@code + * simpleStatement}: + *
              + *
            • {@link Request#getConfigProfileName() boundStatement.getConfigProfileName()} + *
            • {@link Request#getConfigProfile() boundStatement.getConfigProfile()} + *
            • {@link Statement#getPagingState() boundStatement.getPagingState()} + *
            • {@link Request#getRoutingKey() boundStatement.getRoutingKey()} + *
            • {@link Request#getRoutingToken() boundStatement.getRoutingToken()} + *
            • {@link Request#getCustomPayload() boundStatement.getCustomPayload()} + *
            • {@link Request#isIdempotent() boundStatement.isIdempotent()} + *
            • {@link Request#getTimeout() boundStatement.getTimeout()} + *
            • {@link Statement#getPagingState() boundStatement.getPagingState()} + *
            • {@link Statement#getPageSize() boundStatement.getPageSize()} + *
            • {@link Statement#getConsistencyLevel() boundStatement.getConsistencyLevel()} + *
            • {@link Statement#getSerialConsistencyLevel() + * boundStatement.getSerialConsistencyLevel()} + *
            • {@link Request#isTracing() boundStatement.isTracing()} + *
            + *
          • {@link Request#getRoutingKeyspace() boundStatement.getRoutingKeyspace()} is set from + * either {@link Request#getKeyspace() simpleStatement.getKeyspace()} (if it's not {@code + * null}), or {@code simpleStatement.getRoutingKeyspace()}; + *
          • on the other hand, {@link Statement#getTimestamp() boundStatement.getTimestamp()} is + * not copied from the simple statement. It will be set to {@link Long#MIN_VALUE}, + * meaning that the value will be assigned by the session's timestamp generator. + *
          + * + * If you want to customize this behavior, you can write your own implementation of {@link + * PrepareRequest} and pass it to {@link #prepare(PrepareRequest)}. */ @NonNull default PreparedStatement prepare(@NonNull SimpleStatement statement) { @@ -96,6 +138,37 @@ default PreparedStatement prepare(@NonNull String query) { "The CQL prepare processor should never return a null result"); } + /** + * Prepares a CQL statement synchronously (the calling thread blocks until the statement is + * prepared). + * + *

          This variant is exposed in case you use an ad hoc {@link PrepareRequest} implementation to + * customize how attributes are propagated when you prepare a {@link SimpleStatement} (see {@link + * #prepare(SimpleStatement)} for more explanations). Otherwise, you should rarely have to deal + * with {@link PrepareRequest} directly. + */ + @NonNull + default PreparedStatement prepare(@NonNull PrepareRequest request) { + return Objects.requireNonNull( + execute(request, PrepareRequest.SYNC), + "The CQL prepare processor should never return a null result"); + } + + /** + * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was + * sent, generally before the statement is prepared). + * + *

          Note that the bound statements created from the resulting prepared statement will inherit + * some of the attributes of {@code query}; see {@link #prepare(SimpleStatement)} for more + * details. + */ + @NonNull + default CompletionStage prepareAsync(@NonNull SimpleStatement statement) { + return Objects.requireNonNull( + execute(new DefaultPrepareRequest(statement), PrepareRequest.ASYNC), + "The CQL prepare processor should never return a null result"); + } + /** * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was * sent, generally before the statement is prepared). @@ -110,11 +183,16 @@ default CompletionStage prepareAsync(@NonNull String query) { /** * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was * sent, generally before the statement is prepared). + * + *

          This variant is exposed in case you use an ad hoc {@link PrepareRequest} implementation to + * customize how attributes are propagated when you prepare a {@link SimpleStatement} (see {@link + * #prepare(SimpleStatement)} for more explanations). Otherwise, you should rarely have to deal + * with {@link PrepareRequest} directly. */ @NonNull - default CompletionStage prepareAsync(@NonNull SimpleStatement statement) { + default CompletionStage prepareAsync(PrepareRequest request) { return Objects.requireNonNull( - execute(new DefaultPrepareRequest(statement), PrepareRequest.ASYNC), + execute(request, PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index 9987139704e..366c733704d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -15,14 +15,19 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Map; import net.jcip.annotations.NotThreadSafe; @NotThreadSafe @@ -39,13 +44,41 @@ public BoundStatementBuilder( @NonNull PreparedStatement preparedStatement, @NonNull ColumnDefinitions variableDefinitions, @NonNull ByteBuffer[] values, + @Nullable String configProfileName, + @Nullable DriverConfigProfile configProfile, @Nullable CqlIdentifier routingKeyspace, + @Nullable ByteBuffer routingKey, + @Nullable Token routingToken, + @NonNull Map customPayload, + @Nullable Boolean idempotent, + boolean tracing, + long timestamp, + @Nullable ByteBuffer pagingState, + int pageSize, + @Nullable ConsistencyLevel consistencyLevel, + @Nullable ConsistencyLevel serialConsistencyLevel, + @Nullable Duration timeout, @NonNull CodecRegistry codecRegistry, @NonNull ProtocolVersion protocolVersion) { this.preparedStatement = preparedStatement; this.variableDefinitions = variableDefinitions; - this.routingKeyspace = routingKeyspace; this.values = values; + this.configProfileName = configProfileName; + this.configProfile = configProfile; + this.routingKeyspace = routingKeyspace; + this.routingKey = routingKey; + this.routingToken = routingToken; + for (Map.Entry entry : customPayload.entrySet()) { + this.addCustomPayload(entry.getKey(), entry.getValue()); + } + this.idempotent = idempotent; + this.tracing = tracing; + this.timestamp = timestamp; + this.pagingState = pagingState; + this.pageSize = pageSize; + this.consistencyLevel = consistencyLevel; + this.serialConsistencyLevel = serialConsistencyLevel; + this.timeout = timeout; this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; } @@ -54,7 +87,6 @@ public BoundStatementBuilder(@NonNull BoundStatement template) { super(template); this.preparedStatement = template.getPreparedStatement(); this.variableDefinitions = template.getPreparedStatement().getVariableDefinitions(); - this.routingKeyspace = template.getRoutingKeyspace(); this.values = template.getValues().toArray(new ByteBuffer[this.variableDefinitions.size()]); this.codecRegistry = template.codecRegistry(); this.protocolVersion = template.protocolVersion(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 4abac7f3131..198854a698f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -16,8 +16,10 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; @@ -25,6 +27,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -34,6 +37,10 @@ *

          Driver clients should rarely have to deal directly with this type, it's used internally by * {@link Session}'s prepare methods. However a {@link RetryPolicy} implementation might use it if * it needs a custom behavior for prepare requests. + * + *

          A client may also provide their own implementation of this interface to customize which + * attributes are propagated when preparing a simple statement; see {@link + * CqlSession#prepare(SimpleStatement)} for more explanations. */ public interface PrepareRequest extends Request { @@ -97,6 +104,26 @@ default boolean isTracing() { @Nullable DriverConfigProfile getConfigProfileForBoundStatements(); + /** + * The routing keyspace to use for the bound statements that will be created from the prepared + * statement. + */ + CqlIdentifier getRoutingKeyspaceForBoundStatements(); + + /** + * The routing key to use for the bound statements that will be created from the prepared + * statement. + */ + ByteBuffer getRoutingKeyForBoundStatements(); + + /** + * The routing key to use for the bound statements that will be created from the prepared + * statement. + * + *

          If it's not null, it takes precedence over {@link #getRoutingKeyForBoundStatements()}. + */ + Token getRoutingTokenForBoundStatements(); + /** * Returns the custom payload to send alongside the bound statements that will be created from the * prepared statement. @@ -112,6 +139,19 @@ default boolean isTracing() { @Nullable Boolean areBoundStatementsIdempotent(); + /** + * The timeout to use for the bound statements that will be created from the prepared statement. + * If the value is null, the default value will be used from the configuration. + */ + @Nullable + Duration getTimeoutForBoundStatements(); + + /** + * The paging state to use for the bound statements that will be created from the prepared + * statement. + */ + ByteBuffer getPagingStateForBoundStatements(); + /** * The page size to use for the bound statements that will be created from the prepared statement. * If the value is 0 or negative, the default value will be used from the configuration. @@ -120,7 +160,7 @@ default boolean isTracing() { /** * The consistency level to use for the bound statements that will be created from the prepared - * statement or {@link null} to use the default value from the configuration. + * statement or {@code null} to use the default value from the configuration. */ @Nullable ConsistencyLevel getConsistencyLevelForBoundStatements(); @@ -131,4 +171,7 @@ default boolean isTracing() { */ @Nullable ConsistencyLevel getSerialConsistencyLevelForBoundStatements(); + + /** Whether bound statements that will be created from the prepared statement are tracing. */ + boolean areBoundStatementsTracing(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index f5b7072499f..06c22b52d6b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -68,7 +68,7 @@ public SimpleStatementBuilder withQuery(@NonNull String query) { /** @see SimpleStatement#getKeyspace() */ @NonNull - public SimpleStatementBuilder withKeyspace(@NonNull CqlIdentifier keyspace) { + public SimpleStatementBuilder withKeyspace(@Nullable CqlIdentifier keyspace) { this.keyspace = keyspace; return this; } @@ -78,8 +78,8 @@ public SimpleStatementBuilder withKeyspace(@NonNull CqlIdentifier keyspace) { * withKeyspace(CqlIdentifier.fromCql(keyspaceName))}. */ @NonNull - public SimpleStatementBuilder withKeyspace(@NonNull String keyspaceName) { - return withKeyspace(CqlIdentifier.fromCql(keyspaceName)); + public SimpleStatementBuilder withKeyspace(@Nullable String keyspaceName) { + return withKeyspace(keyspaceName == null ? null : CqlIdentifier.fromCql(keyspaceName)); } /** @see SimpleStatement#setPositionalValues(List) */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 356623382ee..7c4332b0df5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -62,8 +62,14 @@ protected StatementBuilder() { protected StatementBuilder(S template) { this.configProfileName = template.getConfigProfileName(); this.configProfile = template.getConfigProfile(); - this.customPayloadBuilder = - NullAllowingImmutableMap.builder().putAll(template.getCustomPayload()); + this.routingKeyspace = template.getRoutingKeyspace(); + this.routingKey = template.getRoutingKey(); + this.routingToken = template.getRoutingToken(); + if (!template.getCustomPayload().isEmpty()) { + this.customPayloadBuilder = + NullAllowingImmutableMap.builder() + .putAll(template.getCustomPayload()); + } this.idempotent = template.isIdempotent(); this.tracing = template.isTracing(); this.timestamp = template.getTimestamp(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 563c2247bc1..faecdc4cefc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -347,18 +347,23 @@ public static DefaultPreparedStatement toPreparedStatement( ? null : ByteBuffer.wrap(response.resultMetadataId).asReadOnlyBuffer(), toColumnDefinitions(response.resultMetadata, context), + request.getKeyspace(), + NullAllowingImmutableMap.copyOf(request.getCustomPayload()), request.getConfigProfileNameForBoundStatements(), request.getConfigProfileForBoundStatements(), - request.getKeyspace(), + request.getRoutingKeyspaceForBoundStatements(), + request.getRoutingKeyForBoundStatements(), + request.getRoutingTokenForBoundStatements(), NullAllowingImmutableMap.copyOf(request.getCustomPayloadForBoundStatements()), request.areBoundStatementsIdempotent(), - context.codecRegistry(), - context.protocolVersion(), - NullAllowingImmutableMap.copyOf(request.getCustomPayload()), + request.getTimeoutForBoundStatements(), + request.getPagingStateForBoundStatements(), request.getPageSizeForBoundStatements(), request.getConsistencyLevelForBoundStatements(), request.getSerialConsistencyLevelForBoundStatements(), - request.getTimeout()); + request.areBoundStatementsTracing(), + context.codecRegistry(), + context.protocolVersion()); } public static ColumnDefinitions toColumnDefinitions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index fc9f6062fa2..271afd72a9f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -33,34 +33,18 @@ * Default implementation of a prepare request, which is built internally to handle calls such as * {@link CqlSession#prepare(String)} and {@link CqlSession#prepare(SimpleStatement)}. * - *

          When a {@link SimpleStatement} gets prepared, some of its fields are propagated automatically. - * For simplicity, this implementation makes the following opinionated choices: + *

          When built from a {@link SimpleStatement}, it propagates the attributes to bound statements + * according to the rules described in {@link CqlSession#prepare(SimpleStatement)}. The prepare + * request itself: * *

            - *
          • the prepare request: - *
              - *
            • will use the same configuration profile (or configuration profile name) as the {@code - * SimpleStatement}; - *
            • will use the same custom payload as the {@code SimpleStatement}; - *
            • will use the same timeout as the {@code SimpleStatement}. - *
            - *
          • any bound statement created from the prepared statement: - *
              - *
            • will use the same configuration profile (or configuration profile name) as the {@code - * SimpleStatement}; - *
            • will use the same custom payload as the {@code SimpleStatement}; - *
            • will use the same page size as the {@code SimpleStatement}; - *
            • will use the same consistency level as the {@code SimpleStatement}; - *
            • will use the same serial consistency level as the {@code SimpleStatement}; - *
            • will use the same timeout as the {@code SimpleStatement}; - *
            • will be idempotent if and only if the {@code SimpleStatement} was idempotent. - *
            + *
          • will use the same configuration profile (or configuration profile name) as the {@code + * SimpleStatement}; + *
          • will use the same custom payload as the {@code SimpleStatement}; + *
          • will use a {@code null} timeout in order to default to the configuration (assuming that if + * a statement with a custom timeout is prepared, it is intended for the bound statements, not + * the preparation itself). *
          - * - *

          This should be appropriate for most use cases; however if you need something more exotic (for - * example, preparing with one profile, but executing bound statements with another one), you can - * either write your own {@code PrepareRequest} implementation, or set the options manually on every - * bound statement. */ @Immutable public class DefaultPrepareRequest implements PrepareRequest { @@ -127,7 +111,7 @@ public Map getCustomPayload() { @Nullable @Override public Duration getTimeout() { - return statement.getTimeout(); + return null; } @Nullable @@ -142,6 +126,26 @@ public DriverConfigProfile getConfigProfileForBoundStatements() { return statement.getConfigProfile(); } + @Nullable + @Override + public CqlIdentifier getRoutingKeyspaceForBoundStatements() { + return (statement.getKeyspace() != null) + ? statement.getKeyspace() + : statement.getRoutingKeyspace(); + } + + @Nullable + @Override + public ByteBuffer getRoutingKeyForBoundStatements() { + return statement.getRoutingKey(); + } + + @Nullable + @Override + public Token getRoutingTokenForBoundStatements() { + return statement.getRoutingToken(); + } + @NonNull @Override public Map getCustomPayloadForBoundStatements() { @@ -154,6 +158,18 @@ public Boolean areBoundStatementsIdempotent() { return statement.isIdempotent(); } + @Nullable + @Override + public Duration getTimeoutForBoundStatements() { + return statement.getTimeout(); + } + + @Nullable + @Override + public ByteBuffer getPagingStateForBoundStatements() { + return statement.getPagingState(); + } + @Override public int getPageSizeForBoundStatements() { return statement.getPageSize(); @@ -170,4 +186,9 @@ public ConsistencyLevel getConsistencyLevelForBoundStatements() { public ConsistencyLevel getSerialConsistencyLevelForBoundStatements() { return statement.getSerialConsistencyLevel(); } + + @Override + public boolean areBoundStatementsTracing() { + return statement.isTracing(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 14a449648d2..ce7514987a2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.data.ValuesHelper; import com.datastax.oss.driver.internal.core.session.RepreparePayload; @@ -43,15 +44,19 @@ public class DefaultPreparedStatement implements PreparedStatement { private volatile ResultMetadata resultMetadata; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; - // The options to propagate to the bound statements: - private final String configProfileName; - private final DriverConfigProfile configProfile; + private final String configProfileNameForBoundStatements; + private final DriverConfigProfile configProfileForBoundStatements; + private final ByteBuffer pagingStateForBoundStatements; + private final CqlIdentifier routingKeyspaceForBoundStatements; + private final ByteBuffer routingKeyForBoundStatements; + private final Token routingTokenForBoundStatements; private final Map customPayloadForBoundStatements; - private final Boolean idempotent; - private final int pageSize; - private final ConsistencyLevel consistencyLevel; - private final ConsistencyLevel serialConsistencyLevel; - private final Duration timeout; + private final Boolean areBoundStatementsIdempotent; + private final boolean areBoundStatementsTracing; + private final int pageSizeForBoundStatements; + private final ConsistencyLevel consistencyLevelForBoundStatements; + private final ConsistencyLevel serialConsistencyLevelForBoundStatements; + private final Duration timeoutForBoundStatements; public DefaultPreparedStatement( ByteBuffer id, @@ -60,18 +65,23 @@ public DefaultPreparedStatement( List partitionKeyIndices, ByteBuffer resultMetadataId, ColumnDefinitions resultSetDefinitions, - String configProfileName, - DriverConfigProfile configProfile, CqlIdentifier keyspace, + Map customPayloadForPrepare, + String configProfileNameForBoundStatements, + DriverConfigProfile configProfileForBoundStatements, + CqlIdentifier routingKeyspaceForBoundStatements, + ByteBuffer routingKeyForBoundStatements, + Token routingTokenForBoundStatements, Map customPayloadForBoundStatements, - Boolean idempotent, + Boolean areBoundStatementsIdempotent, + Duration timeoutForBoundStatements, + ByteBuffer pagingStateForBoundStatements, + int pageSizeForBoundStatements, + ConsistencyLevel consistencyLevelForBoundStatements, + ConsistencyLevel serialConsistencyLevelForBoundStatements, + boolean areBoundStatementsTracing, CodecRegistry codecRegistry, - ProtocolVersion protocolVersion, - Map customPayloadForPrepare, - int pageSize, - ConsistencyLevel consistencyLevel, - ConsistencyLevel serialConsistencyLevel, - Duration timeout) { + ProtocolVersion protocolVersion) { this.id = id; this.partitionKeyIndices = partitionKeyIndices; // It's important that we keep a reference to this object, so that it only gets evicted from @@ -79,16 +89,23 @@ public DefaultPreparedStatement( this.repreparePayload = new RepreparePayload(id, query, keyspace, customPayloadForPrepare); this.variableDefinitions = variableDefinitions; this.resultMetadata = new ResultMetadata(resultMetadataId, resultSetDefinitions); - this.configProfileName = configProfileName; - this.configProfile = configProfile; + + this.configProfileNameForBoundStatements = configProfileNameForBoundStatements; + this.configProfileForBoundStatements = configProfileForBoundStatements; + this.routingKeyspaceForBoundStatements = routingKeyspaceForBoundStatements; + this.routingKeyForBoundStatements = routingKeyForBoundStatements; + this.routingTokenForBoundStatements = routingTokenForBoundStatements; this.customPayloadForBoundStatements = customPayloadForBoundStatements; - this.idempotent = idempotent; + this.areBoundStatementsIdempotent = areBoundStatementsIdempotent; + this.timeoutForBoundStatements = timeoutForBoundStatements; + this.pagingStateForBoundStatements = pagingStateForBoundStatements; + this.pageSizeForBoundStatements = pageSizeForBoundStatements; + this.consistencyLevelForBoundStatements = consistencyLevelForBoundStatements; + this.serialConsistencyLevelForBoundStatements = serialConsistencyLevelForBoundStatements; + this.areBoundStatementsTracing = areBoundStatementsTracing; + this.codecRegistry = codecRegistry; this.protocolVersion = protocolVersion; - this.pageSize = pageSize; - this.consistencyLevel = consistencyLevel; - this.serialConsistencyLevel = serialConsistencyLevel; - this.timeout = timeout; } @NonNull @@ -140,22 +157,20 @@ public BoundStatement bind(@NonNull Object... values) { variableDefinitions, ValuesHelper.encodePreparedValues( values, variableDefinitions, codecRegistry, protocolVersion), - configProfileName, - configProfile, - // If the prepared statement had a per-request keyspace, we want to use that as the routing - // keyspace. - repreparePayload.keyspace, - null, - null, + configProfileNameForBoundStatements, + configProfileForBoundStatements, + routingKeyspaceForBoundStatements, + routingKeyForBoundStatements, + routingTokenForBoundStatements, customPayloadForBoundStatements, - idempotent, - false, + areBoundStatementsIdempotent, + areBoundStatementsTracing, Long.MIN_VALUE, - null, - pageSize, - consistencyLevel, - serialConsistencyLevel, - timeout, + pagingStateForBoundStatements, + pageSizeForBoundStatements, + consistencyLevelForBoundStatements, + serialConsistencyLevelForBoundStatements, + timeoutForBoundStatements, codecRegistry, protocolVersion); } @@ -168,7 +183,20 @@ public BoundStatementBuilder boundStatementBuilder(@NonNull Object... values) { variableDefinitions, ValuesHelper.encodePreparedValues( values, variableDefinitions, codecRegistry, protocolVersion), - repreparePayload.keyspace, + configProfileNameForBoundStatements, + configProfileForBoundStatements, + routingKeyspaceForBoundStatements, + routingKeyForBoundStatements, + routingTokenForBoundStatements, + customPayloadForBoundStatements, + areBoundStatementsIdempotent, + areBoundStatementsTracing, + Long.MIN_VALUE, + pagingStateForBoundStatements, + pageSizeForBoundStatements, + consistencyLevelForBoundStatements, + serialConsistencyLevelForBoundStatements, + timeoutForBoundStatements, codecRegistry, protocolVersion); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java index 276812220df..4b86daf612f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java @@ -269,16 +269,21 @@ private PreparedStatement mockPreparedStatement(String query, Map mockCustomPayload = + NullAllowingImmutableMap.of("key1", Bytes.fromHexString("0xcccc")); + Duration mockTimeout = Duration.ofSeconds(1); + ConsistencyLevel mockCl = DefaultConsistencyLevel.LOCAL_QUORUM; + ConsistencyLevel mockSerialCl = DefaultConsistencyLevel.LOCAL_SERIAL; + int mockPageSize = 2000; + + SimpleStatement simpleStatement = + SimpleStatement.builder("SELECT release_version FROM system.local") + .withConfigProfile(mockProfile) + .withConfigProfileName(mockConfigProfileName) + .withPagingState(mockPagingState) + .withKeyspace(mockKeyspace) + .withRoutingKeyspace(mockRoutingKeyspace) + .withRoutingKey(mockRoutingKey) + .withRoutingToken(mockRoutingToken) + .addCustomPayload("key1", mockCustomPayload.get("key1")) + .withTimestamp(42) + .withIdempotence(true) + .withTracing() + .withTimeout(mockTimeout) + .withConsistencyLevel(mockCl) + .withSerialConsistencyLevel(mockSerialCl) + .withPageSize(mockPageSize) + .build(); + PreparedStatement preparedStatement = session.prepare(simpleStatement); + + // Cover all the ways to create bound statements: + ImmutableList> createMethods = + ImmutableList.of(PreparedStatement::bind, p -> p.boundStatementBuilder().build()); + + for (Function createMethod : createMethods) { + BoundStatement boundStatement = createMethod.apply(preparedStatement); + + assertThat(boundStatement.getConfigProfile()).isEqualTo(mockProfile); + assertThat(boundStatement.getConfigProfileName()).isEqualTo(mockConfigProfileName); + assertThat(boundStatement.getPagingState()).isEqualTo(mockPagingState); + assertThat(boundStatement.getRoutingKeyspace()) + .isEqualTo(mockKeyspace != null ? mockKeyspace : mockRoutingKeyspace); + assertThat(boundStatement.getRoutingKey()).isEqualTo(mockRoutingKey); + assertThat(boundStatement.getRoutingToken()).isEqualTo(mockRoutingToken); + assertThat(boundStatement.getCustomPayload()).isEqualTo(mockCustomPayload); + assertThat(boundStatement.isIdempotent()).isTrue(); + assertThat(boundStatement.isTracing()).isTrue(); + assertThat(boundStatement.getTimeout()).isEqualTo(mockTimeout); + assertThat(boundStatement.getConsistencyLevel()).isEqualTo(mockCl); + assertThat(boundStatement.getSerialConsistencyLevel()).isEqualTo(mockSerialCl); + assertThat(boundStatement.getPageSize()).isEqualTo(mockPageSize); + + // Bound statements do not support per-query keyspaces, so this is not set + assertThat(boundStatement.getKeyspace()).isNull(); + // Should not be propagated + assertThat(boundStatement.getTimestamp()).isEqualTo(Long.MIN_VALUE); + } + } + private static void verifyUnset( CqlSession session, BoundStatement boundStatement, String valueName) { session.execute(boundStatement.unset(1)); @@ -391,4 +478,11 @@ private CqlSession sessionWithCustomCodec(CqlIntToStringCodec codec) { .addTypeCodecs(codec) .build(); } + + private boolean supportsPerRequestKeyspace(CqlSession session) { + InternalDriverContext context = (InternalDriverContext) session.getContext(); + ProtocolVersionRegistry protocolVersionRegistry = context.protocolVersionRegistry(); + return protocolVersionRegistry.supports( + context.protocolVersion(), ProtocolFeature.PER_REQUEST_KEYSPACE); + } } From 2e1461cf817a5cef5bd08aae7adbc11f13b41781 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 10 Jul 2018 19:12:03 -0700 Subject: [PATCH 513/742] Push down isTracing() from Request to Statement --- .../com/datastax/oss/driver/api/core/CqlSession.java | 2 +- .../datastax/oss/driver/api/core/cql/ExecutionInfo.java | 3 +-- .../datastax/oss/driver/api/core/cql/PrepareRequest.java | 6 ------ .../com/datastax/oss/driver/api/core/cql/QueryTrace.java | 3 +-- .../com/datastax/oss/driver/api/core/cql/Statement.java | 3 +++ .../datastax/oss/driver/api/core/session/Request.java | 9 --------- .../oss/driver/example/guava/internal/KeyRequest.java | 5 ----- 7 files changed, 6 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index e2f663597ca..d2220d84bb0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -107,7 +107,7 @@ default CompletionStage executeAsync(@NonNull String query) { *

        • {@link Statement#getConsistencyLevel() boundStatement.getConsistencyLevel()} *
        • {@link Statement#getSerialConsistencyLevel() * boundStatement.getSerialConsistencyLevel()} - *
        • {@link Request#isTracing() boundStatement.isTracing()} + *
        • {@link Statement#isTracing() boundStatement.isTracing()} * *
        • {@link Request#getRoutingKeyspace() boundStatement.getRoutingKeyspace()} is set from * either {@link Request#getKeyspace() simpleStatement.getKeyspace()} (if it's not {@code diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java index dc29e82817c..5187966b720 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ExecutionInfo.java @@ -22,7 +22,6 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; -import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; @@ -150,7 +149,7 @@ public interface ExecutionInfo { boolean isSchemaInAgreement(); /** - * The tracing identifier if tracing was {@link Request#isTracing() enabled} for this query, + * The tracing identifier if tracing was {@link Statement#isTracing() enabled} for this query, * otherwise {@code null}. */ @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 198854a698f..43b2787f373 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -81,12 +81,6 @@ default Boolean isIdempotent() { return true; } - @Override - default boolean isTracing() { - // Tracing prepare requests is unlikely to be useful, we don't expose an API for it. - return false; - } - /** * The name of the driver configuration profile to use for the bound statements that will be * created from the prepared statement. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java index 4cb1518acd3..4af0648ce4c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/QueryTrace.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.api.core.cql; -import com.datastax.oss.driver.api.core.session.Request; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetAddress; import java.util.List; @@ -25,7 +24,7 @@ /** * Tracing information for a query. * - *

          When {@link Request#isTracing() tracing} is enabled for a query, Cassandra generates rows in + *

          When {@link Statement#isTracing() tracing} is enabled for a query, Cassandra generates rows in * the {@code sessions} and {@code events} table of the {@code system_traces} keyspace. This class * is a client-side representation of that information. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index d087227ae26..6534628149b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -298,6 +298,9 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { @NonNull T setSerialConsistencyLevel(@Nullable ConsistencyLevel newSerialConsistencyLevel); + /** Whether tracing information should be recorded for this statement. */ + boolean isTracing(); + /** * Calculates the approximate size in bytes that the statement will have when encoded. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index e743b6aea10..77e663d0a3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -160,13 +160,4 @@ public interface Request { */ @Nullable Duration getTimeout(); - - /** - * Whether tracing information should be recorded for this request. - * - *

          Tracing is rather specific to CQL, but this is exposed in this interface because it is - * available at the protocol level. Request implementations are free to use it if it is relevant - * to them, or always return {@code false} otherwise. - */ - boolean isTracing(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java index b7ab093cac8..bab37da931f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java @@ -85,9 +85,4 @@ public Boolean isIdempotent() { public Duration getTimeout() { return null; } - - @Override - public boolean isTracing() { - return false; - } } From 634408bef32fa212d68c887b88b4b4d8c458acdd Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 11 Jul 2018 16:17:25 -0700 Subject: [PATCH 514/742] Fix column ordering issue in table metadata --- .../metadata/schema/parsing/RawColumn.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java index fc79a830dc8..c9f65de601d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import com.datastax.oss.driver.shaded.guava.common.primitives.Ints; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collection; import java.util.Collections; @@ -113,7 +114,7 @@ public int compareTo(@NonNull RawColumn that) { // First, order by kind. Then order partition key and clustering columns by position. For // other kinds, order by column name. if (!this.kind.equals(that.kind)) { - return this.kind.compareTo(that.kind); + return Ints.compare(rank(this.kind), rank(that.kind)); } else if (kind.equals(KIND_PARTITION_KEY) || kind.equals(KIND_CLUSTERING_COLUMN)) { return Integer.compare(this.position, that.position); } else { @@ -121,6 +122,23 @@ public int compareTo(@NonNull RawColumn that) { } } + private static int rank(String kind) { + switch (kind) { + case KIND_PARTITION_KEY: + return 1; + case KIND_CLUSTERING_COLUMN: + return 2; + case KIND_REGULAR: + return 3; + case KIND_COMPACT_VALUE: + return 4; + case KIND_STATIC: + return 5; + default: + return Integer.MAX_VALUE; + } + } + public static List toRawColumns( Collection rows, CqlIdentifier keyspaceId, From c99cec29b19547b0761077a3cabf21dbec5034c1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 9 Jul 2018 10:37:30 -0700 Subject: [PATCH 515/742] JAVA-1889: Upgrade dependencies to the latest minor versions --- changelog/README.md | 1 + .../com/datastax/oss/driver/Assertions.java | 6 +- .../ChannelFactoryAvailableIdsTest.java | 3 +- .../ChannelFactoryClusterNameTest.java | 9 +-- ...ChannelFactoryProtocolNegotiationTest.java | 11 ++-- .../channel/MockChannelFactoryHelper.java | 2 +- .../core/control/ControlConnectionTest.java | 37 ++++++------ .../control/ControlConnectionTestBase.java | 19 ++++--- .../core/cql/CqlPrepareHandlerTest.java | 25 ++++---- .../core/cql/CqlRequestHandlerRetryTest.java | 15 ++--- ...equestHandlerSpeculativeExecutionTest.java | 13 +++-- .../core/cql/CqlRequestHandlerTest.java | 11 ++-- .../core/cql/DefaultAsyncResultSetTest.java | 3 +- .../core/cql/QueryTraceFetcherTest.java | 11 ++-- .../metadata/DefaultTopologyMonitorTest.java | 19 ++++--- .../core/metadata/MetadataManagerTest.java | 11 ++-- .../metadata/SchemaAgreementCheckerTest.java | 17 +++--- .../queries/Cassandra21SchemaQueriesTest.java | 3 +- .../queries/Cassandra22SchemaQueriesTest.java | 3 +- .../queries/Cassandra3SchemaQueriesTest.java | 7 ++- .../core/pool/ChannelPoolInitTest.java | 9 +-- .../core/pool/ChannelPoolKeyspaceTest.java | 9 +-- .../core/pool/ChannelPoolReconnectTest.java | 7 ++- .../core/pool/ChannelPoolResizeTest.java | 15 ++--- .../core/pool/ChannelPoolShutdownTest.java | 10 ++-- .../core/pool/ChannelPoolTestBase.java | 22 +++---- .../core/session/DefaultSessionPoolsTest.java | 57 ++++++++++--------- .../session/MockChannelPoolFactoryHelper.java | 2 +- .../core/session/ReprepareOnUpTest.java | 17 +++--- ...ncurrencyLimitingRequestThrottlerTest.java | 25 ++++---- .../RateLimitingRequestThrottlerTest.java | 31 +++++----- .../ThreadLocalTimestampGeneratorTest.java | 3 +- .../core/type/codec/CodecTestBase.java | 8 +-- pom.xml | 20 +++---- 34 files changed, 248 insertions(+), 213 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 09d530ae769..25a41d77859 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1889: Upgrade dependencies to the latest minor versions - [improvement] JAVA-1819: Propagate more attributes to bound statements - [improvement] JAVA-1897: Improve extensibility of schema metadata classes - [improvement] JAVA-1437: Enable SSL hostname validation by default diff --git a/core/src/test/java/com/datastax/oss/driver/Assertions.java b/core/src/test/java/com/datastax/oss/driver/Assertions.java index bacdba66748..6137414a5db 100644 --- a/core/src/test/java/com/datastax/oss/driver/Assertions.java +++ b/core/src/test/java/com/datastax/oss/driver/Assertions.java @@ -40,7 +40,11 @@ public static NettyFutureAssert assertThat(Future actual) { return new NettyFutureAssert<>(actual); } - public static CompletionStageAssert assertThat(CompletionStage actual) { + /** + * Use a different name because this clashes with AssertJ's built-in one. Our implementation is a + * bit more flexible for checking completion values and errors. + */ + public static CompletionStageAssert assertThatStage(CompletionStage actual) { return new CompletionStageAssert<>(actual); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 2645c70e8e0..9d71da05a58 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.timeout; @@ -64,7 +65,7 @@ public void should_report_available_ids() { completeSimpleChannelInit(); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isSuccess( channel -> { assertThat(channel.getAvailableIds()).isEqualTo(128); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index e0b6494836d..e9e16f4bd33 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -45,7 +46,7 @@ public void should_set_cluster_name_from_first_connection() { writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); // Then - assertThat(channelFuture).isSuccess(); + assertThatStage(channelFuture).isSuccess(); assertThat(factory.clusterName).isEqualTo("mockClusterName"); } @@ -64,7 +65,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { // open a first connection that will define the cluster name writeInboundFrame(readOutboundFrame(), new Ready()); writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); - assertThat(channelFuture).isSuccess(); + assertThatStage(channelFuture).isSuccess(); // open a second connection that returns the same cluster name channelFuture = factory.connect( @@ -73,7 +74,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("mockClusterName")); // Then - assertThat(channelFuture).isSuccess(); + assertThatStage(channelFuture).isSuccess(); // When // open a third connection that returns a different cluster name @@ -84,7 +85,7 @@ public void should_check_cluster_name_for_next_connections() throws Throwable { writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("wrongClusterName")); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isFailed( e -> assertThat(e) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index f7a37f5edab..b197fc7c248 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -53,7 +54,7 @@ public void should_succeed_if_version_specified_and_supported_by_server() { completeSimpleChannelInit(); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V4); } @@ -81,7 +82,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isFailed( e -> { assertThat(e) @@ -113,7 +114,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V4); } @@ -148,7 +149,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy requestFrame = readOutboundFrame(); writeInboundFrame(requestFrame, TestResponses.clusterNameResponse("mockClusterName")); - assertThat(channelFuture) + assertThatStage(channelFuture) .isSuccess(channel -> assertThat(channel.getClusterName()).isEqualTo("mockClusterName")); assertThat(factory.protocolVersion).isEqualTo(DefaultProtocolVersion.V3); } @@ -185,7 +186,7 @@ public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) requestFrame, new Error(errorCode, "Invalid or unsupported protocol version")); // Then - assertThat(channelFuture) + assertThatStage(channelFuture) .isFailed( e -> { assertThat(e) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index ec205fb825b..272a43b9a6a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -117,7 +117,7 @@ public static class Builder { MultimapBuilder.hashKeys().arrayListValues().build(); public Builder(ChannelFactory channelFactory) { - assertThat(MockUtil.isMock(channelFactory)).isTrue().as("expected a mock"); + assertThat(MockUtil.isMock(channelFactory)).as("expected a mock").isTrue(); Mockito.verifyZeroInteractions(channelFactory); this.channelFactory = channelFactory; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index c47ebc36e11..3abf455d14d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.control; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @@ -44,7 +45,7 @@ public void should_close_successfully_if_it_was_never_init() { CompletionStage closeFuture = controlConnection.forceCloseAsync(); // Then - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); } @Test @@ -60,7 +61,7 @@ public void should_init_with_first_contact_point_if_reachable() { waitForPendingAdminTasks(); // Then - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -80,7 +81,7 @@ public void should_always_return_same_init_future() { CompletionStage initFuture2 = controlConnection.init(false, false); // Then - assertThat(initFuture1).isEqualTo(initFuture2); + assertThatStage(initFuture1).isEqualTo(initFuture2); factoryHelper.verifyNoMoreCalls(); } @@ -102,7 +103,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { waitForPendingAdminTasks(); // Then - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); @@ -128,7 +129,7 @@ public void should_fail_to_init_if_all_contact_points_fail() { waitForPendingAdminTasks(); // Then - assertThat(initFuture).isFailed(); + assertThatStage(initFuture).isFailed(); Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node2)); // no reconnections at init @@ -154,7 +155,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -193,7 +194,7 @@ public void should_reconnect_if_node_becomes_ignored() { factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -241,7 +242,7 @@ private void should_reconnect_if_event(NodeStateEvent event) { factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -285,7 +286,7 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -342,7 +343,7 @@ private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEven factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -385,7 +386,7 @@ public void should_force_reconnection_if_pending() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -423,7 +424,7 @@ public void should_force_reconnection_even_if_connected() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -461,9 +462,9 @@ public void should_not_force_reconnection_if_closed() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); CompletionStage closeFuture = controlConnection.forceCloseAsync(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); // When controlConnection.reconnectNow(); @@ -485,14 +486,14 @@ public void should_close_channel_when_closing() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); // When CompletionStage closeFuture = controlConnection.forceCloseAsync(); waitForPendingAdminTasks(); // Then - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); Mockito.verify(channel1).forceClose(); factoryHelper.verifyNoMoreCalls(); @@ -516,7 +517,7 @@ public void should_close_channel_if_closed_during_reconnection() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); @@ -563,7 +564,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { CompletionStage initFuture = controlConnection.init(false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index a5b4711cdc0..ce27be05144 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; +import io.netty.channel.Channel; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoop; @@ -136,25 +137,27 @@ public void teardown() { } protected DriverChannel newMockDriverChannel(int id) { - DriverChannel channel = Mockito.mock(DriverChannel.class); + DriverChannel driverChannel = Mockito.mock(DriverChannel.class); + Channel channel = Mockito.mock(Channel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); - DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); - Mockito.when(channel.close()) + DefaultChannelPromise closeFuture = new DefaultChannelPromise(channel, adminExecutor); + Mockito.when(driverChannel.close()) .thenAnswer( i -> { closeFuture.trySuccess(null); return closeFuture; }); - Mockito.when(channel.forceClose()) + Mockito.when(driverChannel.forceClose()) .thenAnswer( i -> { closeFuture.trySuccess(null); return closeFuture; }); - Mockito.when(channel.closeFuture()).thenReturn(closeFuture); - Mockito.when(channel.toString()).thenReturn("channel" + id); - Mockito.when(channel.remoteAddress()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); - return channel; + Mockito.when(driverChannel.closeFuture()).thenReturn(closeFuture); + Mockito.when(driverChannel.toString()).thenReturn("channel" + id); + Mockito.when(driverChannel.remoteAddress()) + .thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); + return driverChannel; } // Wait for all the tasks on the admin executor to complete. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index f777329166b..c9fd4ccc3a9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; @@ -101,7 +102,7 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); // The future waits for the reprepare attempt on other nodes, so it's not done yet. - assertThat(prepareFuture).isNotDone(); + assertThatStage(prepareFuture).isNotDone(); // Should now reprepare on the remaining nodes: node2Behavior.verifyWrite(); @@ -112,7 +113,7 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { node3Behavior.setWriteSuccess(); node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); - assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } } @@ -142,7 +143,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); // The future should complete immediately: - assertThat(prepareFuture).isSuccess(); + assertThatStage(prepareFuture).isSuccess(); // And the other nodes should not be contacted: node2Behavior.verifyNoWrite(); @@ -178,7 +179,7 @@ public void should_not_reprepare_on_other_nodes_if_already_cached() { // When the statement already existed, we don't prepare on other nodes, so the future should // complete immediately. - assertThat(prepareFuture) + assertThatStage(prepareFuture) .isSuccess( preparedStatement -> assertThat(preparedStatement).isSameAs(mockExistingStatement)); @@ -206,7 +207,7 @@ public void should_ignore_errors_while_repreparing_on_other_nodes() { "test") .handle(); - assertThat(prepareFuture).isNotDone(); + assertThatStage(prepareFuture).isNotDone(); // Other nodes fail, the future should still succeed when all done node2Behavior.verifyWrite(); @@ -217,7 +218,7 @@ public void should_ignore_errors_while_repreparing_on_other_nodes() { node3Behavior.verifyWrite(); node3Behavior.setWriteFailure(new RuntimeException("mock error")); - assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } } @@ -251,12 +252,12 @@ public void should_retry_initial_prepare_if_recoverable_error() { .handle(); // Success on node2, reprepare on node3 - assertThat(prepareFuture).isNotDone(); + assertThatStage(prepareFuture).isNotDone(); node3Behavior.verifyWrite(); node3Behavior.setWriteSuccess(); node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); - assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } } @@ -290,7 +291,7 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { .handle(); // Success on node2, reprepare on node3 - assertThat(prepareFuture) + assertThatStage(prepareFuture) .isFailed( error -> { assertThat(error).isInstanceOf(OverloadedException.class); @@ -330,7 +331,7 @@ public void should_fail_if_retry_policy_ignores_error() { .handle(); // Success on node2, reprepare on node3 - assertThat(prepareFuture) + assertThatStage(prepareFuture) .isFailed( error -> { assertThat(error) @@ -369,7 +370,7 @@ public void should_propagate_custom_payload_on_single_node() { .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); node2Behavior.verifyNoWrite(); node3Behavior.verifyNoWrite(); - assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } } @@ -402,7 +403,7 @@ public void should_propagate_custom_payload_on_all_nodes() { .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); Mockito.verify(node3Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); - assertThat(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); + assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 1d5eae7f944..4c6b667a442 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -74,7 +75,7 @@ public void should_always_try_next_node_if_bootstrapping( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> { Iterator rows = resultSet.currentPage().iterator(); @@ -114,7 +115,7 @@ public void should_always_rethrow_query_validation_error( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed( error -> { assertThat(error) @@ -153,7 +154,7 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> { Iterator rows = resultSet.currentPage().iterator(); @@ -201,7 +202,7 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> { Iterator rows = resultSet.currentPage().iterator(); @@ -248,7 +249,7 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> { Iterator rows = resultSet.currentPage().iterator(); @@ -294,7 +295,7 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed( error -> { assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); @@ -340,7 +341,7 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed( error -> { assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 30af811e47f..0e3a990fff9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -168,7 +169,7 @@ public void should_not_start_execution_if_result_complete( // Complete the request from the initial execution node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); - assertThat(resultSetFuture).isSuccess(); + assertThatStage(resultSetFuture).isSuccess(); // Pending speculative executions should have been cancelled. However we don't check // firstExecutionTask directly because the request handler's onResponse can sometimes be @@ -215,7 +216,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement harness.nextScheduledTask(); // Discard the timeout task - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); } } @@ -263,7 +264,7 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); // But again the query plan is empty so that should fail the request - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed( error -> { assertThat(error).isInstanceOf(AllNodesFailedException.class); @@ -319,7 +320,7 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( node2Behavior.setResponseSuccess( defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed( error -> { assertThat(error).isInstanceOf(AllNodesFailedException.class); @@ -373,7 +374,7 @@ public void should_retry_in_speculative_executions( defaultFrameOf(new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))); // The second execution should move to node3 and complete the request - assertThat(resultSetFuture).isSuccess(); + assertThatStage(resultSetFuture).isSuccess(); // The request to node1 was still in flight, it should have been cancelled node1Behavior.verifyCancellation(); @@ -417,7 +418,7 @@ public void should_stop_retrying_other_executions_if_result_complete( // Complete the request from the initial execution node1Behavior.setResponseSuccess(defaultFrameOf(singleRow())); - assertThat(resultSetFuture).isSuccess(); + assertThatStage(resultSetFuture).isSuccess(); // node2 replies with a response that would trigger a RETRY_NEXT if the request was still // running diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index a1c1961a7ff..d5f661006c1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; @@ -62,7 +63,7 @@ public void should_complete_result_if_first_node_replies_immediately() { "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> { Iterator rows = resultSet.currentPage().iterator(); @@ -96,7 +97,7 @@ public void should_fail_if_no_node_available() { "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); } } @@ -129,7 +130,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { .isEqualTo(configuredTimeout.toNanos()); scheduledTask.run(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isFailed(t -> assertThat(t).isInstanceOf(DriverTimeoutException.class)); } } @@ -149,7 +150,7 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { "test") .handle(); - assertThat(resultSetFuture) + assertThatStage(resultSetFuture) .isSuccess( resultSet -> Mockito.verify(harness.getSession()) @@ -199,7 +200,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce defaultFrameOf(new Unprepared("mock message", mockId.array()))); // Should now re-prepare, re-execute and succeed. - assertThat(resultSetFuture).isSuccess(); + assertThatStage(resultSetFuture).isSuccess(); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index c66bce3efd1..026fd4f077f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -96,7 +97,7 @@ public void should_invoke_session_to_fetch_next_page() { // Then Mockito.verify(statement).copy(mockPagingState); Mockito.verify(session).executeAsync(mockNextStatement); - assertThat(nextPageFuture).isEqualTo(mockResultFuture); + assertThatStage(nextPageFuture).isEqualTo(mockResultFuture); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index e5d4834e844..03f05971d10 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.times; @@ -129,7 +130,7 @@ public void should_succeed_when_both_queries_succeed_immediately() { assertEventsQuery(statement); Mockito.verifyNoMoreInteractions(session); - assertThat(traceFuture) + assertThatStage(traceFuture) .isSuccess( trace -> { assertThat(trace.getTracingId()).isEqualTo(TRACING_ID); @@ -181,7 +182,7 @@ public void should_succeed_when_events_query_is_paged() { assertThat(statements.get(2).getPagingState()).isEqualTo(PAGING_STATE); Mockito.verifyNoMoreInteractions(session); - assertThat(traceFuture).isSuccess(trace -> assertThat(trace.getEvents()).hasSize(2)); + assertThatStage(traceFuture).isSuccess(trace -> assertThat(trace.getEvents()).hasSize(2)); } @Test @@ -205,7 +206,7 @@ public void should_retry_when_session_row_is_incomplete() { assertEventsQuery(statements.get(2)); Mockito.verifyNoMoreInteractions(session); - assertThat(traceFuture) + assertThatStage(traceFuture) .isSuccess( trace -> { assertThat(trace.getTracingId()).isEqualTo(TRACING_ID); @@ -248,7 +249,7 @@ public void should_fail_when_session_query_fails() { assertSessionQuery(statement); Mockito.verifyNoMoreInteractions(session); - assertThat(traceFuture).isFailed(error -> assertThat(error).isSameAs(mockError)); + assertThatStage(traceFuture).isFailed(error -> assertThat(error).isSameAs(mockError)); } @Test @@ -271,7 +272,7 @@ public void should_fail_when_session_query_still_incomplete_after_max_tries() { assertSessionQuery(statements.get(i)); } - assertThat(traceFuture) + assertThatStage(traceFuture) .isFailed( error -> assertThat(error.getMessage()) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 9060ce0008f..599dfa0dc15 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; @@ -110,7 +111,7 @@ public void should_not_refresh_control_node() { CompletionStage> futureInfo = topologyMonitor.refreshNode(node1); // Then - assertThat(futureInfo).isSuccess(maybeInfo -> assertThat(maybeInfo.isPresent()).isFalse()); + assertThatStage(futureInfo).isSuccess(maybeInfo -> assertThat(maybeInfo.isPresent()).isFalse()); } @Test @@ -128,7 +129,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present() { CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -152,7 +153,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present_v2() CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -176,7 +177,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -208,7 +209,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present_V CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -240,7 +241,7 @@ public void should_get_new_node_from_peers() { CompletionStage> futureInfo = topologyMonitor.getNewNodeInfo(ADDRESS1); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -276,7 +277,7 @@ public void should_get_new_node_from_peers_v2() { CompletionStage> futureInfo = topologyMonitor.getNewNodeInfo(ADDRESS1); // Then - assertThat(futureInfo) + assertThatStage(futureInfo) .isSuccess( maybeInfo -> { assertThat(maybeInfo.isPresent()).isTrue(); @@ -316,7 +317,7 @@ public void should_refresh_node_list_from_local_and_peers() { CompletionStage> futureInfos = topologyMonitor.refreshNodeList(); // Then - assertThat(futureInfos) + assertThatStage(futureInfos) .isSuccess( infos -> { Iterator iterator = infos.iterator(); @@ -343,7 +344,7 @@ public void should_stop_executing_queries_once_closed() throws Exception { CompletionStage> futureInfos = topologyMonitor.refreshNodeList(); // Then - assertThat(futureInfos) + assertThatStage(futureInfos) .isFailed(error -> assertThat(error).isInstanceOf(IllegalStateException.class)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 8ab4965d800..5cd94ae0bf5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.timeout; @@ -110,7 +111,7 @@ public void should_add_contact_points() { waitForPendingAdminTasks(); // Then - assertThat(addContactPointsFuture).isSuccess(); + assertThatStage(addContactPointsFuture).isSuccess(); assertThat(metadataManager.refreshes).hasSize(1); InitContactPointsRefresh refresh = ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); @@ -125,7 +126,7 @@ public void should_use_default_if_no_contact_points_provided() { waitForPendingAdminTasks(); // Then - assertThat(addContactPointsFuture).isSuccess(); + assertThatStage(addContactPointsFuture).isSuccess(); assertThat(metadataManager.refreshes).hasSize(1); InitContactPointsRefresh refresh = ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); @@ -146,7 +147,7 @@ public void should_refresh_all_nodes() { waitForPendingAdminTasks(); // Then - assertThat(refreshNodesFuture).isSuccess(); + assertThatStage(refreshNodesFuture).isSuccess(); assertThat(metadataManager.refreshes).hasSize(1); FullNodeListRefresh refresh = (FullNodeListRefresh) metadataManager.refreshes.get(0); assertThat(refresh.nodeInfos).containsExactlyInAnyOrder(info1, info2); @@ -166,7 +167,7 @@ public void should_refresh_single_node() { // Then // the info should have been copied to the node - assertThat(refreshNodeFuture).isSuccess(); + assertThatStage(refreshNodeFuture).isSuccess(); Mockito.verify(info, timeout(500)).getDatacenter(); assertThat(node.getDatacenter()).isEqualTo("dc1"); } @@ -182,7 +183,7 @@ public void should_ignore_node_refresh_if_topology_monitor_does_not_have_info() CompletionStage refreshNodeFuture = metadataManager.refreshNode(node); // Then - assertThat(refreshNodeFuture).isSuccess(); + assertThatStage(refreshNodeFuture).isSuccess(); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index 49c67771899..2f9f9ee4a44 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.never; @@ -119,7 +120,7 @@ public void should_skip_if_timeout_is_zero() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isFalse()); + assertThatStage(future).isSuccess(b -> assertThat(b).isFalse()); } @Test @@ -137,7 +138,7 @@ public void should_succeed_if_only_one_node() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); } @Test @@ -156,7 +157,7 @@ public void should_succeed_if_versions_match_on_first_try() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); Mockito.verify(addressTranslator).translate(ADDRESS2); } @@ -177,7 +178,7 @@ public void should_ignore_down_peers() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); Mockito.verify(addressTranslator).translate(ADDRESS2); } @@ -197,7 +198,7 @@ public void should_ignore_malformed_rows() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); Mockito.verify(addressTranslator, never()).translate(ADDRESS2); } @@ -220,7 +221,7 @@ public void should_use_peer_if_rpc_address_is_0_0_0_0() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); Mockito.verify(addressTranslator).translate(ADDRESS2); } @@ -249,7 +250,7 @@ public void should_reschedule_if_versions_do_not_match_on_first_try() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isTrue()); + assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); } @Test @@ -271,7 +272,7 @@ public void should_fail_if_versions_do_not_match_after_timeout() { CompletionStage future = checker.run(); // Then - assertThat(future).isSuccess(b -> assertThat(b).isFalse()); + assertThatStage(future).isSuccess(b -> assertThat(b).isFalse()); } /** Extend to mock the query execution logic. */ diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index 523f65212f0..a013890a340 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -71,7 +72,7 @@ public void should_query() { channel.runPendingTasks(); - assertThat(result) + assertThatStage(result) .isSuccess( rows -> { // Keyspace diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index 6e4312b508b..ac218123ec4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -81,7 +82,7 @@ public void should_query() { channel.runPendingTasks(); - assertThat(result) + assertThatStage(result) .isSuccess( rows -> { // Keyspace diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index 4e84a48a69a..59c0fb33e83 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigProfile; @@ -111,7 +112,7 @@ private void should_query_with_where_clause(String whereClause) { channel.runPendingTasks(); - assertThat(result) + assertThatStage(result) .isSuccess( rows -> { // Keyspace @@ -229,7 +230,7 @@ public void should_query_with_paging() { channel.runPendingTasks(); - assertThat(result) + assertThatStage(result) .isSuccess( rows -> { assertThat(rows.columns().keySet()).containsOnly(KS1_ID); @@ -303,7 +304,7 @@ public void should_ignore_malformed_rows() { channel.runPendingTasks(); - assertThat(result) + assertThatStage(result) .isSuccess( rows -> { assertThat(rows.tables().keySet()).containsOnly(KS_ID); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 8fecadc7ce8..6b0b11db9f1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.pool; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -58,7 +59,7 @@ public void should_initialize_when_all_channels_succeed() throws Exception { factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - assertThat(poolFuture) + assertThatStage(poolFuture) .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); @@ -83,7 +84,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); + assertThatStage(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); Mockito.verify(nodeMetricUpdater, times(3)) .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); @@ -108,7 +109,7 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - assertThat(poolFuture) + assertThatStage(poolFuture) .isSuccess( pool -> { assertThat(pool.isInvalidKeyspace()).isTrue(); @@ -171,7 +172,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { factoryHelper.waitForCalls(node, 2); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index 3f087f3b5c1..d7a48d3aedf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.pool; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -50,7 +51,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { factoryHelper.waitForCalls(node, 2); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); @@ -61,7 +62,7 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { Mockito.verify(channel1).setKeyspace(newKeyspace); Mockito.verify(channel2).setKeyspace(newKeyspace); - assertThat(setKeyspaceFuture).isSuccess(); + assertThatStage(setKeyspaceFuture).isSuccess(); factoryHelper.verifyNoMoreCalls(); } @@ -93,7 +94,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { factoryHelper.waitForCalls(node, 2); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); // Check that reconnection has kicked in, but do not complete it yet @@ -105,7 +106,7 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { CqlIdentifier newKeyspace = CqlIdentifier.fromCql("new_keyspace"); CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); waitForPendingAdminTasks(); - assertThat(setKeyspaceFuture).isSuccess(); + assertThatStage(setKeyspaceFuture).isSuccess(); // Now let the two channels succeed to complete the reconnection channel1Future.complete(channel1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index 8194157c839..d5648fc4d66 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.pool; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -63,7 +64,7 @@ public void should_reconnect_when_channel_closes() throws Exception { factoryHelper.waitForCalls(node, 2); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); @@ -115,7 +116,7 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce factoryHelper.waitForCalls(node, 2); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); @@ -165,7 +166,7 @@ public void should_let_current_attempt_complete_when_reconnecting_now() ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); factoryHelper.waitForCalls(node, 1); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelOpened(node)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index a0144ebe4c0..686bd947754 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.pool; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -60,7 +61,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { factoryHelper.waitForCalls(node, 4); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2, channel3, channel4); inOrder.verify(eventBus, times(4)).fire(ChannelEvent.channelOpened(node)); @@ -110,7 +111,7 @@ public void should_shrink_during_reconnection() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); @@ -169,7 +170,7 @@ public void should_grow_outside_of_reconnection() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); @@ -226,7 +227,7 @@ public void should_grow_during_reconnection() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); @@ -294,7 +295,7 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); @@ -352,7 +353,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1); @@ -416,7 +417,7 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); assertThat(pool.channels).containsOnly(channel1, channel2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 01b22fe6763..5f087bac3a5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.pool; -import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -64,7 +64,7 @@ public void should_close_all_channels_when_closed() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); // Simulate graceful shutdown on channel3 @@ -101,7 +101,7 @@ public void should_close_all_channels_when_closed() throws Exception { ((ChannelPromise) channel2.closeFuture()).setSuccess(); ((ChannelPromise) channel3.closeFuture()).setSuccess(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); factoryHelper.verifyNoMoreCalls(); } @@ -135,7 +135,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - assertThat(poolFuture).isSuccess(); + assertThatStage(poolFuture).isSuccess(); ChannelPool pool = poolFuture.toCompletableFuture().get(); inOrder.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); @@ -173,7 +173,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception ((ChannelPromise) channel2.closeFuture()).setSuccess(); ((ChannelPromise) channel3.closeFuture()).setSuccess(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 1aaa3e9e32f..756154a73e1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; +import io.netty.channel.Channel; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoop; @@ -93,18 +94,19 @@ public void teardown() { } DriverChannel newMockDriverChannel(int id) { - DriverChannel channel = Mockito.mock(DriverChannel.class); + DriverChannel driverChannel = Mockito.mock(DriverChannel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); - DefaultChannelPromise closeFuture = new DefaultChannelPromise(null, adminExecutor); - DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(null, adminExecutor); - Mockito.when(channel.close()).thenReturn(closeFuture); - Mockito.when(channel.forceClose()).thenReturn(closeFuture); - Mockito.when(channel.closeFuture()).thenReturn(closeFuture); - Mockito.when(channel.closeStartedFuture()).thenReturn(closeStartedFuture); - Mockito.when(channel.setKeyspace(any(CqlIdentifier.class))) + Channel channel = Mockito.mock(Channel.class); + DefaultChannelPromise closeFuture = new DefaultChannelPromise(channel, adminExecutor); + DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(channel, adminExecutor); + Mockito.when(driverChannel.close()).thenReturn(closeFuture); + Mockito.when(driverChannel.forceClose()).thenReturn(closeFuture); + Mockito.when(driverChannel.closeFuture()).thenReturn(closeFuture); + Mockito.when(driverChannel.closeStartedFuture()).thenReturn(closeStartedFuture); + Mockito.when(driverChannel.setKeyspace(any(CqlIdentifier.class))) .thenReturn(adminExecutor.newSucceededFuture(null)); - Mockito.when(channel.toString()).thenReturn("channel" + id); - return channel; + Mockito.when(driverChannel.toString()).thenReturn("channel" + id); + return driverChannel; } // Wait for all the tasks on the pool's admin executor to complete. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index c1abcf39448..3bf0b530152 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anySet; @@ -218,14 +219,14 @@ public void should_initialize_pools_with_distances() { factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.REMOTE); waitForPendingAdminTasks(); - assertThat(initFuture).isNotDone(); + assertThatStage(initFuture).isNotDone(); pool1Future.complete(pool1); pool2Future.complete(pool2); pool3Future.complete(pool3); waitForPendingAdminTasks(); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()) @@ -250,7 +251,7 @@ public void should_not_connect_to_ignored_nodes() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); @@ -274,7 +275,7 @@ public void should_not_connect_to_forced_down_nodes() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); @@ -302,7 +303,7 @@ public void should_adjust_distance_if_changed_while_init() { factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isNotDone(); + assertThatStage(initFuture).isNotDone(); // Distance changes while init still pending eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); @@ -314,7 +315,7 @@ public void should_adjust_distance_if_changed_while_init() { Mockito.verify(pool2).resize(NodeDistance.REMOTE); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()) @@ -343,7 +344,7 @@ public void should_remove_pool_if_ignored_while_init() { factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isNotDone(); + assertThatStage(initFuture).isNotDone(); // Distance changes while init still pending eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); @@ -355,7 +356,7 @@ public void should_remove_pool_if_ignored_while_init() { Mockito.verify(pool2).closeAsync(); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); @@ -383,7 +384,7 @@ public void should_remove_pool_if_forced_down_while_init() { factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isNotDone(); + assertThatStage(initFuture).isNotDone(); // Forced down while init still pending eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); @@ -395,7 +396,7 @@ public void should_remove_pool_if_forced_down_while_init() { Mockito.verify(pool2).closeAsync(); - assertThat(initFuture) + assertThatStage(initFuture) .isSuccess( session -> assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3)); @@ -419,7 +420,7 @@ public void should_resize_pool_if_distance_changes() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); Mockito.verify(pool2, timeout(500)).resize(NodeDistance.REMOTE); @@ -443,7 +444,7 @@ public void should_remove_pool_if_node_becomes_ignored() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); Mockito.verify(pool2, timeout(500)).closeAsync(); @@ -470,7 +471,7 @@ public void should_do_nothing_if_node_becomes_ignored_but_was_already_ignored() factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); Mockito.verify(pool2, timeout(100)).closeAsync(); @@ -505,7 +506,7 @@ public void should_recreate_pool_if_node_becomes_not_ignored() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -534,7 +535,7 @@ public void should_remove_pool_if_node_is_forced_down() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); Mockito.verify(pool2, timeout(500)).closeAsync(); @@ -564,7 +565,7 @@ public void should_recreate_pool_if_node_is_forced_back_up() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -594,7 +595,7 @@ public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -626,7 +627,7 @@ public void should_adjust_distance_if_changed_while_recreating() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -669,7 +670,7 @@ public void should_remove_pool_if_ignored_while_recreating() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -712,7 +713,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -751,12 +752,12 @@ public void should_close_all_pools_when_closing() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CompletionStage closeFuture = session.closeAsync(); waitForPendingAdminTasks(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); Mockito.verify(pool1).closeAsync(); Mockito.verify(pool2).closeAsync(); @@ -781,12 +782,12 @@ public void should_force_close_all_pools_when_force_closing() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CompletionStage closeFuture = session.forceCloseAsync(); waitForPendingAdminTasks(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); Mockito.verify(pool1).forceCloseAsync(); Mockito.verify(pool2).forceCloseAsync(); @@ -815,7 +816,7 @@ public void should_close_pool_if_recreated_while_closing() { factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -826,7 +827,7 @@ public void should_close_pool_if_recreated_while_closing() { // but the session gets closed before pool init completes CompletionStage closeFuture = session.closeAsync(); waitForPendingAdminTasks(); - assertThat(closeFuture).isSuccess(); + assertThatStage(closeFuture).isSuccess(); // now pool init completes pool2Future.complete(pool2); @@ -854,7 +855,7 @@ public void should_set_keyspace_on_all_pools() { factoryHelper.waitForCall(node2, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); CqlIdentifier newKeyspace = CqlIdentifier.fromInternal("newKeyspace"); @@ -888,7 +889,7 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() factoryHelper.waitForCall(node1, KEYSPACE, NodeDistance.LOCAL); factoryHelper.waitForCall(node3, KEYSPACE, NodeDistance.LOCAL); waitForPendingAdminTasks(); - assertThat(initFuture).isSuccess(); + assertThatStage(initFuture).isSuccess(); DefaultSession session = (DefaultSession) CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(session.getPools()).containsValues(pool1, pool3); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 346e3650666..83ea502283d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -119,7 +119,7 @@ public static class Builder { MultimapBuilder.hashKeys().arrayListValues().build(); private Builder(ChannelPoolFactory channelPoolFactory) { - assertThat(MockUtil.isMock(channelPoolFactory)).isTrue().as("expected a mock"); + assertThat(MockUtil.isMock(channelPoolFactory)).as("expected a mock").isTrue(); Mockito.verifyZeroInteractions(channelPoolFactory); this.channelPoolFactory = channelPoolFactory; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index ebbb963ac38..93477917922 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -106,7 +107,7 @@ public void should_complete_immediately_if_no_prepared_statements() { reprepareOnUp.start(); // Then - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -120,7 +121,7 @@ public void should_complete_immediately_if_pool_empty() { reprepareOnUp.start(); // Then - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -144,7 +145,7 @@ public void should_reprepare_all_if_system_table_query_fails() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -170,7 +171,7 @@ public void should_reprepare_all_if_system_table_empty() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -192,7 +193,7 @@ public void should_reprepare_all_if_system_query_disabled() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -218,7 +219,7 @@ public void should_not_reprepare_already_known_statements() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -261,7 +262,7 @@ public void should_limit_number_of_statements_to_reprepare() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } @Test @@ -303,7 +304,7 @@ public void should_limit_number_of_statements_reprepared_in_parallel() { adminQuery.resultFuture.complete(null); } - assertThat(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); + assertThatStage(done).isSuccess(v -> assertThat(reprepareOnUp.queries).isEmpty()); } private Map getMockPayloads(char... values) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java index 1f693431bde..7ba77d8caee 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session.throttling; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -65,7 +66,7 @@ public void should_start_immediately_when_under_capacity() { throttler.register(request); // Then - assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); assertThat(throttler.getConcurrentRequests()).isEqualTo(1); assertThat(throttler.getQueue()).isEmpty(); } @@ -91,7 +92,7 @@ private void should_allow_new_request_when_active_one_completes( // Given MockThrottled first = new MockThrottled(); throttler.register(first); - assertThat(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); for (int i = 0; i < 4; i++) { // fill to capacity throttler.register(new MockThrottled()); } @@ -106,7 +107,7 @@ private void should_allow_new_request_when_active_one_completes( throttler.register(incoming); // Then - assertThat(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); assertThat(throttler.getConcurrentRequests()).isEqualTo(5); assertThat(throttler.getQueue()).isEmpty(); } @@ -125,7 +126,7 @@ public void should_enqueue_when_over_capacity() { throttler.register(incoming); // Then - assertThat(incoming.started).isNotDone(); + assertThatStage(incoming.started).isNotDone(); assertThat(throttler.getConcurrentRequests()).isEqualTo(5); assertThat(throttler.getQueue()).containsExactly(incoming); } @@ -150,20 +151,20 @@ private void should_dequeue_when_active_completes(Consumer completeCa // Given MockThrottled first = new MockThrottled(); throttler.register(first); - assertThat(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(first.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); for (int i = 0; i < 4; i++) { throttler.register(new MockThrottled()); } MockThrottled incoming = new MockThrottled(); throttler.register(incoming); - assertThat(incoming.started).isNotDone(); + assertThatStage(incoming.started).isNotDone(); // When completeCallback.accept(first); // Then - assertThat(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThatStage(incoming.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); assertThat(throttler.getConcurrentRequests()).isEqualTo(5); assertThat(throttler.getQueue()).isEmpty(); } @@ -182,7 +183,7 @@ public void should_reject_when_queue_is_full() { throttler.register(incoming); // Then - assertThat(incoming.started) + assertThatStage(incoming.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } @@ -201,7 +202,7 @@ public void should_remove_timed_out_request_from_queue() { throttler.signalTimeout(queued1); // Then - assertThat(queued2.started).isNotDone(); + assertThatStage(queued2.started).isNotDone(); assertThat(throttler.getConcurrentRequests()).isEqualTo(5); assertThat(throttler.getQueue()).hasSize(1); } @@ -216,7 +217,7 @@ public void should_reject_enqueued_when_closing() { for (int i = 0; i < 10; i++) { MockThrottled request = new MockThrottled(); throttler.register(request); - assertThat(request.started).isNotDone(); + assertThatStage(request.started).isNotDone(); enqueued.add(request); } @@ -225,7 +226,7 @@ public void should_reject_enqueued_when_closing() { // Then for (MockThrottled request : enqueued) { - assertThat(request.started) + assertThatStage(request.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } @@ -234,7 +235,7 @@ public void should_reject_enqueued_when_closing() { throttler.register(request); // Then - assertThat(request.started) + assertThatStage(request.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java index 212159407c1..a809cbdd171 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.session.throttling; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -94,7 +95,7 @@ public void should_start_immediately_when_under_capacity() { throttler.register(request); // Then - assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); assertThat(throttler.getStoredPermits()).isEqualTo(4); assertThat(throttler.getQueue()).isEmpty(); } @@ -113,7 +114,7 @@ public void should_allow_new_request_when_under_rate() { throttler.register(request); // Then - assertThat(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); + assertThatStage(request.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isFalse()); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).isEmpty(); } @@ -132,7 +133,7 @@ public void should_enqueue_when_over_rate() { throttler.register(request); // Then - assertThat(request.started).isNotDone(); + assertThatStage(request.started).isNotDone(); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).containsExactly(request); @@ -156,7 +157,7 @@ public void should_reject_when_queue_is_full() { throttler.register(request); // Then - assertThat(request.started) + assertThatStage(request.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } @@ -175,7 +176,7 @@ public void should_remove_timed_out_request_from_queue() { throttler.signalTimeout(queued1); // Then - assertThat(queued2.started).isNotDone(); + assertThatStage(queued2.started).isNotDone(); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).containsExactly(queued2); } @@ -189,10 +190,10 @@ public void should_dequeue_when_draining_task_runs() { MockThrottled queued1 = new MockThrottled(); throttler.register(queued1); - assertThat(queued1.started).isNotDone(); + assertThatStage(queued1.started).isNotDone(); MockThrottled queued2 = new MockThrottled(); throttler.register(queued2); - assertThat(queued2.started).isNotDone(); + assertThatStage(queued2.started).isNotDone(); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).hasSize(2); @@ -217,8 +218,8 @@ public void should_dequeue_when_draining_task_runs() { task.run(); // Then - assertThat(queued1.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); - assertThat(queued2.started).isNotDone(); + assertThatStage(queued1.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThatStage(queued2.started).isNotDone(); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).containsExactly(queued2); // task reschedules itself since it did not empty the queue @@ -231,7 +232,7 @@ public void should_dequeue_when_draining_task_runs() { task.run(); // Then - assertThat(queued2.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThatStage(queued2.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); assertThat(throttler.getStoredPermits()).isEqualTo(0); assertThat(throttler.getQueue()).isEmpty(); assertThat(adminExecutor.nextTask()).isNull(); @@ -273,14 +274,14 @@ public void should_keep_accumulating_time_if_no_permits_created() { // Then MockThrottled queued = new MockThrottled(); throttler.register(queued); - assertThat(queued.started).isNotDone(); + assertThatStage(queued.started).isNotDone(); // When clock.add(ONE_HUNDRED_MILLISECONDS); adminExecutor.nextTask().run(); // Then - assertThat(queued.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); + assertThatStage(queued.started).isSuccess(wasDelayed -> assertThat(wasDelayed).isTrue()); } @Test @@ -293,7 +294,7 @@ public void should_reject_enqueued_when_closing() { for (int i = 0; i < 10; i++) { MockThrottled request = new MockThrottled(); throttler.register(request); - assertThat(request.started).isNotDone(); + assertThatStage(request.started).isNotDone(); enqueued.add(request); } @@ -302,7 +303,7 @@ public void should_reject_enqueued_when_closing() { // Then for (MockThrottled request : enqueued) { - assertThat(request.started) + assertThatStage(request.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } @@ -311,7 +312,7 @@ public void should_reject_enqueued_when_closing() { throttler.register(request); // Then - assertThat(request.started) + assertThatStage(request.started) .isFailed(error -> assertThat(error).isInstanceOf(RequestThrottlingException.class)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java index 8d623553f50..977279629b1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.time; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static com.datastax.oss.driver.Assertions.fail; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -70,7 +71,7 @@ public void should_confine_timestamps_to_thread() throws Exception { assertThat(futures).hasSize(testThreadsCount); for (CompletionStage future : futures) { - assertThat(future).isSuccess(); + assertThatStage(future).isSuccess(); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java index 576072ce911..5fba391f94f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CodecTestBase.java @@ -26,7 +26,7 @@ public class CodecTestBase { protected TypeCodec codec; protected String encode(T t, ProtocolVersion protocolVersion) { - assertThat(codec).isNotNull().as("Must set codec before calling this method"); + assertThat(codec).as("Must set codec before calling this method").isNotNull(); ByteBuffer bytes = codec.encode(t, protocolVersion); return (bytes == null) ? null : Bytes.toHexString(bytes); } @@ -36,7 +36,7 @@ protected String encode(T t) { } protected T decode(String hexString, ProtocolVersion protocolVersion) { - assertThat(codec).isNotNull().as("Must set codec before calling this method"); + assertThat(codec).as("Must set codec before calling this method").isNotNull(); ByteBuffer bytes = (hexString == null) ? null : Bytes.fromHexString(hexString); // Decode twice, to assert that decode leaves the input buffer in its original state codec.decode(bytes, protocolVersion); @@ -48,12 +48,12 @@ protected T decode(String hexString) { } protected String format(T t) { - assertThat(codec).isNotNull().as("Must set codec before calling this method"); + assertThat(codec).as("Must set codec before calling this method").isNotNull(); return codec.format(t); } protected T parse(String s) { - assertThat(codec).isNotNull().as("Must set codec before calling this method"); + assertThat(codec).as("Must set codec before calling this method").isNotNull(); return codec.parse(s); } } diff --git a/pom.xml b/pom.xml index b79c12cfe29..2cad3156083 100644 --- a/pom.xml +++ b/pom.xml @@ -46,23 +46,23 @@ true UTF-8 UTF-8 - 1.3.1 + 1.3.3 25.1-jre 2.1.10 4.0.2 1.4.3-SNAPSHOT - 4.1.9.Final + 4.1.26.Final 1.7.25 - 1.1.2.6 + 1.1.7.2 1.4.1 - 3.6.2 + 3.10.0 1.3 2.9.5 4.12 1.2.3 - 4.11.0 + 4.12.0 0.8.3 @@ -107,7 +107,7 @@ com.github.jnr jnr-ffi - 2.1.6 + 2.1.8 org.xerial.snappy @@ -122,7 +122,7 @@ com.github.jnr jnr-posix - 3.0.27 + 3.0.46 io.dropwizard.metrics @@ -147,7 +147,7 @@ com.github.spotbugs spotbugs-annotations - 3.1.3 + 3.1.5 junit @@ -157,7 +157,7 @@ com.tngtech.java junit-dataprovider - 1.12.0 + 1.13.1 org.assertj @@ -167,7 +167,7 @@ org.mockito mockito-core - 2.7.19 + 2.19.0 com.datastax.oss.simulacron From 96d1c38d7be540dd081e08d6b27b2758b0d3f9e4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 6 Jul 2018 14:06:57 -0700 Subject: [PATCH 516/742] JAVA-1880: Rename "config profile" to "execution profile" --- changelog/README.md | 1 + .../oss/driver/api/core/CqlSession.java | 4 +- .../driver/api/core/config/DriverConfig.java | 10 +- ...ofile.java => DriverExecutionProfile.java} | 36 +++---- .../api/core/context/DriverContext.java | 8 +- .../api/core/cql/BatchStatementBuilder.java | 4 +- .../api/core/cql/BoundStatementBuilder.java | 14 +-- .../driver/api/core/cql/PrepareRequest.java | 16 +-- .../api/core/cql/PreparedStatement.java | 2 +- .../api/core/cql/SimpleStatementBuilder.java | 4 +- .../oss/driver/api/core/cql/Statement.java | 12 +-- .../driver/api/core/cql/StatementBuilder.java | 24 ++--- .../oss/driver/api/core/session/Request.java | 18 ++-- .../api/core/session/SessionBuilder.java | 10 +- .../api/core/tracker/RequestTracker.java | 22 ++--- .../core/auth/PlainTextAuthProvider.java | 4 +- .../internal/core/channel/ChannelFactory.java | 18 ++-- .../core/channel/DefaultWriteCoalescer.java | 4 +- .../core/channel/HeartbeatHandler.java | 18 ++-- .../core/channel/ProtocolInitHandler.java | 4 +- .../typesafe/DefaultDriverConfigLoader.java | 4 +- .../config/typesafe/TypesafeDriverConfig.java | 20 ++-- ...va => TypesafeDriverExecutionProfile.java} | 44 +++++---- .../ConstantReconnectionPolicy.java | 4 +- .../ExponentialReconnectionPolicy.java | 4 +- .../core/context/DefaultDriverContext.java | 6 +- .../core/context/DefaultNettyOptions.java | 4 +- .../driver/internal/core/cql/Conversions.java | 8 +- .../core/cql/CqlPrepareHandlerBase.java | 24 ++--- .../core/cql/CqlRequestHandlerBase.java | 63 ++++++------ .../core/cql/DefaultBatchStatement.java | 98 +++++++++---------- .../core/cql/DefaultBoundStatement.java | 84 ++++++++-------- .../core/cql/DefaultExecutionInfo.java | 10 +- .../core/cql/DefaultPrepareRequest.java | 20 ++-- .../core/cql/DefaultPreparedStatement.java | 22 ++--- .../core/cql/DefaultSimpleStatement.java | 92 ++++++++--------- .../internal/core/cql/QueryTraceFetcher.java | 26 +++-- .../DefaultLoadBalancingPolicy.java | 6 +- .../core/metadata/DefaultTopologyMonitor.java | 4 +- .../metadata/LoadBalancingPolicyWrapper.java | 10 +- .../core/metadata/MetadataManager.java | 6 +- .../core/metadata/NodeStateManager.java | 4 +- .../core/metadata/SchemaAgreementChecker.java | 4 +- .../queries/Cassandra21SchemaQueries.java | 4 +- .../queries/Cassandra22SchemaQueries.java | 4 +- .../queries/Cassandra3SchemaQueries.java | 4 +- .../queries/CassandraSchemaQueries.java | 4 +- .../queries/DefaultSchemaQueriesFactory.java | 4 +- .../core/metrics/DropwizardMetricUpdater.java | 4 +- .../metrics/DropwizardMetricsFactory.java | 4 +- .../metrics/DropwizardNodeMetricUpdater.java | 4 +- .../internal/core/session/PoolManager.java | 4 +- .../ConcurrencyLimitingRequestThrottler.java | 4 +- .../RateLimitingRequestThrottler.java | 4 +- .../ConstantSpeculativeExecutionPolicy.java | 4 +- .../core/ssl/DefaultSslEngineFactory.java | 6 +- .../time/MonotonicTimestampGenerator.java | 6 +- .../core/tracker/NoopRequestTracker.java | 10 +- .../internal/core/tracker/RequestLogger.java | 38 +++---- .../driver/internal/core/util/Reflection.java | 6 +- ...onstantSpeculativeExecutionPolicyTest.java | 12 +-- .../ChannelFactoryAvailableIdsTest.java | 8 +- .../ChannelFactoryClusterNameTest.java | 6 +- ...ChannelFactoryProtocolNegotiationTest.java | 21 ++-- .../core/channel/ChannelFactoryTestBase.java | 27 +++-- .../core/channel/ProtocolInitHandlerTest.java | 13 ++- .../DefaultDriverConfigLoaderTest.java | 8 +- .../typesafe/TypesafeDriverConfigTest.java | 24 ++--- .../core/cql/CqlPrepareHandlerTest.java | 10 +- .../core/cql/CqlRequestHandlerRetryTest.java | 41 ++++---- ...equestHandlerSpeculativeExecutionTest.java | 24 ++--- .../core/cql/QueryTraceFetcherTest.java | 10 +- .../core/cql/RequestHandlerTestHarness.java | 21 ++-- .../internal/core/cql/StatementSizeTest.java | 6 +- .../metadata/DefaultTopologyMonitorTest.java | 4 +- .../LoadBalancingPolicyWrapperTest.java | 4 +- .../core/metadata/MetadataManagerTest.java | 4 +- .../core/metadata/NodeStateManagerTest.java | 14 +-- .../metadata/SchemaAgreementCheckerTest.java | 4 +- .../queries/Cassandra21SchemaQueriesTest.java | 4 +- .../queries/Cassandra22SchemaQueriesTest.java | 4 +- .../queries/Cassandra3SchemaQueriesTest.java | 4 +- .../schema/queries/SchemaQueriesTest.java | 4 +- .../core/pool/ChannelPoolTestBase.java | 4 +- .../core/session/DefaultSessionPoolsTest.java | 22 ++--- .../core/session/ReprepareOnUpTest.java | 22 ++--- ...ncurrencyLimitingRequestThrottlerTest.java | 4 +- .../RateLimitingRequestThrottlerTest.java | 4 +- .../MonotonicTimestampGeneratorTestBase.java | 12 +-- .../internal/core/util/ReflectionTest.java | 4 +- ...eIT.java => DriverExecutionProfileIT.java} | 18 ++-- ...va => DriverExecutionProfileReloadIT.java} | 10 +- .../connection/ChannelSocketOptionsIT.java | 4 +- .../driver/api/core/cql/AsyncResultSetIT.java | 10 +- .../driver/api/core/cql/BatchStatementIT.java | 2 +- .../driver/api/core/cql/BoundStatementIT.java | 16 +-- .../api/core/cql/PerRequestKeyspaceIT.java | 2 +- .../cql/PreparedStatementInvalidationIT.java | 8 +- .../api/core/cql/SimpleStatementIT.java | 4 +- .../oss/driver/api/core/data/DataTypeIT.java | 4 +- .../PerProfileLoadBalancingPolicyIT.java | 11 ++- .../driver/api/core/metadata/NodeStateIT.java | 4 +- .../driver/api/core/metadata/SchemaIT.java | 6 +- .../core/retry/PerProfileRetryPolicyIT.java | 10 +- .../api/core/session/RequestProcessorIT.java | 2 +- .../core/specex/SpeculativeExecutionIT.java | 14 +-- .../api/core/tracker/RequestLoggerIT.java | 6 +- .../tracker/RequestNodeLoggerExample.java | 43 ++++---- .../type/codec/registry/CodecRegistryIT.java | 4 +- .../example/guava/internal/KeyRequest.java | 6 +- manual/core/configuration/README.md | 42 ++++---- manual/core/load_balancing/README.md | 2 +- manual/core/query_timestamps/README.md | 2 +- manual/core/retries/README.md | 2 +- manual/core/speculative_execution/README.md | 2 +- .../api/testinfra/session/SessionRule.java | 6 +- .../api/testinfra/session/SessionUtils.java | 16 +-- 117 files changed, 762 insertions(+), 757 deletions(-) rename core/src/main/java/com/datastax/oss/driver/api/core/config/{DriverConfigProfile.java => DriverExecutionProfile.java} (83%) rename core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/{TypesafeDriverConfigProfile.java => TypesafeDriverExecutionProfile.java} (87%) rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/{DriverConfigProfileIT.java => DriverExecutionProfileIT.java} (92%) rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/{DriverConfigProfileReloadIT.java => DriverExecutionProfileReloadIT.java} (95%) diff --git a/changelog/README.md b/changelog/README.md index 25a41d77859..61564ec1df1 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1880: Rename "config profile" to "execution profile" - [improvement] JAVA-1889: Upgrade dependencies to the latest minor versions - [improvement] JAVA-1819: Propagate more attributes to bound statements - [improvement] JAVA-1897: Improve extensibility of schema metadata classes diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index d2220d84bb0..249fbd5288f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -94,8 +94,8 @@ default CompletionStage executeAsync(@NonNull String query) { *

        • the following methods return the same value as their counterpart on {@code * simpleStatement}: *
            - *
          • {@link Request#getConfigProfileName() boundStatement.getConfigProfileName()} - *
          • {@link Request#getConfigProfile() boundStatement.getConfigProfile()} + *
          • {@link Request#getExecutionProfileName() boundStatement.getExecutionProfileName()} + *
          • {@link Request#getExecutionProfile() boundStatement.getExecutionProfile()} *
          • {@link Statement#getPagingState() boundStatement.getPagingState()} *
          • {@link Request#getRoutingKey() boundStatement.getRoutingKey()} *
          • {@link Request#getRoutingToken() boundStatement.getRoutingToken()} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java index 8cf64d5ad42..fae096123c2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfig.java @@ -30,18 +30,18 @@ public interface DriverConfig { /** * Alias to get the default profile, which is stored under the name {@link - * DriverConfigProfile#DEFAULT_NAME} and always present. + * DriverExecutionProfile#DEFAULT_NAME} and always present. */ @NonNull - default DriverConfigProfile getDefaultProfile() { - return getProfile(DriverConfigProfile.DEFAULT_NAME); + default DriverExecutionProfile getDefaultProfile() { + return getProfile(DriverExecutionProfile.DEFAULT_NAME); } /** @throws IllegalArgumentException if there is no profile with this name. */ @NonNull - DriverConfigProfile getProfile(@NonNull String profileName); + DriverExecutionProfile getProfile(@NonNull String profileName); /** Returns an immutable view of all named profiles (including the default profile). */ @NonNull - Map getProfiles(); + Map getProfiles(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.java similarity index 83% rename from core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java rename to core/src/main/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.java index 10962b6b457..57fc7f179f3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.java @@ -37,7 +37,7 @@ * * @see DriverConfig */ -public interface DriverConfigProfile { +public interface DriverExecutionProfile { /** * The name of the default profile (the string {@value}). @@ -64,7 +64,7 @@ default boolean getBoolean(@NonNull DriverOption option, boolean defaultValue) { } @NonNull - DriverConfigProfile withBoolean(@NonNull DriverOption option, boolean value); + DriverExecutionProfile withBoolean(@NonNull DriverOption option, boolean value); @NonNull List getBooleanList(@NonNull DriverOption option); @@ -76,7 +76,8 @@ default List getBooleanList( } @NonNull - DriverConfigProfile withBooleanList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withBooleanList( + @NonNull DriverOption option, @NonNull List value); int getInt(@NonNull DriverOption option); @@ -85,7 +86,7 @@ default int getInt(@NonNull DriverOption option, int defaultValue) { } @NonNull - DriverConfigProfile withInt(@NonNull DriverOption option, int value); + DriverExecutionProfile withInt(@NonNull DriverOption option, int value); @NonNull List getIntList(@NonNull DriverOption option); @@ -97,7 +98,7 @@ default List getIntList( } @NonNull - DriverConfigProfile withIntList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withIntList(@NonNull DriverOption option, @NonNull List value); long getLong(@NonNull DriverOption option); @@ -105,7 +106,7 @@ default long getLong(@NonNull DriverOption option, long defaultValue) { return isDefined(option) ? getLong(option) : defaultValue; } - DriverConfigProfile withLong(@NonNull DriverOption option, long value); + DriverExecutionProfile withLong(@NonNull DriverOption option, long value); @NonNull List getLongList(@NonNull DriverOption option); @@ -116,7 +117,7 @@ default List getLongList(@NonNull DriverOption option, @Nullable List value); + DriverExecutionProfile withLongList(@NonNull DriverOption option, @NonNull List value); double getDouble(@NonNull DriverOption option); @@ -125,7 +126,7 @@ default double getDouble(@NonNull DriverOption option, double defaultValue) { } @NonNull - DriverConfigProfile withDouble(@NonNull DriverOption option, double value); + DriverExecutionProfile withDouble(@NonNull DriverOption option, double value); @NonNull List getDoubleList(@NonNull DriverOption option); @@ -137,7 +138,7 @@ default List getDoubleList( } @NonNull - DriverConfigProfile withDoubleList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withDoubleList(@NonNull DriverOption option, @NonNull List value); @NonNull String getString(@NonNull DriverOption option); @@ -148,7 +149,7 @@ default String getString(@NonNull DriverOption option, @Nullable String defaultV } @NonNull - DriverConfigProfile withString(@NonNull DriverOption option, @NonNull String value); + DriverExecutionProfile withString(@NonNull DriverOption option, @NonNull String value); @NonNull List getStringList(@NonNull DriverOption option); @@ -160,7 +161,7 @@ default List getStringList( } @NonNull - DriverConfigProfile withStringList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withStringList(@NonNull DriverOption option, @NonNull List value); @NonNull Map getStringMap(@NonNull DriverOption option); @@ -172,7 +173,7 @@ default Map getStringMap( } @NonNull - DriverConfigProfile withStringMap( + DriverExecutionProfile withStringMap( @NonNull DriverOption option, @NonNull Map value); /** @@ -188,7 +189,7 @@ default long getBytes(@NonNull DriverOption option, long defaultValue) { /** @see #getBytes(DriverOption) */ @NonNull - DriverConfigProfile withBytes(@NonNull DriverOption option, long value); + DriverExecutionProfile withBytes(@NonNull DriverOption option, long value); /** @see #getBytes(DriverOption) */ @NonNull @@ -201,7 +202,7 @@ default List getBytesList(DriverOption option, @Nullable List defaul /** @see #getBytes(DriverOption) */ @NonNull - DriverConfigProfile withBytesList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withBytesList(@NonNull DriverOption option, @NonNull List value); @NonNull Duration getDuration(@NonNull DriverOption option); @@ -212,7 +213,7 @@ default Duration getDuration(@NonNull DriverOption option, @Nullable Duration de } @NonNull - DriverConfigProfile withDuration(@NonNull DriverOption option, @NonNull Duration value); + DriverExecutionProfile withDuration(@NonNull DriverOption option, @NonNull Duration value); @NonNull List getDurationList(@NonNull DriverOption option); @@ -224,11 +225,12 @@ default List getDurationList( } @NonNull - DriverConfigProfile withDurationList(@NonNull DriverOption option, @NonNull List value); + DriverExecutionProfile withDurationList( + @NonNull DriverOption option, @NonNull List value); /** Unsets an option. */ @NonNull - DriverConfigProfile without(@NonNull DriverOption option); + DriverExecutionProfile without(@NonNull DriverOption option); /** * Returns a representation of all the child options under a given option. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 28ff31ee852..5ec2804e413 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -68,7 +68,7 @@ default LoadBalancingPolicy loadBalancingPolicy(@NonNull String profileName) { // Protect against a non-existent name return (policy != null) ? policy - : loadBalancingPolicies().get(DriverConfigProfile.DEFAULT_NAME); + : loadBalancingPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's load balancing policies; may be empty but never {@code null}. */ @@ -82,7 +82,7 @@ default LoadBalancingPolicy loadBalancingPolicy(@NonNull String profileName) { @NonNull default RetryPolicy retryPolicy(@NonNull String profileName) { RetryPolicy policy = retryPolicies().get(profileName); - return (policy != null) ? policy : retryPolicies().get(DriverConfigProfile.DEFAULT_NAME); + return (policy != null) ? policy : retryPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's load balancing policies; may be empty but never {@code null}. */ @@ -98,7 +98,7 @@ default SpeculativeExecutionPolicy speculativeExecutionPolicy(@NonNull String pr SpeculativeExecutionPolicy policy = speculativeExecutionPolicies().get(profileName); return (policy != null) ? policy - : speculativeExecutionPolicies().get(DriverConfigProfile.DEFAULT_NAME); + : speculativeExecutionPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's timestamp generator; never {@code null}. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index 5997c8298f5..d8d9fde08cf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -132,8 +132,8 @@ public BatchStatement build() { return new DefaultBatchStatement( batchType, statementsBuilder.build(), - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java index 366c733704d..71ea0a17aac 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatementBuilder.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -44,8 +44,8 @@ public BoundStatementBuilder( @NonNull PreparedStatement preparedStatement, @NonNull ColumnDefinitions variableDefinitions, @NonNull ByteBuffer[] values, - @Nullable String configProfileName, - @Nullable DriverConfigProfile configProfile, + @Nullable String executionProfileName, + @Nullable DriverExecutionProfile executionProfile, @Nullable CqlIdentifier routingKeyspace, @Nullable ByteBuffer routingKey, @Nullable Token routingToken, @@ -63,8 +63,8 @@ public BoundStatementBuilder( this.preparedStatement = preparedStatement; this.variableDefinitions = variableDefinitions; this.values = values; - this.configProfileName = configProfileName; - this.configProfile = configProfile; + this.executionProfileName = executionProfileName; + this.executionProfile = executionProfile; this.routingKeyspace = routingKeyspace; this.routingKey = routingKey; this.routingToken = routingToken; @@ -152,8 +152,8 @@ public BoundStatement build() { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java index 43b2787f373..3e7308ccd4f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PrepareRequest.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; @@ -82,21 +82,21 @@ default Boolean isIdempotent() { } /** - * The name of the driver configuration profile to use for the bound statements that will be - * created from the prepared statement. + * The name of the execution profile to use for the bound statements that will be created from the + * prepared statement. * - *

            Note that this will be ignored if {@link #getConfigProfileForBoundStatements()} returns a + *

            Note that this will be ignored if {@link #getExecutionProfileForBoundStatements()} returns a * non-null value. */ @Nullable - String getConfigProfileNameForBoundStatements(); + String getExecutionProfileNameForBoundStatements(); /** - * The configuration profile to use for the bound statements that will be created from the - * prepared statement. + * The execution profile to use for the bound statements that will be created from the prepared + * statement. */ @Nullable - DriverConfigProfile getConfigProfileForBoundStatements(); + DriverExecutionProfile getExecutionProfileForBoundStatements(); /** * The routing keyspace to use for the bound statements that will be created from the prepared diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java index 741546fdec3..b9f9a0fdccf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/PreparedStatement.java @@ -118,7 +118,7 @@ void setResultMetadata( * *

            Note that the built-in bound statement implementation is immutable. If you need to set * multiple execution parameters on the bound statement (such as {@link - * BoundStatement#setConfigProfileName(String)}, {@link + * BoundStatement#setExecutionProfileName(String)}, {@link * BoundStatement#setPagingState(ByteBuffer)}, etc.), consider using {@link * #boundStatementBuilder(Object...)} instead to avoid unnecessary allocations. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 06c22b52d6b..dcbd2df6d19 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -162,8 +162,8 @@ public SimpleStatement build() { ? NullAllowingImmutableList.of() : positionalValuesBuilder.build(), (namedValuesBuilder == null) ? NullAllowingImmutableMap.of() : namedValuesBuilder.build(), - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 6534628149b..c8efd42b4be 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; @@ -63,25 +63,25 @@ public interface Statement> extends Request { new GenericType>() {}; /** - * Sets the name of the driver configuration profile that will be used for execution. + * Sets the name of the execution profile that will be used for this statement. * *

            For all the driver's built-in implementations, this method has no effect if {@link - * #getConfigProfile()} has been called with a non-null argument. + * #setExecutionProfile(DriverExecutionProfile)} has been called with a non-null argument. * *

            All the driver's built-in implementations are immutable, and return a new instance from this * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull - T setConfigProfileName(@Nullable String newConfigProfileName); + T setExecutionProfileName(@Nullable String newConfigProfileName); /** - * Sets the configuration profile to use for execution. + * Sets the execution profile to use for this statement. * *

            All the driver's built-in implementations are immutable, and return a new instance from this * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull - T setConfigProfile(@Nullable DriverConfigProfile newProfile); + T setExecutionProfile(@Nullable DriverExecutionProfile newProfile); /** * Sets the keyspace to use for token-aware routing. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 7c4332b0df5..cd23508c0ee 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; @@ -40,8 +40,8 @@ public abstract class StatementBuilder, S exten @SuppressWarnings("unchecked") private final T self = (T) this; - @Nullable protected String configProfileName; - @Nullable protected DriverConfigProfile configProfile; + @Nullable protected String executionProfileName; + @Nullable protected DriverExecutionProfile executionProfile; @Nullable protected CqlIdentifier routingKeyspace; @Nullable protected ByteBuffer routingKey; @Nullable protected Token routingToken; @@ -60,8 +60,8 @@ protected StatementBuilder() { } protected StatementBuilder(S template) { - this.configProfileName = template.getConfigProfileName(); - this.configProfile = template.getConfigProfile(); + this.executionProfileName = template.getExecutionProfileName(); + this.executionProfile = template.getExecutionProfile(); this.routingKeyspace = template.getRoutingKeyspace(); this.routingKey = template.getRoutingKey(); this.routingToken = template.getRoutingToken(); @@ -80,18 +80,18 @@ protected StatementBuilder(S template) { this.timeout = template.getTimeout(); } - /** @see Statement#setConfigProfileName(String) */ + /** @see Statement#setExecutionProfileName(String) */ @NonNull - public T withConfigProfileName(@Nullable String configProfileName) { - this.configProfileName = configProfileName; + public T withExecutionProfileName(@Nullable String executionProfileName) { + this.executionProfileName = executionProfileName; return self; } - /** @see Statement#setConfigProfile(DriverConfigProfile) */ + /** @see Statement#setExecutionProfile(DriverExecutionProfile) */ @NonNull - public T withConfigProfile(@Nullable DriverConfigProfile configProfile) { - this.configProfile = configProfile; - this.configProfileName = null; + public T withExecutionProfile(@Nullable DriverExecutionProfile executionProfile) { + this.executionProfile = executionProfile; + this.executionProfileName = null; return self; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java index 77e663d0a3f..8c7db3da5dd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Request.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -37,27 +37,27 @@ public interface Request { /** - * The name of the driver configuration profile that will be used for execution, or {@code null} - * if no profile has been set. + * The name of the execution profile that will be used for this request, or {@code null} if no + * profile has been set. * - *

            Note that this will be ignored if {@link #getConfigProfile()} returns a non-null value. + *

            Note that this will be ignored if {@link #getExecutionProfile()} returns a non-null value. * * @see DriverConfig */ @Nullable - String getConfigProfileName(); + String getExecutionProfileName(); /** - * The configuration profile to use for execution, or {@code null} if no profile has been set. + * The execution profile to use for this request, or {@code null} if no profile has been set. * - *

            It is generally simpler to specify a profile name with {@link #getConfigProfileName()}. + *

            It is generally simpler to specify a profile name with {@link #getExecutionProfileName()}. * However, this method can be used to provide a "derived" profile that was built programmatically * by the client code. If specified, it overrides the profile name. * - * @see DriverConfigProfile + * @see DriverExecutionProfile */ @Nullable - DriverConfigProfile getConfigProfile(); + DriverExecutionProfile getExecutionProfile(); /** * The CQL keyspace to execute this request in, or {@code null} if this request does not specify diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 24f9177d574..25fc35862fe 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; @@ -193,8 +193,8 @@ public SelfT withRequestTracker(@Nullable RequestTracker requestTracker) { } /** - * Adds a custom filter to include/exclude nodes for a particular configuration profile. This - * assumes that you're also using a dedicated load balancing policy for that profile. + * Adds a custom filter to include/exclude nodes for a particular execution profile. This assumes + * that you're also using a dedicated load balancing policy for that profile. * *

            The predicate's {@link Predicate#test(Object) test()} method will be invoked each time the * {@link LoadBalancingPolicy} processes a topology or state change: if it returns false, the @@ -218,7 +218,7 @@ public SelfT withNodeFilter(@NonNull String profileName, @NonNull Predicate nodeFilter) { - return withNodeFilter(DriverConfigProfile.DEFAULT_NAME, nodeFilter); + return withNodeFilter(DriverExecutionProfile.DEFAULT_NAME, nodeFilter); } /** @@ -289,7 +289,7 @@ protected final CompletionStage buildDefaultSessionAsync() { try { DriverConfigLoader configLoader = buildIfNull(this.configLoader, this::defaultConfigLoader); - DriverConfigProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); + DriverExecutionProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java index d5369325a09..ba90fc07ebd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.api.core.tracker; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; @@ -38,13 +38,13 @@ public interface RequestTracker extends AutoCloseable { * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the result is made available to the client). - * @param configProfile the configuration profile that this request was executed with. + * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. */ void onSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node); /** @@ -52,44 +52,44 @@ void onSuccess( * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the error is propagated to the client). - * @param configProfile the configuration profile that this request was executed with. + * @param executionProfile the execution profile of this request. * @param node the node that returned the error response, or {@code null} if the error occurred */ void onError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @Nullable Node node); /** * Invoked each time a request fails at the node level. Similar to {@link #onError(Request, - * Throwable, long, DriverConfigProfile, Node)} but at a per node level. + * Throwable, long, DriverExecutionProfile, Node)} but at a per node level. * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the error is propagated to the client). - * @param configProfile the configuration profile that this request was executed with. + * @param executionProfile the execution profile of this request. * @param node the node that returned the error response. */ void onNodeError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node); /** * Invoked each time a request succeeds at the node level. Similar to {@link #onSuccess(Request, - * long, DriverConfigProfile, Node)} but at per Node level. + * long, DriverExecutionProfile, Node)} but at per Node level. * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the result is made available to the client). - * @param configProfile the configuration profile that this request was executed with. + * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. */ void onNodeSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java index aaffba3b31a..23a4baa32ee 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.auth.Authenticator; import com.datastax.oss.driver.api.core.auth.SyncAuthenticator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import edu.umd.cs.findbugs.annotations.NonNull; @@ -54,7 +54,7 @@ public class PlainTextAuthProvider implements AuthProvider { private static final Logger LOG = LoggerFactory.getLogger(PlainTextAuthProvider.class); private final String logPrefix; - private final DriverConfigProfile config; + private final DriverExecutionProfile config; /** Builds a new instance. */ public PlainTextAuthProvider(DriverContext context) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 50ed085dd9c..8568c90b1fc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; @@ -67,7 +67,7 @@ public ChannelFactory(InternalDriverContext context) { this.logPrefix = context.sessionName(); this.context = context; - DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); if (defaultConfig.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) { String versionName = defaultConfig.getString(DefaultDriverOption.PROTOCOL_VERSION); this.protocolVersion = context.protocolVersionRegistry().fromName(versionName); @@ -149,7 +149,7 @@ private void connect( .handler( initializer(address, currentVersion, options, nodeMetricUpdater, resultFuture)); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); boolean tcpNoDelay = config.getBoolean(DefaultDriverOption.SOCKET_TCP_NODELAY); bootstrap = bootstrap.option(ChannelOption.TCP_NODELAY, tcpNoDelay); @@ -241,18 +241,18 @@ ChannelInitializer initializer( @Override protected void initChannel(Channel channel) { try { - DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); long setKeyspaceTimeoutMillis = - defaultConfigProfile + defaultConfig .getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) .toMillis(); int maxFrameLength = - (int) defaultConfigProfile.getBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH); + (int) defaultConfig.getBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH); int maxRequestsPerConnection = - defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); + defaultConfig.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); int maxOrphanRequests = - defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); + defaultConfig.getInt(DefaultDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS); InFlightHandler inFlightHandler = new InFlightHandler( @@ -263,7 +263,7 @@ protected void initChannel(Channel channel) { channel.newPromise(), options.eventCallback, options.ownerLogPrefix); - HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfig); ProtocolInitHandler initHandler = new ProtocolInitHandler( context, protocolVersion, clusterName, options, heartbeatHandler); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index a478e7651c9..94c7f1483e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -54,7 +54,7 @@ public class DefaultWriteCoalescer implements WriteCoalescer { private final ConcurrentMap flushers = new ConcurrentHashMap<>(); public DefaultWriteCoalescer(DriverContext context) { - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.maxRunsWithNoWork = config.getInt(DefaultDriverOption.COALESCER_MAX_RUNS); this.rescheduleIntervalNanos = config.getDuration(DefaultDriverOption.COALESCER_INTERVAL).toNanos(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java index 24469f08931..0c4dba9ffc4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/HeartbeatHandler.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.channel; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Options; @@ -34,16 +34,13 @@ class HeartbeatHandler extends IdleStateHandler { private static final Logger LOG = LoggerFactory.getLogger(HeartbeatHandler.class); - private final DriverConfigProfile defaultConfigProfile; + private final DriverExecutionProfile config; private HeartbeatRequest request; - HeartbeatHandler(DriverConfigProfile defaultConfigProfile) { - super( - (int) defaultConfigProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL).getSeconds(), - 0, - 0); - this.defaultConfigProfile = defaultConfigProfile; + HeartbeatHandler(DriverExecutionProfile config) { + super((int) config.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL).getSeconds(), 0, 0); + this.config = config; } @Override @@ -58,9 +55,8 @@ protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws } else { LOG.debug( "Connection was inactive for {} seconds, sending heartbeat", - defaultConfigProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL).getSeconds()); - long timeoutMillis = - defaultConfigProfile.getDuration(DefaultDriverOption.HEARTBEAT_TIMEOUT).toMillis(); + config.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL).getSeconds()); + long timeoutMillis = config.getDuration(DefaultDriverOption.HEARTBEAT_TIMEOUT).toMillis(); this.request = new HeartbeatRequest(ctx, timeoutMillis); this.request.send(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 17b172b6c48..e2d10d9453d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.auth.Authenticator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ConnectionInitException; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -78,7 +78,7 @@ class ProtocolInitHandler extends ConnectInitHandler { this.context = context; - DriverConfigProfile defaultConfig = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); this.timeoutMillis = defaultConfig.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index f1ec0c7904d..4b04c785969 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -95,7 +95,7 @@ private class SingleThreaded { private final String logPrefix; private final EventExecutor adminExecutor; private final EventBus eventBus; - private final DriverConfigProfile config; + private final DriverExecutionProfile config; private final Object forceLoadListenerKey; private Duration reloadInterval; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java index 44c4c289263..8ed6b80dfd2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfig.java @@ -18,7 +18,7 @@ import static com.typesafe.config.ConfigValueType.OBJECT; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -36,7 +36,7 @@ public class TypesafeDriverConfig implements DriverConfig { private static final Logger LOG = LoggerFactory.getLogger(TypesafeDriverConfig.class); - private final ImmutableMap profiles; + private final ImmutableMap profiles; // Only used to detect if reload saw any change private volatile Config lastLoadedConfig; @@ -45,10 +45,12 @@ public TypesafeDriverConfig(Config config) { Map profileConfigs = extractProfiles(config); - ImmutableMap.Builder builder = ImmutableMap.builder(); + ImmutableMap.Builder builder = + ImmutableMap.builder(); for (Map.Entry entry : profileConfigs.entrySet()) { builder.put( - entry.getKey(), new TypesafeDriverConfigProfile.Base(entry.getKey(), entry.getValue())); + entry.getKey(), + new TypesafeDriverExecutionProfile.Base(entry.getKey(), entry.getValue())); } this.profiles = builder.build(); } @@ -63,7 +65,7 @@ public boolean reload(Config config) { Map profileConfigs = extractProfiles(config); for (Map.Entry entry : profileConfigs.entrySet()) { String profileName = entry.getKey(); - TypesafeDriverConfigProfile.Base profile = this.profiles.get(profileName); + TypesafeDriverExecutionProfile.Base profile = this.profiles.get(profileName); if (profile == null) { LOG.warn( "Unknown profile '{}' while reloading configuration. " @@ -97,7 +99,7 @@ private Map extractProfiles(Config sourceConfig) { ImmutableMap.Builder result = ImmutableMap.builder(); Config defaultProfileConfig = sourceConfig.withoutPath("profiles"); - result.put(DriverConfigProfile.DEFAULT_NAME, defaultProfileConfig); + result.put(DriverExecutionProfile.DEFAULT_NAME, defaultProfileConfig); // The rest of the method is a bit confusing because we navigate between Typesafe config's two // APIs, see https://github.com/typesafehub/config#understanding-config-and-configobject @@ -108,7 +110,7 @@ private Map extractProfiles(Config sourceConfig) { if (rootObject.containsKey("profiles") && rootObject.get("profiles").valueType() == OBJECT) { ConfigObject profilesObject = (ConfigObject) rootObject.get("profiles"); for (String profileName : profilesObject.keySet()) { - if (profileName.equals(DriverConfigProfile.DEFAULT_NAME)) { + if (profileName.equals(DriverExecutionProfile.DEFAULT_NAME)) { throw new IllegalArgumentException( String.format( "Can't have %s as a profile name because it's used internally. Pick another name.", @@ -126,7 +128,7 @@ private Map extractProfiles(Config sourceConfig) { @NonNull @Override - public DriverConfigProfile getProfile(@NonNull String profileName) { + public DriverExecutionProfile getProfile(@NonNull String profileName) { Preconditions.checkArgument( profiles.containsKey(profileName), "Unknown profile '%s'. Check your configuration.", @@ -136,7 +138,7 @@ public DriverConfigProfile getProfile(@NonNull String profileName) { @NonNull @Override - public Map getProfiles() { + public Map getProfiles() { return profiles; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java similarity index 87% rename from core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java index dd84cb7f9d8..1ecdfd04ed2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.config.typesafe; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSortedSet; @@ -41,7 +41,7 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -public abstract class TypesafeDriverConfigProfile implements DriverConfigProfile { +public abstract class TypesafeDriverExecutionProfile implements DriverExecutionProfile { /** The original profile in the driver's configuration that this profile was derived from. */ protected abstract Base getBaseProfile(); @@ -66,7 +66,7 @@ public boolean getBoolean(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withBoolean(@NonNull DriverOption option, boolean value) { + public DriverExecutionProfile withBoolean(@NonNull DriverOption option, boolean value) { return with(option, value); } @@ -78,7 +78,7 @@ public List getBooleanList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withBooleanList( + public DriverExecutionProfile withBooleanList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -90,7 +90,7 @@ public int getInt(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withInt(@NonNull DriverOption option, int value) { + public DriverExecutionProfile withInt(@NonNull DriverOption option, int value) { return with(option, value); } @@ -102,7 +102,7 @@ public List getIntList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withIntList( + public DriverExecutionProfile withIntList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -114,7 +114,7 @@ public long getLong(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withLong(@NonNull DriverOption option, long value) { + public DriverExecutionProfile withLong(@NonNull DriverOption option, long value) { return with(option, value); } @@ -126,7 +126,8 @@ public List getLongList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withLongList(@NonNull DriverOption option, @NonNull List value) { + public DriverExecutionProfile withLongList( + @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -137,7 +138,7 @@ public double getDouble(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withDouble(@NonNull DriverOption option, double value) { + public DriverExecutionProfile withDouble(@NonNull DriverOption option, double value) { return with(option, value); } @@ -149,7 +150,7 @@ public List getDoubleList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withDoubleList( + public DriverExecutionProfile withDoubleList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -162,7 +163,7 @@ public String getString(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withString(@NonNull DriverOption option, @NonNull String value) { + public DriverExecutionProfile withString(@NonNull DriverOption option, @NonNull String value) { return with(option, value); } @@ -174,7 +175,7 @@ public List getStringList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withStringList( + public DriverExecutionProfile withStringList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -194,7 +195,7 @@ public Map getStringMap(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withStringMap( + public DriverExecutionProfile withStringMap( @NonNull DriverOption option, @NonNull Map map) { Base base = getBaseProfile(); // Add the new option to any already derived options @@ -216,7 +217,7 @@ public long getBytes(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withBytes(@NonNull DriverOption option, long value) { + public DriverExecutionProfile withBytes(@NonNull DriverOption option, long value) { return with(option, value); } @@ -228,7 +229,7 @@ public List getBytesList(DriverOption option) { @NonNull @Override - public DriverConfigProfile withBytesList( + public DriverExecutionProfile withBytesList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @@ -241,7 +242,8 @@ public Duration getDuration(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withDuration(@NonNull DriverOption option, @NonNull Duration value) { + public DriverExecutionProfile withDuration( + @NonNull DriverOption option, @NonNull Duration value) { return with(option, value); } @@ -253,14 +255,14 @@ public List getDurationList(@NonNull DriverOption option) { @NonNull @Override - public DriverConfigProfile withDurationList( + public DriverExecutionProfile withDurationList( @NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull @Override - public DriverConfigProfile without(@NonNull DriverOption option) { + public DriverExecutionProfile without(@NonNull DriverOption option) { return with(option, null); } @@ -290,7 +292,7 @@ private T getCached(String path, Function compute) { return t; } - private DriverConfigProfile with(@NonNull DriverOption option, @Nullable Object value) { + private DriverExecutionProfile with(@NonNull DriverOption option, @Nullable Object value) { Base base = getBaseProfile(); // Add the new option to any already derived options Config newAdded = @@ -302,7 +304,7 @@ private DriverConfigProfile with(@NonNull DriverOption option, @Nullable Object /** A profile that was loaded directly from the driver's configuration. */ @ThreadSafe - static class Base extends TypesafeDriverConfigProfile { + static class Base extends TypesafeDriverExecutionProfile { private final String name; private volatile Config options; @@ -368,7 +370,7 @@ private Set getDerivedProfiles() { * A profile that was copied from another profile programatically using {@code withXxx} methods. */ @ThreadSafe - static class Derived extends TypesafeDriverConfigProfile { + static class Derived extends TypesafeDriverExecutionProfile { private final Base baseProfile; private final Config addedOptions; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java index 93dde13aa0a..8ebb32b785f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.connection; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; @@ -52,7 +52,7 @@ public class ConstantReconnectionPolicy implements ReconnectionPolicy { /** Builds a new instance. */ public ConstantReconnectionPolicy(DriverContext context) { this.logPrefix = context.sessionName(); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); Duration delay = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY); if (delay.isNegative()) { throw new IllegalArgumentException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index 43a2611c072..6efea703a9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.connection; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; @@ -64,7 +64,7 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { public ExponentialReconnectionPolicy(DriverContext context) { this.logPrefix = context.sessionName(); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.baseDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY).toMillis(); this.maxDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY).toMillis(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 492b417f020..5a83b594c47 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; @@ -203,7 +203,7 @@ public DefaultDriverContext( ClassLoader classLoader) { this.config = configLoader.getInitialConfig(); this.configLoader = configLoader; - DriverConfigProfile defaultProfile = config.getDefaultProfile(); + DriverExecutionProfile defaultProfile = config.getDefaultProfile(); if (defaultProfile.isDefined(DefaultDriverOption.SESSION_NAME)) { this.sessionName = defaultProfile.getString(DefaultDriverOption.SESSION_NAME); } else { @@ -318,7 +318,7 @@ protected EventBus buildEventBus() { @SuppressWarnings("unchecked") protected Compressor buildCompressor() { - DriverConfigProfile defaultProfile = config().getDefaultProfile(); + DriverExecutionProfile defaultProfile = config().getDefaultProfile(); if (defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_COMPRESSION)) { String name = defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION); if (name.equalsIgnoreCase("lz4")) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index c5f424f41c5..c66db18582b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.context; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.Bootstrap; @@ -47,7 +47,7 @@ public class DefaultNettyOptions implements NettyOptions { private final TimeUnit adminShutdownUnit; public DefaultNettyOptions(InternalDriverContext context) { - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); int ioGroupSize = config.getInt(DefaultDriverOption.NETTY_IO_SIZE); this.ioShutdownQuietPeriod = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_QUIET_PERIOD); this.ioShutdownTimeout = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_TIMEOUT); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index faecdc4cefc..15a38f16c7a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchableStatement; @@ -95,7 +95,7 @@ public class Conversions { public static Message toMessage( - Statement statement, DriverConfigProfile config, InternalDriverContext context) { + Statement statement, DriverExecutionProfile config, InternalDriverContext context) { ConsistencyLevel consistency = statement.getConsistencyLevel() != null ? statement.getConsistencyLevel() @@ -349,8 +349,8 @@ public static DefaultPreparedStatement toPreparedStatement( toColumnDefinitions(response.resultMetadata, context), request.getKeyspace(), NullAllowingImmutableMap.copyOf(request.getCustomPayload()), - request.getConfigProfileNameForBoundStatements(), - request.getConfigProfileForBoundStatements(), + request.getExecutionProfileNameForBoundStatements(), + request.getExecutionProfileForBoundStatements(), request.getRoutingKeyspaceForBoundStatements(), request.getRoutingKeyForBoundStatements(), request.getRoutingTokenForBoundStatements(), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 92dcb10540d..7f944fd1377 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -85,7 +85,7 @@ public abstract class CqlPrepareHandlerBase implements Throttled { private final ConcurrentMap preparedStatementsCache; private final DefaultSession session; private final InternalDriverContext context; - private final DriverConfigProfile configProfile; + private final DriverExecutionProfile executionProfile; private final Queue queryPlan; protected final CompletableFuture result; private final Message message; @@ -117,12 +117,12 @@ protected CqlPrepareHandlerBase( this.session = session; this.context = context; - if (request.getConfigProfile() != null) { - this.configProfile = request.getConfigProfile(); + if (request.getExecutionProfile() != null) { + this.executionProfile = request.getExecutionProfile(); } else { DriverConfig config = context.config(); - String profileName = request.getConfigProfileName(); - this.configProfile = + String profileName = request.getExecutionProfileName(); + this.executionProfile = (profileName == null || profileName.isEmpty()) ? config.getDefaultProfile() : config.getProfile(profileName); @@ -130,8 +130,8 @@ protected CqlPrepareHandlerBase( this.queryPlan = context .loadBalancingPolicyWrapper() - .newQueryPlan(request, configProfile.getName(), session); - this.retryPolicy = context.retryPolicy(configProfile.getName()); + .newQueryPlan(request, executionProfile.getName(), session); + this.retryPolicy = context.retryPolicy(executionProfile.getName()); this.result = new CompletableFuture<>(); this.result.exceptionally( @@ -160,9 +160,9 @@ protected CqlPrepareHandlerBase( this.timeout = request.getTimeout() != null ? request.getTimeout() - : configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); + : executionProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); - this.prepareOnAllNodes = configProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); + this.prepareOnAllNodes = executionProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); this.throttler = context.requestThrottler(); this.throttler.register(this); @@ -175,7 +175,7 @@ public void onThrottleReady(boolean wasDelayed) { .getMetricUpdater() .updateTimer( DefaultSessionMetric.THROTTLING_DELAY, - configProfile.getName(), + executionProfile.getName(), System.nanoTime() - startTimeNanos, TimeUnit.NANOSECONDS); } @@ -350,7 +350,7 @@ private CompletionStage prepareOnOtherNode(Node node) { public void onThrottleFailure(@NonNull RequestThrottlingException error) { session .getMetricUpdater() - .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, configProfile.getName()); + .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, executionProfile.getName()); setFinalError(error); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 9cdcb76f4ae..db450bfa6f3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -98,7 +98,7 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final CqlIdentifier keyspace; private final InternalDriverContext context; private final Queue queryPlan; - @NonNull private final DriverConfigProfile configProfile; + @NonNull private final DriverExecutionProfile executionProfile; private final boolean isIdempotent; protected final CompletableFuture result; private final Message message; @@ -143,12 +143,12 @@ protected CqlRequestHandlerBase( this.keyspace = session.getKeyspace().orElse(null); this.context = context; - if (statement.getConfigProfile() != null) { - this.configProfile = statement.getConfigProfile(); + if (statement.getExecutionProfile() != null) { + this.executionProfile = statement.getExecutionProfile(); } else { DriverConfig config = context.config(); - String profileName = statement.getConfigProfileName(); - this.configProfile = + String profileName = statement.getExecutionProfileName(); + this.executionProfile = (profileName == null || profileName.isEmpty()) ? config.getDefaultProfile() : config.getProfile(profileName); @@ -156,13 +156,14 @@ protected CqlRequestHandlerBase( this.queryPlan = context .loadBalancingPolicyWrapper() - .newQueryPlan(statement, configProfile.getName(), session); - this.retryPolicy = context.retryPolicy(configProfile.getName()); - this.speculativeExecutionPolicy = context.speculativeExecutionPolicy(configProfile.getName()); + .newQueryPlan(statement, executionProfile.getName(), session); + this.retryPolicy = context.retryPolicy(executionProfile.getName()); + this.speculativeExecutionPolicy = + context.speculativeExecutionPolicy(executionProfile.getName()); Boolean statementIsIdempotent = statement.isIdempotent(); this.isIdempotent = (statementIsIdempotent == null) - ? configProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) + ? executionProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : statementIsIdempotent; this.result = new CompletableFuture<>(); this.result.exceptionally( @@ -176,13 +177,13 @@ protected CqlRequestHandlerBase( } return null; }); - this.message = Conversions.toMessage(statement, configProfile, context); + this.message = Conversions.toMessage(statement, executionProfile, context); this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); this.timeout = statement.getTimeout() != null ? statement.getTimeout() - : configProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); + : executionProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); this.timeoutFuture = scheduleTimeout(timeout); this.activeExecutionsCount = new AtomicInteger(1); @@ -201,7 +202,7 @@ public void onThrottleReady(boolean wasDelayed) { .getMetricUpdater() .updateTimer( DefaultSessionMetric.THROTTLING_DELAY, - configProfile.getName(), + executionProfile.getName(), System.nanoTime() - startTimeNanos, TimeUnit.NANOSECONDS); } @@ -307,15 +308,15 @@ private void setFinalResult( long nodeLatencyNanos = now - callback.start; context .requestTracker() - .onNodeSuccess(statement, nodeLatencyNanos, configProfile, callback.node); + .onNodeSuccess(statement, nodeLatencyNanos, executionProfile, callback.node); context .requestTracker() - .onSuccess(statement, totalLatencyNanos, configProfile, callback.node); + .onSuccess(statement, totalLatencyNanos, executionProfile, callback.node); session .getMetricUpdater() .updateTimer( DefaultSessionMetric.CQL_REQUESTS, - configProfile.getName(), + executionProfile.getName(), totalLatencyNanos, TimeUnit.NANOSECONDS); } @@ -342,14 +343,14 @@ private ExecutionInfo buildExecutionInfo( schemaInAgreement, session, context, - configProfile); + executionProfile); } @Override public void onThrottleFailure(@NonNull RequestThrottlingException error) { session .getMetricUpdater() - .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, configProfile.getName()); + .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, executionProfile.getName()); setFinalError(error, null, -1); } @@ -368,17 +369,17 @@ private void setFinalError(Throwable error, Node node, int execution) { true, session, context, - configProfile)); + executionProfile)); } if (result.completeExceptionally(error)) { cancelScheduledTasks(); long latencyNanos = System.nanoTime() - startTimeNanos; - context.requestTracker().onError(statement, error, latencyNanos, configProfile, node); + context.requestTracker().onError(statement, error, latencyNanos, executionProfile, node); if (error instanceof DriverTimeoutException) { throttler.signalTimeout(this); session .getMetricUpdater() - .incrementCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS, configProfile.getName()); + .incrementCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS, executionProfile.getName()); } else if (!(error instanceof RequestThrottlingException)) { throttler.signalError(this, error); } @@ -439,7 +440,7 @@ public void operationComplete(Future future) throws Exception { trackNodeError(node, error); ((DefaultNode) node) .getMetricUpdater() - .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS, configProfile.getName()); + .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS, executionProfile.getName()); sendRequest(null, execution, retryCount, scheduleNextExecution); // try next node } } else { @@ -476,7 +477,7 @@ public void operationComplete(Future future) throws Exception { .getMetricUpdater() .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, - configProfile.getName()); + executionProfile.getName()); sendRequest(null, nextExecution, 0, true); } }, @@ -499,7 +500,7 @@ public void onResponse(Frame responseFrame) { .getMetricUpdater() .updateTimer( DefaultNodeMetric.CQL_MESSAGES, - configProfile.getName(), + executionProfile.getName(), System.nanoTime() - start, TimeUnit.NANOSECONDS); inFlightCallbacks.remove(this); @@ -612,7 +613,7 @@ private void processErrorResponse(Error errorMessage) { || error instanceof FunctionFailureException || error instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error, rethrowing", logPrefix); - metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS, configProfile.getName()); + metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS, executionProfile.getName()); trackNodeError(node, error); setFinalError(error, node, execution); } else { @@ -711,16 +712,16 @@ private void updateErrorMetrics( DefaultNodeMetric error, DefaultNodeMetric retriesOnError, DefaultNodeMetric ignoresOnError) { - metricUpdater.incrementCounter(error, configProfile.getName()); + metricUpdater.incrementCounter(error, executionProfile.getName()); switch (decision) { case RETRY_SAME: case RETRY_NEXT: - metricUpdater.incrementCounter(DefaultNodeMetric.RETRIES, configProfile.getName()); - metricUpdater.incrementCounter(retriesOnError, configProfile.getName()); + metricUpdater.incrementCounter(DefaultNodeMetric.RETRIES, executionProfile.getName()); + metricUpdater.incrementCounter(retriesOnError, executionProfile.getName()); break; case IGNORE: - metricUpdater.incrementCounter(DefaultNodeMetric.IGNORES, configProfile.getName()); - metricUpdater.incrementCounter(ignoresOnError, configProfile.getName()); + metricUpdater.incrementCounter(DefaultNodeMetric.IGNORES, executionProfile.getName()); + metricUpdater.incrementCounter(ignoresOnError, executionProfile.getName()); break; case RETHROW: // nothing do do @@ -761,7 +762,7 @@ public void cancel() { private void trackNodeError(Node node, Throwable error) { long latencyNanos = System.nanoTime() - this.start; - context.requestTracker().onNodeError(statement, error, latencyNanos, configProfile, node); + context.requestTracker().onNodeError(statement, error, latencyNanos, executionProfile, node); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index f2f530e25f5..15e29daa834 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchType; import com.datastax.oss.driver.api.core.cql.BatchableStatement; @@ -39,8 +39,8 @@ public class DefaultBatchStatement implements BatchStatement { private final BatchType batchType; private final List> statements; - private final String configProfileName; - private final DriverConfigProfile configProfile; + private final String executionProfileName; + private final DriverExecutionProfile executionProfile; private final CqlIdentifier keyspace; private final CqlIdentifier routingKeyspace; private final ByteBuffer routingKey; @@ -58,8 +58,8 @@ public class DefaultBatchStatement implements BatchStatement { public DefaultBatchStatement( BatchType batchType, List> statements, - String configProfileName, - DriverConfigProfile configProfile, + String executionProfileName, + DriverExecutionProfile executionProfile, CqlIdentifier keyspace, CqlIdentifier routingKeyspace, ByteBuffer routingKey, @@ -75,8 +75,8 @@ public DefaultBatchStatement( Duration timeout) { this.batchType = batchType; this.statements = ImmutableList.copyOf(statements); - this.configProfileName = configProfileName; - this.configProfile = configProfile; + this.executionProfileName = executionProfileName; + this.executionProfile = executionProfile; this.keyspace = keyspace; this.routingKeyspace = routingKeyspace; this.routingKey = routingKey; @@ -104,8 +104,8 @@ public BatchStatement setBatchType(@NonNull BatchType newBatchType) { return new DefaultBatchStatement( newBatchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -127,8 +127,8 @@ public BatchStatement setKeyspace(@Nullable CqlIdentifier newKeyspace) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, newKeyspace, routingKeyspace, routingKey, @@ -154,8 +154,8 @@ public BatchStatement add(@NonNull BatchableStatement statement) { return new DefaultBatchStatement( batchType, ImmutableList.>builder().addAll(statements).add(statement).build(), - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -185,8 +185,8 @@ public BatchStatement addAll(@NonNull Iterable> .addAll(statements) .addAll(newStatements) .build(), - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -214,8 +214,8 @@ public BatchStatement clear() { return new DefaultBatchStatement( batchType, ImmutableList.of(), - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -248,8 +248,8 @@ public BatchStatement setPagingState(ByteBuffer newPagingState) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -276,8 +276,8 @@ public BatchStatement setPageSize(int newPageSize) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -304,8 +304,8 @@ public BatchStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsiste return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -334,8 +334,8 @@ public BatchStatement setSerialConsistencyLevel( return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -352,18 +352,18 @@ public BatchStatement setSerialConsistencyLevel( } @Override - public String getConfigProfileName() { - return configProfileName; + public String getExecutionProfileName() { + return executionProfileName; } @NonNull @Override - public BatchStatement setConfigProfileName(@Nullable String newConfigProfileName) { + public BatchStatement setExecutionProfileName(@Nullable String newConfigProfileName) { return new DefaultBatchStatement( batchType, statements, newConfigProfileName, - configProfile, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -380,17 +380,17 @@ public BatchStatement setConfigProfileName(@Nullable String newConfigProfileName } @Override - public DriverConfigProfile getConfigProfile() { - return configProfile; + public DriverExecutionProfile getExecutionProfile() { + return executionProfile; } @NonNull @Override - public DefaultBatchStatement setConfigProfile(@Nullable DriverConfigProfile newProfile) { + public DefaultBatchStatement setExecutionProfile(@Nullable DriverExecutionProfile newProfile) { return new DefaultBatchStatement( batchType, statements, - configProfileName, + executionProfileName, newProfile, keyspace, routingKeyspace, @@ -442,8 +442,8 @@ public BatchStatement setRoutingKeyspace(CqlIdentifier newRoutingKeyspace) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, newRoutingKeyspace, routingKey, @@ -480,8 +480,8 @@ public BatchStatement setRoutingKey(ByteBuffer newRoutingKey) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, newRoutingKey, @@ -518,8 +518,8 @@ public BatchStatement setRoutingToken(Token newRoutingToken) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -547,8 +547,8 @@ public DefaultBatchStatement setCustomPayload(@NonNull Map n return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -581,8 +581,8 @@ public DefaultBatchStatement setIdempotent(Boolean newIdempotence) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -609,8 +609,8 @@ public BatchStatement setTracing(boolean newTracing) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -637,8 +637,8 @@ public BatchStatement setTimestamp(long newTimestamp) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -660,8 +660,8 @@ public BatchStatement setTimeout(@Nullable Duration newTimeout) { return new DefaultBatchStatement( batchType, statements, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 3dc902791b4..a0df6b7d65b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -41,8 +41,8 @@ public class DefaultBoundStatement implements BoundStatement { private final PreparedStatement preparedStatement; private final ColumnDefinitions variableDefinitions; private final ByteBuffer[] values; - private final String configProfileName; - private final DriverConfigProfile configProfile; + private final String executionProfileName; + private final DriverExecutionProfile executionProfile; private final CqlIdentifier routingKeyspace; private final ByteBuffer routingKey; private final Token routingToken; @@ -62,8 +62,8 @@ public DefaultBoundStatement( PreparedStatement preparedStatement, ColumnDefinitions variableDefinitions, ByteBuffer[] values, - String configProfileName, - DriverConfigProfile configProfile, + String executionProfileName, + DriverExecutionProfile executionProfile, CqlIdentifier routingKeyspace, ByteBuffer routingKey, Token routingToken, @@ -81,8 +81,8 @@ public DefaultBoundStatement( this.preparedStatement = preparedStatement; this.variableDefinitions = variableDefinitions; this.values = values; - this.configProfileName = configProfileName; - this.configProfile = configProfile; + this.executionProfileName = executionProfileName; + this.executionProfile = executionProfile; this.routingKeyspace = routingKeyspace; this.routingKey = routingKey; this.routingToken = routingToken; @@ -154,8 +154,8 @@ public BoundStatement setBytesUnsafe(int i, ByteBuffer v) { preparedStatement, variableDefinitions, newValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -185,19 +185,19 @@ public List getValues() { } @Override - public String getConfigProfileName() { - return configProfileName; + public String getExecutionProfileName() { + return executionProfileName; } @NonNull @Override - public BoundStatement setConfigProfileName(@Nullable String newConfigProfileName) { + public BoundStatement setExecutionProfileName(@Nullable String newConfigProfileName) { return new DefaultBoundStatement( preparedStatement, variableDefinitions, values, newConfigProfileName, - configProfile, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -215,19 +215,19 @@ public BoundStatement setConfigProfileName(@Nullable String newConfigProfileName } @Override - public DriverConfigProfile getConfigProfile() { - return configProfile; + public DriverExecutionProfile getExecutionProfile() { + return executionProfile; } @NonNull @Override - public BoundStatement setConfigProfile(@Nullable DriverConfigProfile newConfigProfile) { + public BoundStatement setExecutionProfile(@Nullable DriverExecutionProfile newProfile) { return new DefaultBoundStatement( preparedStatement, variableDefinitions, values, - configProfileName, - newConfigProfile, + executionProfileName, + newProfile, routingKeyspace, routingKey, routingToken, @@ -263,8 +263,8 @@ public BoundStatement setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeysp preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, newRoutingKeyspace, routingKey, routingToken, @@ -313,8 +313,8 @@ public BoundStatement setRoutingKey(@Nullable ByteBuffer newRoutingKey) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, newRoutingKey, routingToken, @@ -343,8 +343,8 @@ public BoundStatement setRoutingToken(@Nullable Token newRoutingToken) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, newRoutingToken, @@ -374,8 +374,8 @@ public BoundStatement setCustomPayload(@NonNull Map newCusto preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -404,8 +404,8 @@ public BoundStatement setIdempotent(@Nullable Boolean newIdempotence) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -434,8 +434,8 @@ public BoundStatement setTracing(boolean newTracing) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -464,8 +464,8 @@ public BoundStatement setTimestamp(long newTimestamp) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -495,8 +495,8 @@ public BoundStatement setTimeout(@Nullable Duration newTimeout) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -525,8 +525,8 @@ public BoundStatement setPagingState(@Nullable ByteBuffer newPagingState) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -555,8 +555,8 @@ public BoundStatement setPageSize(int newPageSize) { preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -585,8 +585,8 @@ public BoundStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsiste preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, @@ -617,8 +617,8 @@ public BoundStatement setSerialConsistencyLevel( preparedStatement, variableDefinitions, values, - configProfileName, - configProfile, + executionProfileName, + executionProfile, routingKeyspace, routingKey, routingToken, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java index 10f2cea218f..bf542923405 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultExecutionInfo.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Statement; @@ -51,7 +51,7 @@ public class DefaultExecutionInfo implements ExecutionInfo { private final boolean schemaInAgreement; private final DefaultSession session; private final InternalDriverContext context; - private final DriverConfigProfile configProfile; + private final DriverExecutionProfile executionProfile; public DefaultExecutionInfo( Statement statement, @@ -64,7 +64,7 @@ public DefaultExecutionInfo( boolean schemaInAgreement, DefaultSession session, InternalDriverContext context, - DriverConfigProfile configProfile) { + DriverExecutionProfile executionProfile) { this.statement = statement; this.coordinator = coordinator; this.speculativeExecutionCount = speculativeExecutionCount; @@ -81,7 +81,7 @@ public DefaultExecutionInfo( this.schemaInAgreement = schemaInAgreement; this.session = session; this.context = context; - this.configProfile = configProfile; + this.executionProfile = executionProfile; } @NonNull @@ -150,7 +150,7 @@ public CompletionStage getQueryTraceAsync() { return CompletableFutures.failedFuture( new IllegalStateException("Tracing was disabled for this request")); } else { - return new QueryTraceFetcher(tracingId, session, context, configProfile).fetch(); + return new QueryTraceFetcher(tracingId, session, context, executionProfile).fetch(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index 271afd72a9f..045ef5a9df9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.token.Token; @@ -38,7 +38,7 @@ * request itself: * *

              - *
            • will use the same configuration profile (or configuration profile name) as the {@code + *
            • will use the same execution profile (or execution profile name) as the {@code * SimpleStatement}; *
            • will use the same custom payload as the {@code SimpleStatement}; *
            • will use a {@code null} timeout in order to default to the configuration (assuming that if @@ -67,14 +67,14 @@ public String getQuery() { @Nullable @Override - public String getConfigProfileName() { - return statement.getConfigProfileName(); + public String getExecutionProfileName() { + return statement.getExecutionProfileName(); } @Nullable @Override - public DriverConfigProfile getConfigProfile() { - return statement.getConfigProfile(); + public DriverExecutionProfile getExecutionProfile() { + return statement.getExecutionProfile(); } @Nullable @@ -116,14 +116,14 @@ public Duration getTimeout() { @Nullable @Override - public String getConfigProfileNameForBoundStatements() { - return statement.getConfigProfileName(); + public String getExecutionProfileNameForBoundStatements() { + return statement.getExecutionProfileName(); } @Nullable @Override - public DriverConfigProfile getConfigProfileForBoundStatements() { - return statement.getConfigProfile(); + public DriverExecutionProfile getExecutionProfileForBoundStatements() { + return statement.getExecutionProfile(); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index ce7514987a2..05743385524 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; @@ -44,8 +44,8 @@ public class DefaultPreparedStatement implements PreparedStatement { private volatile ResultMetadata resultMetadata; private final CodecRegistry codecRegistry; private final ProtocolVersion protocolVersion; - private final String configProfileNameForBoundStatements; - private final DriverConfigProfile configProfileForBoundStatements; + private final String executionProfileNameForBoundStatements; + private final DriverExecutionProfile executionProfileForBoundStatements; private final ByteBuffer pagingStateForBoundStatements; private final CqlIdentifier routingKeyspaceForBoundStatements; private final ByteBuffer routingKeyForBoundStatements; @@ -67,8 +67,8 @@ public DefaultPreparedStatement( ColumnDefinitions resultSetDefinitions, CqlIdentifier keyspace, Map customPayloadForPrepare, - String configProfileNameForBoundStatements, - DriverConfigProfile configProfileForBoundStatements, + String executionProfileNameForBoundStatements, + DriverExecutionProfile executionProfileForBoundStatements, CqlIdentifier routingKeyspaceForBoundStatements, ByteBuffer routingKeyForBoundStatements, Token routingTokenForBoundStatements, @@ -90,8 +90,8 @@ public DefaultPreparedStatement( this.variableDefinitions = variableDefinitions; this.resultMetadata = new ResultMetadata(resultMetadataId, resultSetDefinitions); - this.configProfileNameForBoundStatements = configProfileNameForBoundStatements; - this.configProfileForBoundStatements = configProfileForBoundStatements; + this.executionProfileNameForBoundStatements = executionProfileNameForBoundStatements; + this.executionProfileForBoundStatements = executionProfileForBoundStatements; this.routingKeyspaceForBoundStatements = routingKeyspaceForBoundStatements; this.routingKeyForBoundStatements = routingKeyForBoundStatements; this.routingTokenForBoundStatements = routingTokenForBoundStatements; @@ -157,8 +157,8 @@ public BoundStatement bind(@NonNull Object... values) { variableDefinitions, ValuesHelper.encodePreparedValues( values, variableDefinitions, codecRegistry, protocolVersion), - configProfileNameForBoundStatements, - configProfileForBoundStatements, + executionProfileNameForBoundStatements, + executionProfileForBoundStatements, routingKeyspaceForBoundStatements, routingKeyForBoundStatements, routingTokenForBoundStatements, @@ -183,8 +183,8 @@ public BoundStatementBuilder boundStatementBuilder(@NonNull Object... values) { variableDefinitions, ValuesHelper.encodePreparedValues( values, variableDefinitions, codecRegistry, protocolVersion), - configProfileNameForBoundStatements, - configProfileForBoundStatements, + executionProfileNameForBoundStatements, + executionProfileForBoundStatements, routingKeyspaceForBoundStatements, routingKeyForBoundStatements, routingTokenForBoundStatements, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index c23e4da7547..654f4706345 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableList; @@ -36,8 +36,8 @@ public class DefaultSimpleStatement implements SimpleStatement { private final String query; private final List positionalValues; private final Map namedValues; - private final String configProfileName; - private final DriverConfigProfile configProfile; + private final String executionProfileName; + private final DriverExecutionProfile executionProfile; private final CqlIdentifier keyspace; private final CqlIdentifier routingKeyspace; private final ByteBuffer routingKey; @@ -58,8 +58,8 @@ public DefaultSimpleStatement( String query, List positionalValues, Map namedValues, - String configProfileName, - DriverConfigProfile configProfile, + String executionProfileName, + DriverExecutionProfile executionProfile, CqlIdentifier keyspace, CqlIdentifier routingKeyspace, ByteBuffer routingKey, @@ -79,8 +79,8 @@ public DefaultSimpleStatement( this.query = query; this.positionalValues = NullAllowingImmutableList.copyOf(positionalValues); this.namedValues = NullAllowingImmutableMap.copyOf(namedValues); - this.configProfileName = configProfileName; - this.configProfile = configProfile; + this.executionProfileName = executionProfileName; + this.executionProfile = executionProfile; this.keyspace = keyspace; this.routingKeyspace = routingKeyspace; this.routingKey = routingKey; @@ -109,8 +109,8 @@ public SimpleStatement setQuery(@NonNull String newQuery) { newQuery, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -139,8 +139,8 @@ public SimpleStatement setPositionalValues(@NonNull List newPositionalVa query, newPositionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -169,8 +169,8 @@ public SimpleStatement setNamedValuesWithIds(@NonNull Map query, positionalValues, newNamedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -188,19 +188,19 @@ public SimpleStatement setNamedValuesWithIds(@NonNull Map @Nullable @Override - public String getConfigProfileName() { - return configProfileName; + public String getExecutionProfileName() { + return executionProfileName; } @NonNull @Override - public SimpleStatement setConfigProfileName(@Nullable String newConfigProfileName) { + public SimpleStatement setExecutionProfileName(@Nullable String newConfigProfileName) { return new DefaultSimpleStatement( query, positionalValues, namedValues, newConfigProfileName, - configProfile, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -218,13 +218,13 @@ public SimpleStatement setConfigProfileName(@Nullable String newConfigProfileNam @Nullable @Override - public DriverConfigProfile getConfigProfile() { - return configProfile; + public DriverExecutionProfile getExecutionProfile() { + return executionProfile; } @NonNull @Override - public SimpleStatement setConfigProfile(@Nullable DriverConfigProfile newProfile) { + public SimpleStatement setExecutionProfile(@Nullable DriverExecutionProfile newProfile) { return new DefaultSimpleStatement( query, positionalValues, @@ -259,8 +259,8 @@ public SimpleStatement setKeyspace(@Nullable CqlIdentifier newKeyspace) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, newKeyspace, routingKeyspace, routingKey, @@ -289,8 +289,8 @@ public SimpleStatement setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeys query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, newRoutingKeyspace, routingKey, @@ -319,8 +319,8 @@ public SimpleStatement setRoutingKey(@Nullable ByteBuffer newRoutingKey) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, newRoutingKey, @@ -349,8 +349,8 @@ public SimpleStatement setRoutingToken(@Nullable Token newRoutingToken) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -379,8 +379,8 @@ public SimpleStatement setCustomPayload(@NonNull Map newCust query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -409,8 +409,8 @@ public SimpleStatement setIdempotent(@Nullable Boolean newIdempotence) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -438,8 +438,8 @@ public SimpleStatement setTracing(boolean newTracing) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -467,8 +467,8 @@ public SimpleStatement setTimestamp(long newTimestamp) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -497,8 +497,8 @@ public SimpleStatement setTimeout(@Nullable Duration newTimeout) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -527,8 +527,8 @@ public SimpleStatement setPagingState(@Nullable ByteBuffer newPagingState) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -556,8 +556,8 @@ public SimpleStatement setPageSize(int newPageSize) { query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -585,8 +585,8 @@ public SimpleStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsist query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, @@ -616,8 +616,8 @@ public SimpleStatement setSerialConsistencyLevel( query, positionalValues, namedValues, - configProfileName, - configProfile, + executionProfileName, + executionProfile, keyspace, routingKeyspace, routingKey, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index 590b10f4b48..e2ed393af1c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.QueryTrace; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -42,7 +42,7 @@ class QueryTraceFetcher { private final UUID tracingId; private final CqlSession session; - private final DriverConfigProfile configProfile; + private final DriverExecutionProfile config; private final int maxAttempts; private final long intervalNanos; private final EventExecutor scheduler; @@ -52,27 +52,25 @@ class QueryTraceFetcher { UUID tracingId, CqlSession session, InternalDriverContext context, - DriverConfigProfile configProfile) { + DriverExecutionProfile config) { this.tracingId = tracingId; this.session = session; ConsistencyLevel regularConsistency = context .consistencyLevelRegistry() - .fromName(configProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); + .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); ConsistencyLevel traceConsistency = context .consistencyLevelRegistry() - .fromName(configProfile.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)); - this.configProfile = + .fromName(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)); + this.config = (traceConsistency.equals(regularConsistency)) - ? configProfile - : configProfile.withString( - DefaultDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); + ? config + : config.withString(DefaultDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); - this.maxAttempts = configProfile.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS); - this.intervalNanos = - configProfile.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); + this.maxAttempts = config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS); + this.intervalNanos = config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); this.scheduler = context.nettyOptions().adminEventExecutorGroup().next(); querySession(maxAttempts); @@ -87,7 +85,7 @@ private void querySession(int remainingAttempts) { .executeAsync( SimpleStatement.builder("SELECT * FROM system_traces.sessions WHERE session_id = ?") .addPositionalValue(tracingId) - .withConfigProfile(configProfile) + .withExecutionProfile(config) .build()) .whenComplete( (rs, error) -> { @@ -122,7 +120,7 @@ private void queryEvents(Row sessionRow, List events, ByteBuffer pagingStat SimpleStatement.builder("SELECT * FROM system_traces.events WHERE session_id = ?") .addPositionalValue(tracingId) .withPagingState(pagingState) - .withConfigProfile(configProfile) + .withExecutionProfile(config) .build()) .whenComplete( (rs, error) -> { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 54c61b4365a..d334e91dee3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -91,7 +91,7 @@ public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull Strin getLocalDcFromConfig(context, profileName), getFilterFromConfig(context, profileName), context, - profileName.equals(DriverConfigProfile.DEFAULT_NAME)); + profileName.equals(DriverExecutionProfile.DEFAULT_NAME)); } @VisibleForTesting @@ -305,7 +305,7 @@ public void close() { } private static String getLocalDcFromConfig(DriverContext context, String profileName) { - DriverConfigProfile config = context.config().getProfile(profileName); + DriverExecutionProfile config = context.config().getProfile(profileName); return config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index b8de08de6af..2f9deb81684 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -72,7 +72,7 @@ public DefaultTopologyMonitor(InternalDriverContext context) { this.context = context; this.controlConnection = context.controlConnection(); this.addressTranslator = context.addressTranslator(); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.timeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.reconnectOnInit = config.getBoolean(DefaultDriverOption.RECONNECT_ON_INIT); this.closeFuture = new CompletableFuture<>(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index 225556f2272..3548b8cb488 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -141,7 +141,7 @@ public void init() { */ @NonNull public Queue newQueryPlan( - @Nullable Request request, @NonNull String configProfileName, @Nullable Session session) { + @Nullable Request request, @NonNull String executionProfileName, @Nullable Session session) { switch (stateRef.get()) { case BEFORE_INIT: case DURING_INIT: @@ -152,9 +152,9 @@ public Queue newQueryPlan( Collections.shuffle(nodes); return new ConcurrentLinkedQueue<>(nodes); case RUNNING: - LoadBalancingPolicy policy = policiesPerProfile.get(configProfileName); + LoadBalancingPolicy policy = policiesPerProfile.get(executionProfileName); if (policy == null) { - policy = policiesPerProfile.get(DriverConfigProfile.DEFAULT_NAME); + policy = policiesPerProfile.get(DriverExecutionProfile.DEFAULT_NAME); } return policy.newQueryPlan(request, session); default: @@ -164,7 +164,7 @@ public Queue newQueryPlan( @NonNull public Queue newQueryPlan() { - return newQueryPlan(null, DriverConfigProfile.DEFAULT_NAME, null); + return newQueryPlan(null, DriverExecutionProfile.DEFAULT_NAME, null); } // when it comes in from the outside diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index de9c03d8ab4..39e8d45d71c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -57,7 +57,7 @@ public class MetadataManager implements AsyncAutoCloseable { private final InternalDriverContext context; private final String logPrefix; private final EventExecutor adminExecutor; - private final DriverConfigProfile config; + private final DriverExecutionProfile config; private final SingleThreaded singleThreaded; private final ControlConnection controlConnection; @@ -262,7 +262,7 @@ private class SingleThreaded { private boolean didFirstNodeListRefresh; - private SingleThreaded(InternalDriverContext context, DriverConfigProfile config) { + private SingleThreaded(InternalDriverContext context, DriverExecutionProfile config) { this.schemaRefreshDebouncer = new Debouncer<>( adminExecutor, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 32ea583f169..eaebdf8d3bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; @@ -98,7 +98,7 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.metadataManager = context.metadataManager(); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.topologyEventDebouncer = new Debouncer<>( adminExecutor, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java index b2aba377ebc..633f9728402 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; @@ -75,7 +75,7 @@ class SchemaAgreementChecker { this.context = context; this.port = port; this.logPrefix = logPrefix; - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.queryTimeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.intervalNs = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL).toNanos(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java index b64c7eae1fb..bfa8efa61ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import java.util.Optional; @@ -27,7 +27,7 @@ class Cassandra21SchemaQueries extends CassandraSchemaQueries { Cassandra21SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, false, refreshFuture, config, logPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java index bbed924788d..180f9ee0975 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import java.util.Optional; @@ -27,7 +27,7 @@ class Cassandra22SchemaQueries extends CassandraSchemaQueries { Cassandra22SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, false, refreshFuture, config, logPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java index 286ba8bde45..ac7fa44ebd2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata.schema.queries; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import java.util.Optional; @@ -27,7 +27,7 @@ class Cassandra3SchemaQueries extends CassandraSchemaQueries { Cassandra3SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, true, refreshFuture, config, logPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java index 128f90068e2..bbbe9b9631f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -64,7 +64,7 @@ protected CassandraSchemaQueries( DriverChannel channel, boolean isCassandraV3, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { this.channel = channel; this.adminExecutor = channel.eventLoop(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index 0343fd732d1..955f1ea66a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import com.datastax.oss.driver.api.core.Version; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -69,7 +69,7 @@ protected SchemaQueries newInstance( } else { version = version.nextStable(); } - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); LOG.debug("[{}] Sending schema queries to {} with version {}", logPrefix, node, version); if (version.compareTo(Version.V2_2_0) < 0) { return new Cassandra21SchemaQueries(channel, refreshFuture, config, logPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java index 199d90b6e39..d804e95d3e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java @@ -19,7 +19,7 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import java.time.Duration; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -89,7 +89,7 @@ protected void initializeDefaultCounter(MetricT metric, String profileName) { protected void initializeHdrTimer( MetricT metric, - DriverConfigProfile config, + DriverExecutionProfile config, DefaultDriverOption highestLatencyOption, DefaultDriverOption significantDigitsOption, DefaultDriverOption intervalOption) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java index 65b14d6af4d..1b998cfdeef 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java @@ -17,7 +17,7 @@ import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; @@ -51,7 +51,7 @@ public DropwizardMetricsFactory(InternalDriverContext context) { this.logPrefix = context.sessionName(); this.context = context; - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); Set enabledSessionMetrics = parseSessionMetricPaths(config.getStringList(DefaultDriverOption.METRICS_SESSION_ENABLED)); this.enabledNodeMetrics = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java index 3f858e5aaa1..071b4b0c8b8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java @@ -18,7 +18,7 @@ import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.NodeMetric; @@ -46,7 +46,7 @@ public DropwizardNodeMetricUpdater( super(enabledMetrics, registry); this.metricNamePrefix = buildPrefix(context.sessionName(), node.getConnectAddress()); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); if (enabledMetrics.contains(DefaultNodeMetric.OPEN_CONNECTIONS)) { this.registry.register( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java index c90cf9860fb..60452db2f40 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -87,7 +87,7 @@ public class PoolManager implements AsyncAutoCloseable { private final String logPrefix; private final EventExecutor adminExecutor; - private final DriverConfigProfile config; + private final DriverExecutionProfile config; private final SingleThreaded singleThreaded; public PoolManager(InternalDriverContext context) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java index f35730d6af4..3ab2771c51e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.api.core.session.throttling.Throttled; @@ -72,7 +72,7 @@ public class ConcurrencyLimitingRequestThrottler implements RequestThrottler { public ConcurrencyLimitingRequestThrottler(DriverContext context) { this.logPrefix = context.sessionName(); - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.maxConcurrentRequests = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS); this.maxQueueSize = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java index d154d90a17f..32337f89938 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.api.core.session.throttling.Throttled; @@ -90,7 +90,7 @@ public RateLimitingRequestThrottler(DriverContext context) { this.logPrefix = context.sessionName(); this.clock = clock; - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.maxRequestsPerSecond = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java index 829e7c552e3..daa1ee56b62 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; @@ -52,7 +52,7 @@ public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionP private final long constantDelayMillis; public ConstantSpeculativeExecutionPolicy(DriverContext context, String profileName) { - DriverConfigProfile config = context.config().getProfile(profileName); + DriverExecutionProfile config = context.config().getProfile(profileName); this.maxExecutions = config.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX); if (this.maxExecutions < 1) { throw new IllegalArgumentException("Max must be at least 1"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 312a7b0822a..cd5e22b3865 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.ssl; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import edu.umd.cs.findbugs.annotations.NonNull; @@ -66,7 +66,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory { /** Builds a new instance from the driver configuration. */ public DefaultSslEngineFactory(DriverContext driverContext) { - DriverConfigProfile config = driverContext.config().getDefaultProfile(); + DriverExecutionProfile config = driverContext.config().getDefaultProfile(); try { this.sslContext = buildContext(config); } catch (Exception e) { @@ -105,7 +105,7 @@ public SSLEngine newSslEngine(@NonNull SocketAddress remoteEndpoint) { return engine; } - protected SSLContext buildContext(DriverConfigProfile config) throws Exception { + protected SSLContext buildContext(DriverExecutionProfile config) throws Exception { if (config.isDefined(DefaultDriverOption.SSL_KEYSTORE_PATH) || config.isDefined(DefaultDriverOption.SSL_TRUSTSTORE_PATH)) { SSLContext context = SSLContext.getInstance("SSL"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java index f40a6326512..da60654f7a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.time; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.time.TimestampGenerator; import java.time.Duration; @@ -46,7 +46,7 @@ protected MonotonicTimestampGenerator(DriverContext context) { protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { this.clock = clock; - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); this.warningThresholdMicros = config .getDuration( @@ -103,7 +103,7 @@ private void maybeLog(long currentTick, long last) { } private static Clock buildClock(DriverContext context) { - DriverConfigProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.config().getDefaultProfile(); boolean forceJavaClock = config.getBoolean(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK, false); return Clock.getInstance(forceJavaClock); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java index faccd45a0eb..bb4dca8d1ba 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.tracker; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; @@ -54,7 +54,7 @@ public NoopRequestTracker(@SuppressWarnings("unused") DriverContext context) { public void onSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { // nothing to do } @@ -64,7 +64,7 @@ public void onError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, Node node) { // nothing to do } @@ -74,7 +74,7 @@ public void onNodeError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { // nothing to do } @@ -83,7 +83,7 @@ public void onNodeError( public void onNodeSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index f5009bb161f..3a1fce01a27 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.internal.core.tracker; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; @@ -79,19 +79,19 @@ protected RequestLogger(String logPrefix, RequestLogFormatter formatter) { public void onSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { boolean successEnabled = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, false); + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, false); boolean slowEnabled = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, false); + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, false); if (!successEnabled && !slowEnabled) { return; } long slowThresholdNanos = - configProfile + executionProfile .getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD, Duration.ofSeconds(1)) .toNanos(); boolean isSlow = latencyNanos > slowThresholdNanos; @@ -100,11 +100,12 @@ public void onSuccess( } int maxQueryLength = - configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); - int maxValues = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + boolean showValues = + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); + int maxValues = executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); int maxValueLength = - configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); logSuccess( request, latencyNanos, isSlow, node, maxQueryLength, showValues, maxValues, maxValueLength); @@ -115,22 +116,23 @@ public void onError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, Node node) { - if (!configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, false)) { + if (!executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, false)) { return; } int maxQueryLength = - configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); - int maxValues = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + boolean showValues = + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); + int maxValues = executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); int maxValueLength = - configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); boolean showStackTraces = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, false); + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, false); logError( request, @@ -149,7 +151,7 @@ public void onNodeError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { // Nothing to do } @@ -158,7 +160,7 @@ public void onNodeError( public void onNodeSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { // Nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 13c8ec6ccbd..57ce4cec189 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -128,7 +128,7 @@ public static Map buildFromConfigProfiles( // Find out how many distinct configurations we have ListMultimap profilesByConfig = MultimapBuilder.hashKeys().arrayListValues().build(); - for (DriverConfigProfile profile : context.config().getProfiles().values()) { + for (DriverExecutionProfile profile : context.config().getProfiles().values()) { profilesByConfig.put(profile.getComparisonKey(rootOption), profile.getName()); } @@ -165,7 +165,7 @@ public static Optional buildFromConfig( Class expectedSuperType, String... defaultPackages) { - DriverConfigProfile config = + DriverExecutionProfile config = (profileName == null) ? context.config().getDefaultProfile() : context.config().getProfile(profileName); diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index 830aad4f577..6e1cc6e3f8a 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.specex.ConstantSpeculativeExecutionPolicy; @@ -35,13 +35,13 @@ public class ConstantSpeculativeExecutionPolicyTest { @Mock private DriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private Request request; @Before public void setup() { Mockito.when(context.config()).thenReturn(config); - Mockito.when(config.getProfile(DriverConfigProfile.DEFAULT_NAME)).thenReturn(defaultProfile); + Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); } private void mockOptions(int maxExecutions, long constantDelayMillis) { @@ -54,20 +54,20 @@ private void mockOptions(int maxExecutions, long constantDelayMillis) { @Test(expected = IllegalArgumentException.class) public void should_fail_if_delay_negative() { mockOptions(1, -10); - new ConstantSpeculativeExecutionPolicy(context, DriverConfigProfile.DEFAULT_NAME); + new ConstantSpeculativeExecutionPolicy(context, DriverExecutionProfile.DEFAULT_NAME); } @Test(expected = IllegalArgumentException.class) public void should_fail_if_max_less_than_one() { mockOptions(0, 10); - new ConstantSpeculativeExecutionPolicy(context, DriverConfigProfile.DEFAULT_NAME); + new ConstantSpeculativeExecutionPolicy(context, DriverExecutionProfile.DEFAULT_NAME); } @Test public void should_return_delay_until_max() { mockOptions(3, 10); SpeculativeExecutionPolicy policy = - new ConstantSpeculativeExecutionPolicy(context, DriverConfigProfile.DEFAULT_NAME); + new ConstantSpeculativeExecutionPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // Initial execution starts, schedule first speculative execution assertThat(policy.nextExecution(null, null, request, 1)).isEqualTo(10); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 9d71da05a58..182637538d7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -41,13 +41,11 @@ public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Override public void setup() throws InterruptedException { super.setup(); - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn("V4"); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) .thenReturn(128); Mockito.when(responseCallback.isLastResponse(any(Frame.class))).thenReturn(true); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index e9e16f4bd33..6bdaf6f24df 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -32,8 +32,7 @@ public class ChannelFactoryClusterNameTest extends ChannelFactoryTestBase { @Test public void should_set_cluster_name_from_first_connection() { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(false); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -53,8 +52,7 @@ public void should_set_cluster_name_from_first_connection() { @Test public void should_check_cluster_name_for_next_connections() throws Throwable { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(false); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index b197fc7c248..ad702571a8a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -39,10 +39,8 @@ public class ChannelFactoryProtocolNegotiationTest extends ChannelFactoryTestBas @Test public void should_succeed_if_version_specified_and_supported_by_server() { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn("V4"); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -63,10 +61,8 @@ public void should_succeed_if_version_specified_and_supported_by_server() { @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_version_specified_and_not_supported_by_server(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn("V4"); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -96,8 +92,7 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err @Test public void should_succeed_if_version_not_specified_and_server_supports_latest_supported() { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(false); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); @@ -123,8 +118,7 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s @UseDataProvider("unsupportedProtocolCodes") public void should_negotiate_if_version_not_specified_and_server_supports_legacy(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(false); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) .thenReturn(Optional.of(DefaultProtocolVersion.V3)); @@ -158,8 +152,7 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) { // Given - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(false); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) .thenReturn(Optional.of(DefaultProtocolVersion.V3)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 0ae54ec44ae..c95b1b71e4d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -88,7 +88,7 @@ public abstract class ChannelFactoryTestBase { @Mock InternalDriverContext context; @Mock DriverConfig driverConfig; - @Mock DriverConfigProfile defaultConfigProfile; + @Mock DriverExecutionProfile defaultProfile; @Mock NettyOptions nettyOptions; @Mock ProtocolVersionRegistry protocolVersionRegistry; @Mock EventBus eventBus; @@ -112,18 +112,15 @@ public void setup() throws InterruptedException { clientGroup = new DefaultEventLoopGroup(1); Mockito.when(context.config()).thenReturn(driverConfig); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)) + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)) .thenReturn(false); - Mockito.when( - defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when( - defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) - .thenReturn(1); - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)).thenReturn(1); + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); @@ -235,14 +232,14 @@ ChannelInitializer initializer( @Override protected void initChannel(Channel channel) throws Exception { try { - DriverConfigProfile defaultConfigProfile = context.config().getDefaultProfile(); + DriverExecutionProfile defaultProfile = context.config().getDefaultProfile(); long setKeyspaceTimeoutMillis = - defaultConfigProfile + defaultProfile .getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT) .toMillis(); int maxRequestsPerConnection = - defaultConfigProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); + defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); InFlightHandler inFlightHandler = new InFlightHandler( @@ -254,7 +251,7 @@ protected void initChannel(Channel channel) throws Exception { null, "test"); - HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultProfile); ProtocolInitHandler initHandler = new ProtocolInitHandler( context, protocolVersion, clusterName, options, heartbeatHandler); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index c3079644ba1..50647478f45 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.auth.AuthenticationException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; @@ -63,7 +63,7 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private Compressor compressor; private ProtocolVersionRegistry protocolVersionRegistry = @@ -76,11 +76,10 @@ public void setup() { super.setup(); MockitoAnnotations.initMocks(this); Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when( - defaultConfigProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); Mockito.when(internalDriverContext.protocolVersionRegistry()) .thenReturn(protocolVersionRegistry); @@ -100,7 +99,7 @@ public void setup() { null, "test")); - heartbeatHandler = new HeartbeatHandler(defaultConfigProfile); + heartbeatHandler = new HeartbeatHandler(defaultProfile); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index 5d5a6b06b66..87b93ff3b92 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -45,7 +45,7 @@ public class DefaultDriverConfigLoaderTest { @Mock private NettyOptions nettyOptions; @Mock private EventLoopGroup adminEventExecutorGroup; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; private ScheduledTaskCapturingEventLoop adminExecutor; private EventBus eventBus; private AtomicReference configSource; @@ -68,8 +68,8 @@ public void setup() { // In real life, it's the object managed by the loader, but in this test it's simpler to mock // it. Mockito.when(context.config()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) .thenReturn(Duration.ofSeconds(12)); configSource = new AtomicReference<>("int1 = 42"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java index 3003397a699..32889e24afb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java @@ -18,7 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import java.util.HashMap; @@ -68,8 +68,8 @@ public void should_override_option_in_profile() { @Test public void should_create_derived_profile_with_new_option() { TypesafeDriverConfig config = parse("int1 = 42"); - DriverConfigProfile base = config.getDefaultProfile(); - DriverConfigProfile derived = base.withInt(MockOptions.INT2, 43); + DriverExecutionProfile base = config.getDefaultProfile(); + DriverExecutionProfile derived = base.withInt(MockOptions.INT2, 43); assertThat(base.isDefined(MockOptions.INT2)).isFalse(); assertThat(derived.isDefined(MockOptions.INT2)).isTrue(); @@ -79,8 +79,8 @@ public void should_create_derived_profile_with_new_option() { @Test public void should_create_derived_profile_overriding_option() { TypesafeDriverConfig config = parse("int1 = 42"); - DriverConfigProfile base = config.getDefaultProfile(); - DriverConfigProfile derived = base.withInt(MockOptions.INT1, 43); + DriverExecutionProfile base = config.getDefaultProfile(); + DriverExecutionProfile derived = base.withInt(MockOptions.INT1, 43); assertThat(base.getInt(MockOptions.INT1)).isEqualTo(42); assertThat(derived.getInt(MockOptions.INT1)).isEqualTo(43); @@ -89,8 +89,8 @@ public void should_create_derived_profile_overriding_option() { @Test public void should_create_derived_profile_unsetting_option() { TypesafeDriverConfig config = parse("int1 = 42\n int2 = 43"); - DriverConfigProfile base = config.getDefaultProfile(); - DriverConfigProfile derived = base.without(MockOptions.INT2); + DriverExecutionProfile base = config.getDefaultProfile(); + DriverExecutionProfile derived = base.without(MockOptions.INT2); assertThat(base.getInt(MockOptions.INT2)).isEqualTo(43); assertThat(derived.isDefined(MockOptions.INT2)).isFalse(); @@ -101,7 +101,7 @@ public void should_fetch_string_map() { TypesafeDriverConfig config = parse( "int1 = 42 \n auth_provider { auth_thing_one= one \n auth_thing_two = two \n auth_thing_three = three}"); - DriverConfigProfile base = config.getDefaultProfile(); + DriverExecutionProfile base = config.getDefaultProfile(); base.getStringMap(MockOptions.AUTH_PROVIDER); Map map = base.getStringMap(MockOptions.AUTH_PROVIDER); assertThat(map.entrySet().size()).isEqualTo(3); @@ -117,8 +117,8 @@ public void should_create_derived_profile_with_string_map() { authThingMap.put("auth_thing_one", "one"); authThingMap.put("auth_thing_two", "two"); authThingMap.put("auth_thing_three", "three"); - DriverConfigProfile base = config.getDefaultProfile(); - DriverConfigProfile mapBase = base.withStringMap(MockOptions.AUTH_PROVIDER, authThingMap); + DriverExecutionProfile base = config.getDefaultProfile(); + DriverExecutionProfile mapBase = base.withStringMap(MockOptions.AUTH_PROVIDER, authThingMap); Map fetchedMap = mapBase.getStringMap(MockOptions.AUTH_PROVIDER); assertThat(fetchedMap).isEqualTo(authThingMap); } @@ -137,9 +137,9 @@ public void should_reload() { public void should_update_derived_profiles_after_reloading() { TypesafeDriverConfig config = parse("int1 = 42\n profiles { profile1 { int1 = 43 } }"); - DriverConfigProfile derivedFromDefault = + DriverExecutionProfile derivedFromDefault = config.getDefaultProfile().withInt(MockOptions.INT2, 50); - DriverConfigProfile derivedFromProfile1 = + DriverExecutionProfile derivedFromProfile1 = config.getProfile("profile1").withInt(MockOptions.INT2, 51); config.reload(ConfigFactory.parseString("int1 = 44\n profiles { profile1 { int1 = 45 } }")); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index c9fd4ccc3a9..f19a1a3c89a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -126,7 +126,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = @@ -315,7 +315,7 @@ public void should_fail_if_retry_policy_ignores_error() { // Make node1's error unrecoverable, will rethrow RetryPolicy mockRetryPolicy = - harness.getContext().retryPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().retryPolicy(DriverExecutionProfile.DEFAULT_NAME); Mockito.when( mockRetryPolicy.onErrorResponse( eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) @@ -356,7 +356,7 @@ public void should_propagate_custom_payload_on_single_node() { PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = new CqlPrepareAsyncHandler( @@ -387,7 +387,7 @@ public void should_propagate_custom_payload_on_all_nodes() { node2Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverConfigProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); CompletionStage prepareFuture = new CqlPrepareAsyncHandler( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 4c6b667a442..86a1ca650c4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -25,7 +25,7 @@ import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.HeartbeatException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -125,11 +125,11 @@ public void should_always_rethrow_query_validation_error( Mockito.verify(nodeMetricUpdater1) .incrementCounter( - DefaultNodeMetric.OTHER_ERRORS, DriverConfigProfile.DEFAULT_NAME); + DefaultNodeMetric.OTHER_ERRORS, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -168,16 +168,17 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.errorMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) - .incrementCounter(DefaultNodeMetric.RETRIES, DriverConfigProfile.DEFAULT_NAME); + .incrementCounter( + DefaultNodeMetric.RETRIES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.retryMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -216,16 +217,17 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.errorMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) - .incrementCounter(DefaultNodeMetric.RETRIES, DriverConfigProfile.DEFAULT_NAME); + .incrementCounter( + DefaultNodeMetric.RETRIES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.retryMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(2)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -261,16 +263,17 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.errorMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) - .incrementCounter(DefaultNodeMetric.IGNORES, DriverConfigProfile.DEFAULT_NAME); + .incrementCounter( + DefaultNodeMetric.IGNORES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.ignoreMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.ignoreMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -302,11 +305,11 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.errorMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -352,11 +355,11 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re Mockito.verify(nodeMetricUpdater1) .incrementCounter( - failureScenario.errorMetric, DriverConfigProfile.DEFAULT_NAME); + failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 0e3a990fff9..3eb13a0b430 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -52,7 +52,7 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); @@ -79,7 +79,7 @@ public void should_schedule_speculative_executions( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; long secondExecutionDelay = 200L; Mockito.when( @@ -111,7 +111,7 @@ public void should_schedule_speculative_executions( firstExecutionTask.run(); Mockito.verify(nodeMetricUpdater1) .incrementCounter( - DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverConfigProfile.DEFAULT_NAME); + DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); @@ -123,7 +123,7 @@ public void should_schedule_speculative_executions( secondExecutionTask.run(); Mockito.verify(nodeMetricUpdater2) .incrementCounter( - DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverConfigProfile.DEFAULT_NAME); + DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); node3Behavior.verifyWrite(); node3Behavior.setWriteSuccess(); @@ -146,7 +146,7 @@ public void should_not_start_execution_if_result_complete( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -187,7 +187,7 @@ public void should_not_start_execution_if_result_complete( Mockito.verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), - eq(DriverConfigProfile.DEFAULT_NAME), + eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -203,7 +203,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -234,7 +234,7 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -287,7 +287,7 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -344,7 +344,7 @@ public void should_retry_in_speculative_executions( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -393,7 +393,7 @@ public void should_stop_retrying_other_executions_if_result_complete( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 03f05971d10..de7f9fdbfc1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.QueryTrace; @@ -68,8 +68,8 @@ public class QueryTraceFetcherTest { @Mock private CqlSession session; @Mock private InternalDriverContext context; - @Mock private DriverConfigProfile config; - @Mock private DriverConfigProfile traceConfig; + @Mock private DriverExecutionProfile config; + @Mock private DriverExecutionProfile traceConfig; @Mock private NettyOptions nettyOptions; @Mock private EventExecutorGroup adminEventExecutorGroup; @Mock private EventExecutor eventExecutor; @@ -363,13 +363,13 @@ private void assertSessionQuery(SimpleStatement statement) { assertThat(statement.getQuery()) .isEqualTo("SELECT * FROM system_traces.sessions WHERE session_id = ?"); assertThat(statement.getPositionalValues()).containsOnly(TRACING_ID); - assertThat(statement.getConfigProfile()).isEqualTo(traceConfig); + assertThat(statement.getExecutionProfile()).isEqualTo(traceConfig); } private void assertEventsQuery(SimpleStatement statement) { assertThat(statement.getQuery()) .isEqualTo("SELECT * FROM system_traces.events WHERE session_id = ?"); assertThat(statement.getPositionalValues()).containsOnly(TRACING_ID); - assertThat(statement.getConfigProfile()).isEqualTo(traceConfig); + assertThat(statement.getExecutionProfile()).isEqualTo(traceConfig); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 4730d0550ee..dccc20ea83f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; @@ -81,7 +81,7 @@ public static Builder builder() { @Mock private EventLoopGroup eventLoopGroup; @Mock private NettyOptions nettyOptions; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @Mock private RetryPolicy retryPolicy; @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; @@ -97,22 +97,21 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); - Mockito.when(defaultConfigProfile.getName()).thenReturn(DriverConfigProfile.DEFAULT_NAME); + Mockito.when(defaultProfile.getName()).thenReturn(DriverExecutionProfile.DEFAULT_NAME); // TODO make configurable in the test, also handle profiles - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + Mockito.when(defaultProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE)) - .thenReturn(5000); - Mockito.when(defaultConfigProfile.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); + Mockito.when(defaultProfile.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.SERIAL.name()); - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)) + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)) .thenReturn(true); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(context.config()).thenReturn(config); Mockito.when( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java index d1cbc0a5a7a..83ca38065e5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; @@ -61,7 +61,7 @@ public class StatementSizeTest { @Mock PreparedStatement preparedStatement; @Mock InternalDriverContext driverContext; @Mock DriverConfig config; - @Mock DriverConfigProfile defaultConfigProfile; + @Mock DriverExecutionProfile defaultProfile; @Mock TimestampGenerator timestampGenerator; @Before @@ -85,7 +85,7 @@ public void setup() { Mockito.when(driverContext.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); Mockito.when(driverContext.protocolVersionRegistry()) .thenReturn(new CassandraProtocolVersionRegistry(null)); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(driverContext.config()).thenReturn(config); Mockito.when(driverContext.timestampGenerator()).thenReturn(timestampGenerator); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 599dfa0dc15..897023f175a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; @@ -61,7 +61,7 @@ public class DefaultTopologyMonitorTest { @Mock private InternalDriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfig; + @Mock private DriverExecutionProfile defaultConfig; @Mock private ControlConnection controlConnection; @Mock private DriverChannel channel; @Mock protected MetricsFactory metricsFactory; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 0317e421f08..342712cef83 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -101,7 +101,7 @@ public void setup() { new LoadBalancingPolicyWrapper( context, ImmutableMap.of( - DriverConfigProfile.DEFAULT_NAME, + DriverExecutionProfile.DEFAULT_NAME, policy1, "profile1", policy1, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 5cd94ae0bf5..868700efe9a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -62,7 +62,7 @@ public class MetadataManagerTest { @Mock private NettyOptions nettyOptions; @Mock private TopologyMonitor topologyMonitor; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private EventBus eventBus; @Mock private SchemaQueriesFactory schemaQueriesFactory; @Mock private SchemaParserFactory schemaParserFactory; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 6b446de4159..7316115b21a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -56,7 +56,7 @@ public class NodeStateManagerTest { @Mock private InternalDriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private NettyOptions nettyOptions; @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; @@ -70,11 +70,11 @@ public void setup() { MockitoAnnotations.initMocks(this); // Disable debouncing by default, tests that need it will override - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofSeconds(0)); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(context.config()).thenReturn(config); this.eventBus = Mockito.spy(new EventBus("test")); @@ -382,9 +382,9 @@ public void should_ignore_removal_of_nonexistent_node() { @Test public void should_coalesce_topology_events() { // Given - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofDays(1)); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(5); new NodeStateManager(context); node1.state = NodeState.FORCED_DOWN; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index 2f9f9ee4a44..e493a34653a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -64,7 +64,7 @@ public class SchemaAgreementCheckerTest { @Mock private InternalDriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfig; + @Mock private DriverExecutionProfile defaultConfig; @Mock private DriverChannel channel; @Mock private EventLoop eventLoop; @Mock private MetadataManager metadataManager; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index a013890a340..87758c85f41 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -19,7 +19,7 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -119,7 +119,7 @@ static class SchemaQueriesWithMockedChannel extends Cassandra21SchemaQueries { SchemaQueriesWithMockedChannel( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, refreshFuture, config, logPrefix); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index ac218123ec4..5337503b4d1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -19,7 +19,7 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -140,7 +140,7 @@ static class SchemaQueriesWithMockedChannel extends Cassandra22SchemaQueries { SchemaQueriesWithMockedChannel( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, refreshFuture, config, logPrefix); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index 59c0fb33e83..22258d75fd9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -19,7 +19,7 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -333,7 +333,7 @@ static class SchemaQueriesWithMockedChannel extends Cassandra3SchemaQueries { SchemaQueriesWithMockedChannel( DriverChannel channel, CompletableFuture refreshFuture, - DriverConfigProfile config, + DriverExecutionProfile config, String logPrefix) { super(channel, refreshFuture, config, logPrefix); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java index 98b39fa1386..df632be39d9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; @@ -43,7 +43,7 @@ public abstract class SchemaQueriesTest { protected static final CqlIdentifier FOO_ID = CqlIdentifier.fromInternal("foo"); @Mock protected Node node; - @Mock protected DriverConfigProfile config; + @Mock protected DriverExecutionProfile config; @Mock protected DriverChannel driverChannel; protected EmbeddedChannel channel; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 756154a73e1..39023773f7d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -20,7 +20,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; @@ -53,7 +53,7 @@ abstract class ChannelPoolTestBase { @Mock protected InternalDriverContext context; @Mock private DriverConfig config; - @Mock protected DriverConfigProfile defaultProfile; + @Mock protected DriverExecutionProfile defaultProfile; @Mock private ReconnectionPolicy reconnectionPolicy; @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; @Mock private NettyOptions nettyOptions; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index 3bf0b530152..31f6ccb9639 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -28,7 +28,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Metadata; @@ -87,7 +87,7 @@ public class DefaultSessionPoolsTest { @Mock private DriverConfigLoader configLoader; @Mock private Metadata metadata; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private ReconnectionPolicy reconnectionPolicy; @Mock private RetryPolicy retryPolicy; @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; @@ -113,17 +113,16 @@ public void setup() { Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); // Config: - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) .thenReturn(false); - Mockito.when(defaultConfigProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) - .thenReturn(true); - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ZERO); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(context.config()).thenReturn(config); // Init sequence: @@ -168,9 +167,10 @@ public void setup() { // Shutdown sequence: Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(context.retryPolicy(DriverConfigProfile.DEFAULT_NAME)).thenReturn(retryPolicy); + Mockito.when(context.retryPolicy(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(retryPolicy); Mockito.when(context.speculativeExecutionPolicies()) - .thenReturn(ImmutableMap.of(DriverConfigProfile.DEFAULT_NAME, speculativeExecutionPolicy)); + .thenReturn( + ImmutableMap.of(DriverExecutionProfile.DEFAULT_NAME, speculativeExecutionPolicy)); Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); Mockito.when(context.nodeStateListener()).thenReturn(nodeStateListener); Mockito.when(context.schemaChangeListener()).thenReturn(schemaChangeListener); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index 93477917922..724bdc51077 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -64,7 +64,7 @@ public class ReprepareOnUpTest { @Mock private EventLoop eventLoop; @Mock private InternalDriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultConfigProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private TopologyMonitor topologyMonitor; @Mock private MetricsFactory metricsFactory; @Mock private SessionMetricUpdater metricUpdater; @@ -79,14 +79,13 @@ public void setup() { Mockito.when(channel.eventLoop()).thenReturn(eventLoop); Mockito.when(eventLoop.inEventLoop()).thenReturn(true); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(true); - Mockito.when(defaultConfigProfile.getDuration(DefaultDriverOption.REPREPARE_TIMEOUT)) + Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REPREPARE_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)) - .thenReturn(0); - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(0); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(100); Mockito.when(context.config()).thenReturn(config); @@ -176,7 +175,7 @@ public void should_reprepare_all_if_system_table_empty() { @Test public void should_reprepare_all_if_system_query_disabled() { - Mockito.when(defaultConfigProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(false); MockReprepareOnUp reprepareOnUp = @@ -238,8 +237,7 @@ public void should_proceed_if_schema_agreement_fails() { @Test public void should_limit_number_of_statements_to_reprepare() { - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)) - .thenReturn(3); + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(3); MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( @@ -267,7 +265,7 @@ public void should_limit_number_of_statements_to_reprepare() { @Test public void should_limit_number_of_statements_reprepared_in_parallel() { - Mockito.when(defaultConfigProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) + Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(3); MockReprepareOnUp reprepareOnUp = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java index 7ba77d8caee..909e8321cae 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.throttling.Throttled; import com.datastax.oss.driver.shaded.guava.common.collect.Lists; @@ -39,7 +39,7 @@ public class ConcurrencyLimitingRequestThrottlerTest { @Mock private DriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultProfile; + @Mock private DriverExecutionProfile defaultProfile; private ConcurrencyLimitingRequestThrottler throttler; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java index a809cbdd171..c797eeae491 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; @@ -52,7 +52,7 @@ public class RateLimitingRequestThrottlerTest { @Mock private InternalDriverContext context; @Mock private DriverConfig config; - @Mock private DriverConfigProfile defaultProfile; + @Mock private DriverExecutionProfile defaultProfile; @Mock private NettyOptions nettyOptions; @Mock private EventLoopGroup adminGroup; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java index 0d8ff547ead..f95c6d9df50 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java @@ -23,7 +23,7 @@ import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import java.time.Duration; import org.junit.After; @@ -42,7 +42,7 @@ abstract class MonotonicTimestampGeneratorTestBase { @Mock protected Clock clock; @Mock protected InternalDriverContext context; @Mock private DriverConfig config; - @Mock protected DriverConfigProfile defaultConfigProfile; + @Mock protected DriverExecutionProfile defaultProfile; @Mock private Appender appender; @Captor private ArgumentCaptor loggingEventCaptor; @@ -53,17 +53,17 @@ abstract class MonotonicTimestampGeneratorTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfigProfile); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(context.config()).thenReturn(config); // Disable warnings by default Mockito.when( - defaultConfigProfile.getDuration( + defaultProfile.getDuration( DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) .thenReturn(Duration.ZERO); // Actual value doesn't really matter since we only test the first warning Mockito.when( - defaultConfigProfile.getDuration( + defaultProfile.getDuration( DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) .thenReturn(Duration.ofSeconds(10)); @@ -107,7 +107,7 @@ public void should_increment_if_clock_does_not_increase() { @Test public void should_warn_if_timestamps_drift() { Mockito.when( - defaultConfigProfile.getDuration( + defaultProfile.getDuration( DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) .thenReturn(Duration.ofNanos(2 * 1000)); Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java index 5408d173831..b31be019547 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.config.typesafe.TypesafeDriverConfig; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -67,7 +67,7 @@ public void should_build_policies_per_profile() { "com.datastax.oss.driver.internal.core.specex"); assertThat(policies).hasSize(5); - SpeculativeExecutionPolicy defaultPolicy = policies.get(DriverConfigProfile.DEFAULT_NAME); + SpeculativeExecutionPolicy defaultPolicy = policies.get(DriverExecutionProfile.DEFAULT_NAME); SpeculativeExecutionPolicy policy1 = policies.get("profile1"); SpeculativeExecutionPolicy policy2 = policies.get("profile2"); SpeculativeExecutionPolicy policy3 = policies.get("profile3"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java similarity index 92% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index 1b6a180c12b..3bd96f4d290 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -46,7 +46,7 @@ import org.junit.rules.ExpectedException; @Category(ParallelizableTests.class) -public class DriverConfigProfileIT { +public class DriverExecutionProfileIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); @@ -61,7 +61,7 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { try (CqlSession session = SessionUtils.newSession(simulacron)) { SimpleStatement statement = SimpleStatement.builder("select * from system.local") - .withConfigProfileName("IDONTEXIST") + .withExecutionProfileName("IDONTEXIST") .build(); thrown.expect(IllegalArgumentException.class); @@ -87,7 +87,7 @@ public void should_use_profile_request_timeout() { } // Execute query with profile, should not timeout since waits up to 10 seconds. - session.execute(SimpleStatement.builder(query).withConfigProfileName("olap").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("olap").build()); } } @@ -110,7 +110,7 @@ public void should_use_profile_default_idempotence() { // Execute query with profile, should retry on all hosts since query is idempotent. thrown.expect(AllNodesFailedException.class); - session.execute(SimpleStatement.builder(query).withConfigProfileName("idem").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("idem").build()); } } @@ -146,7 +146,7 @@ public void should_use_profile_consistency() { simulacron.cluster().clearLogs(); // Execute query with profile, should use profile CLs - session.execute(SimpleStatement.builder(query).withConfigProfileName("cl").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("cl").build()); log = simulacron @@ -176,7 +176,7 @@ public void should_use_profile_page_size() { "profiles.smallpages.basic.request.page-size = 10")) { CqlIdentifier keyspace = SessionUtils.uniqueKeyspaceId(); - DriverConfigProfile slowProfile = SessionUtils.slowProfile(session); + DriverExecutionProfile slowProfile = SessionUtils.slowProfile(session); SessionUtils.createKeyspace(session, keyspace, slowProfile); session.execute(String.format("USE %s", keyspace.asCql(false))); @@ -185,11 +185,11 @@ public void should_use_profile_page_size() { session.execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k int, v int, PRIMARY KEY (k,v))") - .withConfigProfile(slowProfile) + .withExecutionProfile(slowProfile) .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(DefaultBatchType.UNLOGGED).withConfigProfile(slowProfile); + BatchStatement.builder(DefaultBatchType.UNLOGGED).withExecutionProfile(slowProfile); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } @@ -206,7 +206,7 @@ public void should_use_profile_page_size() { // Execute query with profile, should use profile page size result = session.execute( - SimpleStatement.builder(query).withConfigProfileName("smallpages").build()); + SimpleStatement.builder(query).withExecutionProfileName("smallpages").build()); assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); // next fetch should also be 10 pages. result.fetchNextPage(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java similarity index 95% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index d709ea40523..128405b3278 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -38,7 +38,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class DriverConfigProfileReloadIT { +public class DriverExecutionProfileReloadIT { @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); @@ -136,7 +136,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { // Expect failure because profile doesn't exist. try { - session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { // expected. @@ -149,7 +149,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { // Execute again, should expect to fail again because doesn't allow to dynamically define // profile. thrown.expect(IllegalArgumentException.class); - session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); } } @@ -175,7 +175,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio // Expect failure because profile doesn't exist. try { - session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); fail("Expected DriverTimeoutException"); } catch (DriverTimeoutException e) { // expected. @@ -186,7 +186,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should succeed because profile timeout was increased. - session.execute(SimpleStatement.builder(query).withConfigProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java index f8db9fd60cc..9b01840dcd7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -23,7 +23,7 @@ import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.SOCKET_TCP_NODELAY; import static org.assertj.core.api.Assertions.assertThat; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -58,7 +58,7 @@ public class ChannelSocketOptionsIT { @Test public void should_report_socket_options() { DefaultSession session = sessionRule.session(); - DriverConfigProfile config = session.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = session.getContext().config().getDefaultProfile(); assertThat(config.getBoolean(SOCKET_TCP_NODELAY)).isTrue(); assertThat(config.getBoolean(SOCKET_KEEP_ALIVE)).isFalse(); assertThat(config.getBoolean(SOCKET_REUSE_ADDRESS)).isFalse(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 8e492a876ab..f76eaf0d553 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -52,7 +52,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k0 text, k1 int, v int, PRIMARY KEY(k0, k1))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); PreparedStatement prepared = @@ -66,8 +66,12 @@ public static void setupSchema() { prepared.bind(PARTITION_KEY2, i + ROWS_PER_PARTITION, i + ROWS_PER_PARTITION)); } - sessionRule.session().execute(batchPart1.withConfigProfile(sessionRule.slowProfile()).build()); - sessionRule.session().execute(batchPart2.withConfigProfile(sessionRule.slowProfile()).build()); + sessionRule + .session() + .execute(batchPart1.withExecutionProfile(sessionRule.slowProfile()).build()); + sessionRule + .session() + .execute(batchPart2.withExecutionProfile(sessionRule.slowProfile()).build()); } @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 97b83ad24c6..4f53ff7a0a3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -57,7 +57,7 @@ public void createTable() { .session() .execute( SimpleStatement.newInstance(schemaStatement) - .setConfigProfile(sessionRule.slowProfile())); + .setExecutionProfile(sessionRule.slowProfile())); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index fc17deda83f..f843f4839a2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -26,7 +26,7 @@ import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; @@ -91,7 +91,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule @@ -107,7 +107,7 @@ public static void setupSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v0 int)") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -383,7 +383,7 @@ public void should_use_timeout() { public void should_propagate_attributes_when_preparing_a_simple_statement() { CqlSession session = sessionRule.session(); - DriverConfigProfile mockProfile = + DriverExecutionProfile mockProfile = session .getContext() .config() @@ -406,8 +406,8 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { SimpleStatement simpleStatement = SimpleStatement.builder("SELECT release_version FROM system.local") - .withConfigProfile(mockProfile) - .withConfigProfileName(mockConfigProfileName) + .withExecutionProfile(mockProfile) + .withExecutionProfileName(mockConfigProfileName) .withPagingState(mockPagingState) .withKeyspace(mockKeyspace) .withRoutingKeyspace(mockRoutingKeyspace) @@ -431,8 +431,8 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { for (Function createMethod : createMethods) { BoundStatement boundStatement = createMethod.apply(preparedStatement); - assertThat(boundStatement.getConfigProfile()).isEqualTo(mockProfile); - assertThat(boundStatement.getConfigProfileName()).isEqualTo(mockConfigProfileName); + assertThat(boundStatement.getExecutionProfile()).isEqualTo(mockProfile); + assertThat(boundStatement.getExecutionProfileName()).isEqualTo(mockConfigProfileName); assertThat(boundStatement.getPagingState()).isEqualTo(mockPagingState); assertThat(boundStatement.getRoutingKeyspace()) .isEqualTo(mockKeyspace != null ? mockKeyspace : mockRoutingKeyspace); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index d8624a95560..f56cfe403c7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -55,7 +55,7 @@ public void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS foo (k text, cc int, v int, PRIMARY KEY(k, cc))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index 38d903687f5..04a022bd8b1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -67,7 +67,9 @@ public void setupSchema() { sessionRule .session() .execute( - SimpleStatement.builder(query).withConfigProfile(sessionRule.slowProfile()).build()); + SimpleStatement.builder(query) + .withExecutionProfile(sessionRule.slowProfile()) + .build()); } } @@ -83,7 +85,7 @@ public void should_update_metadata_when_schema_changed_across_executions() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); BoundStatement bs = ps.bind(1); ResultSet rows = session.execute(bs); @@ -128,7 +130,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); // Then diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 51dd0487157..89fd48db95a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -75,7 +75,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule @@ -91,7 +91,7 @@ public static void setupSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v int)") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 48d05748809..e88291a8289 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -285,7 +285,7 @@ public static void createTable() { String.format( "CREATE TABLE IF NOT EXISTS %s (k int primary key, %s)", tableName, String.join(",", columnData))) - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -765,7 +765,7 @@ private static String typeFor(DataType dataType) { String.format( "CREATE TYPE IF NOT EXISTS %s (%s)", udt.getName().asCql(false), String.join(",", fieldParts))) - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); // Chances are the UDT isn't labeled as frozen in the context we're given, so we add it as diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java index bb34df22de8..24ebd2dbfa2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -69,10 +69,11 @@ public static void setup() { assertThat(context.loadBalancingPolicies()) .hasSize(3) - .containsKeys(DriverConfigProfile.DEFAULT_NAME, "profile1", "profile2"); + .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); DefaultLoadBalancingPolicy defaultPolicy = - (DefaultLoadBalancingPolicy) context.loadBalancingPolicy(DriverConfigProfile.DEFAULT_NAME); + (DefaultLoadBalancingPolicy) + context.loadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); DefaultLoadBalancingPolicy policy1 = (DefaultLoadBalancingPolicy) context.loadBalancingPolicy("profile1"); DefaultLoadBalancingPolicy policy2 = @@ -91,7 +92,7 @@ public static void setup() { @Test public void should_use_policy_from_request_profile() { // Since profile1 uses dc3 as localDC, only those nodes should receive these queries. - Statement statement = QUERY.setConfigProfileName("profile1"); + Statement statement = QUERY.setExecutionProfileName("profile1"); for (int i = 0; i < 10; i++) { ResultSet result = sessionRule.session().execute(statement); assertThat(result.getExecutionInfo().getCoordinator().getDatacenter()).isEqualTo("dc3"); @@ -105,7 +106,7 @@ public void should_use_policy_from_request_profile() { @Test public void should_use_policy_from_config_when_not_configured_in_request_profile() { // Since profile2 does not define an lbp config, it should use default which uses dc1. - Statement statement = QUERY.setConfigProfileName("profile2"); + Statement statement = QUERY.setExecutionProfileName("profile2"); for (int i = 0; i < 10; i++) { ResultSet result = sessionRule.session().execute(statement); assertThat(result.getExecutionInfo().getCoordinator().getDatacenter()).isEqualTo("dc1"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 6379415968d..20577f411aa 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.timeout; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -109,7 +109,7 @@ public void setup() { defaultLoadBalancingPolicy = (ConfigurableIgnoresPolicy) - driverContext.loadBalancingPolicy(DriverConfigProfile.DEFAULT_NAME); + driverContext.loadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); // Sanity check: the driver should have connected to simulacron ConditionChecker.checkThat( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 08eee7e5808..2da280097e2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -114,12 +114,12 @@ public void should_disable_schema_programmatically_when_enabled_in_config() { assertThat(session.isSchemaMetadataEnabled()).isFalse(); // Create a table, metadata should not be updated - DriverConfigProfile slowProfile = SessionUtils.slowProfile(session); + DriverExecutionProfile slowProfile = SessionUtils.slowProfile(session); sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE foo(k int primary key)") - .withConfigProfile(slowProfile) + .withExecutionProfile(slowProfile) .build()); assertThat(session.getMetadata().getKeyspace(sessionRule.keyspace()).get().getTables()) .doesNotContainKey(CqlIdentifier.fromInternal("foo")); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java index e0baf16e751..00c9e12ea90 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -91,9 +91,9 @@ public static void setup() { assertThat(context.retryPolicies()) .hasSize(3) - .containsKeys(DriverConfigProfile.DEFAULT_NAME, "profile1", "profile2"); + .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); - RetryPolicy defaultPolicy = context.retryPolicy(DriverConfigProfile.DEFAULT_NAME); + RetryPolicy defaultPolicy = context.retryPolicy(DriverExecutionProfile.DEFAULT_NAME); RetryPolicy policy1 = context.retryPolicy("profile1"); RetryPolicy policy2 = context.retryPolicy("profile2"); assertThat(defaultPolicy) @@ -106,14 +106,14 @@ public static void setup() { @Test(expected = UnavailableException.class) public void should_use_policy_from_request_profile() { // since profile1 uses a NoRetryPolicy, UnavailableException should surface to client. - sessionRule.session().execute(QUERY.setConfigProfileName("profile1")); + sessionRule.session().execute(QUERY.setExecutionProfileName("profile1")); } @Test public void should_use_policy_from_config_when_not_configured_in_request_profile() { // since profile2 has no configured retry policy, it should defer to configuration which uses // DefaultRetryPolicy, which should try request on next host (host 1). - ResultSet result = sessionRule.session().execute(QUERY.setConfigProfileName("profile2")); + ResultSet result = sessionRule.session().execute(QUERY.setExecutionProfileName("profile2")); // expect an unavailable exception to be present in errors. List> errors = result.getExecutionInfo().getErrors(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 34dbeb4a148..900d66df588 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -80,7 +80,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v0 int, v1 int, PRIMARY KEY(k, v0))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 05b6dbbc873..74d50feb4a1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfig; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -245,7 +245,7 @@ public void should_use_policy_from_request_profile() { // Set large delay for default so we ensure profile is used. try (CqlSession session = buildSessionWithProfile(3, 100, 2, 0)) { - ResultSet resultSet = session.execute(QUERY.setConfigProfileName("profile1")); + ResultSet resultSet = session.execute(QUERY.setExecutionProfileName("profile1")); // Expect only 1 speculative execution as that is all profile called for. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); @@ -267,7 +267,7 @@ public void should_use_policy_from_request_profile_when_not_configured_in_config // Disable in primary configuration try (CqlSession session = buildSessionWithProfile(-1, -1, 3, 0)) { - ResultSet resultSet = session.execute(QUERY.setConfigProfileName("profile1")); + ResultSet resultSet = session.execute(QUERY.setExecutionProfileName("profile1")); // Expect speculative executions on each node. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); @@ -289,7 +289,7 @@ public void should_use_policy_from_config_when_not_configured_in_request_profile try (CqlSession session = buildSessionWithProfile(3, 0, 3, 0)) { // use profile where speculative execution is not configured. - ResultSet resultSet = session.execute(QUERY.setConfigProfileName("profile2")); + ResultSet resultSet = session.execute(QUERY.setExecutionProfileName("profile2")); // Expect speculative executions on each node since default configuration is used. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); @@ -311,7 +311,7 @@ public void should_not_speculatively_execute_when_defined_in_profile() { // Disable in profile try (CqlSession session = buildSessionWithProfile(3, 100, -1, -1)) { - ResultSet resultSet = session.execute(QUERY.setConfigProfileName("profile1")); + ResultSet resultSet = session.execute(QUERY.setExecutionProfileName("profile1")); // Expect no speculative executions. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); @@ -399,10 +399,10 @@ private CqlSession buildSessionWithProfile( assertThat(context.speculativeExecutionPolicies()) .hasSize(3) - .containsKeys(DriverConfigProfile.DEFAULT_NAME, "profile1", "profile2"); + .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); SpeculativeExecutionPolicy defaultPolicy = - context.speculativeExecutionPolicy(DriverConfigProfile.DEFAULT_NAME); + context.speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); SpeculativeExecutionPolicy policy1 = context.speculativeExecutionPolicy("profile1"); SpeculativeExecutionPolicy policy2 = context.speculativeExecutionPolicy("profile2"); Class expectedDefaultPolicyClass = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 0bfed655b62..59d8be31ddb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -217,7 +217,7 @@ public void should_log_failed_request_without_stack_trace() { try { sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withConfigProfileName("no-traces").build()); + .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("no-traces").build()); fail("Expected a ServerError"); } catch (ServerError error) { // expected @@ -239,7 +239,7 @@ public void should_log_slow_request() { // When sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withConfigProfileName("low-threshold").build()); + .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("low-threshold").build()); // Then Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -255,7 +255,7 @@ public void should_not_log_when_disabled() throws InterruptedException { // When sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withConfigProfileName("no-logs").build()); + .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("no-logs").build()); // Then // We expect no messages. The request logger is invoked asynchronously, so simply wait a bit diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java index 24b30a7e8c4..8c593fe5575 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.core.tracker; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Request; @@ -35,20 +35,23 @@ public void onNodeError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { - if (!configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED)) { + if (!executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED)) { return; } - int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); + int maxQueryLength = + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); + boolean showValues = executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); int maxValues = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + showValues ? executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; int maxValueLength = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + showValues + ? executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) + : 0; boolean showStackTraces = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES); + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES); logError( request, @@ -66,30 +69,36 @@ public void onNodeError( public void onNodeSuccess( @NonNull Request request, long latencyNanos, - @NonNull DriverConfigProfile configProfile, + @NonNull DriverExecutionProfile executionProfile, @NonNull Node node) { boolean successEnabled = - configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED); - boolean slowEnabled = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED); + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED); + boolean slowEnabled = + executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED); if (!successEnabled && !slowEnabled) { return; } long slowThresholdNanos = - configProfile.isDefined(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD) - ? configProfile.getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD).toNanos() + executionProfile.isDefined(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD) + ? executionProfile + .getDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD) + .toNanos() : Long.MAX_VALUE; boolean isSlow = latencyNanos > slowThresholdNanos; if ((isSlow && !slowEnabled) || (!isSlow && !successEnabled)) { return; } - int maxQueryLength = configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); - boolean showValues = configProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); + int maxQueryLength = + executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH); + boolean showValues = executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES); int maxValues = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; + showValues ? executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES) : 0; int maxValueLength = - showValues ? configProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) : 0; + showValues + ? executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH) + : 0; logSuccess( request, latencyNanos, isSlow, node, maxQueryLength, showValues, maxValues, maxValueLength); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index c139f035fbb..a540898a84a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -69,7 +69,7 @@ public static void createSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k text primary key, v int)") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); // table with map value sessionRule @@ -77,7 +77,7 @@ public static void createSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test2 (k0 text, k1 int, v map, primary key (k0, k1))") - .withConfigProfile(sessionRule.slowProfile()) + .withExecutionProfile(sessionRule.slowProfile()) .build()); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java index bab37da931f..1a9c2a6d061 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.example.guava.internal; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; @@ -40,12 +40,12 @@ public int getKey() { } @Override - public String getConfigProfileName() { + public String getExecutionProfileName() { return null; } @Override - public DriverConfigProfile getConfigProfile() { + public DriverExecutionProfile getExecutionProfile() { return null; } diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 0a20659c02e..cdf440e59a0 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -16,14 +16,14 @@ For a complete list of built-in options, see the [reference configuration][refer Essentially, an option is a path in the configuration with an expected type, for example `basic.request.timeout`, representing a duration. -#### Profiles +#### Execution profiles Imagine an application that does both transactional and analytical requests. Transactional requests are simpler and must return quickly, so they will typically use a short timeout, let's say 100 milliseconds; analytical requests are more complex and less frequent so a higher SLA is acceptable, for example 5 seconds. In addition, maybe you want to use a different consistency level. -Instead of manually adjusting the options on every request, you can create configuration profiles: +Instead of manually adjusting the options on every request, you can create execution profiles: ``` datastax-java-driver { @@ -44,7 +44,7 @@ Now each request only needs a profile name: ```java SimpleStatement s = SimpleStatement.builder("SELECT name FROM user WHERE id = 1") - .withConfigProfileName("oltp") + .withExecutionProfileName("oltp") .build(); session.execute(s); ``` @@ -133,14 +133,13 @@ The driver's context exposes a [DriverConfig] instance: ```java DriverConfig config = session.getContext().config(); -DriverConfigProfile defaultProfile = config.getDefaultProfile(); -DriverConfigProfile olapProfile = config.getProfile("olap"); +DriverExecutionProfile defaultProfile = config.getDefaultProfile(); +DriverExecutionProfile olapProfile = config.getProfile("olap"); -// This method creates a defensive copy of the map, do not use in performance-sensitive code: config.getProfiles().forEach((name, profile) -> ...); ``` -[DriverConfigProfile] has typed option getters: +[DriverExecutionProfile] has typed option getters: ```java Duration requestTimeout = defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); @@ -153,7 +152,7 @@ more generic [DriverOption] interface. This is intended to allow custom options, #### Derived profiles -Configuration profiles are hard-coded in the configuration, and can't be changed at runtime (except +Execution profiles are hard-coded in the configuration, and can't be changed at runtime (except by modifying and reloading the files). What if you want to adjust an option for a single request, without having a dedicated profile for it? @@ -161,8 +160,8 @@ To allow this, you start from an existing profile in the configuration and build that overrides a subset of options: ```java -DriverConfigProfile defaultProfile = session.getContext().config().getDefaultProfile(); -DriverConfigProfile dynamicProfile = +DriverExecutionProfile defaultProfile = session.getContext().config().getDefaultProfile(); +DriverExecutionProfile dynamicProfile = defaultProfile.withString( DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.EACH_QUORUM.name()); SimpleStatement s = @@ -280,16 +279,17 @@ CqlSession session = CqlSession.builder().withConfigLoader(loader).build(); If Typesafe Config doesn't work for you, it is possible to get rid of it entirely. -You will need to provide your own implementations of [DriverConfig] and [DriverConfigProfile]. Then -write a [DriverConfigLoader] and pass it to the session at initialization, as shown in the previous -sections. Study the built-in implementation (package +You will need to provide your own implementations of [DriverConfig] and [DriverExecutionProfile]. +Then write a [DriverConfigLoader] and pass it to the session at initialization, as shown in the +previous sections. Study the built-in implementation (package `com.datastax.oss.driver.internal.core.config.typesafe`) for reference. Reloading is not mandatory: you can choose not to implement it, and the driver will simply keep using the initial configuration. -Note that the option getters (`DriverConfigProfile.getInt` and similar) are invoked very frequently -on the hot code path; if your implementation is slow, consider caching the results between reloads. +Note that the option getters (`DriverExecutionProfile.getInt` and similar) are invoked very +frequently on the hot code path; if your implementation is slow, consider caching the results +between reloads. #### Configuration change events @@ -433,12 +433,12 @@ config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL); config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); ``` -[DriverConfig]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html -[DriverConfigProfile]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigProfile.html -[DriverContext]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/context/DriverContext.html -[DriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html -[DefaultDriverOption]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html -[DriverConfigLoader]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html +[DriverConfig]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html +[DriverExecutionProfile]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.html +[DriverContext]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/context/DriverContext.html +[DriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html +[DefaultDriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html +[DriverConfigLoader]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html [Typesafe Config]: https://github.com/typesafehub/config [config standard behavior]: https://github.com/typesafehub/config#standard-behavior diff --git a/manual/core/load_balancing/README.md b/manual/core/load_balancing/README.md index b44bfe69eb1..4bf1aad1b20 100644 --- a/manual/core/load_balancing/README.md +++ b/manual/core/load_balancing/README.md @@ -228,7 +228,7 @@ Study the [LoadBalancingPolicy] interface and the default implementation for the ### Using multiple policies -The load balancing policy can be overridden in [configuration profiles](../configuration/#profiles): +The load balancing policy can be overridden in [execution profiles](../configuration/#profiles): ``` datastax-java-driver { diff --git a/manual/core/query_timestamps/README.md b/manual/core/query_timestamps/README.md index 6feb988cba6..6c751e72811 100644 --- a/manual/core/query_timestamps/README.md +++ b/manual/core/query_timestamps/README.md @@ -116,7 +116,7 @@ implementation class from the configuration. #### Using multiple generators -The timestamp generator can be overridden in [configuration profiles](../configuration/#profiles): +The timestamp generator can be overridden in [execution profiles](../configuration/#profiles): ``` datastax-java-driver { diff --git a/manual/core/retries/README.md b/manual/core/retries/README.md index d4941474f7c..bc19986cfbf 100644 --- a/manual/core/retries/README.md +++ b/manual/core/retries/README.md @@ -135,7 +135,7 @@ directly to the user. These include: ### Using multiple policies -The retry policy can be overridden in [configuration profiles](../configuration/#profiles): +The retry policy can be overridden in [execution profiles](../configuration/#profiles): ``` datastax-java-driver { diff --git a/manual/core/speculative_execution/README.md b/manual/core/speculative_execution/README.md index b56522a0843..e50407eb1fd 100644 --- a/manual/core/speculative_execution/README.md +++ b/manual/core/speculative_execution/README.md @@ -208,7 +208,7 @@ Or use a client-side [timestamp generator](../query_timestamps/). ### Using multiple policies -The speculative execution policy can be overridden in [configuration +The speculative execution policy can be overridden in [execution profiles](../configuration/#profiles): ``` diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java index c927d5c1359..878118525e8 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.NoNodeAvailableException; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; @@ -64,7 +64,7 @@ public class SessionRule extends ExternalResource { // the session that is auto created for this rule and is tied to the given keyspace. private SessionT session; - private DriverConfigProfile slowProfile; + private DriverExecutionProfile slowProfile; /** * Returns a builder to construct an instance with a fluent API. @@ -143,7 +143,7 @@ public CqlIdentifier keyspace() { } /** @return a config profile where the request timeout is 30 seconds. * */ - public DriverConfigProfile slowProfile() { + public DriverExecutionProfile slowProfile() { return slowProfile; } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index e7eed162d75..0e1d4a3c500 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigProfile; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; @@ -141,19 +141,19 @@ public static CqlIdentifier uniqueKeyspaceId() { /** Creates a keyspace through the given session instance, with the given profile. */ public static void createKeyspace( - Session session, CqlIdentifier keyspace, DriverConfigProfile profile) { + Session session, CqlIdentifier keyspace, DriverExecutionProfile profile) { SimpleStatement createKeyspace = SimpleStatement.builder( String.format( "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", keyspace.asCql(false))) - .withConfigProfile(profile) + .withExecutionProfile(profile) .build(); session.execute(createKeyspace, Statement.SYNC); } /** - * Calls {@link #createKeyspace(Session, CqlIdentifier, DriverConfigProfile)} with {@link + * Calls {@link #createKeyspace(Session, CqlIdentifier, DriverExecutionProfile)} with {@link * #slowProfile(Session)} as the third argument. * *

              Note that this creates a derived profile for each invocation, which has a slight performance @@ -166,16 +166,16 @@ public static void createKeyspace(Session session, CqlIdentifier keyspace) { /** Drops a keyspace through the given session instance, with the given profile. */ public static void dropKeyspace( - Session session, CqlIdentifier keyspace, DriverConfigProfile profile) { + Session session, CqlIdentifier keyspace, DriverExecutionProfile profile) { session.execute( SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql(false))) - .withConfigProfile(profile) + .withExecutionProfile(profile) .build(), Statement.SYNC); } /** - * Calls {@link #dropKeyspace(Session, CqlIdentifier, DriverConfigProfile)} with {@link + * Calls {@link #dropKeyspace(Session, CqlIdentifier, DriverExecutionProfile)} with {@link * #slowProfile(Session)} as the third argument. * *

              Note that this creates a derived profile for each invocation, which has a slight performance @@ -190,7 +190,7 @@ public static void dropKeyspace(Session session, CqlIdentifier keyspace) { * Builds a profile derived from the given cluster's default profile, with a higher request * timeout (30 seconds) that is appropriate for DML operations. */ - public static DriverConfigProfile slowProfile(Session session) { + public static DriverExecutionProfile slowProfile(Session session) { return session .getContext() .config() From 318122dad26dc21eb023132f0a4fe7ca9f063759 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 12 Jul 2018 08:42:08 -0700 Subject: [PATCH 517/742] Move RequestLoggerIT to serial tests --- .../oss/driver/api/core/tracker/RequestLoggerIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 59d8be31ddb..c0de346e019 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -15,7 +15,10 @@ */ package com.datastax.oss.driver.api.core.tracker; -import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.*; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.rows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.serverError; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.unavailable; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -33,7 +36,6 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; -import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.codec.ConsistencyLevel; @@ -43,7 +45,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -54,7 +55,6 @@ import org.mockito.verification.Timeout; import org.slf4j.LoggerFactory; -@Category(ParallelizableTests.class) @RunWith(MockitoJUnitRunner.class) public class RequestLoggerIT { From 6b04c21b0bdb7e1a6fd731fef8766e3b0a20edaa Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 12 Jul 2018 09:02:05 -0700 Subject: [PATCH 518/742] Upgrade Netty to 4.1.27 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2cad3156083..51ad7aaad82 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ 2.1.10 4.0.2 1.4.3-SNAPSHOT - 4.1.26.Final + 4.1.27.Final 1.7.25 1.1.7.2 From 292bf8ff4d1036f41a6b7f6a2aee3f0d928548e6 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 12 Jul 2018 16:08:47 -0500 Subject: [PATCH 519/742] Fix unset and custom payload assumptions in BoundStatementIT * CassandraRequirement does not work at method level when using CcmRule with ClassRule annotation. * Don't add Custom Payload to Statement when V4 is not used. --- .../driver/api/core/cql/BoundStatementIT.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index f843f4839a2..065e9bdb31b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -19,6 +19,7 @@ import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.query; import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -29,7 +30,6 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; -import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -71,6 +71,8 @@ public class BoundStatementIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; + @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm, "basic.request.page-size = 20"); @@ -131,8 +133,8 @@ public void should_not_allow_unset_value_when_protocol_less_than_v4() { } @Test - @CassandraRequirement(min = "2.2") public void should_not_write_tombstone_if_value_is_implicitly_unset() { + assumeThat(atLeastV4).as("unset values require protocol V4+").isTrue(); try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); @@ -146,8 +148,8 @@ public void should_not_write_tombstone_if_value_is_implicitly_unset() { } @Test - @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset() { + assumeThat(atLeastV4).as("unset values require protocol V4+").isTrue(); try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); @@ -165,8 +167,8 @@ public void should_write_tombstone_if_value_is_explicitly_unset() { } @Test - @CassandraRequirement(min = "2.2") public void should_write_tombstone_if_value_is_explicitly_unset_on_builder() { + assumeThat(atLeastV4).as("unset values require protocol V4+").isTrue(); try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { PreparedStatement prepared = session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); @@ -379,7 +381,6 @@ public void should_use_timeout() { } @Test - @CassandraRequirement(min = "2.2") public void should_propagate_attributes_when_preparing_a_simple_statement() { CqlSession session = sessionRule.session(); @@ -404,7 +405,7 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { ConsistencyLevel mockSerialCl = DefaultConsistencyLevel.LOCAL_SERIAL; int mockPageSize = 2000; - SimpleStatement simpleStatement = + SimpleStatementBuilder simpleStatementBuilder = SimpleStatement.builder("SELECT release_version FROM system.local") .withExecutionProfile(mockProfile) .withExecutionProfileName(mockConfigProfileName) @@ -413,16 +414,20 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { .withRoutingKeyspace(mockRoutingKeyspace) .withRoutingKey(mockRoutingKey) .withRoutingToken(mockRoutingToken) - .addCustomPayload("key1", mockCustomPayload.get("key1")) .withTimestamp(42) .withIdempotence(true) .withTracing() .withTimeout(mockTimeout) .withConsistencyLevel(mockCl) .withSerialConsistencyLevel(mockSerialCl) - .withPageSize(mockPageSize) - .build(); - PreparedStatement preparedStatement = session.prepare(simpleStatement); + .withPageSize(mockPageSize); + + if (atLeastV4) { + simpleStatementBuilder = + simpleStatementBuilder.addCustomPayload("key1", mockCustomPayload.get("key1")); + } + + PreparedStatement preparedStatement = session.prepare(simpleStatementBuilder.build()); // Cover all the ways to create bound statements: ImmutableList> createMethods = @@ -438,7 +443,9 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { .isEqualTo(mockKeyspace != null ? mockKeyspace : mockRoutingKeyspace); assertThat(boundStatement.getRoutingKey()).isEqualTo(mockRoutingKey); assertThat(boundStatement.getRoutingToken()).isEqualTo(mockRoutingToken); - assertThat(boundStatement.getCustomPayload()).isEqualTo(mockCustomPayload); + if (atLeastV4) { + assertThat(boundStatement.getCustomPayload()).isEqualTo(mockCustomPayload); + } assertThat(boundStatement.isIdempotent()).isTrue(); assertThat(boundStatement.isTracing()).isTrue(); assertThat(boundStatement.getTimeout()).isEqualTo(mockTimeout); From cf767798e8c04d27556c22f57fb11ce1ea1deca1 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 12 Jul 2018 17:46:23 -0500 Subject: [PATCH 520/742] Use new image in jenkins --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 84b090fd579..bed14768a76 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ java: - openjdk8 os: - - ubuntu/trusty64/m3.large + - ubuntu/bionic64/java-driver cassandra: - '2.1' - '2.2' From 80fb1b663dd4fd0335a4d40ef8c6b9b375208350 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 12 Jul 2018 18:09:11 -0500 Subject: [PATCH 521/742] Remove native-protocol pull from build.yaml, slack notifier --- build.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build.yaml b/build.yaml index bed14768a76..dcafd823f29 100644 --- a/build.yaml +++ b/build.yaml @@ -8,12 +8,6 @@ cassandra: - '3.0' - '3.11' build: - - script: | - git clone --depth 1 https://github.com/datastax/native-protocol.git - pushd native-protocol - mvn install -Dmaven.javadoc.skip=true -B -V - popd - rm -rf native-protocol - type: maven version: 3.2.5 goals: verify @@ -24,3 +18,5 @@ build: - "**/target/failsafe-reports/TEST-*.xml" - jacoco: true disable_commit_status: true +notify: + slack: java-driver-dev-bots From c23ce0eabf0d282791c157d82b28b449808fb3d3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 9 Jul 2018 14:41:22 -0700 Subject: [PATCH 522/742] JAVA-1913: Expose additional counters on Node --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 1 + .../oss/driver/api/core/metadata/Node.java | 24 +++++++ .../core/cql/CqlPrepareHandlerBase.java | 7 ++ .../core/cql/CqlRequestHandlerBase.java | 9 ++- .../internal/core/metadata/DefaultNode.java | 21 +++++- .../core/metadata/NodeStateManager.java | 5 ++ core/src/main/resources/reference.conf | 14 ++++ .../api/core/metadata/NodeMetadataIT.java | 65 +++++++++++++++++-- 9 files changed, 141 insertions(+), 6 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 61564ec1df1..c11e1494585 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [improvement] JAVA-1913: Expose additional counters on Node - [improvement] JAVA-1880: Rename "config profile" to "execution profile" - [improvement] JAVA-1889: Upgrade dependencies to the latest minor versions - [improvement] JAVA-1819: Propagate more attributes to bound statements diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 95a8f7b290c..30838a91955 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -139,6 +139,7 @@ public enum DefaultDriverOption implements DriverOption { METADATA_SCHEMA_WINDOW("advanced.metadata.schema.debouncer.window"), METADATA_SCHEMA_MAX_EVENTS("advanced.metadata.schema.debouncer.max-events"), METADATA_TOKEN_MAP_ENABLED("advanced.metadata.token-map.enabled"), + METADATA_LAST_RESPONSE_TIME_ENABLED("advanced.metadata.nodes.last-response-time.enabled"), CONTROL_CONNECTION_TIMEOUT("advanced.control-connection.timeout"), CONTROL_CONNECTION_AGREEMENT_INTERVAL("advanced.control-connection.schema-agreement.interval"), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index 5fb634f54f1..d39339ed306 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import edu.umd.cs.findbugs.annotations.NonNull; @@ -108,6 +109,12 @@ public interface Node { @NonNull NodeState getState(); + /** + * The last time that this node transitioned to the UP state, in milliseconds since the epoch, or + * -1 if it's not up at the moment. + */ + long getUpSinceMillis(); + /** * The total number of active connections currently open by this driver instance to the node. This * can be either pooled connections, or the control connection. @@ -149,4 +156,21 @@ public interface Node { */ @Nullable UUID getSchemaVersion(); + + /** + * The last time that the driver received a response from this node, or -1 if it has not responded + * yet. + * + *

              This is recorded with {@link System#nanoTime()}, and should only be used to measure elapsed + * time. + * + *

              Note that this feature is controlled by a configuration option ({@code + * advanced.metadata.nodes.last-response-time.enabled}) and disabled by default (i.e. will + * always return -1). You might want to enable it for information purposes, or for use in custom + * policy implementations. It incurs a bit of overhead (namely, a volatile write for every + * request). + * + * @see DefaultDriverOption#METADATA_LAST_RESPONSE_TIME_ENABLED + */ + long getLastResponseTimeNanos(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 7f944fd1377..bd64364be1e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -42,6 +42,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -96,6 +97,7 @@ public abstract class CqlPrepareHandlerBase implements Throttled { private final RequestThrottler throttler; private final Boolean prepareOnAllNodes; private volatile InitialPrepareCallback initialCallback; + private final boolean recordLastResponseTime; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -127,6 +129,8 @@ protected CqlPrepareHandlerBase( ? config.getDefaultProfile() : config.getProfile(profileName); } + this.recordLastResponseTime = + executionProfile.getBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, false); this.queryPlan = context .loadBalancingPolicyWrapper() @@ -403,6 +407,9 @@ public void operationComplete(Future future) { @Override public void onResponse(Frame responseFrame) { + if (recordLastResponseTime) { + ((DefaultNode) node).setLastResponseTimeNanos(System.nanoTime()); + } if (result.isDone()) { return; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index db450bfa6f3..9ccafced206 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -123,6 +123,7 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; private final RequestThrottler throttler; + private final boolean recordLastResponseTime; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -165,6 +166,8 @@ protected CqlRequestHandlerBase( (statementIsIdempotent == null) ? executionProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : statementIsIdempotent; + this.recordLastResponseTime = + executionProfile.getBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, false); this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -496,12 +499,16 @@ public void operationComplete(Future future) throws Exception { @Override public void onResponse(Frame responseFrame) { + long now = System.nanoTime(); + if (recordLastResponseTime) { + ((DefaultNode) node).setLastResponseTimeNanos(now); + } ((DefaultNode) node) .getMetricUpdater() .updateTimer( DefaultNodeMetric.CQL_MESSAGES, executionProfile.getName(), - System.nanoTime() - start, + now - start, TimeUnit.NANOSECONDS); inFlightCallbacks.remove(this); if (result.isDone()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 3fa282f84b9..9f430b7299b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -51,13 +51,16 @@ public class DefaultNode implements Node { volatile UUID hostId; volatile UUID schemaVersion; - // These 3 fields are read concurrently, but only mutated on NodeStateManager's admin thread + // These 4 fields are read concurrently, but only mutated on NodeStateManager's admin thread volatile NodeState state; volatile int openConnections; volatile int reconnections; + volatile long upSinceMillis; volatile NodeDistance distance; + private volatile long lastResponseTimeNanos; + public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext context) { this.connectAddress = connectAddress; this.state = NodeState.UNKNOWN; @@ -67,6 +70,8 @@ public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext conte // We leak a reference to a partially constructed object (this), but in practice this won't be a // problem because the node updater only needs the connect address to initialize. this.metricUpdater = context.metricsFactory().newNodeUpdater(this); + this.upSinceMillis = -1; + this.lastResponseTimeNanos = -1; } @NonNull @@ -129,6 +134,11 @@ public NodeState getState() { return state; } + @Override + public long getUpSinceMillis() { + return upSinceMillis; + } + @Override public int getOpenConnections() { return openConnections; @@ -149,6 +159,15 @@ public NodeMetricUpdater getMetricUpdater() { return metricUpdater; } + @Override + public long getLastResponseTimeNanos() { + return lastResponseTimeNanos; + } + + public void setLastResponseTimeNanos(long lastResponseTimeNanos) { + this.lastResponseTimeNanos = lastResponseTimeNanos; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index eaebdf8d3bf..f498c68e3b0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -301,6 +301,11 @@ private void setState(DefaultNode node, NodeState newState, String reason) { newState, reason); node.state = newState; + if (newState == NodeState.UP) { + node.upSinceMillis = System.currentTimeMillis(); + } else { + node.upSinceMillis = -1; + } // Fire the state change event, either immediately, or after a refresh if the node just came // back up. // If oldState == UNKNOWN, the node was just added, we already refreshed while processing diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index b7e529a1227..31f6093bffb 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -1174,6 +1174,20 @@ datastax-java-driver { # Modifiable at runtime: yes, the new value will be used for refreshes issued after the change. # Overridable in a profile: no token-map.enabled = true + + nodes { + # Whether to record the last time that the driver received a response from each node. + # + # If enabled, the driver will track the time that each node replies as part of a + # session.execute() or session.prepare() call, and store this information in + # Node.getLastResponseTimeNanos(). + # If disabled, Node.getLastResponseTimeNanos() will always return -1; + # + # Required: no (will default to false if absent) + # Modifiable at runtime: yes, the new value will be used for requests issued after the change. + # Overridable in a profile: yes (only the requests in this profile will update the field) + last-response-time.enabled = false + } } advanced.control-connection { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index e4012740074..5b1be5dd741 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -21,7 +21,12 @@ import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.context.EventBus; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import java.util.Collection; import org.junit.ClassRule; import org.junit.Rule; @@ -37,10 +42,8 @@ public class NodeMetadataIT { @Test public void should_expose_node_metadata() { - Collection values = sessionRule.session().getMetadata().getNodes().values(); - assertThat(values).hasSize(1); - - Node node = values.iterator().next(); + CqlSession session = sessionRule.session(); + Node node = getUniqueNode(session); // Run a few basic checks given what we know about our test environment: assertThat(node.getConnectAddress()).isNotNull(); @@ -58,7 +61,61 @@ public void should_expose_node_metadata() { assertThat(node.getDistance()).isSameAs(NodeDistance.LOCAL); assertThat(node.getHostId()).isNotNull(); assertThat(node.getSchemaVersion()).isNotNull(); + long upTime1 = node.getUpSinceMillis(); + assertThat(upTime1).isGreaterThan(-1); + assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); // Note: open connections and reconnection status are covered in NodeStateIT + + // Force the node down and back up to check that upSinceMillis gets updated + EventBus eventBus = ((InternalDriverContext) session.getContext()).eventBus(); + eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); + ConditionChecker.checkThat(() -> node.getState() == NodeState.FORCED_DOWN).becomesTrue(); + assertThat(node.getUpSinceMillis()).isEqualTo(-1); + eventBus.fire(TopologyEvent.forceUp(node.getConnectAddress())); + ConditionChecker.checkThat(() -> node.getState() == NodeState.UP).becomesTrue(); + assertThat(node.getUpSinceMillis()).isGreaterThan(upTime1); + } + + @Test + public void should_not_record_last_response_time_if_disabled() { + CqlSession session = sessionRule.session(); + Node node = getUniqueNode(session); + assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); + + for (int i = 0; i < 10; i++) { + session.execute("SELECT release_version FROM system.local"); + assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); + } + } + + @Test + public void should_record_last_response_time_if_enabled() { + CqlSession session = + SessionUtils.newSession( + ccmRule, "advanced.metadata.nodes.last-response-time.enabled = true"); + Node node = getUniqueNode(session); + + // Ensure that we get increasing timestamps as long as we keep querying + long[] timestamps = new long[10]; + timestamps[0] = System.nanoTime(); + for (int i = 1; i < 9; i++) { + session.execute("SELECT release_version FROM system.local"); + timestamps[i] = node.getLastResponseTimeNanos(); + } + timestamps[9] = System.nanoTime(); + + for (int i = 0; i < 9; i++) { + assertThat(timestamps[i]).isLessThan(timestamps[i + 1]); + } + + // Ensure that the value doesn't change since the last query + assertThat(node.getLastResponseTimeNanos()).isEqualTo(timestamps[8]); + } + + private static Node getUniqueNode(CqlSession session) { + Collection nodes = session.getMetadata().getNodes().values(); + assertThat(nodes).hasSize(1); + return nodes.iterator().next(); } } From 09115d7201e0f19db05eb17a94c42042150a1446 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 13 Jul 2018 17:09:01 -0500 Subject: [PATCH 523/742] JAVA-1869: Add DefaultDriverConfigLoaderBuilder (#1032) Motivation: To enable a convenient way to configure a Driver Session programmatically. Modifications: Added DefaultDriverConfigLoaderBuilder to enable passing in configuration programmatically. Exposed via DefaultDriverConfigLoader.builder(). Added class-based DriverOption back to they may be configured. Updated SessionUtils.configLoaderBuilder to provide a way of constructing a Session programatically easily in integration tests. Result: DefaultDriverConfigLoader.builder() may be used to construct a DriverConfigLoader which may be passed in via SessionBuilder.withConfigLoader. --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 3 + .../config/DriverOptionConfigBuilder.java | 152 ++++++++++++++++++ .../typesafe/DefaultDriverConfigLoader.java | 21 +++ .../DefaultDriverConfigLoaderBuilder.java | 123 ++++++++++++++ .../oss/driver/api/core/ConnectIT.java | 25 +-- .../driver/api/core/ConnectKeyspaceIT.java | 13 +- .../ProtocolVersionInitialNegotiationIT.java | 14 +- .../core/ProtocolVersionMixedClusterIT.java | 55 ++++--- .../core/auth/PlainTextAuthProviderIT.java | 29 ++-- .../core/compression/DirectCompressionIT.java | 21 ++- .../core/compression/HeapCompressionIT.java | 21 ++- .../core/config/DriverConfigValidationIT.java | 35 ++-- .../core/config/DriverExecutionProfileIT.java | 55 +++++-- .../DriverExecutionProfileReloadIT.java | 37 +++-- .../connection/ChannelSocketOptionsIT.java | 29 ++-- .../api/core/connection/FrameLengthIT.java | 18 ++- .../driver/api/core/cql/AsyncResultSetIT.java | 9 +- .../driver/api/core/cql/BatchStatementIT.java | 11 +- .../driver/api/core/cql/BoundStatementIT.java | 15 +- .../api/core/cql/PerRequestKeyspaceIT.java | 10 +- .../cql/PreparedStatementInvalidationIT.java | 24 ++- .../oss/driver/api/core/cql/QueryTraceIT.java | 5 +- .../api/core/cql/SimpleStatementIT.java | 9 +- .../oss/driver/api/core/data/DataTypeIT.java | 2 +- .../core/heartbeat/HeartbeatDisabledIT.java | 10 +- .../api/core/heartbeat/HeartbeatIT.java | 43 ++--- .../DefaultLoadBalancingPolicyIT.java | 7 +- .../PerProfileLoadBalancingPolicyIT.java | 21 ++- .../api/core/metadata/ByteOrderedTokenIT.java | 11 +- .../metadata/ByteOrderedTokenVnodesIT.java | 11 +- .../driver/api/core/metadata/DescribeIT.java | 19 ++- .../api/core/metadata/Murmur3TokenIT.java | 11 +- .../core/metadata/Murmur3TokenVnodesIT.java | 11 +- .../api/core/metadata/NodeMetadataIT.java | 43 ++--- .../driver/api/core/metadata/NodeStateIT.java | 96 ++++++----- .../api/core/metadata/RandomTokenIT.java | 11 +- .../core/metadata/RandomTokenVnodesIT.java | 11 +- .../api/core/metadata/SchemaAgreementIT.java | 31 ++-- .../api/core/metadata/SchemaChangesIT.java | 118 +++++++------- .../driver/api/core/metadata/SchemaIT.java | 39 +++-- .../driver/api/core/metrics/MetricsIT.java | 39 +++-- .../core/retry/PerProfileRetryPolicyIT.java | 26 ++- .../driver/api/core/session/ExceptionIT.java | 14 +- .../api/core/session/RequestProcessorIT.java | 6 +- .../core/specex/SpeculativeExecutionIT.java | 105 +++++++----- ...tSslEngineFactoryHostnameValidationIT.java | 24 +-- .../core/ssl/DefaultSslEngineFactoryIT.java | 55 ++++--- ...efaultSslEngineFactoryPropertyBasedIT.java | 11 +- ...eFactoryPropertyBasedWithClientAuthIT.java | 14 +- ...faultSslEngineFactoryWithClientAuthIT.java | 55 ++++--- .../api/core/throttling/ThrottlingIT.java | 21 ++- .../api/core/tracker/RequestLoggerIT.java | 108 ++++++++----- .../type/codec/registry/CodecRegistryIT.java | 25 +-- .../core/retry/DefaultRetryPolicyIT.java | 17 +- .../datastax/oss/driver/osgi/OsgiBaseIT.java | 10 +- .../osgi/OsgiCustomLoadBalancingPolicyIT.java | 13 +- .../com/datastax/oss/driver/osgi/OsgiIT.java | 5 - .../datastax/oss/driver/osgi/OsgiLz4IT.java | 9 +- .../oss/driver/osgi/OsgiShadedIT.java | 5 - .../oss/driver/osgi/OsgiSnappyIT.java | 9 +- .../src/test/resources/application.conf | 9 ++ manual/core/configuration/README.md | 55 +++++-- .../session/CqlSessionRuleBuilder.java | 2 +- .../DefaultSessionBuilderInstantiator.java | 29 ---- .../api/testinfra/session/SessionRule.java | 16 +- .../testinfra/session/SessionRuleBuilder.java | 7 +- .../api/testinfra/session/SessionUtils.java | 83 +++++++--- .../testinfra/session/TestConfigLoader.java | 45 ------ 69 files changed, 1395 insertions(+), 652 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderBuilder.java delete mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java delete mode 100644 test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java diff --git a/changelog/README.md b/changelog/README.md index c11e1494585..ce742ba068e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta1 (in progress) +- [new feature] JAVA-1869: Add DefaultDriverConfigLoaderBuilder - [improvement] JAVA-1913: Expose additional counters on Node - [improvement] JAVA-1880: Rename "config profile" to "execution profile" - [improvement] JAVA-1889: Upgrade dependencies to the latest minor versions diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 30838a91955..21c43c5bc53 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -35,6 +35,7 @@ public enum DefaultDriverOption implements DriverOption { REQUEST_DEFAULT_IDEMPOTENCE("basic.request.default-idempotence"), LOAD_BALANCING_POLICY("basic.load-balancing-policy"), + LOAD_BALANCING_POLICY_CLASS("basic.load-balancing-policy.class"), LOAD_BALANCING_LOCAL_DATACENTER("basic.load-balancing-policy.local-datacenter"), LOAD_BALANCING_FILTER_CLASS("basic.load-balancing-policy.filter.class"), @@ -53,8 +54,10 @@ public enum DefaultDriverOption implements DriverOption { RECONNECTION_MAX_DELAY("advanced.reconnection-policy.max-delay"), RETRY_POLICY("advanced.retry-policy"), + RETRY_POLICY_CLASS("advanced.retry-policy.class"), SPECULATIVE_EXECUTION_POLICY("advanced.speculative-execution-policy"), + SPECULATIVE_EXECUTION_POLICY_CLASS("advanced.speculative-execution-policy.class"), SPECULATIVE_EXECUTION_MAX("advanced.speculative-execution-policy.max-executions"), SPECULATIVE_EXECUTION_DELAY("advanced.speculative-execution-policy.delay"), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java new file mode 100644 index 00000000000..cc6c8cbe901 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config; + +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +/** + * A builder that allows specifying multiple configuration options keyed by {@link DriverOption}. + * + *

              The intent of this interface is to reduce code duplication by sharing common {@code withXXX} + * methods among types that build configuration. It is not meant to be used to represent anything + * specifically. + * + *

              The {@code withXXX} methods are similar to those found in {@link DriverExecutionProfile}, but + * do not share an interface as this interface is intended for internal implementations only. + * + *

              It is not recommended to create implementations of this interface or use directly in code. + */ +public interface DriverOptionConfigBuilder { + + @NonNull + default SelfT withBoolean(@NonNull DriverOption option, boolean value) { + return with(option, value); + } + + @NonNull + default SelfT withBooleanList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withInt(@NonNull DriverOption option, int value) { + return with(option, value); + } + + @NonNull + default SelfT withIntList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withLong(@NonNull DriverOption option, long value) { + return with(option, value); + } + + @NonNull + default SelfT withLongList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withDouble(@NonNull DriverOption option, double value) { + return with(option, value); + } + + @NonNull + default SelfT withDoubleList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withString(@NonNull DriverOption option, @NonNull String value) { + return with(option, value); + } + + @NonNull + default SelfT withStringList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @SuppressWarnings("unchecked") + @NonNull + default SelfT withStringMap(@NonNull DriverOption option, @NonNull Map value) { + SelfT v = (SelfT) this; + for (String key : value.keySet()) { + v = (SelfT) v.with(option.getPath() + "." + key, value.get(key)); + } + return v; + } + + /** + * Specifies a size in bytes. This is separate from {@link #withLong(DriverOption, long)}, in case + * implementations want to allow users to provide sizes in a more human-readable way, for example + * "256 MB". + */ + @NonNull + default SelfT withBytes(@NonNull DriverOption option, @NonNull String value) { + return with(option, value); + } + + @NonNull + default SelfT withBytes(@NonNull DriverOption option, long value) { + return with(option, value); + } + + @NonNull + default SelfT withBytesList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withDuration(@NonNull DriverOption option, @NonNull Duration value) { + return with(option, value); + } + + @NonNull + default SelfT withDurationList(@NonNull DriverOption option, @NonNull List value) { + return with(option, value); + } + + @NonNull + default SelfT withClass(@NonNull DriverOption option, @NonNull Class value) { + return with(option, value.getName()); + } + + /** Unsets an option. */ + @NonNull + default SelfT without(@NonNull DriverOption option) { + return with(option, null); + } + + @NonNull + default SelfT with(@NonNull DriverOption option, @Nullable Object value) { + return with(option.getPath(), value); + } + + /** + * Provides a simple path to value mapping, all default methods invoke this method directly. It is + * not recommended that it is used directly other than by these defaults. + */ + @NonNull + SelfT with(@NonNull String path, @Nullable Object value); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index 4b04c785969..d78745e7ceb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -91,6 +91,27 @@ public void close() { } } + /** + * Constructs a builder that may be used to provide additional configuration beyond those defined + * in your configuration files programmatically. For example: + * + *

              {@code
              +   * CqlSession session = CqlSession.builder()
              +   *   .withConfigLoader(DefaultDriverConfigLoader.builder()
              +   *     .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(500))
              +   *     .build())
              +   *   .build();
              +   * }
              + * + *

              In the general case, use of this is not recommended, but it may be useful in situations + * where configuration must be defined at runtime or is derived from some other configuration + * source. + */ + @NonNull + public static DefaultDriverConfigLoaderBuilder builder() { + return new DefaultDriverConfigLoaderBuilder(); + } + private class SingleThreaded { private final String logPrefix; private final EventExecutor adminExecutor; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderBuilder.java new file mode 100644 index 00000000000..58b33c13cd9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderBuilder.java @@ -0,0 +1,123 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.config.typesafe; + +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.internal.core.config.DriverOptionConfigBuilder; +import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Map; +import net.jcip.annotations.NotThreadSafe; + +/** + * Provides a mechanism for constructing a {@link DriverConfigLoader} programmatically that uses + * {@link com.datastax.oss.driver.api.core.CqlSession}'s default config loader with the values of + * {@code withXXX(...)} methods overriding the configuration defined in configuration files. + * + *

              The built {@link DriverConfigLoader} provided by {@link #build()} can be passed to {@link + * com.datastax.oss.driver.api.core.session.SessionBuilder#withConfigLoader(DriverConfigLoader)}. + */ +@NotThreadSafe +public class DefaultDriverConfigLoaderBuilder + implements DriverOptionConfigBuilder { + + private NullAllowingImmutableMap.Builder values = + NullAllowingImmutableMap.builder(); + + /** + * @return a new {@link ProfileBuilder} to provide programmatic configuration at a profile level. + * @see #withProfile(String, Profile) + */ + @NonNull + public static ProfileBuilder profileBuilder() { + return new ProfileBuilder(); + } + + /** Adds configuration for a profile constructed using {@link #profileBuilder()} by name. */ + @NonNull + public DefaultDriverConfigLoaderBuilder withProfile( + @NonNull String profileName, @NonNull Profile profile) { + String prefix = "profiles." + profileName + "."; + for (Map.Entry entry : profile.values.entrySet()) { + this.with(prefix + entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * @return constructed {@link DriverConfigLoader} using the configuration passed into this + * builder. + */ + @NonNull + public DriverConfigLoader build() { + // fallback on the default config supplier (config file) + return new DefaultDriverConfigLoader( + () -> buildConfig().withFallback(DefaultDriverConfigLoader.DEFAULT_CONFIG_SUPPLIER.get())); + } + + /** @return A {@link Config} containing only the options provided */ + protected Config buildConfig() { + Config config = ConfigFactory.empty(); + for (Map.Entry entry : values.build().entrySet()) { + config = config.withValue(entry.getKey(), ConfigValueFactory.fromAnyRef(entry.getValue())); + } + return config; + } + + @NonNull + @Override + public DefaultDriverConfigLoaderBuilder with(@NonNull String path, @Nullable Object value) { + values.put(path, value); + return this; + } + + /** A builder for specifying options at a profile level using {@code withXXX} methods. */ + public static final class ProfileBuilder implements DriverOptionConfigBuilder { + + final NullAllowingImmutableMap.Builder values = + NullAllowingImmutableMap.builder(); + + private ProfileBuilder() {} + + @NonNull + @Override + public ProfileBuilder with(@NonNull String path, @Nullable Object value) { + values.put(path, value); + return this; + } + + @NonNull + public Profile build() { + return new Profile(values.build()); + } + } + + /** + * A single-purpose holder of profile options as a map to be consumed by {@link + * DefaultDriverConfigLoaderBuilder}. + */ + public static final class Profile { + final Map values; + + private Profile(Map values) { + this.values = values; + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 8da90e17026..19262d11ad0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -17,13 +17,16 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; +import com.datastax.oss.driver.internal.core.connection.ConstantReconnectionPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.server.RejectScope; +import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @@ -61,14 +64,16 @@ public void should_wait_for_contact_points_if_reconnection_enabled() throws Exce simulacronRule.cluster().rejectConnections(0, RejectScope.STOP); // When + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true) + .withClass( + DefaultDriverOption.RECONNECTION_POLICY_CLASS, ConstantReconnectionPolicy.class) + // Use a short delay so we don't have to wait too long: + .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofMillis(500)) + .build(); CompletableFuture sessionFuture = - newSessionAsync( - simulacronRule, - "advanced.reconnect-on-init = true", - // Use a short delay so we don't have to wait too long: - "advanced.reconnection-policy.class = ConstantReconnectionPolicy", - "advanced.reconnection-policy.base-delay = 500 milliseconds") - .toCompletableFuture(); + newSessionAsync(simulacronRule, loader).toCompletableFuture(); // wait a bit to ensure we have a couple of reconnections, otherwise we might race and allow // reconnections before the initial attempt TimeUnit.SECONDS.sleep(2); @@ -87,10 +92,10 @@ public void should_wait_for_contact_points_if_reconnection_enabled() throws Exce @SuppressWarnings("unchecked") private CompletionStage newSessionAsync( - SimulacronRule serverRule, String... options) { + SimulacronRule serverRule, DriverConfigLoader loader) { return SessionUtils.baseBuilder() .addContactPoints(serverRule.getContactPoints()) - .withConfigLoader(new TestConfigLoader(options)) + .withConfigLoader(loader) .buildAsync(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java index 341d70a65f0..ec5f92ba9c5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java @@ -17,6 +17,8 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -49,17 +51,20 @@ public void should_connect_with_no_keyspace() { @Test(expected = InvalidKeyspaceException.class) public void should_fail_to_connect_to_non_existent_keyspace_when_not_reconnecting_on_init() { - should_fail_to_connect_to_non_existent_keyspace(); + should_fail_to_connect_to_non_existent_keyspace(null); } @Test(expected = InvalidKeyspaceException.class) public void should_fail_to_connect_to_non_existent_keyspace_when_reconnecting_on_init() { // Just checking that we don't trigger retries for this unrecoverable error - should_fail_to_connect_to_non_existent_keyspace("advanced.reconnect-on-init = true"); + should_fail_to_connect_to_non_existent_keyspace( + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true) + .build()); } - private void should_fail_to_connect_to_non_existent_keyspace(String... options) { + private void should_fail_to_connect_to_non_existent_keyspace(DriverConfigLoader loader) { CqlIdentifier keyspace = CqlIdentifier.fromInternal("does not exist"); - SessionUtils.newSession(ccm, keyspace, options); + SessionUtils.newSession(ccm, keyspace, loader); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 73040b5e618..19ac7d2b089 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -52,7 +54,11 @@ public void should_downgrade_to_v3() { ) @Test public void should_fail_if_provided_version_isnt_supported() { - try (CqlSession session = SessionUtils.newSession(ccm, "advanced.protocol.version = V4")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4") + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); fail("Expected an AllNodesFailedException"); @@ -78,7 +84,11 @@ public void should_not_downgrade_if_server_supports_latest_version() { @CassandraRequirement(min = "2.2", description = "required to use an older protocol version") @Test public void should_use_explicitly_provided_protocol_version() { - try (CqlSession session = SessionUtils.newSession(ccm, "advanced.protocol.version = V3")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V3") + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 6add57c5659..f5120c81dfd 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -18,10 +18,12 @@ import static com.datastax.oss.driver.assertions.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.DataCenterSpec; @@ -47,13 +49,18 @@ public class ProtocolVersionMixedClusterIT { @Test public void should_downgrade_if_peer_does_not_support_negotiated_version() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.1.0"); BoundNode contactPoint = simulacron.node(0); CqlSession session = - CqlSession.builder() - .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader("advanced.metadata.schema.enabled = false")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(loader) + .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V3); @@ -89,13 +96,18 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { @Test public void should_keep_current_if_supported_by_all_peers() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "3.11"); BoundNode contactPoint = simulacron.node(0); CqlSession session = - CqlSession.builder() - .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader("advanced.metadata.schema.enabled = false")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(loader) + .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); @@ -119,26 +131,29 @@ public void should_fail_if_peer_does_not_support_v3() { try (BoundCluster simulacron = mixedVersions("3.0.0", "2.0.9", "3.11"); BoundNode contactPoint = simulacron.node(0); CqlSession ignored = - CqlSession.builder() - .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader(new TestConfigLoader()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(contactPoint.inetSocketAddress()) + .build()) { fail("Cluster init should have failed"); } } @Test public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4") + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); try (BoundCluster simulacron = mixedVersions("3.0.0", "2.2.0", "2.0.0"); BoundNode contactPoint = simulacron.node(0); CqlSession session = - CqlSession.builder() - .addContactPoint(contactPoint.inetSocketAddress()) - .withConfigLoader( - new TestConfigLoader( - "advanced.protocol.version = V4", - "advanced.metadata.schema.enabled = false")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(contactPoint.inetSocketAddress()) + .withConfigLoader(loader) + .build()) { assertThat(session.getContext().protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(4); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java index 0c397f253aa..d25e0b275ff 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderIT.java @@ -18,8 +18,11 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.internal.core.auth.PlainTextAuthProvider; import com.google.common.util.concurrent.Uninterruptibles; import java.util.concurrent.TimeUnit; import org.junit.BeforeClass; @@ -45,24 +48,26 @@ public static void sleepForAuth() { @Test public void should_connect_with_credentials() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.auth-provider.class = PlainTextAuthProvider", - "advanced.auth-provider.username = cassandra", - "advanced.auth-provider.password = cassandra")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.AUTH_PROVIDER_CLASS, PlainTextAuthProvider.class) + .withString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, "cassandra") + .withString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, "cassandra") + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_invalid_credentials() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.auth-provider.class = PlainTextAuthProvider", - "advanced.auth-provider.username = baduser", - "advanced.auth-provider.password = badpass")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.AUTH_PROVIDER_CLASS, PlainTextAuthProvider.class) + .withString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, "baduser") + .withString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, "badpass") + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 9243ec94f60..7ba32a43024 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.offset; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -27,6 +29,7 @@ import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -39,7 +42,12 @@ public class DirectCompressionIT { @ClassRule public static SessionRule schemaSessionRule = - new SessionRule<>(ccmRule, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); @BeforeClass public static void setup() { @@ -57,7 +65,7 @@ public static void setup() { */ @Test public void should_execute_queries_with_snappy_compression() throws Exception { - createAndCheckCluster("advanced.protocol.compression = snappy"); + createAndCheckCluster("snappy"); } /** @@ -69,13 +77,16 @@ public void should_execute_queries_with_snappy_compression() throws Exception { */ @Test public void should_execute_queries_with_lz4_compression() throws Exception { - createAndCheckCluster("advanced.protocol.compression = lz4"); + createAndCheckCluster("lz4"); } private void createAndCheckCluster(String compressorOption) { - + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, compressorOption) + .build(); try (CqlSession session = - SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), compressorOption)) { + SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), loader)) { // Run a couple of simple test queries ResultSet rs = session.execute( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index 675fecfd9dd..ea490631d9e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.offset; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; @@ -27,6 +29,7 @@ import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -44,7 +47,12 @@ public class HeapCompressionIT { @ClassRule public static SessionRule schemaSessionRule = - new SessionRule<>(ccmRule, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); @BeforeClass public static void setup() { @@ -61,7 +69,7 @@ public static void setup() { */ @Test public void should_execute_queries_with_snappy_compression() throws Exception { - createAndCheckCluster("advanced.protocol.compression = snappy"); + createAndCheckCluster("snappy"); } /** @@ -72,13 +80,16 @@ public void should_execute_queries_with_snappy_compression() throws Exception { */ @Test public void should_execute_queries_with_lz4_compression() throws Exception { - createAndCheckCluster("advanced.protocol.compression = lz4"); + createAndCheckCluster("lz4"); } private void createAndCheckCluster(String compressorOption) { - + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, compressorOption) + .build(); try (CqlSession session = - SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), compressorOption)) { + SessionUtils.newSession(ccmRule, schemaSessionRule.keyspace(), loader)) { // Run a couple of simple test queries ResultSet rs = session.execute( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java index 17be961ec50..57cac459aee 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java @@ -36,24 +36,25 @@ public class DriverConfigValidationIT { @Test public void should_fail_to_init_with_invalid_policy() { - should_fail_to_init_with_invalid_policy("basic.load-balancing-policy.class"); - should_fail_to_init_with_invalid_policy("advanced.reconnection-policy.class"); - should_fail_to_init_with_invalid_policy("advanced.retry-policy.class"); - should_fail_to_init_with_invalid_policy("advanced.speculative-execution-policy.class"); - should_fail_to_init_with_invalid_policy("advanced.auth-provider.class"); - should_fail_to_init_with_invalid_policy("advanced.ssl-engine-factory.class"); - should_fail_to_init_with_invalid_policy("advanced.timestamp-generator.class"); - should_fail_to_init_with_invalid_policy("advanced.request-tracker.class"); - should_fail_to_init_with_invalid_policy("advanced.throttler.class"); - should_fail_to_init_with_invalid_policy("advanced.node-state-listener.class"); - should_fail_to_init_with_invalid_policy("advanced.schema-change-listener.class"); - should_fail_to_init_with_invalid_policy("advanced.address-translator.class"); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.RECONNECTION_POLICY_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.RETRY_POLICY_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.AUTH_PROVIDER_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.TIMESTAMP_GENERATOR_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.REQUEST_TRACKER_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.REQUEST_THROTTLER_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.METADATA_NODE_STATE_LISTENER_CLASS); + should_fail_to_init_with_invalid_policy( + DefaultDriverOption.METADATA_SCHEMA_CHANGE_LISTENER_CLASS); + should_fail_to_init_with_invalid_policy(DefaultDriverOption.ADDRESS_TRANSLATOR_CLASS); } - private void should_fail_to_init_with_invalid_policy(String classConfigPath) { - assertThatThrownBy( - () -> - SessionUtils.newSession(simulacron, classConfigPath + " = AClassThatDoesNotExist")) + private void should_fail_to_init_with_invalid_policy(DefaultDriverOption option) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder().withString(option, "AClassThatDoesNotExist").build(); + assertThatThrownBy(() -> SessionUtils.newSession(simulacron, loader)) .satisfies( error -> { assertThat(error).isInstanceOf(DriverExecutionException.class); @@ -62,7 +63,7 @@ private void should_fail_to_init_with_invalid_policy(String classConfigPath) { .hasMessage( "Can't find class AClassThatDoesNotExist " + "(specified by " - + classConfigPath + + option.getPath() + ")"); }); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index 3bd96f4d290..9fa8d0b49da 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -36,8 +36,10 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; +import java.time.Duration; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.junit.Rule; @@ -72,8 +74,15 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { @Test public void should_use_profile_request_timeout() { - try (CqlSession session = - SessionUtils.newSession(simulacron, "profiles.olap.basic.request.timeout = 10s")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withProfile( + "olap", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(10)) + .build()) + .build(); + try (CqlSession session = SessionUtils.newSession(simulacron, loader)) { String query = "mockquery"; // configure query with delay of 4 seconds. simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); @@ -93,9 +102,15 @@ public void should_use_profile_request_timeout() { @Test public void should_use_profile_default_idempotence() { - try (CqlSession session = - SessionUtils.newSession( - simulacron, "profiles.idem.basic.request.default-idempotence = true")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withProfile( + "idem", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true) + .build()) + .build(); + try (CqlSession session = SessionUtils.newSession(simulacron, loader)) { String query = "mockquery"; // configure query with server error which should invoke onRequestError in retry policy. simulacron.cluster().prime(when(query).then(serverError("fail"))); @@ -116,11 +131,16 @@ public void should_use_profile_default_idempotence() { @Test public void should_use_profile_consistency() { - try (CqlSession session = - SessionUtils.newSession( - simulacron, - "profiles.cl.basic.request.consistency = LOCAL_QUORUM", - "profiles.cl.basic.request.serial-consistency = LOCAL_SERIAL")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withProfile( + "cl", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString(DefaultDriverOption.REQUEST_CONSISTENCY, "LOCAL_QUORUM") + .withString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY, "LOCAL_SERIAL") + .build()) + .build(); + try (CqlSession session = SessionUtils.newSession(simulacron, loader)) { String query = "mockquery"; // Execute query without profile, should use default CLs (LOCAL_ONE, SERIAL). @@ -169,11 +189,16 @@ public void should_use_profile_consistency() { @Test public void should_use_profile_page_size() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "basic.request.page-size = 100", - "profiles.smallpages.basic.request.page-size = 10")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 100) + .withProfile( + "smallpages", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 10) + .build()) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { CqlIdentifier keyspace = SessionUtils.uniqueKeyspaceId(); DriverExecutionProfile slowProfile = SessionUtils.slowProfile(session); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index 128405b3278..93df6017bf1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; @@ -56,10 +57,11 @@ public void should_periodically_reload_configuration() throws Exception { "basic.config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); // Expect timeout since default timeout is 2s @@ -90,10 +92,11 @@ public void should_reload_configuration_when_event_fired() throws Exception { ConfigFactory.parseString("basic.config-reload-interval = 0\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); // Expect timeout since default timeout is 2s @@ -128,10 +131,11 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { "basic.config-reload-interval = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); // Expect failure because profile doesn't exist. @@ -167,10 +171,11 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .withConfigLoader(loader) + .addContactPoints(simulacron.getContactPoints()) + .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); // Expect failure because profile doesn't exist. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java index 9b01840dcd7..ae66fb98834 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -23,9 +23,13 @@ import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.SOCKET_TCP_NODELAY; import static org.assertj.core.api.Assertions.assertThat; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -44,20 +48,23 @@ public class ChannelSocketOptionsIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + private static DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.SOCKET_TCP_NODELAY, true) + .withBoolean(DefaultDriverOption.SOCKET_KEEP_ALIVE, false) + .withBoolean(DefaultDriverOption.SOCKET_REUSE_ADDRESS, false) + .withInt(DefaultDriverOption.SOCKET_LINGER_INTERVAL, 10) + .withInt(DefaultDriverOption.SOCKET_RECEIVE_BUFFER_SIZE, 123456) + .withInt(DefaultDriverOption.SOCKET_SEND_BUFFER_SIZE, 123456) + .build(); + @ClassRule - public static SessionRule sessionRule = - new SessionRule<>( - simulacron, - "advanced.socket.tcp-no-delay = true", - "advanced.socket.keep-alive = false", - "advanced.socket.reuse-address = false", - "advanced.socket.linger-interval = 10", - "advanced.socket.receive-buffer-size = 123456", - "advanced.socket.send-buffer-size = 123456"); + public static SessionRule sessionRule = + SessionRule.builder(simulacron).withConfigLoader(loader).build(); @Test public void should_report_socket_options() { - DefaultSession session = sessionRule.session(); + CqlSession session = sessionRule.session(); DriverExecutionProfile config = session.getContext().config().getDefaultProfile(); assertThat(config.getBoolean(SOCKET_TCP_NODELAY)).isTrue(); assertThat(config.getBoolean(SOCKET_KEEP_ALIVE)).isFalse(); @@ -66,7 +73,7 @@ public void should_report_socket_options() { assertThat(config.getInt(SOCKET_RECEIVE_BUFFER_SIZE)).isEqualTo(123456); assertThat(config.getInt(SOCKET_SEND_BUFFER_SIZE)).isEqualTo(123456); Node node = session.getMetadata().getNodes().values().iterator().next(); - DriverChannel channel = session.getChannel(node, null); + DriverChannel channel = ((DefaultSession) session).getChannel(node, null); assertThat(channel.config()).isInstanceOf(SocketChannelConfig.class); SocketChannelConfig socketConfig = (SocketChannelConfig) channel.config(); assertThat(socketConfig.isTcpNoDelay()).isTrue(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 128e0d7422f..af887bd1d1d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -23,12 +23,16 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.retry.RetryDecision; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.retry.DefaultRetryPolicy; @@ -48,13 +52,17 @@ public class FrameLengthIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + private static DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, SortingLoadBalancingPolicy.class) + .withClass(DefaultDriverOption.RETRY_POLICY_CLASS, AlwaysRetryAbortedPolicy.class) + .withBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH, "100 kilobytes") + .build(); + @ClassRule public static SessionRule sessionRule = - new SessionRule<>( - simulacron, - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "advanced.retry-policy.class = \"com.datastax.oss.driver.api.core.connection.FrameLengthIT$AlwaysRetryAbortedPolicy\"", - "advanced.protocol.max-frame-length = 100 kilobytes"); + SessionRule.builder(simulacron).withConfigLoader(loader).build(); private static final SimpleStatement LARGE_QUERY = SimpleStatement.newInstance("select * from foo").setIdempotent(true); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index f76eaf0d553..fb76d1d3f85 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -18,8 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; import java.util.concurrent.CompletableFuture; @@ -42,7 +44,12 @@ public class AsyncResultSetIT { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccm, "basic.request.page-size = " + PAGE_SIZE); + SessionRule.builder(ccm) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, PAGE_SIZE) + .build()) + .build(); @BeforeClass public static void setupSchema() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 4f53ff7a0a3..f7e3212c54a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -36,7 +38,7 @@ public class BatchStatementIT { @Rule public CcmRule ccm = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = new SessionRule<>(ccm); + @Rule public SessionRule sessionRule = SessionRule.builder(ccm).build(); @Rule public TestName name = new TestName(); @@ -323,8 +325,11 @@ public void should_fail_counter_batch_with_non_counter_increment() { @Test(expected = IllegalStateException.class) public void should_not_allow_unset_value_when_protocol_less_than_v4() { // CREATE TABLE test (k0 text, k1 int, v int, PRIMARY KEY (k0, k1)) - try (CqlSession v3Session = - SessionUtils.newSession(ccm, sessionRule.keyspace(), "advanced.protocol.version = V3")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V3") + .build(); + try (CqlSession v3Session = SessionUtils.newSession(ccm, sessionRule.keyspace(), loader)) { PreparedStatement prepared = v3Session.prepare("INSERT INTO test (k0, k1, v) values (?, ?, ?)"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 065e9bdb31b..286a08a1195 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; @@ -75,7 +76,12 @@ public class BoundStatementIT { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccm, "basic.request.page-size = 20"); + SessionRule.builder(ccm) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 20) + .build()) + .build(); @Rule public TestName name = new TestName(); @@ -121,8 +127,11 @@ public void clearPrimes() { @Test(expected = IllegalStateException.class) public void should_not_allow_unset_value_when_protocol_less_than_v4() { - try (CqlSession v3Session = - SessionUtils.newSession(ccm, sessionRule.keyspace(), "advanced.protocol.version = V3")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V3") + .build(); + try (CqlSession v3Session = SessionUtils.newSession(ccm, sessionRule.keyspace(), loader)) { PreparedStatement prepared = v3Session.prepare("INSERT INTO test2 (k, v0) values (?, ?)"); BoundStatement boundStatement = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index f56cfe403c7..0f567de5f29 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -43,7 +45,7 @@ public class PerRequestKeyspaceIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); + @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public TestName nameRule = new TestName(); @@ -93,7 +95,11 @@ public void should_reject_batch_statement_with_inferred_keyspace_in_protocol_v4( } private void should_reject_statement_with_keyspace_in_protocol_v4(Statement statement) { - try (CqlSession session = SessionUtils.newSession(ccmRule, "advanced.protocol.version = V4")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4") + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Can't use per-request keyspace with protocol V4"); session.execute(statement); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index 04a022bd8b1..5e19b6c1bcc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; @@ -29,6 +31,7 @@ import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; +import java.time.Duration; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -50,8 +53,13 @@ public class PreparedStatementInvalidationIT { @Rule public SessionRule sessionRule = - new SessionRule<>( - ccmRule, "basic.request.page-size = 2", "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 2) + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -223,12 +231,12 @@ public void should_not_store_metadata_for_conditional_updates() { @Test @CassandraRequirement(min = "2.2") public void should_not_store_metadata_for_conditional_updates_in_legacy_protocol() { - try (CqlSession session = - SessionUtils.newSession( - ccmRule, - sessionRule.keyspace(), - "advanced.protocol.version = V4", - "basic.request.timeout = 30 seconds")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4") + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, sessionRule.keyspace(), loader)) { should_not_store_metadata_for_conditional_updates(session); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index b33fac20368..c37c94722d9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -34,7 +34,10 @@ public class QueryTraceIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccmRule); + + @ClassRule + public static SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + @Rule public ExpectedException thrown = ExpectedException.none(); @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 89fd48db95a..0e7bb80329d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -23,9 +23,11 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.protocol.internal.Message; @@ -55,7 +57,12 @@ public class SimpleStatementIT { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccm, "basic.request.page-size = 20"); + SessionRule.builder(ccm) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 20) + .build()) + .build(); @ClassRule public static SessionRule simulacronSessionRule = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index e88291a8289..52a7f38272e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -82,7 +82,7 @@ public class DataTypeIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); + @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); @Rule public TestName name = new TestName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java index 4d7d30b1a32..c6312e6484b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatDisabledIT.java @@ -19,9 +19,12 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import org.junit.ClassRule; import org.junit.Test; @@ -36,8 +39,11 @@ public class HeartbeatDisabledIT { public void should_not_send_heartbeat_when_disabled() throws InterruptedException { // Disable heartbeats entirely, wait longer than the default timeout and make sure we didn't // receive any - try (CqlSession session = - SessionUtils.newSession(simulacron, "advanced.heartbeat.interval = 0 second")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.HEARTBEAT_INTERVAL, Duration.ofSeconds(0)) + .build(); + try (CqlSession session = SessionUtils.newSession(simulacron, loader)) { AtomicInteger heartbeats = registerHeartbeatListener(); SECONDS.sleep(35); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java index b97dc6e3083..34c33560726 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/heartbeat/HeartbeatIT.java @@ -26,11 +26,14 @@ import static org.assertj.core.api.Assertions.fail; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import com.datastax.oss.protocol.internal.request.Register; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; @@ -41,7 +44,7 @@ import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.RejectScope; import java.net.SocketAddress; -import java.util.Arrays; +import java.time.Duration; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; @@ -111,11 +114,11 @@ public void should_not_send_heartbeat_during_protocol_initialization() { @Test public void should_send_heartbeat_on_control_connection() { - - try (CqlSession session = - newSession( - // Ensure we only have the control connection - "advanced.connection.pool.local.size = 0")) { + // Ensure we only have the control connection) + DefaultDriverConfigLoaderBuilder loader = + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE, 0); + try (CqlSession session = newSession(loader)) { AtomicInteger heartbeats = countHeartbeatsOnControlConnection(); checkThat(() -> heartbeats.get() > 0).becomesTrue(); } @@ -200,18 +203,22 @@ public void should_close_connection_when_heartbeat_times_out() { } } - private CqlSession newSession(String... extraOptions) { - String[] defaultOptions = - new String[] { - "advanced.heartbeat.interval = 1 second", - "advanced.heartbeat.timeout = 500 milliseconds", - "advanced.connection.init-query-timeout = 2 seconds", - "advanced.connection.reconnection-policy.max-delay = 1 second" - }; - String[] allOptions = - Arrays.copyOf(defaultOptions, defaultOptions.length + extraOptions.length); - System.arraycopy(extraOptions, 0, allOptions, defaultOptions.length, extraOptions.length); - return SessionUtils.newSession(simulacron, allOptions); + private CqlSession newSession() { + return newSession(null); + } + + private CqlSession newSession(DefaultDriverConfigLoaderBuilder loaderBuilder) { + if (loaderBuilder == null) { + loaderBuilder = SessionUtils.configLoaderBuilder(); + } + DriverConfigLoader loader = + loaderBuilder + .withDuration(DefaultDriverOption.HEARTBEAT_INTERVAL, Duration.ofSeconds(1)) + .withDuration(DefaultDriverOption.HEARTBEAT_TIMEOUT, Duration.ofMillis(500)) + .withDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(2)) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofSeconds(1)) + .build(); + return SessionUtils.newSession(simulacron, loader); } private AtomicInteger countHeartbeatsOnRegularConnection() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 27cdcda6448..76bdcb56008 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -38,6 +39,7 @@ import com.google.common.collect.ImmutableList; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -58,7 +60,10 @@ public class DefaultLoadBalancingPolicyIT { public static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) - .withOptions("basic.request.timeout = 30 seconds") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) .build(); @BeforeClass diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java index 24ebd2dbfa2..a7269ada90a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java @@ -18,6 +18,7 @@ import static com.datastax.oss.driver.assertions.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -26,8 +27,10 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import org.junit.Before; @@ -46,10 +49,20 @@ public class PerProfileLoadBalancingPolicyIT { // default lb policy should consider dc1 local, profile1 dc3, profile2 empty. public static @ClassRule SessionRule sessionRule = SessionRule.builder(simulacron) - .withOptions( - "basic.load-balancing-policy.local-datacenter = dc1", - "profiles.profile1.basic.load-balancing-policy.local-datacenter = dc3", - "profiles.profile2 = {}") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, "dc1") + .withProfile( + "profile1", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, "dc3") + .build()) + .withProfile( + "profile2", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString(DefaultDriverOption.REQUEST_CONSISTENCY, "ONE") + .build()) + .build()) .build(); private static String QUERY_STRING = "select * from foo"; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index 09ca0d00bae..ea80e45baf5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,7 +33,13 @@ public class ByteOrderedTokenIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public ByteOrderedTokenIT() { super(ByteOrderedToken.class, false); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index 1f9bfbdc9ed..1bd2bd933fb 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -34,7 +37,13 @@ public class ByteOrderedTokenVnodesIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public ByteOrderedTokenVnodesIT() { super(ByteOrderedToken.class, true); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 4b16da1c188..49042d24344 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -32,6 +33,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.time.Duration; import java.util.Optional; import org.junit.ClassRule; import org.junit.Test; @@ -46,16 +48,17 @@ public class DescribeIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + // disable debouncer to speed up test. @ClassRule public static SessionRule sessionRule = - new SessionRule<>( - ccmRule, - false, - null, - null, - "basic.request.timeout = 30 seconds", - "advanced.metadata.schema.debouncer.window = 0 seconds"); // disable debouncer to speed up - // test. + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW, Duration.ofSeconds(0)) + .build()) + .build(); /** * Creates a keyspace using a variety of features and ensures {@link diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index 0f86404583d..c8fbe1aed2d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -28,7 +31,13 @@ public class Murmur3TokenIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public Murmur3TokenIT() { super(Murmur3Token.class, false); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index d3f21fc7f90..05bd93ff50f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,7 +33,13 @@ public class Murmur3TokenVnodesIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public Murmur3TokenVnodesIT() { super(Murmur3Token.class, true); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index 5b1be5dd741..b93a1304bc6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; @@ -38,7 +39,7 @@ public class NodeMetadataIT { @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); + @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); @Test public void should_expose_node_metadata() { @@ -91,26 +92,30 @@ public void should_not_record_last_response_time_if_disabled() { @Test public void should_record_last_response_time_if_enabled() { - CqlSession session = + try (CqlSession session = SessionUtils.newSession( - ccmRule, "advanced.metadata.nodes.last-response-time.enabled = true"); - Node node = getUniqueNode(session); - - // Ensure that we get increasing timestamps as long as we keep querying - long[] timestamps = new long[10]; - timestamps[0] = System.nanoTime(); - for (int i = 1; i < 9; i++) { - session.execute("SELECT release_version FROM system.local"); - timestamps[i] = node.getLastResponseTimeNanos(); + ccmRule, + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, true) + .build())) { + Node node = getUniqueNode(session); + + // Ensure that we get increasing timestamps as long as we keep querying + long[] timestamps = new long[10]; + timestamps[0] = System.nanoTime(); + for (int i = 1; i < 9; i++) { + session.execute("SELECT release_version FROM system.local"); + timestamps[i] = node.getLastResponseTimeNanos(); + } + timestamps[9] = System.nanoTime(); + + for (int i = 0; i < 9; i++) { + assertThat(timestamps[i]).isLessThan(timestamps[i + 1]); + } + + // Ensure that the value doesn't change since the last query + assertThat(node.getLastResponseTimeNanos()).isEqualTo(timestamps[8]); } - timestamps[9] = System.nanoTime(); - - for (int i = 0; i < 9; i++) { - assertThat(timestamps[i]).isLessThan(timestamps[i + 1]); - } - - // Ensure that the value doesn't change since the last query - assertThat(node.getLastResponseTimeNanos()).isEqualTo(timestamps[8]); } private static Node getUniqueNode(CqlSession session) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 20577f411aa..65d9f8b1197 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -21,6 +21,8 @@ import static org.mockito.Mockito.timeout; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -36,7 +38,6 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.NodeConnectionReport; import com.datastax.oss.simulacron.common.stubbing.CloseType; @@ -47,6 +48,7 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; +import java.time.Duration; import java.util.Iterator; import java.util.Map; import java.util.Queue; @@ -80,12 +82,14 @@ public class NodeStateIT { public @Rule SessionRule sessionRule = SessionRule.builder(simulacron) - .withOptions( - "advanced.connection.pool.local.size = 2", - "advanced.reconnection-policy.max-delay = 1 second", - String.format( - "basic.load-balancing-policy.class = \"%s$ConfigurableIgnoresPolicy\"", - NodeStateIT.class.getName())) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE, 2) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofSeconds(1)) + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, + NodeStateIT.ConfigurableIgnoresPolicy.class) + .build()) .withNodeStateListener(nodeStateListener) .build(); @@ -304,16 +308,14 @@ public void should_ignore_down_topology_event_when_still_connected() throws Inte public void should_force_immediate_reconnection_when_up_topology_event() throws InterruptedException { // This test requires a longer reconnection interval, so create a separate driver instance + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofHours(1)) + .build(); NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); try (CqlSession session = - SessionUtils.newSession( - simulacron, - null, - localNodeStateListener, - null, - null, - "advanced.reconnection-policy.base-delay = 1 hour", - "advanced.reconnection-policy.max-delay = 1 hour")) { + SessionUtils.newSession(simulacron, null, localNodeStateListener, null, null, loader)) { BoundNode localSimulacronNode = simulacron.cluster().getNodes().iterator().next(); assertThat(localSimulacronNode).isNotNull(); @@ -438,17 +440,19 @@ public void should_signal_non_contact_points_as_added() { InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofHours(1)) + .withInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE, 0) + .build(); try (CqlSession localSession = - CqlSession.builder() - .addContactPoint(address1) - .withNodeStateListener(localNodeStateListener) - .withConfigLoader( - new TestConfigLoader( - "avanced.reconnection-policy.base-delay = 1 hour", - "advanced.reconnection-policy.max-delay = 1 hour", - // Ensure we only have the control connection - "advanced.connection.pool.local.size = 0")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(address1) + .withNodeStateListener(localNodeStateListener) + .withConfigLoader(loader) + .build()) { Map nodes = localSession.getMetadata().getNodes(); Node localMetadataNode1 = nodes.get(address1); @@ -471,16 +475,19 @@ public void should_remove_invalid_contact_point() { // Initialize the driver with 1 wrong address and 1 valid address InetSocketAddress wrongContactPoint = withUnusedPort(address1); + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofHours(1)) + .build(); try (CqlSession localSession = - CqlSession.builder() - .addContactPoint(address1) - .addContactPoint(wrongContactPoint) - .withNodeStateListener(localNodeStateListener) - .withConfigLoader( - new TestConfigLoader( - "advanced.reconnection-policy.base-delay = 1 hour", - "advanced.reconnection-policy.max-delay = 1 hour")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(address1) + .addContactPoint(wrongContactPoint) + .withNodeStateListener(localNodeStateListener) + .withConfigLoader(loader) + .build()) { Map nodes = localSession.getMetadata().getNodes(); assertThat(nodes).doesNotContainKey(wrongContactPoint); @@ -517,17 +524,20 @@ public void should_mark_unreachable_contact_point_down() { try { // Since contact points are shuffled, we have a 50% chance that our bad contact point will be // hit first. So we retry the scenario a few times if needed. + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) + .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofHours(1)) + .build(); for (int i = 0; i < 10; i++) { try (CqlSession localSession = - CqlSession.builder() - .addContactPoint(address1) - .addContactPoint(address2) - .withNodeStateListener(localNodeStateListener) - .withConfigLoader( - new TestConfigLoader( - "advanced.reconnection-policy.base-delay = 1 hour", - "advanced.reconnection-policy.max-delay = 1 hour")) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addContactPoint(address1) + .addContactPoint(address2) + .withNodeStateListener(localNodeStateListener) + .withConfigLoader(loader) + .build()) { Map nodes = localSession.getMetadata().getNodes(); Node localMetadataNode1 = nodes.get(address1); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index 2d2f76017f9..0fb67fbf7f7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -30,7 +33,13 @@ public class RandomTokenIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public RandomTokenIT() { super(RandomToken.class, false); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index 7c0ef7cc48d..a67bae9fd41 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; +import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -34,7 +37,13 @@ public class RandomTokenVnodesIT extends TokenITBase { @ClassRule public static SessionRule sessionRule = - new SessionRule<>(ccmRule, false, null, null, "basic.request.timeout = 30 seconds"); + SessionRule.builder(ccmRule) + .withKeyspace(false) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); public RandomTokenVnodesIT() { super(RandomToken.class, true); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java index c9b535d590f..80934c5e129 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaAgreementIT.java @@ -18,10 +18,14 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import org.junit.ClassRule; import org.junit.Rule; @@ -34,10 +38,16 @@ public class SchemaAgreementIT { private static CustomCcmRule ccm = CustomCcmRule.builder().withNodes(3).build(); private static SessionRule sessionRule = SessionRule.builder(ccm) - .withOptions( - "basic.request.timeout = 30s", - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "advanced.control-connection.schema-agreement.timeout = 3s") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, + SortingLoadBalancingPolicy.class) + .withDuration( + DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT, + Duration.ofSeconds(3)) + .build()) .build(); @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(ccm).around(sessionRule); @@ -82,12 +92,13 @@ public void should_agree_when_up_nodes_agree() { @Test public void should_fail_if_timeout_is_zero() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - sessionRule.keyspace(), - "basic.request.timeout = 30s", - "advanced.control-connection.schema-agreement.timeout = 0s")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withDuration( + DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT, Duration.ofSeconds(0)) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace(), loader)) { ResultSet result = createTable(session); // Should not agree because schema metadata is disabled diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 56b7d4320f8..922b643b326 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -29,12 +31,13 @@ import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.collect.ImmutableList; -import java.util.Arrays; +import com.google.common.collect.Lists; +import java.time.Duration; +import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Rule; @@ -50,10 +53,13 @@ public class SchemaChangesIT { // A client that we only use to set up the tests @Rule public SessionRule adminSessionRule = - new SessionRule<>( - ccmRule, - "basic.request.timeout = 30 seconds", - "advanced.metadata.schema.debouncer.window = 0 seconds"); + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW, Duration.ofSeconds(0)) + .build()) + .build(); @Before public void setup() { @@ -404,20 +410,6 @@ public void should_handle_aggregate_update() { Mockito.verify(listener).onAggregateUpdated(newAggregate, oldAggregate)); } - private String keyspaceFilterOption(CqlIdentifier... keyspaces) { - // create option to filter keyspace refreshes based on input keyspaces, if none are provided, - // assume the - // one associated wiht the cluster rule. - if (keyspaces.length == 0) { - keyspaces = new CqlIdentifier[] {adminSessionRule.keyspace()}; - } - - String keyspaceStr = - Arrays.stream(keyspaces).map(i -> i.asCql(false)).collect(Collectors.joining(",")); - - return String.format("advanced.metadata.schema.refreshed-keyspaces = [%s]", keyspaceStr); - } - private void should_handle_creation( String beforeStatement, String createStatement, @@ -435,18 +427,23 @@ private void should_handle_creation( // cluster1 executes the DDL query and gets a SCHEMA_CHANGE response. // cluster2 gets a SCHEMA_CHANGE push event on its control connection. + + List keyspaceList = Lists.newArrayList(); + for (CqlIdentifier keyspace : keyspaces) { + keyspaceList.add(keyspace.asInternal()); + } + + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, keyspaceList) + .build(); + try (CqlSession session1 = SessionUtils.newSession( - ccmRule, - adminSessionRule.keyspace(), - null, - listener1, - null, - "basic.request.timeout = 30 seconds", - keyspaceFilterOption(keyspaces)); + ccmRule, adminSessionRule.keyspace(), null, listener1, null, loader); CqlSession session2 = - SessionUtils.newSession( - ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { + SessionUtils.newSession(ccmRule, null, null, listener2, null, loader)) { session1.execute(createStatement); @@ -480,18 +477,22 @@ private void should_handle_drop( SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + + List keyspaceList = Lists.newArrayList(); + for (CqlIdentifier keyspace : keyspaces) { + keyspaceList.add(keyspace.asInternal()); + } + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, keyspaceList) + .build(); + try (CqlSession session1 = SessionUtils.newSession( - ccmRule, - adminSessionRule.keyspace(), - null, - listener1, - null, - "basic.request.timeout = 30 seconds", - keyspaceFilterOption(keyspaces)); + ccmRule, adminSessionRule.keyspace(), null, listener1, null, loader); CqlSession session2 = - SessionUtils.newSession( - ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { + SessionUtils.newSession(ccmRule, null, null, listener2, null, loader)) { T oldElement = extract.apply(session1.getMetadata()).orElseThrow(AssertionError::new); assertThat(oldElement).isNotNull(); @@ -524,18 +525,21 @@ private void should_handle_update( SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + List keyspaceList = Lists.newArrayList(); + for (CqlIdentifier keyspace : keyspaces) { + keyspaceList.add(keyspace.asInternal()); + } + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, keyspaceList) + .build(); + try (CqlSession session1 = SessionUtils.newSession( - ccmRule, - adminSessionRule.keyspace(), - null, - listener1, - null, - "basic.request.timeout = 30 seconds", - keyspaceFilterOption(keyspaces)); + ccmRule, adminSessionRule.keyspace(), null, listener1, null, loader); CqlSession session2 = - SessionUtils.newSession( - ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { + SessionUtils.newSession(ccmRule, null, null, listener2, null, loader)) { T oldElement = extract.apply(session1.getMetadata()).orElseThrow(AssertionError::new); assertThat(oldElement).isNotNull(); @@ -573,18 +577,20 @@ private void should_handle_update_via_drop_and_recreate( SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + List keyspaceList = Lists.newArrayList(); + for (CqlIdentifier keyspace : keyspaces) { + keyspaceList.add(keyspace.asInternal()); + } + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withStringList(DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, keyspaceList) + .build(); try (CqlSession session1 = SessionUtils.newSession( - ccmRule, - adminSessionRule.keyspace(), - null, - listener1, - null, - "basic.request.timeout = 30 seconds", - keyspaceFilterOption(keyspaces)); + ccmRule, adminSessionRule.keyspace(), null, listener1, null, loader); CqlSession session2 = - SessionUtils.newSession( - ccmRule, null, null, listener2, null, keyspaceFilterOption(keyspaces))) { + SessionUtils.newSession(ccmRule, null, null, listener2, null, loader)) { T oldElement = extract.apply(session1.getMetadata()).orElseThrow(AssertionError::new); assertThat(oldElement).isNotNull(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 2da280097e2..8f287ec5e34 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -19,6 +19,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; @@ -27,6 +29,7 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; +import java.util.Collections; import java.util.Map; import org.junit.Rule; import org.junit.Test; @@ -37,7 +40,7 @@ public class SchemaIT { @Rule public CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = new SessionRule<>(ccmRule); + @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); @Test public void should_expose_system_and_test_keyspace() { @@ -56,12 +59,13 @@ public void should_expose_system_and_test_keyspace() { @Test public void should_filter_by_keyspaces() { - try (CqlSession session = - SessionUtils.newSession( - ccmRule, - String.format( - "advanced.metadata.schema.refreshed-keyspaces = [ \"%s\"] ", - sessionRule.keyspace().asInternal()))) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, + Collections.singletonList(sessionRule.keyspace().asInternal())) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { assertThat(session.getMetadata().getKeyspaces()).containsOnlyKeys(sessionRule.keyspace()); @@ -74,8 +78,11 @@ public void should_filter_by_keyspaces() { @Test public void should_not_load_schema_if_disabled_in_config() { - try (CqlSession session = - SessionUtils.newSession(ccmRule, "advanced.metadata.schema.enabled = false")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { assertThat(session.isSchemaMetadataEnabled()).isFalse(); assertThat(session.getMetadata().getKeyspaces()).isEmpty(); @@ -84,8 +91,11 @@ public void should_not_load_schema_if_disabled_in_config() { @Test public void should_enable_schema_programmatically_when_disabled_in_config() { - try (CqlSession session = - SessionUtils.newSession(ccmRule, "advanced.metadata.schema.enabled = false")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { assertThat(session.isSchemaMetadataEnabled()).isFalse(); assertThat(session.getMetadata().getKeyspaces()).isEmpty(); @@ -137,8 +147,11 @@ public void should_disable_schema_programmatically_when_enabled_in_config() { @Test public void should_refresh_schema_manually() { - try (CqlSession session = - SessionUtils.newSession(ccmRule, "advanced.metadata.schema.enabled = false")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED, false) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { assertThat(session.isSchemaMetadataEnabled()).isFalse(); assertThat(session.getMetadata().getKeyspaces()).isEmpty(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java index b14583f8df1..927cda77b94 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java @@ -20,10 +20,14 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.Timer; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.google.common.collect.Lists; +import java.util.Collections; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -35,8 +39,13 @@ public class MetricsIT { @Test public void should_expose_metrics() { - try (CqlSession session = - SessionUtils.newSession(ccmRule, "advanced.metrics.session.enabled = [ cql-requests ]")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withStringList( + DefaultDriverOption.METRICS_SESSION_ENABLED, + Collections.singletonList("cql-requests")) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { for (int i = 0; i < 10; i++) { session.execute("SELECT release_version FROM system.local"); } @@ -56,11 +65,16 @@ public void should_expose_metrics() { @Test public void should_expose_bytes_sent_and_received() { - try (CqlSession session = - SessionUtils.newSession( - ccmRule, - "advanced.metrics.session.enabled = [ bytes-sent, bytes-received ]", - "advanced.metrics.node.enabled = [ bytes-sent, bytes-received ]")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withStringList( + DefaultDriverOption.METRICS_SESSION_ENABLED, + Lists.newArrayList("bytes-sent", "bytes-received")) + .withStringList( + DefaultDriverOption.METRICS_NODE_ENABLED, + Lists.newArrayList("bytes-sent", "bytes-received")) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { for (int i = 0; i < 10; i++) { session.execute("SELECT release_version FROM system.local"); } @@ -90,11 +104,12 @@ public void should_expose_bytes_sent_and_received() { @Test public void should_not_expose_metrics_if_disabled() { - try (CqlSession session = - SessionUtils.newSession( - ccmRule, - "advanced.metrics.session.enabled = []", - "advanced.metrics.node.enabled = []")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withStringList(DefaultDriverOption.METRICS_SESSION_ENABLED, Collections.emptyList()) + .withStringList(DefaultDriverOption.METRICS_NODE_ENABLED, Collections.emptyList()) + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { for (int i = 0; i < 10; i++) { session.execute("SELECT release_version FROM system.local"); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java index 00c9e12ea90..28889580007 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; @@ -32,9 +33,12 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import com.datastax.oss.driver.internal.core.retry.DefaultRetryPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import edu.umd.cs.findbugs.annotations.NonNull; @@ -55,11 +59,23 @@ public class PerProfileRetryPolicyIT { public static @ClassRule SessionRule sessionRule = SessionRule.builder(simulacron) - .withOptions( - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "advanced.retry-policy.class = DefaultRetryPolicy", - "profiles.profile1.advanced.retry-policy.class = \"com.datastax.oss.driver.api.core.retry.PerProfileRetryPolicyIT$NoRetryPolicy\"", - "profiles.profile2 {}") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, + SortingLoadBalancingPolicy.class) + .withClass(DefaultDriverOption.RETRY_POLICY_CLASS, DefaultRetryPolicy.class) + .withProfile( + "profile1", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withClass(DefaultDriverOption.RETRY_POLICY_CLASS, NoRetryPolicy.class) + .build()) + .withProfile( + "profile2", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 100) + .build()) + .build()) .build(); private static String QUERY_STRING = "select * from foo"; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java index 4773e988d1b..b29cbd2d79e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java @@ -21,14 +21,18 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.retry.DefaultRetryPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; import java.util.List; @@ -47,9 +51,13 @@ public class ExceptionIT { @ClassRule public static SessionRule sessionRule = SessionRule.builder(simulacron) - .withOptions( - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "advanced.retry-policy.class = DefaultRetryPolicy") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, + SortingLoadBalancingPolicy.class) + .withClass(DefaultDriverOption.RETRY_POLICY_CLASS, DefaultRetryPolicy.class) + .build()) .build(); private static String QUERY_STRING = "select * from foo"; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 900d66df588..e18e29be01a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -32,7 +32,6 @@ import com.datastax.oss.driver.example.guava.internal.KeyRequest; import com.datastax.oss.driver.example.guava.internal.KeyRequestProcessor; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; @@ -66,7 +65,7 @@ public class RequestProcessorIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); + @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -92,11 +91,10 @@ public static void setupSchema() { } } - private GuavaSession newSession(CqlIdentifier keyspace, String... options) { + private GuavaSession newSession(CqlIdentifier keyspace) { return GuavaSessionUtils.builder() .addContactPoints(ccm.getContactPoints()) .withKeyspace(keyspace) - .withConfigLoader(new TestConfigLoader(options)) .build(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 74d50feb4a1..af2f32d71ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -22,20 +22,23 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder.ProfileBuilder; import com.datastax.oss.driver.internal.core.specex.ConstantSpeculativeExecutionPolicy; import com.datastax.oss.driver.internal.core.specex.NoSpeculativeExecutionPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.PrimeDsl; -import com.google.common.collect.Lists; -import java.util.List; +import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; @@ -327,14 +330,20 @@ public void should_not_speculatively_execute_when_defined_in_profile() { private CqlSession buildSession(int maxSpeculativeExecutions, long speculativeDelayMs) { return SessionUtils.newSession( simulacron, - String.format("basic.request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10), - "basic.request.default-idempotence = true", - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy", - "advanced.speculative-execution-policy.class = ConstantSpeculativeExecutionPolicy", - String.format( - "advanced.speculative-execution-policy.max-executions = %d", maxSpeculativeExecutions), - String.format( - "advanced.speculative-execution-policy.delay = %d milliseconds", speculativeDelayMs)); + SessionUtils.configLoaderBuilder() + .withDuration( + DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(SPECULATIVE_DELAY * 10)) + .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true) + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, SortingLoadBalancingPolicy.class) + .withClass( + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + ConstantSpeculativeExecutionPolicy.class) + .withInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX, maxSpeculativeExecutions) + .withDuration( + DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY, + Duration.ofMillis(speculativeDelayMs)) + .build()); } private CqlSession buildSessionWithProfile( @@ -343,54 +352,70 @@ private CqlSession buildSessionWithProfile( int profile1MaxSpeculativeExecutions, long profile1SpeculativeDelayMs) { - List config = Lists.newArrayList(); - config.add(String.format("basic.request.timeout = %d milliseconds", SPECULATIVE_DELAY * 10)); - config.add("basic.request.default-idempotence = true"); - config.add( - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy"); + DefaultDriverConfigLoaderBuilder builder = + SessionUtils.configLoaderBuilder() + .withDuration( + DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(SPECULATIVE_DELAY * 10)) + .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true); if (defaultMaxSpeculativeExecutions != -1 || defaultSpeculativeDelayMs != -1) { - config.add( - "advanced.speculative-execution-policy.class = ConstantSpeculativeExecutionPolicy"); + builder = + builder.withClass( + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + ConstantSpeculativeExecutionPolicy.class); if (defaultMaxSpeculativeExecutions != -1) { - config.add( - String.format( - "advanced.speculative-execution-policy.max-executions = %d", - defaultMaxSpeculativeExecutions)); + builder = + builder.withInt( + DefaultDriverOption.SPECULATIVE_EXECUTION_MAX, defaultMaxSpeculativeExecutions); } if (defaultSpeculativeDelayMs != -1) { - config.add( - String.format( - "advanced.speculative-execution-policy.delay = %d milliseconds", - defaultSpeculativeDelayMs)); + builder = + builder.withDuration( + DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY, + Duration.ofMillis(defaultSpeculativeDelayMs)); } } else { - config.add("advanced.speculative-execution-policy.class = NoSpeculativeExecutionPolicy"); + builder = + builder.withClass( + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + NoSpeculativeExecutionPolicy.class); } + ProfileBuilder profile1 = DefaultDriverConfigLoaderBuilder.profileBuilder(); if (profile1MaxSpeculativeExecutions != -1 || profile1SpeculativeDelayMs != -1) { - config.add( - "profiles.profile1.advanced.speculative-execution-policy.class = ConstantSpeculativeExecutionPolicy"); + profile1 = + profile1.withClass( + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + ConstantSpeculativeExecutionPolicy.class); + if (profile1MaxSpeculativeExecutions != -1) { - config.add( - String.format( - "profiles.profile1.advanced.speculative-execution-policy.max-executions = %d", - profile1MaxSpeculativeExecutions)); + profile1 = + profile1.withInt( + DefaultDriverOption.SPECULATIVE_EXECUTION_MAX, profile1MaxSpeculativeExecutions); } if (profile1SpeculativeDelayMs != -1) { - config.add( - String.format( - "profiles.profile1.advanced.speculative-execution-policy.delay = %d milliseconds", - profile1SpeculativeDelayMs)); + profile1 = + profile1.withDuration( + DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY, + Duration.ofMillis(profile1SpeculativeDelayMs)); } } else { - config.add( - "profiles.profile1.advanced.speculative-execution-policy.class = NoSpeculativeExecutionPolicy"); + profile1 = + profile1.withClass( + DefaultDriverOption.SPECULATIVE_EXECUTION_POLICY_CLASS, + NoSpeculativeExecutionPolicy.class); } - config.add("profiles.profile2 = {}"); + builder = builder.withProfile("profile1", profile1.build()); + + builder = + builder.withProfile( + "profile2", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString(DefaultDriverOption.REQUEST_CONSISTENCY, "ONE") + .build()); - CqlSession session = SessionUtils.newSession(simulacron, config.toArray(new String[0])); + CqlSession session = SessionUtils.newSession(simulacron, builder.build()); // validate profile data DriverContext context = session.getContext(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java index b6a9312e21c..eb594fcb22f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryHostnameValidationIT.java @@ -16,9 +16,12 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import org.junit.ClassRule; import org.junit.Test; @@ -34,15 +37,18 @@ public class DefaultSslEngineFactoryHostnameValidationIT { */ @Test public void should_connect_if_hostname_validation_enabled_and_hostname_matches() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = true", - "advanced.ssl-engine-factory.truststore-path = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.truststore-password = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, true) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java index a6dc4bbc15b..cdc972f2dce 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryIT.java @@ -17,9 +17,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import org.junit.ClassRule; import org.junit.Test; @@ -29,15 +32,19 @@ public class DefaultSslEngineFactoryIT { @Test public void should_connect_with_ssl() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = false", - "advanced.ssl-engine-factory.truststore-path = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.truststore-password = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } @@ -46,25 +53,29 @@ public void should_connect_with_ssl() { public void should_not_connect_if_hostname_validation_enabled_and_hostname_does_not_match() { // should not succeed as certificate does not have a CN that would match hostname, // (unless hostname is node1). - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.truststore-path = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.truststore-password = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_if_truststore_not_provided() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = false")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java index 4670abe5992..98eeff1cab8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedIT.java @@ -16,10 +16,13 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; +import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -35,9 +38,11 @@ public void should_connect_with_ssl() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (CqlSession session = - SessionUtils.newSession( - ccm, "advanced.ssl-engine-factory.class = DefaultSslEngineFactory")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java index ca98f0071cb..e0fcdb81503 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryPropertyBasedWithClientAuthIT.java @@ -16,10 +16,13 @@ package com.datastax.oss.driver.api.core.ssl; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; +import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -39,11 +42,12 @@ public void should_connect_with_ssl_using_client_auth() { "javax.net.ssl.trustStore", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()); System.setProperty( "javax.net.ssl.trustStorePassword", CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD); - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = false")) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java index 1c8c3dd7c39..b0fd67b91ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ssl/DefaultSslEngineFactoryWithClientAuthIT.java @@ -17,9 +17,12 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import org.junit.ClassRule; import org.junit.Test; @@ -29,34 +32,42 @@ public class DefaultSslEngineFactoryWithClientAuthIT { @Test public void should_connect_with_ssl_using_client_auth() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = false", - "advanced.ssl-engine-factory.keystore-path = " - + CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.keystore-password = " - + CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD, - "advanced.ssl-engine-factory.truststore-path = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.truststore-password = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .withString( + DefaultDriverOption.SSL_KEYSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_KEYSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_KEYSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_KEYSTORE_PASSWORD) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } @Test(expected = AllNodesFailedException.class) public void should_not_connect_with_ssl_using_client_auth_if_keystore_not_set() { - try (CqlSession session = - SessionUtils.newSession( - ccm, - "advanced.ssl-engine-factory.class = DefaultSslEngineFactory", - "advanced.ssl-engine-factory.hostname-validation = false", - "advanced.ssl-engine-factory.truststore-path = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath(), - "advanced.ssl-engine-factory.truststore-password = " - + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + try (CqlSession session = SessionUtils.newSession(ccm, loader)) { session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java index eb0b6433976..5e2acc06fd2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/throttling/ThrottlingIT.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.RequestThrottlingException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler; @@ -45,13 +47,18 @@ public void should_reject_request_when_throttling_by_concurrency() { int maxConcurrentRequests = 10; int maxQueueSize = 10; - try (CqlSession session = - SessionUtils.newSession( - simulacron, - "advanced.throttler.class = " - + ConcurrencyLimitingRequestThrottler.class.getSimpleName(), - "advanced.throttler.max-concurrent-requests = " + maxConcurrentRequests, - "advanced.throttler.max-queue-size = " + maxQueueSize)) { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.REQUEST_THROTTLER_CLASS, + ConcurrencyLimitingRequestThrottler.class) + .withInt( + DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS, + maxConcurrentRequests) + .withInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE, maxQueueSize) + .build(); + + try (CqlSession session = SessionUtils.newSession(simulacron, loader)) { // Saturate the session and fill the queue for (int i = 0; i < maxConcurrentRequests + maxQueueSize; i++) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index c0de346e019..09b160ddbc6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -31,14 +31,19 @@ import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.codec.ConsistencyLevel; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.After; @@ -63,60 +68,75 @@ public class RequestLoggerIT { @Rule public SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + private final DefaultDriverConfigLoaderBuilder.Profile lowThresholdProfile = + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withDuration(DefaultDriverOption.REQUEST_LOGGER_SLOW_THRESHOLD, Duration.ofNanos(1)) + .build(); + + private final DefaultDriverConfigLoaderBuilder.Profile noLogsProfile = + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, false) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, false) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, false) + .build(); + + private final DefaultDriverConfigLoaderBuilder.Profile noTracesProfile = + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, false) + .build(); + + private final DriverConfigLoader requestLoader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.REQUEST_TRACKER_CLASS, RequestLogger.class) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, true) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, true) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, true) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, true) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 50) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 50) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, true) + .withProfile("low-threshold", lowThresholdProfile) + .withProfile("no-logs", noLogsProfile) + .withProfile("no-traces", noTracesProfile) + .build(); + @Rule public SessionRule sessionRuleRequest = - SessionRule.builder(simulacronRule) - .withOptions( - "advanced.request-tracker.class = com.datastax.oss.driver.internal.core.tracker.RequestLogger", - "advanced.request-tracker.logs.success.enabled = true", - "advanced.request-tracker.logs.slow.enabled = true", - "advanced.request-tracker.logs.error.enabled = true", - "advanced.request-tracker.logs.max-query-length = 500", - "advanced.request-tracker.logs.show-values = true", - "advanced.request-tracker.logs.max-value-length = 50", - "advanced.request-tracker.logs.max-values = 50", - "advanced.request-tracker.logs.show-stack-traces = true", - "advanced.request-tracker.logs.node-level = false", - "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", - "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.error.enabled = false", - "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") + SessionRule.builder(simulacronRule).withConfigLoader(requestLoader).build(); + + private final DriverConfigLoader nodeLoader = + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.REQUEST_TRACKER_CLASS, RequestNodeLoggerExample.class) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, true) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, true) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, true) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, true) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 50) + .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 50) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, true) + .withProfile("low-threshold", lowThresholdProfile) + .withProfile("no-logs", noLogsProfile) + .withProfile("no-traces", noTracesProfile) .build(); @Rule public SessionRule sessionRuleNode = - SessionRule.builder(simulacronRule) - .withOptions( - "basic.request.consistency = ONE", - "advanced.request-tracker.class = com.datastax.oss.driver.api.core.tracker.RequestNodeLoggerExample", - "advanced.request-tracker.logs.success.enabled = true", - "advanced.request-tracker.logs.slow.enabled = true", - "advanced.request-tracker.logs.error.enabled = true", - "advanced.request-tracker.logs.max-query-length = 500", - "advanced.request-tracker.logs.show-values = true", - "advanced.request-tracker.logs.max-value-length = 50", - "advanced.request-tracker.logs.max-values = 50", - "advanced.request-tracker.logs.show-stack-traces = true", - "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", - "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.error.enabled = false", - "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") - .build(); + SessionRule.builder(simulacronRule).withConfigLoader(nodeLoader).build(); @Rule public SessionRule sessionRuleDefaults = SessionRule.builder(simulacronRule) - .withOptions( - "advanced.request-tracker.class = com.datastax.oss.driver.internal.core.tracker.RequestLogger", - "advanced.request-tracker.logs.success.enabled = true", - "advanced.request-tracker.logs.error.enabled = true", - "profiles.low-threshold.advanced.request-tracker.logs.slow.threshold = 1 nanosecond", - "profiles.no-logs.advanced.request-tracker.logs.success.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.slow.enabled = false", - "profiles.no-logs.advanced.request-tracker.logs.error.enabled = false", - "profiles.no-traces.advanced.request-tracker.logs.show-stack-traces = false") + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withClass(DefaultDriverOption.REQUEST_TRACKER_CLASS, RequestLogger.class) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, true) + .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, true) + .withProfile("low-threshold", lowThresholdProfile) + .withProfile("no-logs", noLogsProfile) + .withProfile("no-traces", noTracesProfile) + .build()) .build(); @Captor private ArgumentCaptor loggingEventCaptor; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index a540898a84a..4547823f378 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericTypeParameter; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.type.codec.IntCodec; import edu.umd.cs.findbugs.annotations.NonNull; @@ -56,7 +57,7 @@ public class CodecRegistryIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = new SessionRule<>(ccm); + @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); @Rule public TestName name = new TestName(); @@ -162,11 +163,12 @@ public void should_throw_exception_if_no_codec_registered_for_type_get() { public void should_be_able_to_register_and_use_custom_codec() { // create a cluster with a registered codec from Float <-> cql int. try (CqlSession session = - CqlSession.builder() - .addTypeCodecs(new FloatCIntCodec()) - .addContactPoints(ccm.getContactPoints()) - .withKeyspace(sessionRule.keyspace()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addTypeCodecs(new FloatCIntCodec()) + .addContactPoints(ccm.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) + .build()) { PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); // float value for int column should work. @@ -281,11 +283,12 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() TypeCodecs.mapOf(TypeCodecs.INT, new OptionalCodec<>(TypeCodecs.TEXT)); try (CqlSession session = - CqlSession.builder() - .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) - .addContactPoints(ccm.getContactPoints()) - .withKeyspace(sessionRule.keyspace()) - .build()) { + (CqlSession) + SessionUtils.baseBuilder() + .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) + .addContactPoints(ccm.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) + .build()) { PreparedStatement prepared = session.prepare("INSERT INTO test2 (k0, k1, v) values (?, ?, ?)"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index e1b01456c16..1e0b995d42d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -36,6 +36,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -45,7 +46,9 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.CloseType; @@ -70,11 +73,17 @@ public class DefaultRetryPolicyIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + public @Rule SessionRule sessionRule = - new SessionRule<>( - simulacron, - "basic.request.default-idempotence = true", - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy"); + SessionRule.builder(simulacron) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true) + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, + SortingLoadBalancingPolicy.class) + .build()) + .build(); private static String queryStr = "select * from foo"; private static final SimpleStatement query = SimpleStatement.builder(queryStr).build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java index fc32fdedeba..32884e4ca0a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java @@ -24,13 +24,13 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; import com.google.common.collect.ObjectArrays; import org.junit.ClassRule; import org.junit.Test; @@ -65,8 +65,10 @@ public Option[] config() { Option.class); } - /** @return Additional options that should be used during session construction. */ - public abstract String[] sessionOptions(); + /** @return config loader to be used to create session. */ + public DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder().build(); + } /** * A very simple test that ensures a session can be established and a query made when running in @@ -80,7 +82,7 @@ public void should_connect_and_query() { .addContactPoints(ccmRule.getContactPoints()) // use the driver's ClassLoader instead of the OSGI application thread's. .withClassLoader(CqlSession.class.getClassLoader()) - .withConfigLoader(new TestConfigLoader(sessionOptions())); + .withConfigLoader(configLoader()); try (CqlSession session = builder.build()) { ResultSet result = session.execute(selectFrom("system", "local").all().build()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java index a5eea5b5116..b151c21ad58 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java @@ -15,6 +15,10 @@ */ package com.datastax.oss.driver.osgi; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import org.ops4j.pax.exam.Option; /** @@ -29,9 +33,10 @@ public Option[] additionalOptions() { } @Override - public String[] sessionOptions() { - return new String[] { - "basic.load-balancing-policy.class = com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy" - }; + public DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, SortingLoadBalancingPolicy.class) + .build(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java index a49dd31b510..7c59471df13 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java @@ -23,9 +23,4 @@ public class OsgiIT extends OsgiBaseIT { public Option[] additionalOptions() { return new Option[0]; } - - @Override - public String[] sessionOptions() { - return new String[0]; - } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java index 1c1e72cac1f..4b2c74aebdf 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java @@ -18,6 +18,9 @@ import static com.datastax.oss.driver.osgi.BundleOptions.lz4Bundle; import static org.ops4j.pax.exam.CoreOptions.options; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import org.ops4j.pax.exam.Option; public class OsgiLz4IT extends OsgiBaseIT { @@ -28,7 +31,9 @@ public Option[] additionalOptions() { } @Override - public String[] sessionOptions() { - return new String[] {"advanced.protocol.compression = lz4"}; + public DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, "lz4") + .build(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java index 29f69451bbe..9f069197706 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java @@ -39,9 +39,4 @@ public Option[] config() { public Option[] additionalOptions() { return new Option[0]; } - - @Override - public String[] sessionOptions() { - return new String[0]; - } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java index 696562183a3..fcf56cfe5f3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java @@ -18,6 +18,9 @@ import static com.datastax.oss.driver.osgi.BundleOptions.snappyBundle; import static org.ops4j.pax.exam.CoreOptions.options; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import org.ops4j.pax.exam.Option; public class OsgiSnappyIT extends OsgiBaseIT { @@ -28,7 +31,9 @@ public Option[] additionalOptions() { } @Override - public String[] sessionOptions() { - return new String[] {"advanced.protocol.compression = snappy"}; + public DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, "snappy") + .build(); } } diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf index 989f23b1f66..275b0be511b 100644 --- a/integration-tests/src/test/resources/application.conf +++ b/integration-tests/src/test/resources/application.conf @@ -25,5 +25,14 @@ datastax-java-driver { session.cql_requests.highest_latency = 30 seconds node.cql_messages.highest_latency = 30 seconds } + // adjust quiet period to 0 seconds to speed up tests + netty { + io-group { + shutdown {quiet-period = 0, timeout = 15, unit = SECONDS} + } + admin-group { + shutdown {quiet-period = 0, timeout = 15, unit = SECONDS} + } + } } } diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index cdf440e59a0..636c636442f 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -146,10 +146,6 @@ Duration requestTimeout = defaultProfile.getDuration(DefaultDriverOption.REQUEST int maxRequestsPerConnection = defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); ``` -Note that we use the [DefaultDriverOption] enum to access built-in options, but the method takes a -more generic [DriverOption] interface. This is intended to allow custom options, see the -[Advanced topics](#custom-options) section. - #### Derived profiles Execution profiles are hard-coded in the configuration, and can't be changed at runtime (except @@ -185,6 +181,44 @@ least, try to cache derived profiles if you reuse them multiple times. *Note: all the features described in this section use the driver's internal API, which is subject to the restrictions explained in [API conventions]*. +#### Overriding configuration programmatically + +In some cases, an application may call for providing configuration programmatically. For example, +if configuration is determined at runtime or is derived from some other configuration source. +The driver includes [DefaultDriverConfigLoaderBuilder] for this very purpose, which may be used in +the following manner: + +```java +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; +import java.time.Duration; + +DefaultDriverConfigLoaderBuilder configBuilder = + DefaultDriverConfigLoader.builder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(500)) + .withProfile( + "profile1", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString( + DefaultDriverOption.REQUEST_CONSISTENCY, + DefaultConsistencyLevel.EACH_QUORUM.name()) + .build()); + +CqlSession session = CqlSession.builder() + .withConfigLoader(configBuilder.build()) + .build(); +``` + +Note that any options provided to the builder will override values defined in configuration files +and do not change the contents of the configuration files. + +Also note that we use the [DefaultDriverOption] enum to access built-in options, but the method +takes a more generic [DriverOption] interface. This is intended to allow custom options, see the +[Custom options](#custom-options) section. + #### Changing the config prefix As mentioned earlier, all configuration options are looked up under the `datastax-java-driver` @@ -433,12 +467,13 @@ config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL); config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); ``` -[DriverConfig]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html -[DriverExecutionProfile]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.html -[DriverContext]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/context/DriverContext.html -[DriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html -[DefaultDriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html -[DriverConfigLoader]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html +[DriverConfig]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html +[DriverConfigProfile]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigProfile.html +[DriverContext]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/context/DriverContext.html +[DriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html +[DefaultDriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html +[DriverConfigLoader]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigLoader.html +[DefaultDriverConfigLoaderBuilder]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderBuilder.html [Typesafe Config]: https://github.com/typesafehub/config [config standard behavior]: https://github.com/typesafehub/config#standard-behavior diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java index f68df8cf93e..3d864ab007c 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/CqlSessionRuleBuilder.java @@ -27,6 +27,6 @@ public CqlSessionRuleBuilder(CassandraResourceRule cassandraResource) { @Override public SessionRule build() { return new SessionRule<>( - cassandraResource, createKeyspace, nodeStateListener, schemaChangeListener, options); + cassandraResource, createKeyspace, nodeStateListener, schemaChangeListener, loader); } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java deleted file mode 100644 index 67898e10e07..00000000000 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/DefaultSessionBuilderInstantiator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.api.testinfra.session; - -import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.session.SessionBuilder; - -public class DefaultSessionBuilderInstantiator { - public static SessionBuilder builder() { - return CqlSession.builder(); - } - - public static String configPath() { - return "datastax-java-driver"; - } -} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java index 878118525e8..766ce4bdc84 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.NoNodeAvailableException; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -59,7 +60,7 @@ public class SessionRule extends ExternalResource { private final NodeStateListener nodeStateListener; private final SchemaChangeListener schemaChangeListener; private final CqlIdentifier keyspace; - private final String[] defaultOptions; + private final DriverConfigLoader configLoader; // the session that is auto created for this rule and is tied to the given keyspace. private SessionT session; @@ -76,17 +77,12 @@ public static CqlSessionRuleBuilder builder(CassandraResourceRule cassandraResou } /** @see #builder(CassandraResourceRule) */ - public SessionRule(CassandraResourceRule cassandraResource, String... options) { - this(cassandraResource, true, null, null, options); - } - - /** @see #builder(CassandraResourceRule) */ - public SessionRule( + SessionRule( CassandraResourceRule cassandraResource, boolean createKeyspace, NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, - String... options) { + DriverConfigLoader configLoader) { this.cassandraResource = cassandraResource; this.nodeStateListener = nodeStateListener; this.schemaChangeListener = schemaChangeListener; @@ -94,7 +90,7 @@ public SessionRule( (cassandraResource instanceof SimulacronRule || !createKeyspace) ? null : SessionUtils.uniqueKeyspaceId(); - this.defaultOptions = options; + this.configLoader = configLoader; } @Override @@ -104,7 +100,7 @@ protected void before() { session = SessionUtils.newSession( - cassandraResource, null, nodeStateListener, schemaChangeListener, null, defaultOptions); + cassandraResource, null, nodeStateListener, schemaChangeListener, null, configLoader); slowProfile = SessionUtils.slowProfile(session); if (keyspace != null) { SessionUtils.createKeyspace(session, keyspace, slowProfile); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java index 68c4aef4f27..5bc56b0c4a2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRuleBuilder.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.testinfra.session; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.session.Session; @@ -27,9 +28,9 @@ public abstract class SessionRuleBuilder< protected final CassandraResourceRule cassandraResource; protected boolean createKeyspace = true; - protected String[] options = new String[] {}; protected NodeStateListener nodeStateListener; protected SchemaChangeListener schemaChangeListener; + protected DriverConfigLoader loader; @SuppressWarnings("unchecked") protected final SelfT self = (SelfT) this; @@ -58,8 +59,8 @@ public SelfT withKeyspace(boolean createKeyspace) { } /** A set of options to override in the session configuration. */ - public SelfT withOptions(String... options) { - this.options = options; + public SelfT withConfigLoader(DriverConfigLoader loader) { + this.loader = loader; return self; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 0e1d4a3c500..d2f73b94ca4 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; @@ -27,7 +28,8 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; -import com.datastax.oss.driver.internal.testinfra.session.TestConfigLoader; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; @@ -61,11 +63,9 @@ public class SessionUtils { private static final Logger LOG = LoggerFactory.getLogger(SessionUtils.class); private static final AtomicInteger keyspaceId = new AtomicInteger(); - + private static final String DEFAULT_SESSION_CLASS_NAME = CqlSession.class.getName(); private static final String SESSION_BUILDER_CLASS = - System.getProperty( - "session.builder", - "com.datastax.oss.driver.api.testinfra.session.DefaultSessionBuilderInstantiator"); + System.getProperty("session.builder", DEFAULT_SESSION_CLASS_NAME); @SuppressWarnings("unchecked") public static SessionBuilder baseBuilder() { @@ -75,21 +75,29 @@ public static SessionBuilder baseBuilder return (SessionBuilder) m.invoke(null); } catch (Exception e) { LOG.warn( - "Could not construct SessionBuilder from {}, using default implementation.", + "Could not construct SessionBuilder from {} using builder(), using default " + + "implementation.", SESSION_BUILDER_CLASS, e); return (SessionBuilder) CqlSession.builder(); } } - public static String getConfigPath() { + /** @leaks-private-api Tests use this for programmatic config loading. */ + public static DefaultDriverConfigLoaderBuilder configLoaderBuilder() { try { Class clazz = Class.forName(SESSION_BUILDER_CLASS); - Method m = clazz.getMethod("configPath"); - return (String) m.invoke(null); + Method m = clazz.getMethod("configLoaderBuilder"); + return (DefaultDriverConfigLoaderBuilder) m.invoke(null); } catch (Exception e) { - LOG.warn("Could not get config path from {}, using default.", SESSION_BUILDER_CLASS, e); - return "datastax-java-driver"; + if (!SESSION_BUILDER_CLASS.equals(DEFAULT_SESSION_CLASS_NAME)) { + LOG.warn( + "Could not construct DefaultDriverConfigLoaderBuilder from {} using " + + "configLoaderBuilder(), using default implementation.", + SESSION_BUILDER_CLASS, + e); + } + return DefaultDriverConfigLoader.builder(); } } @@ -100,24 +108,36 @@ public static String getConfigPath() { */ @SuppressWarnings("TypeParameterUnusedInFormals") public static SessionT newSession( - CassandraResourceRule cassandraResource, String... options) { - return newSession(cassandraResource, null, null, null, null, options); + CassandraResourceRule cassandraResource) { + return newSession(cassandraResource, null, null); } @SuppressWarnings("TypeParameterUnusedInFormals") public static SessionT newSession( - CassandraResourceRule cassandraResource, CqlIdentifier keyspace, String... options) { - return newSession(cassandraResource, keyspace, null, null, null, options); + CassandraResourceRule cassandraResource, CqlIdentifier keyspace) { + return newSession(cassandraResource, keyspace, null, null, null); } - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) + @SuppressWarnings("TypeParameterUnusedInFormals") + public static SessionT newSession( + CassandraResourceRule cassandraResourceRule, DriverConfigLoader loader) { + return newSession(cassandraResourceRule, null, null, null, null, loader); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") public static SessionT newSession( + CassandraResourceRule cassandraResourceRule, + CqlIdentifier keyspace, + DriverConfigLoader loader) { + return newSession(cassandraResourceRule, keyspace, null, null, null, loader); + } + + private static SessionBuilder builder( CassandraResourceRule cassandraResource, CqlIdentifier keyspace, NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, - Predicate nodeFilter, - String... options) { + Predicate nodeFilter) { SessionBuilder builder = baseBuilder() .addContactPoints(cassandraResource.getContactPoints()) @@ -127,7 +147,32 @@ public static SessionT newSession( if (nodeFilter != null) { builder = builder.withNodeFilter(nodeFilter); } - return (SessionT) builder.withConfigLoader(new TestConfigLoader(options)).build(); + return builder; + } + + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) + public static SessionT newSession( + CassandraResourceRule cassandraResource, + CqlIdentifier keyspace, + NodeStateListener nodeStateListener, + SchemaChangeListener schemaChangeListener, + Predicate nodeFilter) { + SessionBuilder builder = + builder(cassandraResource, keyspace, nodeStateListener, schemaChangeListener, nodeFilter); + return (SessionT) builder.build(); + } + + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) + public static SessionT newSession( + CassandraResourceRule cassandraResource, + CqlIdentifier keyspace, + NodeStateListener nodeStateListener, + SchemaChangeListener schemaChangeListener, + Predicate nodeFilter, + DriverConfigLoader loader) { + SessionBuilder builder = + builder(cassandraResource, keyspace, nodeStateListener, schemaChangeListener, nodeFilter); + return (SessionT) builder.withConfigLoader(loader).build(); } /** diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java deleted file mode 100644 index 5b286ef2f24..00000000000 --- a/test-infra/src/main/java/com/datastax/oss/driver/internal/testinfra/session/TestConfigLoader.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.testinfra.session; - -import com.datastax.oss.driver.api.testinfra.session.SessionUtils; -import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; - -public class TestConfigLoader extends DefaultDriverConfigLoader { - - public TestConfigLoader(String... customOptions) { - super(() -> buildConfig(customOptions)); - } - - private static Config buildConfig(String... customOptions) { - String customConfig = String.join("\n", customOptions); - // Add additional config for overriding quiet period on netty shutdown. - String additionalCustomConfig = - String.join( - "\n", - customConfig, - "advanced.netty.io-group.shutdown.quiet-period = 0", - "advanced.netty.admin-group.shutdown.quiet-period = 0"); - return ConfigFactory.parseString(additionalCustomConfig).withFallback(getConfig()); - } - - public static Config getConfig() { - ConfigFactory.invalidateCaches(); - return ConfigFactory.load().getConfig(SessionUtils.getConfigPath()); - } -} From 05eda926d67d5acc19dd98e22b6a3d5a4ef741a8 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Fri, 13 Jul 2018 18:26:40 -0500 Subject: [PATCH 524/742] Make SessionRule constructor public --- .../datastax/oss/driver/api/testinfra/session/SessionRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java index 766ce4bdc84..da214af9b51 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java @@ -77,7 +77,7 @@ public static CqlSessionRuleBuilder builder(CassandraResourceRule cassandraResou } /** @see #builder(CassandraResourceRule) */ - SessionRule( + public SessionRule( CassandraResourceRule cassandraResource, boolean createKeyspace, NodeStateListener nodeStateListener, From 31abbec2bb8061885e4b07e6af0d1881b8cbf188 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 16 Jul 2018 09:54:17 -0700 Subject: [PATCH 525/742] Revert Node.getLastResponseTimeNanos() This is a bit too specific to be included in the driver out of the box. It can still be implemented relatively easily with a custom RequestTracker. --- .../api/core/config/DefaultDriverOption.java | 1 - .../oss/driver/api/core/metadata/Node.java | 18 -------- .../core/cql/CqlPrepareHandlerBase.java | 7 --- .../core/cql/CqlRequestHandlerBase.java | 9 +--- .../internal/core/metadata/DefaultNode.java | 12 ------ core/src/main/resources/reference.conf | 14 ------ .../api/core/metadata/NodeMetadataIT.java | 43 ------------------- 7 files changed, 1 insertion(+), 103 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 21c43c5bc53..a9800278f3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -142,7 +142,6 @@ public enum DefaultDriverOption implements DriverOption { METADATA_SCHEMA_WINDOW("advanced.metadata.schema.debouncer.window"), METADATA_SCHEMA_MAX_EVENTS("advanced.metadata.schema.debouncer.max-events"), METADATA_TOKEN_MAP_ENABLED("advanced.metadata.token-map.enabled"), - METADATA_LAST_RESPONSE_TIME_ENABLED("advanced.metadata.nodes.last-response-time.enabled"), CONTROL_CONNECTION_TIMEOUT("advanced.control-connection.timeout"), CONTROL_CONNECTION_AGREEMENT_INTERVAL("advanced.control-connection.schema-agreement.interval"), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index d39339ed306..031d3474062 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -17,7 +17,6 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; -import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import edu.umd.cs.findbugs.annotations.NonNull; @@ -156,21 +155,4 @@ public interface Node { */ @Nullable UUID getSchemaVersion(); - - /** - * The last time that the driver received a response from this node, or -1 if it has not responded - * yet. - * - *

              This is recorded with {@link System#nanoTime()}, and should only be used to measure elapsed - * time. - * - *

              Note that this feature is controlled by a configuration option ({@code - * advanced.metadata.nodes.last-response-time.enabled}) and disabled by default (i.e. will - * always return -1). You might want to enable it for information purposes, or for use in custom - * policy implementations. It incurs a bit of overhead (namely, a volatile write for every - * request). - * - * @see DefaultDriverOption#METADATA_LAST_RESPONSE_TIME_ENABLED - */ - long getLastResponseTimeNanos(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index bd64364be1e..7f944fd1377 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -42,7 +42,6 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -97,7 +96,6 @@ public abstract class CqlPrepareHandlerBase implements Throttled { private final RequestThrottler throttler; private final Boolean prepareOnAllNodes; private volatile InitialPrepareCallback initialCallback; - private final boolean recordLastResponseTime; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -129,8 +127,6 @@ protected CqlPrepareHandlerBase( ? config.getDefaultProfile() : config.getProfile(profileName); } - this.recordLastResponseTime = - executionProfile.getBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, false); this.queryPlan = context .loadBalancingPolicyWrapper() @@ -407,9 +403,6 @@ public void operationComplete(Future future) { @Override public void onResponse(Frame responseFrame) { - if (recordLastResponseTime) { - ((DefaultNode) node).setLastResponseTimeNanos(System.nanoTime()); - } if (result.isDone()) { return; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 9ccafced206..db450bfa6f3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -123,7 +123,6 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; private final RequestThrottler throttler; - private final boolean recordLastResponseTime; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -166,8 +165,6 @@ protected CqlRequestHandlerBase( (statementIsIdempotent == null) ? executionProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE) : statementIsIdempotent; - this.recordLastResponseTime = - executionProfile.getBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, false); this.result = new CompletableFuture<>(); this.result.exceptionally( t -> { @@ -499,16 +496,12 @@ public void operationComplete(Future future) throws Exception { @Override public void onResponse(Frame responseFrame) { - long now = System.nanoTime(); - if (recordLastResponseTime) { - ((DefaultNode) node).setLastResponseTimeNanos(now); - } ((DefaultNode) node) .getMetricUpdater() .updateTimer( DefaultNodeMetric.CQL_MESSAGES, executionProfile.getName(), - now - start, + System.nanoTime() - start, TimeUnit.NANOSECONDS); inFlightCallbacks.remove(this); if (result.isDone()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 9f430b7299b..700a4c3a8c0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -59,8 +59,6 @@ public class DefaultNode implements Node { volatile NodeDistance distance; - private volatile long lastResponseTimeNanos; - public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext context) { this.connectAddress = connectAddress; this.state = NodeState.UNKNOWN; @@ -71,7 +69,6 @@ public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext conte // problem because the node updater only needs the connect address to initialize. this.metricUpdater = context.metricsFactory().newNodeUpdater(this); this.upSinceMillis = -1; - this.lastResponseTimeNanos = -1; } @NonNull @@ -159,15 +156,6 @@ public NodeMetricUpdater getMetricUpdater() { return metricUpdater; } - @Override - public long getLastResponseTimeNanos() { - return lastResponseTimeNanos; - } - - public void setLastResponseTimeNanos(long lastResponseTimeNanos) { - this.lastResponseTimeNanos = lastResponseTimeNanos; - } - @Override public boolean equals(Object other) { if (other == this) { diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 31f6093bffb..b7e529a1227 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -1174,20 +1174,6 @@ datastax-java-driver { # Modifiable at runtime: yes, the new value will be used for refreshes issued after the change. # Overridable in a profile: no token-map.enabled = true - - nodes { - # Whether to record the last time that the driver received a response from each node. - # - # If enabled, the driver will track the time that each node replies as part of a - # session.execute() or session.prepare() call, and store this information in - # Node.getLastResponseTimeNanos(). - # If disabled, Node.getLastResponseTimeNanos() will always return -1; - # - # Required: no (will default to false if absent) - # Modifiable at runtime: yes, the new value will be used for requests issued after the change. - # Overridable in a profile: yes (only the requests in this profile will update the field) - last-response-time.enabled = false - } } advanced.control-connection { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index b93a1304bc6..502f52de155 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -18,11 +18,9 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; -import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -64,7 +62,6 @@ public void should_expose_node_metadata() { assertThat(node.getSchemaVersion()).isNotNull(); long upTime1 = node.getUpSinceMillis(); assertThat(upTime1).isGreaterThan(-1); - assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); // Note: open connections and reconnection status are covered in NodeStateIT @@ -78,46 +75,6 @@ public void should_expose_node_metadata() { assertThat(node.getUpSinceMillis()).isGreaterThan(upTime1); } - @Test - public void should_not_record_last_response_time_if_disabled() { - CqlSession session = sessionRule.session(); - Node node = getUniqueNode(session); - assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); - - for (int i = 0; i < 10; i++) { - session.execute("SELECT release_version FROM system.local"); - assertThat(node.getLastResponseTimeNanos()).isEqualTo(-1); - } - } - - @Test - public void should_record_last_response_time_if_enabled() { - try (CqlSession session = - SessionUtils.newSession( - ccmRule, - SessionUtils.configLoaderBuilder() - .withBoolean(DefaultDriverOption.METADATA_LAST_RESPONSE_TIME_ENABLED, true) - .build())) { - Node node = getUniqueNode(session); - - // Ensure that we get increasing timestamps as long as we keep querying - long[] timestamps = new long[10]; - timestamps[0] = System.nanoTime(); - for (int i = 1; i < 9; i++) { - session.execute("SELECT release_version FROM system.local"); - timestamps[i] = node.getLastResponseTimeNanos(); - } - timestamps[9] = System.nanoTime(); - - for (int i = 0; i < 9; i++) { - assertThat(timestamps[i]).isLessThan(timestamps[i + 1]); - } - - // Ensure that the value doesn't change since the last query - assertThat(node.getLastResponseTimeNanos()).isEqualTo(timestamps[8]); - } - } - private static Node getUniqueNode(CqlSession session) { Collection nodes = session.getMetadata().getNodes().values(); assertThat(nodes).hasSize(1); From 4e760fa55a578b805b0e0c9fe07a4410974809ba Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 20 Jul 2018 10:47:38 -0700 Subject: [PATCH 526/742] Update version in docs --- README.md | 2 +- changelog/README.md | 2 +- manual/core/README.md | 2 +- manual/core/compression/README.md | 2 +- manual/core/integration/README.md | 2 +- manual/core/shaded_jar/README.md | 6 +++--- manual/query_builder/README.md | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 124fee5589b..8f913e1879f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-alpha3](https://github.com/datastax/java-driver/tree/4.0.0-alpha3).* +[4.0.0-beta1](https://github.com/datastax/java-driver/tree/4.0.0-beta1).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and [DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query diff --git a/changelog/README.md b/changelog/README.md index ce742ba068e..0df14b8a169 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-beta1 (in progress) +### 4.0.0-beta1 - [new feature] JAVA-1869: Add DefaultDriverConfigLoaderBuilder - [improvement] JAVA-1913: Expose additional counters on Node diff --git a/manual/core/README.md b/manual/core/README.md index 2c85269d6e8..886fd68b0ed 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-alpha3 + 4.0.0-beta1 ``` diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index 70ca2537ea8..2180c52782f 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -65,4 +65,4 @@ Dependency: Always double-check the exact Snappy version needed; you can find it in the driver's [parent POM]. -[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-alpha3%7Cpom \ No newline at end of file +[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta1%7Cpom \ No newline at end of file diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 06dcdbc620e..4df5a73097b 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -176,7 +176,7 @@ repositories { } dependencies { - compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-alpha4-SNAPSHOT' + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta1' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } ``` diff --git a/manual/core/shaded_jar/README.md b/manual/core/shaded_jar/README.md index b750cf432c1..180e220c744 100644 --- a/manual/core/shaded_jar/README.md +++ b/manual/core/shaded_jar/README.md @@ -12,7 +12,7 @@ package name: com.datastax.oss java-driver-core-shaded - 4.0.0-alpha3 + 4.0.0-beta1 ``` @@ -23,12 +23,12 @@ dependency to the non-shaded JAR: com.datastax.oss java-driver-core-shaded - 4.0.0-alpha3 + 4.0.0-beta1 com.datastax.oss java-driver-query-builder - 4.0.0-alpha3 + 4.0.0-beta1 com.datastax.oss diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index d817c944951..c3a52e45984 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -14,7 +14,7 @@ To use it in your application, add the following dependency: com.datastax.oss java-driver-query-builder - 4.0.0-alpha3 + 4.0.0-beta1 ``` From 59d0f5c593239089d9c319d2b508e61f6145fdee Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 20 Jul 2018 11:01:30 -0700 Subject: [PATCH 527/742] Bump native-protocol to 1.4.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51ad7aaad82..da0ca5334c5 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 25.1-jre 2.1.10 4.0.2 - 1.4.3-SNAPSHOT + 1.4.3 4.1.27.Final 1.7.25 From cfea08cf757a9be89bd0f242cf9966edf987621d Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 20 Jul 2018 11:41:42 -0700 Subject: [PATCH 528/742] [maven-release-plugin] prepare release 4.0.0-beta1 --- core-shaded/pom.xml | 6 ++---- core/pom.xml | 9 +++------ distribution/pom.xml | 6 ++---- integration-tests/pom.xml | 2 +- pom.xml | 8 +++----- query-builder/pom.xml | 6 ++---- test-infra/pom.xml | 2 +- 7 files changed, 14 insertions(+), 25 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 688dc24a1b3..c6bc597394d 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -16,15 +16,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 118876848ff..5adb47af9b9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-core @@ -270,8 +268,7 @@ - + META-INF/maven/org.jctools/jctools-core/pom.properties META-INF/maven/org.jctools/jctools-core/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 8b3af2f6bac..6f1ecb34dd9 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 82d75ac9c7a..c492f69f602 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index da0ca5334c5..55903be85f8 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,12 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 pom DataStax Java driver for Apache Cassandra(R) @@ -585,7 +583,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-beta1 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index ae1c6890cfd..e4a1c82d428 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 19b9ac19bcc..bae139254e0 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1-SNAPSHOT + 4.0.0-beta1 java-driver-test-infra From 068cded86fff33cd02107e31e2845eb12dd1a928 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 20 Jul 2018 11:43:17 -0700 Subject: [PATCH 529/742] [maven-release-plugin] prepare for next development iteration --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index c6bc597394d..d6765c15b71 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 5adb47af9b9..b98e13db5a3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index 6f1ecb34dd9..bed09179d7c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c492f69f602..426a8ac4ec6 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 55903be85f8..0d756ab82df 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT pom DataStax Java driver for Apache Cassandra(R) @@ -583,7 +583,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-beta1 + HEAD diff --git a/query-builder/pom.xml b/query-builder/pom.xml index e4a1c82d428..48965f2f0ec 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index bae139254e0..ffefd68fa98 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta1 + 4.0.0-beta2-SNAPSHOT java-driver-test-infra From c103fc73536ccbf64cf1fefb126e011cba5f1caa Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 20 Jul 2018 16:36:00 -0700 Subject: [PATCH 530/742] Prepare changelog for 4.0.0-beta2 --- changelog/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 0df14b8a169..92441a3b971 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,6 +2,9 @@ +### 4.0.0-beta2 (in progress) + + ### 4.0.0-beta1 - [new feature] JAVA-1869: Add DefaultDriverConfigLoaderBuilder From 0df59acbd263c4649f1d9164605ed091028266d2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 13 Jul 2018 13:18:40 -0700 Subject: [PATCH 531/742] JAVA-1544: Check API compatibility with Revapi --- changelog/README.md | 1 + core-shaded/pom.xml | 7 +++++++ core/revapi.json | 16 ++++++++++++++++ distribution/pom.xml | 7 +++++++ integration-tests/pom.xml | 7 +++++++ pom.xml | 29 +++++++++++++++++++++++++++++ query-builder/revapi.json | 16 ++++++++++++++++ test-infra/revapi.json | 16 ++++++++++++++++ 8 files changed, 99 insertions(+) create mode 100644 core/revapi.json create mode 100644 query-builder/revapi.json create mode 100644 test-infra/revapi.json diff --git a/changelog/README.md b/changelog/README.md index 92441a3b971..218797ef621 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1544: Check API compatibility with Revapi ### 4.0.0-beta1 diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index d6765c15b71..abc941c101a 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -121,6 +121,13 @@ + + org.revapi + revapi-maven-plugin + + true + + diff --git a/core/revapi.json b/core/revapi.json new file mode 100644 index 00000000000..3925316a9f1 --- /dev/null +++ b/core/revapi.json @@ -0,0 +1,16 @@ +// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between +// successive driver versions. +{ + "revapi": { + "java": { + "filter": { + "packages": { + "regex": true, + "include": [ + "com\\.datastax\\.oss\\.driver\\.api\\..*" + ] + } + } + } + } +} diff --git a/distribution/pom.xml b/distribution/pom.xml index bed09179d7c..39f25716830 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -90,6 +90,13 @@ true + + org.revapi + revapi-maven-plugin + + true + + diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 426a8ac4ec6..75b410a4969 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -216,6 +216,13 @@ + + org.revapi + revapi-maven-plugin + + true + + diff --git a/pom.xml b/pom.xml index 0d756ab82df..016d5e72707 100644 --- a/pom.xml +++ b/pom.xml @@ -289,6 +289,18 @@ maven-bundle-plugin 3.5.0 + + org.revapi + revapi-maven-plugin + 0.10.4 + + + org.revapi + revapi-java + 0.18.0 + + + @@ -542,6 +554,23 @@ limitations under the License.]]> + + org.revapi + revapi-maven-plugin + + + + check + + + + + revapi.json + + + + + diff --git a/query-builder/revapi.json b/query-builder/revapi.json new file mode 100644 index 00000000000..3925316a9f1 --- /dev/null +++ b/query-builder/revapi.json @@ -0,0 +1,16 @@ +// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between +// successive driver versions. +{ + "revapi": { + "java": { + "filter": { + "packages": { + "regex": true, + "include": [ + "com\\.datastax\\.oss\\.driver\\.api\\..*" + ] + } + } + } + } +} diff --git a/test-infra/revapi.json b/test-infra/revapi.json new file mode 100644 index 00000000000..3925316a9f1 --- /dev/null +++ b/test-infra/revapi.json @@ -0,0 +1,16 @@ +// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between +// successive driver versions. +{ + "revapi": { + "java": { + "filter": { + "packages": { + "regex": true, + "include": [ + "com\\.datastax\\.oss\\.driver\\.api\\..*" + ] + } + } + } + } +} From 00a463896f48bf9f45a45d7687e7c9f681e99b04 Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Mon, 30 Jul 2018 14:49:51 -0500 Subject: [PATCH 532/742] JAVA-1900: Support virtual tables (aka system views) (#1054) * Java-1900 Adding system views --- changelog/README.md | 1 + core/revapi.json | 101 +++++++++++++++++- .../datastax/oss/driver/api/core/Version.java | 1 + .../metadata/schema/KeyspaceMetadata.java | 31 ++++-- .../metadata/schema/RelationMetadata.java | 3 +- .../core/metadata/schema/TableMetadata.java | 33 ++++-- .../schema/DefaultKeyspaceMetadata.java | 8 ++ .../metadata/schema/DefaultTableMetadata.java | 21 ++-- .../metadata/schema/DefaultViewMetadata.java | 6 +- .../schema/parsing/CassandraSchemaParser.java | 41 +++++++ .../metadata/schema/parsing/TableParser.java | 58 ++++++++++ .../queries/Cassandra21SchemaQueries.java | 15 +++ .../queries/Cassandra22SchemaQueries.java | 15 +++ .../queries/Cassandra3SchemaQueries.java | 15 +++ .../queries/Cassandra4SchemaQueries.java | 49 +++++++++ .../queries/CassandraSchemaQueries.java | 12 +++ .../schema/queries/CassandraSchemaRows.java | 51 +++++++++ .../queries/DefaultSchemaQueriesFactory.java | 4 +- .../metadata/schema/queries/SchemaRows.java | 6 ++ .../schema/refresh/SchemaRefreshTest.java | 1 + .../driver/api/core/metadata/SchemaIT.java | 59 ++++++++++ query-builder/revapi.json | 8 +- test-infra/revapi.json | 7 +- .../driver/api/testinfra/ccm/CcmBridge.java | 5 + 24 files changed, 509 insertions(+), 42 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java diff --git a/changelog/README.md b/changelog/README.md index 218797ef621..969bd60ac63 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -5,6 +5,7 @@ ### 4.0.0-beta2 (in progress) - [improvement] JAVA-1544: Check API compatibility with Revapi +- [new feature] JAVA-1900: Add support for virtual tables ### 4.0.0-beta1 diff --git a/core/revapi.json b/core/revapi.json index 3925316a9f1..c434321f49c 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -6,11 +6,104 @@ "filter": { "packages": { "regex": true, - "include": [ - "com\\.datastax\\.oss\\.driver\\.api\\..*" - ] + "exclude": [ + "com\\.datastax\\.oss\\.driver\\.internal\\..*", + "com\\.datastax\\.oss\\.driver\\.shaded\\..*" + ] } } + }, + "ignore": [ + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::isVirtual()", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata", + "classSimpleName": "KeyspaceMetadata", + "methodName": "isVirtual", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.method.returnTypeChanged", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "oldType": "java.util.UUID", + "newType": "java.util.Optional", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", + "classSimpleName": "RelationMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", + "classSimpleName": "RelationMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification":"Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "classSimpleName": "TableMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::isVirtual()", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "classSimpleName": "TableMetadata", + "methodName": "isVirtual", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "classSimpleName": "ViewMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.class.externalClassExposedInAPI", + "new": "class com.datastax.oss.driver.api.core.CqlIdentifier", + "classSimpleName": "CqlIdentifier", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "exampleUseChainInNewApi": "com.datastax.oss.driver.api.core.CqlIdentifier is used as parameter in method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) (method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) is part of the API)", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlIdentifier", + "elementKind": "class", + "justification": "Work around revapi filtering" + } + ] } } -} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/Version.java b/core/src/main/java/com/datastax/oss/driver/api/core/Version.java index 921a68f659d..f70db10c252 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/Version.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/Version.java @@ -45,6 +45,7 @@ public class Version implements Comparable { public static final Version V2_1_0 = parse("2.1.0"); public static final Version V2_2_0 = parse("2.2.0"); public static final Version V3_0_0 = parse("3.0.0"); + public static final Version V4_0_0 = parse("4.0.0"); private final int major; private final int minor; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java index 09ed16037bf..db4a592aceb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java @@ -34,6 +34,9 @@ public interface KeyspaceMetadata extends Describable { /** Whether durable writes are set on this keyspace. */ boolean isDurableWrites(); + /** Whether this keyspace is virtual */ + boolean isVirtual(); + /** The replication options defined for this keyspace. */ @NonNull Map getReplication(); @@ -195,13 +198,18 @@ default Optional getAggregate( @NonNull @Override default String describe(boolean pretty) { - ScriptBuilder builder = - new ScriptBuilder(pretty) - .append("CREATE KEYSPACE ") - .append(getName()) - .append(" WITH replication = { 'class' : '") - .append(getReplication().get("class")) - .append("'"); + ScriptBuilder builder = new ScriptBuilder(pretty); + if (isVirtual()) { + builder.append("/* VIRTUAL "); + } else { + builder.append("CREATE "); + } + builder + .append("KEYSPACE ") + .append(getName()) + .append(" WITH replication = { 'class' : '") + .append(getReplication().get("class")) + .append("'"); for (Map.Entry entry : getReplication().entrySet()) { if (!entry.getKey().equals("class")) { builder @@ -212,11 +220,14 @@ default String describe(boolean pretty) { .append("'"); } } - return builder + builder .append(" } AND durable_writes = ") .append(Boolean.toString(isDurableWrites())) - .append(";") - .build(); + .append(";"); + if (isVirtual()) { + builder.append(" */"); + } + return builder.build(); } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java index f1c30c59759..5c8f01ca498 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java @@ -33,8 +33,7 @@ public interface RelationMetadata extends Describable { CqlIdentifier getName(); /** The unique id generated by the server for this element. */ - @NonNull - UUID getId(); + Optional getId(); /** * Convenience method to get all the primary key columns (partition key + clustering columns) in a diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java index 8caf3baa2f3..8db9961f7a6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java @@ -26,21 +26,30 @@ public interface TableMetadata extends RelationMetadata { boolean isCompactStorage(); + /** Whether this table is virtual */ + boolean isVirtual(); + @NonNull Map getIndexes(); @NonNull @Override default String describe(boolean pretty) { - ScriptBuilder builder = - new ScriptBuilder(pretty) - .append("CREATE TABLE ") - .append(getKeyspace()) - .append(".") - .append(getName()) - .append(" (") - .newLine() - .increaseIndent(); + ScriptBuilder builder = new ScriptBuilder(pretty); + if (isVirtual()) { + builder.append("/* VIRTUAL "); + } else { + builder.append("CREATE "); + } + + builder + .append("TABLE ") + .append(getKeyspace()) + .append(".") + .append(getName()) + .append(" (") + .newLine() + .increaseIndent(); for (ColumnMetadata column : getColumns().values()) { builder.append(column.getName()).append(" ").append(column.getType().asCql(true, pretty)); @@ -95,7 +104,11 @@ default String describe(boolean pretty) { } Map options = getOptions(); RelationParser.appendOptions(options, builder); - return builder.append(";").build(); + builder.append(";"); + if (isVirtual()) { + builder.append(" */"); + } + return builder.build(); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java index 2396b3e8f8c..86a91f68481 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java @@ -33,6 +33,7 @@ public class DefaultKeyspaceMetadata implements KeyspaceMetadata { @NonNull private final CqlIdentifier name; private final boolean durableWrites; + private final boolean virtual; @NonNull private final Map replication; @NonNull private final Map types; @NonNull private final Map tables; @@ -43,6 +44,7 @@ public class DefaultKeyspaceMetadata implements KeyspaceMetadata { public DefaultKeyspaceMetadata( @NonNull CqlIdentifier name, boolean durableWrites, + boolean virtual, @NonNull Map replication, @NonNull Map types, @NonNull Map tables, @@ -51,6 +53,7 @@ public DefaultKeyspaceMetadata( @NonNull Map aggregates) { this.name = name; this.durableWrites = durableWrites; + this.virtual = virtual; this.replication = replication; this.types = types; this.tables = tables; @@ -70,6 +73,11 @@ public boolean isDurableWrites() { return durableWrites; } + @Override + public boolean isVirtual() { + return virtual; + } + @NonNull @Override public Map getReplication() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java index 2282833997d..9b00eefee43 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java @@ -21,10 +21,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import net.jcip.annotations.Immutable; @Immutable @@ -34,6 +31,7 @@ public class DefaultTableMetadata implements TableMetadata { @NonNull private final CqlIdentifier name; @NonNull private final UUID id; private final boolean compactStorage; + private final boolean virtual; @NonNull private final List partitionKey; @NonNull private final Map clusteringColumns; @NonNull private final Map columns; @@ -43,8 +41,9 @@ public class DefaultTableMetadata implements TableMetadata { public DefaultTableMetadata( @NonNull CqlIdentifier keyspace, @NonNull CqlIdentifier name, - @NonNull UUID id, + UUID id, boolean compactStorage, + boolean virtual, @NonNull List partitionKey, @NonNull Map clusteringColumns, @NonNull Map columns, @@ -54,6 +53,7 @@ public DefaultTableMetadata( this.name = name; this.id = id; this.compactStorage = compactStorage; + this.virtual = virtual; this.partitionKey = partitionKey; this.clusteringColumns = clusteringColumns; this.columns = columns; @@ -75,8 +75,8 @@ public CqlIdentifier getName() { @NonNull @Override - public UUID getId() { - return id; + public Optional getId() { + return Optional.ofNullable(id); } @Override @@ -84,6 +84,11 @@ public boolean isCompactStorage() { return compactStorage; } + @Override + public boolean isVirtual() { + return virtual; + } + @NonNull @Override public List getPartitionKey() { @@ -122,7 +127,7 @@ public boolean equals(Object other) { TableMetadata that = (TableMetadata) other; return Objects.equals(this.keyspace, that.getKeyspace()) && Objects.equals(this.name, that.getName()) - && Objects.equals(this.id, that.getId()) + && Objects.equals(Optional.of(this.id), that.getId()) && this.compactStorage == that.isCompactStorage() && Objects.equals(this.partitionKey, that.getPartitionKey()) && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java index 02f32b5d09a..35df9dae8e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java @@ -81,8 +81,8 @@ public CqlIdentifier getName() { @NonNull @Override - public UUID getId() { - return id; + public Optional getId() { + return Optional.of(id); } @NonNull @@ -137,7 +137,7 @@ public boolean equals(Object other) { && Objects.equals(this.baseTable, that.getBaseTable()) && this.includesAllColumns == that.includesAllColumns() && Objects.equals(this.whereClause, that.getWhereClause().orElse(null)) - && Objects.equals(this.id, that.getId()) + && Objects.equals(Optional.of(this.id), that.getId()) && Objects.equals(this.partitionKey, that.getPartitionKey()) && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) && Objects.equals(this.columns, that.getColumns()) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java index cd722ed09aa..f305484cfda 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.internal.core.util.NanoTime; import com.datastax.oss.driver.shaded.guava.common.base.MoreObjects; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import java.util.Collections; import java.util.Map; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; @@ -74,6 +75,10 @@ public SchemaRefresh parse() { KeyspaceMetadata keyspace = parseKeyspace(row); keyspacesBuilder.put(keyspace.getName(), keyspace); } + for (AdminRow row : rows.virtualKeyspaces()) { + KeyspaceMetadata keyspace = parseVirtualKeyspace(row); + keyspacesBuilder.put(keyspace.getName(), keyspace); + } SchemaRefresh refresh = new SchemaRefresh(keyspacesBuilder.build()); LOG.debug("[{}] Schema parsing took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); return refresh; @@ -118,6 +123,7 @@ private KeyspaceMetadata parseKeyspace(AdminRow keyspaceRow) { return new DefaultKeyspaceMetadata( keyspaceId, durableWrites, + false, replicationOptions, types, parseTables(keyspaceId, types), @@ -126,10 +132,45 @@ private KeyspaceMetadata parseKeyspace(AdminRow keyspaceRow) { parseAggregates(keyspaceId, types)); } + private KeyspaceMetadata parseVirtualKeyspace(AdminRow keyspaceRow) { + + CqlIdentifier keyspaceId = CqlIdentifier.fromInternal(keyspaceRow.getString("keyspace_name")); + boolean durableWrites = + MoreObjects.firstNonNull(keyspaceRow.getBoolean("durable_writes"), false); + + Map replicationOptions = Collections.emptyMap(); + ; + + Map types = parseTypes(keyspaceId); + + return new DefaultKeyspaceMetadata( + keyspaceId, + durableWrites, + true, + replicationOptions, + types, + parseVirtualTables(keyspaceId, types), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap()); + } + private Map parseTypes(CqlIdentifier keyspaceId) { return userDefinedTypeParser.parse(rows.types().get(keyspaceId), keyspaceId); } + private Map parseVirtualTables( + CqlIdentifier keyspaceId, Map types) { + ImmutableMap.Builder tablesBuilder = ImmutableMap.builder(); + for (AdminRow tableRow : rows.virtualTables().get(keyspaceId)) { + TableMetadata table = tableParser.parseVirtualTable(tableRow, keyspaceId, types); + if (table != null) { + tablesBuilder.put(table.getName(), table); + } + } + return tablesBuilder.build(); + } + private Map parseTables( CqlIdentifier keyspaceId, Map types) { ImmutableMap.Builder tablesBuilder = ImmutableMap.builder(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java index 675a1867386..d7b09b7b11f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParser.java @@ -219,6 +219,7 @@ public TableMetadata parseTable( tableId, uuid, isCompactStorage, + false, partitionKeyBuilder.build(), clusteringColumnsBuilder.build(), allColumnsBuilder.build(), @@ -226,6 +227,63 @@ public TableMetadata parseTable( indexesBuilder.build()); } + TableMetadata parseVirtualTable( + AdminRow tableRow, CqlIdentifier keyspaceId, Map userTypes) { + + CqlIdentifier tableId = CqlIdentifier.fromInternal(tableRow.getString("table_name")); + + List rawColumns = + RawColumn.toRawColumns( + rows.virtualColumns().getOrDefault(keyspaceId, ImmutableMultimap.of()).get(tableId), + keyspaceId, + userTypes); + if (rawColumns.isEmpty()) { + LOG.warn( + "[{}] Processing TABLE refresh for {}.{} but found no matching rows, skipping", + logPrefix, + keyspaceId, + tableId); + return null; + } + + Collections.sort(rawColumns); + ImmutableMap.Builder allColumnsBuilder = ImmutableMap.builder(); + ImmutableList.Builder partitionKeyBuilder = ImmutableList.builder(); + ImmutableMap.Builder clusteringColumnsBuilder = + ImmutableMap.builder(); + + for (RawColumn raw : rawColumns) { + DataType dataType = rows.dataTypeParser().parse(keyspaceId, raw.dataType, userTypes, context); + ColumnMetadata column = + new DefaultColumnMetadata( + keyspaceId, tableId, raw.name, dataType, raw.kind.equals(RawColumn.KIND_STATIC)); + switch (raw.kind) { + case RawColumn.KIND_PARTITION_KEY: + partitionKeyBuilder.add(column); + break; + case RawColumn.KIND_CLUSTERING_COLUMN: + clusteringColumnsBuilder.put( + column, raw.reversed ? ClusteringOrder.DESC : ClusteringOrder.ASC); + break; + default: + } + + allColumnsBuilder.put(column.getName(), column); + } + + return new DefaultTableMetadata( + keyspaceId, + tableId, + null, + false, + true, + partitionKeyBuilder.build(), + clusteringColumnsBuilder.build(), + allColumnsBuilder.build(), + Collections.emptyMap(), + Collections.emptyMap()); + } + // In C*<=2.2, index information is stored alongside the column. private IndexMetadata buildLegacyIndex(RawColumn raw, ColumnMetadata column) { if (raw.indexName == null) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java index bfa8efa61ca..05c12cdc4a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java @@ -71,4 +71,19 @@ protected Optional selectFunctionsQuery() { protected Optional selectAggregatesQuery() { return Optional.empty(); } + + @Override + protected Optional selectVirtualKeyspacesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualTablesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualColumnsQuery() { + return Optional.empty(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java index 180f9ee0975..936af222adb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java @@ -71,4 +71,19 @@ protected Optional selectFunctionsQuery() { protected Optional selectAggregatesQuery() { return Optional.of("SELECT * FROM system.schema_aggregates"); } + + @Override + protected Optional selectVirtualKeyspacesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualTablesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualColumnsQuery() { + return Optional.empty(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java index ac7fa44ebd2..91828977b2b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java @@ -71,4 +71,19 @@ protected Optional selectFunctionsQuery() { protected Optional selectAggregatesQuery() { return Optional.of("SELECT * FROM system_schema.aggregates"); } + + @Override + protected Optional selectVirtualKeyspacesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualTablesQuery() { + return Optional.empty(); + } + + @Override + protected Optional selectVirtualColumnsQuery() { + return Optional.empty(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java new file mode 100644 index 00000000000..cba4195efe5 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java @@ -0,0 +1,49 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata.schema.queries; + +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import net.jcip.annotations.ThreadSafe; + +@ThreadSafe +class Cassandra4SchemaQueries extends Cassandra3SchemaQueries { + Cassandra4SchemaQueries( + DriverChannel channel, + CompletableFuture refreshFuture, + DriverExecutionProfile config, + String logPrefix) { + super(channel, refreshFuture, config, logPrefix); + } + + @Override + protected Optional selectVirtualKeyspacesQuery() { + return Optional.of("SELECT * FROM system_virtual_schema.keyspaces"); + } + + @Override + protected Optional selectVirtualTablesQuery() { + return Optional.of("SELECT * FROM system_virtual_schema.tables"); + } + + @Override + protected Optional selectVirtualColumnsQuery() { + return Optional.of("SELECT * FROM system_virtual_schema.columns"); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java index bbbe9b9631f..47c8beb8e41 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java @@ -100,14 +100,20 @@ private static String buildWhereClause(List refreshedKeyspaces) { protected abstract String selectKeyspacesQuery(); + protected abstract Optional selectVirtualKeyspacesQuery(); + protected abstract String selectTablesQuery(); + protected abstract Optional selectVirtualTablesQuery(); + protected abstract Optional selectViewsQuery(); protected abstract Optional selectIndexesQuery(); protected abstract String selectColumnsQuery(); + protected abstract Optional selectVirtualColumnsQuery(); + protected abstract String selectTypesQuery(); protected abstract Optional selectFunctionsQuery(); @@ -137,6 +143,12 @@ private void executeOnAdminExecutor() { .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions)); selectAggregatesQuery() .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates)); + selectVirtualKeyspacesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualKeyspaces)); + selectVirtualTablesQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualTables)); + selectVirtualColumnsQuery() + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualColumns)); } private void query( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java index f50ee68c5e1..49a49764021 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaRows.java @@ -40,21 +40,27 @@ public class CassandraSchemaRows implements SchemaRows { private final DataTypeParser dataTypeParser; private final CompletableFuture refreshFuture; private final List keyspaces; + private final List virtualKeyspaces; private final Multimap tables; + private final Multimap virtualTables; private final Multimap views; private final Multimap types; private final Multimap functions; private final Multimap aggregates; private final Map> columns; + private final Map> virtualColumns; private final Map> indexes; private CassandraSchemaRows( boolean isCassandraV3, CompletableFuture refreshFuture, List keyspaces, + List virtualKeyspaces, Multimap tables, + Multimap virtualTables, Multimap views, Map> columns, + Map> virtualColumns, Map> indexes, Multimap types, Multimap functions, @@ -63,9 +69,12 @@ private CassandraSchemaRows( isCassandraV3 ? new DataTypeCqlNameParser() : new DataTypeClassNameParser(); this.refreshFuture = refreshFuture; this.keyspaces = keyspaces; + this.virtualKeyspaces = virtualKeyspaces; this.tables = tables; + this.virtualTables = virtualTables; this.views = views; this.columns = columns; + this.virtualColumns = virtualColumns; this.indexes = indexes; this.types = types; this.functions = functions; @@ -87,11 +96,21 @@ public List keyspaces() { return keyspaces; } + @Override + public List virtualKeyspaces() { + return virtualKeyspaces; + } + @Override public Multimap tables() { return tables; } + @Override + public Multimap virtualTables() { + return virtualTables; + } + @Override public Multimap views() { return views; @@ -117,6 +136,11 @@ public Map> columns() { return columns; } + @Override + public Map> virtualColumns() { + return virtualColumns; + } + @Override public Map> indexes() { return indexes; @@ -130,8 +154,11 @@ public static class Builder { private final String tableNameColumn; private final String logPrefix; private final ImmutableList.Builder keyspacesBuilder = ImmutableList.builder(); + private final ImmutableList.Builder virtualKeyspacesBuilder = ImmutableList.builder(); private final ImmutableMultimap.Builder tablesBuilder = ImmutableListMultimap.builder(); + private final ImmutableMultimap.Builder virtualTablesBuilder = + ImmutableListMultimap.builder(); private final ImmutableMultimap.Builder viewsBuilder = ImmutableListMultimap.builder(); private final ImmutableMultimap.Builder typesBuilder = @@ -142,6 +169,8 @@ public static class Builder { ImmutableListMultimap.builder(); private final Map> columnsBuilders = new LinkedHashMap<>(); + private final Map> + virtualColumnsBuilders = new LinkedHashMap<>(); private final Map> indexesBuilders = new LinkedHashMap<>(); @@ -158,6 +187,11 @@ public Builder withKeyspaces(Iterable rows) { return this; } + public Builder withVirtualKeyspaces(Iterable rows) { + virtualKeyspacesBuilder.addAll(rows); + return this; + } + public Builder withTables(Iterable rows) { for (AdminRow row : rows) { putByKeyspace(row, tablesBuilder); @@ -165,6 +199,13 @@ public Builder withTables(Iterable rows) { return this; } + public Builder withVirtualTables(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspace(row, virtualTablesBuilder); + } + return this; + } + public Builder withViews(Iterable rows) { for (AdminRow row : rows) { putByKeyspace(row, viewsBuilder); @@ -200,6 +241,13 @@ public Builder withColumns(Iterable rows) { return this; } + public Builder withVirtualColumns(Iterable rows) { + for (AdminRow row : rows) { + putByKeyspaceAndTable(row, virtualColumnsBuilders); + } + return this; + } + public Builder withIndexes(Iterable rows) { for (AdminRow row : rows) { putByKeyspaceAndTable(row, indexesBuilders); @@ -239,9 +287,12 @@ public CassandraSchemaRows build() { isCassandraV3, refreshFuture, keyspacesBuilder.build(), + virtualKeyspacesBuilder.build(), tablesBuilder.build(), + virtualTablesBuilder.build(), viewsBuilder.build(), build(columnsBuilders), + build(virtualColumnsBuilders), build(indexesBuilders), typesBuilder.build(), functionsBuilder.build(), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index 955f1ea66a6..4873b4cf2af 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -75,8 +75,10 @@ protected SchemaQueries newInstance( return new Cassandra21SchemaQueries(channel, refreshFuture, config, logPrefix); } else if (version.compareTo(Version.V3_0_0) < 0) { return new Cassandra22SchemaQueries(channel, refreshFuture, config, logPrefix); - } else { + } else if (version.compareTo(Version.V4_0_0) < 0) { return new Cassandra3SchemaQueries(channel, refreshFuture, config, logPrefix); + } else { + return new Cassandra4SchemaQueries(channel, refreshFuture, config, logPrefix); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java index 18148f23948..b8242517241 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaRows.java @@ -34,8 +34,12 @@ public interface SchemaRows { List keyspaces(); + List virtualKeyspaces(); + Multimap tables(); + Multimap virtualTables(); + Multimap views(); Multimap types(); @@ -46,6 +50,8 @@ public interface SchemaRows { Map> columns(); + Map> virtualColumns(); + Map> indexes(); DataTypeParser dataTypeParser(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java index 29b894627dc..1f171d90611 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefreshTest.java @@ -149,6 +149,7 @@ private static DefaultKeyspaceMetadata newKeyspace( return new DefaultKeyspaceMetadata( CqlIdentifier.fromInternal(name), durableWrites, + false, Collections.emptyMap(), typesMapBuilder.build(), Collections.emptyMap(), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 8f287ec5e34..a2728c14810 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -23,7 +23,11 @@ import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -166,4 +170,59 @@ public void should_refresh_schema_manually() { assertThat(session.getMetadata()).isSameAs(newMetadata); } } + + @CassandraRequirement(min = "4.0", description = "virtual tables introduced in 4.0") + @Test + public void should_get_virtual_metadata() { + Metadata md = sessionRule.session().getMetadata(); + KeyspaceMetadata kmd = md.getKeyspace("system_views").get(); + + // Keyspace name should be set, marked as virtual, and have a clients table. + // All other values should be defaulted since they are not defined in the virtual schema tables. + assertThat(kmd.getTables().size()).isGreaterThanOrEqualTo(2); + assertThat(kmd.isVirtual()).isTrue(); + assertThat(kmd.isDurableWrites()).isFalse(); + assertThat(kmd.getName().asCql(true)).isEqualTo("system_views"); + assertThat(kmd.getUserDefinedTypes().size()).isEqualTo(0); + assertThat(kmd.getFunctions().size()).isEqualTo(0); + assertThat(kmd.getViews().size()).isEqualTo(0); + assertThat(kmd.getAggregates().size()).isEqualTo(0); + assertThat(kmd.describe(true)) + .isEqualTo( + "/* VIRTUAL KEYSPACE system_views WITH replication = { 'class' : 'null' } " + + "AND durable_writes = false; */"); + // Table name should be set, marked as virtual, and it should have columns set. + // indexes, views, clustering column, clustering order and id are not defined in the virtual + // schema tables. + TableMetadata tm = kmd.getTable("sstable_tasks").get(); + assertThat(tm).isNotNull(); + assertThat(tm.getName().toString()).isEqualTo("sstable_tasks"); + assertThat(tm.isVirtual()).isTrue(); + assertThat(tm.getColumns().size()).isEqualTo(7); + assertThat(tm.getIndexes().size()).isEqualTo(0); + assertThat(tm.getPartitionKey().size()).isEqualTo(1); + assertThat(tm.getPartitionKey().get(0).getName().toString()).isEqualTo("keyspace_name"); + assertThat(tm.getClusteringColumns().size()).isEqualTo(2); + assertThat(tm.getId().isPresent()).isFalse(); + assertThat(tm.getOptions().size()).isEqualTo(0); + assertThat(tm.getKeyspace()).isEqualTo(kmd.getName()); + assertThat(tm.describe(true)) + .isEqualTo( + "/* VIRTUAL TABLE system_views.sstable_tasks (\n" + + " keyspace_name text,\n" + + " table_name text,\n" + + " task_id uuid,\n" + + " kind text,\n" + + " progress bigint,\n" + + " total bigint,\n" + + " unit text,\n" + + " PRIMARY KEY (keyspace_name, table_name, task_id)\n" + + "); */"); + // ColumnMetadata is as expected + ColumnMetadata cm = tm.getColumn("progress").get(); + assertThat(cm).isNotNull(); + assertThat(cm.getParent()).isEqualTo(tm.getName()); + assertThat(cm.getType()).isEqualTo(DataTypes.BIGINT); + assertThat(cm.getName().toString()).isEqualTo("progress"); + } } diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 3925316a9f1..7c69cf2450f 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -6,11 +6,15 @@ "filter": { "packages": { "regex": true, - "include": [ - "com\\.datastax\\.oss\\.driver\\.api\\..*" + "exclude": [ + "com\\.datastax\\.oss\\.driver\\.internal\\..*", + "com\\.datastax\\.oss\\.driver\\.shaded\\..*", + // Don't re-check sibling modules that this module depends on + "com\\.datastax\\.oss\\.driver\\.api\\.core\\..*" ] } } } } } + diff --git a/test-infra/revapi.json b/test-infra/revapi.json index 3925316a9f1..f1da1aa19e4 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -6,8 +6,11 @@ "filter": { "packages": { "regex": true, - "include": [ - "com\\.datastax\\.oss\\.driver\\.api\\..*" + "exclude": [ + "com\\.datastax\\.oss\\.driver\\.internal\\..*", + "com\\.datastax\\.oss\\.driver\\.shaded\\..*", + // Don't re-check sibling modules that this module depends on + "com\\.datastax\\.oss\\.driver\\.api\\.core\\..*" ] } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 7530c356a1b..20b10ea8e75 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -71,6 +71,8 @@ public class CcmBridge implements AutoCloseable { public static final String INSTALL_DIRECTORY = System.getProperty("ccm.directory"); + public static final String BRANCH = System.getProperty("ccm.branch"); + public static final Boolean DSE_ENABLEMENT = Boolean.getBoolean("ccm.dse"); public static final String DEFAULT_CLIENT_TRUSTSTORE_PASSWORD = "cassandra1sfun"; @@ -184,6 +186,9 @@ public void create() { if (created.compareAndSet(false, true)) { if (INSTALL_DIRECTORY != null) { createOptions.add("--install-dir=" + new File(INSTALL_DIRECTORY).getAbsolutePath()); + } else if (BRANCH != null) { + createOptions.add("-v git:" + BRANCH.trim().replaceAll("\"", "")); + } else { createOptions.add("-v " + VERSION.toString()); } From 3e03034f61bd584c335032337436abdb5e9360a2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 3 Aug 2018 10:42:37 -0700 Subject: [PATCH 533/742] Fix indentation in revapi.json --- core/revapi.json | 188 +++++++++++++++++++++++------------------------ 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index c434321f49c..9dd18422438 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -9,101 +9,101 @@ "exclude": [ "com\\.datastax\\.oss\\.driver\\.internal\\..*", "com\\.datastax\\.oss\\.driver\\.shaded\\..*" - ] + ] } } }, - "ignore": [ - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::isVirtual()", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata", - "classSimpleName": "KeyspaceMetadata", - "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.method.returnTypeChanged", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "oldType": "java.util.UUID", - "newType": "java.util.Optional", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", - "classSimpleName": "RelationMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", - "classSimpleName": "RelationMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification":"Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "classSimpleName": "TableMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::isVirtual()", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "classSimpleName": "TableMetadata", - "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "classSimpleName": "ViewMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.class.externalClassExposedInAPI", - "new": "class com.datastax.oss.driver.api.core.CqlIdentifier", - "classSimpleName": "CqlIdentifier", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", - "exampleUseChainInNewApi": "com.datastax.oss.driver.api.core.CqlIdentifier is used as parameter in method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) (method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) is part of the API)", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlIdentifier", - "elementKind": "class", - "justification": "Work around revapi filtering" - } - ] - } + "ignore": [ + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::isVirtual()", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata", + "classSimpleName": "KeyspaceMetadata", + "methodName": "isVirtual", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.method.returnTypeChanged", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "oldType": "java.util.UUID", + "newType": "java.util.Optional", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", + "classSimpleName": "RelationMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", + "classSimpleName": "RelationMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "classSimpleName": "TableMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::isVirtual()", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", + "classSimpleName": "TableMetadata", + "methodName": "isVirtual", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.annotation.removed", + "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.core.metadata.schema", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", + "classSimpleName": "ViewMetadata", + "methodName": "getId", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Adding virtual tables" + }, + { + "code": "java.class.externalClassExposedInAPI", + "new": "class com.datastax.oss.driver.api.core.CqlIdentifier", + "classSimpleName": "CqlIdentifier", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "exampleUseChainInNewApi": "com.datastax.oss.driver.api.core.CqlIdentifier is used as parameter in method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) (method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) is part of the API)", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlIdentifier", + "elementKind": "class", + "justification": "Work around revapi filtering" + } + ] } +} From a5e961fd133c7a12f47175889acc35cc799d8d16 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 3 Aug 2018 10:43:42 -0700 Subject: [PATCH 534/742] Remove externalClassExposedInAPI ignore --- core/revapi.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 9dd18422438..98f3a700fbe 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -92,17 +92,6 @@ "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", "elementKind": "method", "justification": "Adding virtual tables" - }, - { - "code": "java.class.externalClassExposedInAPI", - "new": "class com.datastax.oss.driver.api.core.CqlIdentifier", - "classSimpleName": "CqlIdentifier", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", - "exampleUseChainInNewApi": "com.datastax.oss.driver.api.core.CqlIdentifier is used as parameter in method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) (method com.datastax.oss.driver.api.querybuilder.condition.ConditionBuilder com.datastax.oss.driver.api.querybuilder.condition.Condition::element(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) is part of the API)", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlIdentifier", - "elementKind": "class", - "justification": "Work around revapi filtering" } ] } From 05a3036c5789b097f7dd43a7ae363d12d0c1ac10 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 6 Aug 2018 16:56:46 +0200 Subject: [PATCH 535/742] Annotate DefaultTableMetadata.id with @Nullable to account for JAVA-1900 --- .../core/metadata/schema/DefaultTableMetadata.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java index 9b00eefee43..92e8e052370 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.*; import net.jcip.annotations.Immutable; @@ -29,7 +30,8 @@ public class DefaultTableMetadata implements TableMetadata { @NonNull private final CqlIdentifier keyspace; @NonNull private final CqlIdentifier name; - @NonNull private final UUID id; + // null for virtual tables + @Nullable private final UUID id; private final boolean compactStorage; private final boolean virtual; @NonNull private final List partitionKey; @@ -41,7 +43,7 @@ public class DefaultTableMetadata implements TableMetadata { public DefaultTableMetadata( @NonNull CqlIdentifier keyspace, @NonNull CqlIdentifier name, - UUID id, + @Nullable UUID id, boolean compactStorage, boolean virtual, @NonNull List partitionKey, @@ -127,7 +129,7 @@ public boolean equals(Object other) { TableMetadata that = (TableMetadata) other; return Objects.equals(this.keyspace, that.getKeyspace()) && Objects.equals(this.name, that.getName()) - && Objects.equals(Optional.of(this.id), that.getId()) + && Objects.equals(Optional.ofNullable(this.id), that.getId()) && this.compactStorage == that.isCompactStorage() && Objects.equals(this.partitionKey, that.getPartitionKey()) && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) From d837d42e108bd396d7daeb80feffb4cc1b597584 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Jul 2018 09:59:44 -0700 Subject: [PATCH 536/742] JAVA-1925: Rename context getters --- changelog/README.md | 1 + core/console.scala | 2 +- core/revapi.json | 407 ++++++++++++++++++ .../api/core/context/DriverContext.java | 48 +-- .../driver/api/core/cql/BatchStatement.java | 4 +- .../driver/api/core/cql/BoundStatement.java | 2 +- .../driver/api/core/cql/SimpleStatement.java | 5 +- .../api/core/detach/AttachmentPoint.java | 8 +- .../Ec2MultiRegionAddressTranslator.java | 2 +- .../core/auth/PlainTextAuthProvider.java | 4 +- .../internal/core/channel/ChannelFactory.java | 29 +- .../core/channel/DefaultWriteCoalescer.java | 2 +- .../core/channel/ProtocolInitHandler.java | 8 +- .../typesafe/DefaultDriverConfigLoader.java | 10 +- .../ConstantReconnectionPolicy.java | 4 +- .../ExponentialReconnectionPolicy.java | 4 +- .../core/context/DefaultDriverContext.java | 96 ++--- .../core/context/DefaultNettyOptions.java | 6 +- .../core/context/InternalDriverContext.java | 48 +-- .../internal/core/context/NettyOptions.java | 4 +- .../core/control/ControlConnection.java | 42 +- .../driver/internal/core/cql/Conversions.java | 30 +- .../core/cql/CqlPrepareHandlerBase.java | 14 +- .../core/cql/CqlRequestHandlerBase.java | 26 +- .../driver/internal/core/cql/DefaultRow.java | 4 +- .../internal/core/cql/QueryTraceFetcher.java | 6 +- .../internal/core/data/DefaultTupleValue.java | 8 +- .../internal/core/data/DefaultUdtValue.java | 8 +- .../DefaultLoadBalancingPolicy.java | 9 +- .../core/metadata/AddNodeRefresh.java | 2 +- .../core/metadata/DefaultMetadata.java | 4 +- .../internal/core/metadata/DefaultNode.java | 2 +- .../core/metadata/DefaultTopologyMonitor.java | 8 +- .../core/metadata/FullNodeListRefresh.java | 4 +- .../metadata/InitContactPointsRefresh.java | 2 +- .../metadata/LoadBalancingPolicyWrapper.java | 10 +- .../core/metadata/MetadataManager.java | 24 +- .../core/metadata/NodeStateManager.java | 10 +- .../core/metadata/RemoveNodeRefresh.java | 2 +- .../core/metadata/SchemaAgreementChecker.java | 6 +- .../schema/parsing/AggregateParser.java | 4 +- .../schema/parsing/CassandraSchemaParser.java | 2 +- .../parsing/DataTypeClassNameParser.java | 4 +- .../schema/parsing/FunctionParser.java | 2 +- .../schema/parsing/RelationParser.java | 2 +- .../queries/DefaultSchemaQueriesFactory.java | 8 +- .../DefaultReplicationStrategyFactory.java | 2 +- .../token/DefaultTokenFactoryRegistry.java | 2 +- .../metrics/DropwizardMetricsFactory.java | 4 +- .../metrics/DropwizardNodeMetricUpdater.java | 6 +- .../DropwizardSessionMetricUpdater.java | 10 +- .../internal/core/pool/ChannelPool.java | 10 +- .../internal/core/protocol/Lz4Compressor.java | 2 +- .../core/retry/DefaultRetryPolicy.java | 2 +- .../internal/core/session/DefaultSession.java | 109 ++--- .../internal/core/session/PoolManager.java | 24 +- .../internal/core/session/ReprepareOnUp.java | 6 +- .../ConcurrencyLimitingRequestThrottler.java | 4 +- .../RateLimitingRequestThrottler.java | 6 +- .../ConstantSpeculativeExecutionPolicy.java | 2 +- .../core/ssl/DefaultSslEngineFactory.java | 2 +- .../time/MonotonicTimestampGenerator.java | 4 +- .../core/tracker/RequestLogFormatter.java | 8 +- .../internal/core/tracker/RequestLogger.java | 2 +- .../internal/core/type/codec/TupleCodec.java | 4 +- .../internal/core/type/codec/UdtCodec.java | 4 +- .../driver/internal/core/util/Reflection.java | 10 +- .../oss/driver/internal/core/util/Sizes.java | 2 +- core/src/main/resources/reference.conf | 2 +- ...onstantSpeculativeExecutionPolicyTest.java | 2 +- .../core/channel/ChannelFactoryTestBase.java | 18 +- .../core/channel/ProtocolInitHandlerTest.java | 12 +- .../DefaultDriverConfigLoaderTest.java | 8 +- .../control/ControlConnectionTestBase.java | 16 +- .../core/cql/CqlPrepareHandlerTest.java | 12 +- .../core/cql/CqlRequestHandlerRetryTest.java | 17 +- ...equestHandlerSpeculativeExecutionTest.java | 16 +- .../core/cql/CqlRequestHandlerTest.java | 2 +- .../core/cql/DefaultAsyncResultSetTest.java | 4 +- .../core/cql/QueryTraceFetcherTest.java | 4 +- .../core/cql/RequestHandlerTestHarness.java | 27 +- .../internal/core/cql/StatementSizeTest.java | 10 +- .../core/data/AccessibleByIndexTestBase.java | 4 +- ...faultLoadBalancingPolicyQueryPlanTest.java | 2 +- .../core/metadata/AddNodeRefreshTest.java | 2 +- .../metadata/DefaultMetadataTokenMapTest.java | 2 +- .../metadata/DefaultTopologyMonitorTest.java | 8 +- .../metadata/FullNodeListRefreshTest.java | 2 +- .../InitContactPointsRefreshTest.java | 2 +- .../LoadBalancingPolicyWrapperTest.java | 6 +- .../core/metadata/MetadataManagerTest.java | 14 +- .../core/metadata/NodeStateManagerTest.java | 10 +- .../core/metadata/RemoveNodeRefreshTest.java | 2 +- .../metadata/SchemaAgreementCheckerTest.java | 6 +- .../schema/parsing/AggregateParserTest.java | 4 +- .../schema/parsing/SchemaParserTest.java | 2 +- .../core/pool/ChannelPoolTestBase.java | 10 +- .../core/session/DefaultSessionPoolsTest.java | 39 +- .../core/session/ReprepareOnUpTest.java | 4 +- ...ncurrencyLimitingRequestThrottlerTest.java | 2 +- .../RateLimitingRequestThrottlerTest.java | 4 +- .../MonotonicTimestampGeneratorTestBase.java | 2 +- .../core/tracker/RequestLogFormatterTest.java | 8 +- .../core/type/codec/TupleCodecTest.java | 4 +- .../core/type/codec/UdtCodecTest.java | 4 +- .../internal/core/util/ReflectionTest.java | 2 +- .../ProtocolVersionInitialNegotiationIT.java | 8 +- .../core/ProtocolVersionMixedClusterIT.java | 8 +- .../DriverExecutionProfileReloadIT.java | 4 +- .../connection/ChannelSocketOptionsIT.java | 2 +- .../driver/api/core/cql/BoundStatementIT.java | 6 +- .../oss/driver/api/core/data/DataTypeIT.java | 11 +- .../DefaultLoadBalancingPolicyIT.java | 4 +- .../PerProfileLoadBalancingPolicyIT.java | 10 +- .../api/core/metadata/NodeMetadataIT.java | 2 +- .../driver/api/core/metadata/NodeStateIT.java | 44 +- .../driver/api/core/metadata/TokenITBase.java | 4 +- .../core/retry/PerProfileRetryPolicyIT.java | 10 +- .../api/core/session/RequestProcessorIT.java | 2 +- .../core/specex/SpeculativeExecutionIT.java | 10 +- .../tracker/RequestNodeLoggerExample.java | 2 +- .../guava/internal/GuavaDriverContext.java | 4 +- manual/core/configuration/README.md | 8 +- manual/core/custom_codecs/README.md | 2 +- manual/core/load_balancing/README.md | 4 +- manual/core/metadata/node/README.md | 4 +- manual/core/metadata/token/README.md | 2 +- manual/core/native_protocol/README.md | 2 +- manual/core/statements/simple/README.md | 4 +- manual/query_builder/term/README.md | 2 +- pom.xml | 6 +- .../api/querybuilder/QueryBuilderDsl.java | 4 +- .../querybuilder/select/OngoingSelection.java | 4 +- .../api/testinfra/session/SessionUtils.java | 2 +- 134 files changed, 1034 insertions(+), 598 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 969bd60ac63..f7e2e31316e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1925: Rename context getters - [improvement] JAVA-1544: Check API compatibility with Revapi - [new feature] JAVA-1900: Add support for virtual tables diff --git a/core/console.scala b/core/console.scala index eabaf632fc2..0ae13620ff8 100644 --- a/core/console.scala +++ b/core/console.scala @@ -35,5 +35,5 @@ println("* implicit val session = builder.build *") println("********************************************") def fire(event: AnyRef)(implicit session: CqlSession): Unit = { - session.getContext.asInstanceOf[InternalDriverContext].eventBus().fire(event) + session.getContext.asInstanceOf[InternalDriverContext].getEventBus().fire(event) } \ No newline at end of file diff --git a/core/revapi.json b/core/revapi.json index 98f3a700fbe..fbf26f5ba77 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -92,6 +92,413 @@ "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", "elementKind": "method", "justification": "Adding virtual tables" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator com.datastax.oss.driver.api.core.context.DriverContext::addressTranslator()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "addressTranslator", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::authProvider()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "authProvider", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.config.DriverConfig com.datastax.oss.driver.api.core.context.DriverContext::config()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "config", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.config.DriverConfigLoader com.datastax.oss.driver.api.core.context.DriverContext::configLoader()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "configLoader", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator com.datastax.oss.driver.api.core.context.DriverContext::getAddressTranslator()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getAddressTranslator", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::getAuthProvider()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getAuthProvider", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.config.DriverConfig com.datastax.oss.driver.api.core.context.DriverContext::getConfig()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getConfig", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.config.DriverConfigLoader com.datastax.oss.driver.api.core.context.DriverContext::getConfigLoader()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getConfigLoader", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getLoadBalancingPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getLoadBalancingPolicies", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.metadata.NodeStateListener com.datastax.oss.driver.api.core.context.DriverContext::getNodeStateListener()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getNodeStateListener", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy com.datastax.oss.driver.api.core.context.DriverContext::getReconnectionPolicy()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getReconnectionPolicy", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.session.throttling.RequestThrottler com.datastax.oss.driver.api.core.context.DriverContext::getRequestThrottler()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getRequestThrottler", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.tracker.RequestTracker com.datastax.oss.driver.api.core.context.DriverContext::getRequestTracker()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getRequestTracker", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getRetryPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getRetryPolicies", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener com.datastax.oss.driver.api.core.context.DriverContext::getSchemaChangeListener()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getSchemaChangeListener", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.lang.String com.datastax.oss.driver.api.core.context.DriverContext::getSessionName()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getSessionName", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getSpeculativeExecutionPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getSpeculativeExecutionPolicies", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::getSslEngineFactory()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getSslEngineFactory", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.time.TimestampGenerator com.datastax.oss.driver.api.core.context.DriverContext::getTimestampGenerator()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "getTimestampGenerator", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::loadBalancingPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "loadBalancingPolicies", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy com.datastax.oss.driver.api.core.context.DriverContext::loadBalancingPolicy(java.lang.String)", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "loadBalancingPolicy", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.metadata.NodeStateListener com.datastax.oss.driver.api.core.context.DriverContext::nodeStateListener()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "nodeStateListener", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy com.datastax.oss.driver.api.core.context.DriverContext::reconnectionPolicy()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "reconnectionPolicy", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.session.throttling.RequestThrottler com.datastax.oss.driver.api.core.context.DriverContext::requestThrottler()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "requestThrottler", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.tracker.RequestTracker com.datastax.oss.driver.api.core.context.DriverContext::requestTracker()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "requestTracker", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::retryPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "retryPolicies", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.retry.RetryPolicy com.datastax.oss.driver.api.core.context.DriverContext::retryPolicy(java.lang.String)", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "retryPolicy", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener com.datastax.oss.driver.api.core.context.DriverContext::schemaChangeListener()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "schemaChangeListener", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.lang.String com.datastax.oss.driver.api.core.context.DriverContext::sessionName()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "sessionName", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::speculativeExecutionPolicies()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "speculativeExecutionPolicies", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy com.datastax.oss.driver.api.core.context.DriverContext::speculativeExecutionPolicy(java.lang.String)", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "speculativeExecutionPolicy", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::sslEngineFactory()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "sslEngineFactory", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.time.TimestampGenerator com.datastax.oss.driver.api.core.context.DriverContext::timestampGenerator()", + "package": "com.datastax.oss.driver.api.core.context", + "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", + "classSimpleName": "DriverContext", + "methodName": "timestampGenerator", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry com.datastax.oss.driver.api.core.detach.AttachmentPoint::codecRegistry()", + "package": "com.datastax.oss.driver.api.core.detach", + "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", + "classSimpleName": "AttachmentPoint", + "methodName": "codecRegistry", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry com.datastax.oss.driver.api.core.detach.AttachmentPoint::getCodecRegistry()", + "package": "com.datastax.oss.driver.api.core.detach", + "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", + "classSimpleName": "AttachmentPoint", + "methodName": "getCodecRegistry", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.ProtocolVersion com.datastax.oss.driver.api.core.detach.AttachmentPoint::getProtocolVersion()", + "package": "com.datastax.oss.driver.api.core.detach", + "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", + "classSimpleName": "AttachmentPoint", + "methodName": "getProtocolVersion", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.ProtocolVersion com.datastax.oss.driver.api.core.detach.AttachmentPoint::protocolVersion()", + "package": "com.datastax.oss.driver.api.core.detach", + "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", + "classSimpleName": "AttachmentPoint", + "methodName": "protocolVersion", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "Renamed context getters for uniformity" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 5ec2804e413..2a26b9bc5ae 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -44,96 +44,96 @@ public interface DriverContext extends AttachmentPoint { * a reference to the context. */ @NonNull - String sessionName(); + String getSessionName(); /** @return The driver's configuration; never {@code null}. */ @NonNull - DriverConfig config(); + DriverConfig getConfig(); /** @return The driver's configuration loader; never {@code null}. */ @NonNull - DriverConfigLoader configLoader(); + DriverConfigLoader getConfigLoader(); /** @return The driver's load balancing policies; may be empty but never {@code null}. */ @NonNull - Map loadBalancingPolicies(); + Map getLoadBalancingPolicies(); /** * @param profileName the profile name; never {@code null}. * @return The driver's load balancing policy for the given profile; never {@code null}. */ @NonNull - default LoadBalancingPolicy loadBalancingPolicy(@NonNull String profileName) { - LoadBalancingPolicy policy = loadBalancingPolicies().get(profileName); + default LoadBalancingPolicy getLoadBalancingPolicy(@NonNull String profileName) { + LoadBalancingPolicy policy = getLoadBalancingPolicies().get(profileName); // Protect against a non-existent name return (policy != null) ? policy - : loadBalancingPolicies().get(DriverExecutionProfile.DEFAULT_NAME); + : getLoadBalancingPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's load balancing policies; may be empty but never {@code null}. */ @NonNull - Map retryPolicies(); + Map getRetryPolicies(); /** * @param profileName the profile name; never {@code null}. * @return The driver's retry policy for the given profile; never {@code null}. */ @NonNull - default RetryPolicy retryPolicy(@NonNull String profileName) { - RetryPolicy policy = retryPolicies().get(profileName); - return (policy != null) ? policy : retryPolicies().get(DriverExecutionProfile.DEFAULT_NAME); + default RetryPolicy getRetryPolicy(@NonNull String profileName) { + RetryPolicy policy = getRetryPolicies().get(profileName); + return (policy != null) ? policy : getRetryPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's load balancing policies; may be empty but never {@code null}. */ @NonNull - Map speculativeExecutionPolicies(); + Map getSpeculativeExecutionPolicies(); /** * @param profileName the profile name; never {@code null}. * @return The driver's speculative execution policy for the given profile; never {@code null}. */ @NonNull - default SpeculativeExecutionPolicy speculativeExecutionPolicy(@NonNull String profileName) { - SpeculativeExecutionPolicy policy = speculativeExecutionPolicies().get(profileName); + default SpeculativeExecutionPolicy getSpeculativeExecutionPolicy(@NonNull String profileName) { + SpeculativeExecutionPolicy policy = getSpeculativeExecutionPolicies().get(profileName); return (policy != null) ? policy - : speculativeExecutionPolicies().get(DriverExecutionProfile.DEFAULT_NAME); + : getSpeculativeExecutionPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } /** @return The driver's timestamp generator; never {@code null}. */ @NonNull - TimestampGenerator timestampGenerator(); + TimestampGenerator getTimestampGenerator(); /** @return The driver's reconnection policy; never {@code null}. */ @NonNull - ReconnectionPolicy reconnectionPolicy(); + ReconnectionPolicy getReconnectionPolicy(); /** @return The driver's address translator; never {@code null}. */ @NonNull - AddressTranslator addressTranslator(); + AddressTranslator getAddressTranslator(); /** @return The authentication provider, if authentication was configured. */ @NonNull - Optional authProvider(); + Optional getAuthProvider(); /** @return The SSL engine factory, if SSL was configured. */ @NonNull - Optional sslEngineFactory(); + Optional getSslEngineFactory(); /** @return The driver's request tracker; never {@code null}. */ @NonNull - RequestTracker requestTracker(); + RequestTracker getRequestTracker(); /** @return The driver's request throttler; never {@code null}. */ @NonNull - RequestThrottler requestThrottler(); + RequestThrottler getRequestThrottler(); /** @return The driver's node state listener; never {@code null}. */ @NonNull - NodeStateListener nodeStateListener(); + NodeStateListener getNodeStateListener(); /** @return The driver's schema change listener; never {@code null}. */ @NonNull - SchemaChangeListener schemaChangeListener(); + SchemaChangeListener getSchemaChangeListener(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 7c8de2bfde3..5801c82d4e0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -203,7 +203,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { for (BatchableStatement batchableStatement : this) { size += Sizes.sizeOfInnerBatchStatementInBytes( - batchableStatement, context.protocolVersion(), context.codecRegistry()); + batchableStatement, context.getProtocolVersion(), context.getCodecRegistry()); } // per-query keyspace @@ -212,7 +212,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { } // timestamp - if (!(context.timestampGenerator() instanceof ServerSideTimestampGenerator) + if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) || getTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index 9a9796e7cbd..3c9981af4ff 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -85,7 +85,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { } // timestamp - if (!(context.timestampGenerator() instanceof ServerSideTimestampGenerator) + if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) || getTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index f12cff6934e..2e2272104d2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -264,7 +264,8 @@ default int computeSizeInBytes(@NonNull DriverContext context) { // parameters size += - Sizes.sizeOfSimpleStatementValues(this, context.protocolVersion(), context.codecRegistry()); + Sizes.sizeOfSimpleStatementValues( + this, context.getProtocolVersion(), context.getCodecRegistry()); // per-query keyspace if (getKeyspace() != null) { @@ -280,7 +281,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { } // timestamp - if (!(context.timestampGenerator() instanceof ServerSideTimestampGenerator) + if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) || getTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java b/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java index 5c90c1052d1..930a72a35fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/detach/AttachmentPoint.java @@ -25,20 +25,20 @@ public interface AttachmentPoint { new AttachmentPoint() { @NonNull @Override - public ProtocolVersion protocolVersion() { + public ProtocolVersion getProtocolVersion() { return ProtocolVersion.DEFAULT; } @NonNull @Override - public CodecRegistry codecRegistry() { + public CodecRegistry getCodecRegistry() { return CodecRegistry.DEFAULT; } }; @NonNull - ProtocolVersion protocolVersion(); + ProtocolVersion getProtocolVersion(); @NonNull - CodecRegistry codecRegistry(); + CodecRegistry getCodecRegistry(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java index 0f279583bd0..055da787381 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java @@ -61,7 +61,7 @@ public class Ec2MultiRegionAddressTranslator implements AddressTranslator { public Ec2MultiRegionAddressTranslator( @SuppressWarnings("unused") @NonNull DriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); @SuppressWarnings("JdkObsolete") Hashtable env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java index 23a4baa32ee..2983c5b2b11 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java @@ -58,8 +58,8 @@ public class PlainTextAuthProvider implements AuthProvider { /** Builds a new instance. */ public PlainTextAuthProvider(DriverContext context) { - this.logPrefix = context.sessionName(); - this.config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + this.config = context.getConfig().getDefaultProfile(); } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 8568c90b1fc..7b55efc8b2e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -64,13 +64,13 @@ public class ChannelFactory { @VisibleForTesting volatile String clusterName; public ChannelFactory(InternalDriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); this.context = context; - DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.getConfig().getDefaultProfile(); if (defaultConfig.isDefined(DefaultDriverOption.PROTOCOL_VERSION)) { String versionName = defaultConfig.getString(DefaultDriverOption.PROTOCOL_VERSION); - this.protocolVersion = context.protocolVersionRegistry().fromName(versionName); + this.protocolVersion = context.getProtocolVersionRegistry().fromName(versionName); } // else it will be negotiated with the first opened connection } @@ -115,7 +115,7 @@ CompletionStage connect( currentVersion = protocolVersion; isNegotiating = false; } else { - currentVersion = context.protocolVersionRegistry().highestNonBeta(); + currentVersion = context.getProtocolVersionRegistry().highestNonBeta(); isNegotiating = true; } @@ -139,7 +139,7 @@ private void connect( List attemptedVersions, CompletableFuture resultFuture) { - NettyOptions nettyOptions = context.nettyOptions(); + NettyOptions nettyOptions = context.getNettyOptions(); Bootstrap bootstrap = new Bootstrap() @@ -149,7 +149,7 @@ private void connect( .handler( initializer(address, currentVersion, options, nodeMetricUpdater, resultFuture)); - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); boolean tcpNoDelay = config.getBoolean(DefaultDriverOption.SOCKET_TCP_NODELAY); bootstrap = bootstrap.option(ChannelOption.TCP_NODELAY, tcpNoDelay); @@ -187,7 +187,7 @@ private void connect( if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); DriverChannel driverChannel = - new DriverChannel(channel, context.writeCoalescer(), currentVersion); + new DriverChannel(channel, context.getWriteCoalescer(), currentVersion); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -202,7 +202,7 @@ private void connect( if (error instanceof UnsupportedProtocolVersionException && isNegotiating) { attemptedVersions.add(currentVersion); Optional downgraded = - context.protocolVersionRegistry().downgrade(currentVersion); + context.getProtocolVersionRegistry().downgrade(currentVersion); if (downgraded.isPresent()) { LOG.info( "[{}] Failed to connect with protocol {}, retrying with {}", @@ -241,7 +241,7 @@ ChannelInitializer initializer( @Override protected void initChannel(Channel channel) { try { - DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.getConfig().getDefaultProfile(); long setKeyspaceTimeoutMillis = defaultConfig @@ -270,12 +270,13 @@ protected void initChannel(Channel channel) { ChannelPipeline pipeline = channel.pipeline(); context - .sslHandlerFactory() + .getSslHandlerFactory() .map(f -> f.newSslHandler(channel, address)) .map(h -> pipeline.addLast("ssl", h)); // Only add meter handlers on the pipeline if metrics are enabled. - SessionMetricUpdater sessionMetricUpdater = context.metricsFactory().getSessionUpdater(); + SessionMetricUpdater sessionMetricUpdater = + context.getMetricsFactory().getSessionUpdater(); if (nodeMetricUpdater.isEnabled(DefaultNodeMetric.BYTES_RECEIVED, null) || sessionMetricUpdater.isEnabled(DefaultSessionMetric.BYTES_RECEIVED, null)) { pipeline.addLast( @@ -291,13 +292,13 @@ protected void initChannel(Channel channel) { } pipeline - .addLast("encoder", new FrameEncoder(context.frameCodec(), maxFrameLength)) - .addLast("decoder", new FrameDecoder(context.frameCodec(), maxFrameLength)) + .addLast("encoder", new FrameEncoder(context.getFrameCodec(), maxFrameLength)) + .addLast("decoder", new FrameDecoder(context.getFrameCodec(), maxFrameLength)) // Note: HeartbeatHandler is inserted here once init completes .addLast("inflight", inFlightHandler) .addLast("init", initHandler); - context.nettyOptions().afterChannelInitialized(channel); + context.getNettyOptions().afterChannelInitialized(channel); } catch (Throwable t) { // If the init handler throws an exception, Netty swallows it and closes the channel. We // want to propagate it instead, so fail the outer future (the result of connect()). diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index 94c7f1483e3..ecf8b9a6d5b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -54,7 +54,7 @@ public class DefaultWriteCoalescer implements WriteCoalescer { private final ConcurrentMap flushers = new ConcurrentHashMap<>(); public DefaultWriteCoalescer(DriverContext context) { - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.maxRunsWithNoWork = config.getInt(DefaultDriverOption.COALESCER_MAX_RUNS); this.rescheduleIntervalNanos = config.getDuration(DefaultDriverOption.COALESCER_INTERVAL).toNanos(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index e2d10d9453d..eda34429267 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -78,7 +78,7 @@ class ProtocolInitHandler extends ConnectInitHandler { this.context = context; - DriverExecutionProfile defaultConfig = context.config().getDefaultProfile(); + DriverExecutionProfile defaultConfig = context.getConfig().getDefaultProfile(); this.timeoutMillis = defaultConfig.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT).toMillis(); @@ -142,7 +142,7 @@ String describe() { Message getRequest() { switch (step) { case STARTUP: - return new Startup(context.compressor().algorithm()); + return new Startup(context.getCompressor().algorithm()); case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: @@ -166,7 +166,7 @@ void onResponse(Message response) { try { if (step == Step.STARTUP && response instanceof Ready) { context - .authProvider() + .getAuthProvider() .ifPresent(provider -> provider.onMissingChallenge(channel.remoteAddress())); step = Step.GET_CLUSTER_NAME; send(); @@ -301,7 +301,7 @@ void fail(String message, Throwable cause) { private Authenticator buildAuthenticator(SocketAddress address, String authenticator) { return context - .authProvider() + .getAuthProvider() .map(p -> p.newAuthenticator(address, authenticator)) .orElseThrow( () -> diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index d78745e7ceb..d19f9065968 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -124,13 +124,13 @@ private class SingleThreaded { private boolean closeWasCalled; private SingleThreaded(InternalDriverContext context) { - this.logPrefix = context.sessionName(); - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.eventBus = context.eventBus(); - this.config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); + this.eventBus = context.getEventBus(); + this.config = context.getConfig().getDefaultProfile(); this.reloadInterval = context - .config() + .getConfig() .getDefaultProfile() .getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java index 8ebb32b785f..6d89fd14c64 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java @@ -51,8 +51,8 @@ public class ConstantReconnectionPolicy implements ReconnectionPolicy { /** Builds a new instance. */ public ConstantReconnectionPolicy(DriverContext context) { - this.logPrefix = context.sessionName(); - DriverExecutionProfile config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); Duration delay = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY); if (delay.isNegative()) { throw new IllegalArgumentException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index 6efea703a9a..c78981156ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -62,9 +62,9 @@ public class ExponentialReconnectionPolicy implements ReconnectionPolicy { /** Builds a new instance. */ public ExponentialReconnectionPolicy(DriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.baseDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY).toMillis(); this.maxDelayMs = config.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY).toMillis(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 5a83b594c47..89424a6f568 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -313,12 +313,12 @@ protected Optional buildSslEngineFactory() { } protected EventBus buildEventBus() { - return new EventBus(sessionName()); + return new EventBus(getSessionName()); } @SuppressWarnings("unchecked") protected Compressor buildCompressor() { - DriverExecutionProfile defaultProfile = config().getDefaultProfile(); + DriverExecutionProfile defaultProfile = getConfig().getDefaultProfile(); if (defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_COMPRESSION)) { String name = defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION); if (name.equalsIgnoreCase("lz4")) { @@ -338,11 +338,11 @@ protected Compressor buildCompressor() { protected FrameCodec buildFrameCodec() { return FrameCodec.defaultClient( - new ByteBufPrimitiveCodec(nettyOptions().allocator()), compressor()); + new ByteBufPrimitiveCodec(getNettyOptions().allocator()), getCompressor()); } protected ProtocolVersionRegistry buildProtocolVersionRegistry() { - return new CassandraProtocolVersionRegistry(sessionName()); + return new CassandraProtocolVersionRegistry(getSessionName()); } protected ConsistencyLevelRegistry buildConsistencyLevelRegistry() { @@ -382,7 +382,7 @@ protected MetadataManager buildMetadataManager() { } protected LoadBalancingPolicyWrapper buildLoadBalancingPolicyWrapper() { - return new LoadBalancingPolicyWrapper(this, loadBalancingPolicies()); + return new LoadBalancingPolicyWrapper(this, getLoadBalancingPolicies()); } protected ControlConnection buildControlConnection() { @@ -390,7 +390,7 @@ protected ControlConnection buildControlConnection() { } protected RequestProcessorRegistry buildRequestProcessorRegistry() { - return RequestProcessorRegistry.defaultCqlProcessors(sessionName()); + return RequestProcessorRegistry.defaultCqlProcessors(getSessionName()); } protected CodecRegistry buildCodecRegistry(String logPrefix, List> codecs) { @@ -488,247 +488,247 @@ protected RequestTracker buildRequestTracker(RequestTracker requestTrackerFromBu @NonNull @Override - public String sessionName() { + public String getSessionName() { return sessionName; } @NonNull @Override - public DriverConfig config() { + public DriverConfig getConfig() { return config; } @NonNull @Override - public DriverConfigLoader configLoader() { + public DriverConfigLoader getConfigLoader() { return configLoader; } @NonNull @Override - public Map loadBalancingPolicies() { + public Map getLoadBalancingPolicies() { return loadBalancingPoliciesRef.get(); } @NonNull @Override - public Map retryPolicies() { + public Map getRetryPolicies() { return retryPoliciesRef.get(); } @NonNull @Override - public Map speculativeExecutionPolicies() { + public Map getSpeculativeExecutionPolicies() { return speculativeExecutionPoliciesRef.get(); } @NonNull @Override - public TimestampGenerator timestampGenerator() { + public TimestampGenerator getTimestampGenerator() { return timestampGeneratorRef.get(); } @NonNull @Override - public ReconnectionPolicy reconnectionPolicy() { + public ReconnectionPolicy getReconnectionPolicy() { return reconnectionPolicyRef.get(); } @NonNull @Override - public AddressTranslator addressTranslator() { + public AddressTranslator getAddressTranslator() { return addressTranslatorRef.get(); } @NonNull @Override - public Optional authProvider() { + public Optional getAuthProvider() { return authProviderRef.get(); } @NonNull @Override - public Optional sslEngineFactory() { + public Optional getSslEngineFactory() { return sslEngineFactoryRef.get(); } @NonNull @Override - public EventBus eventBus() { + public EventBus getEventBus() { return eventBusRef.get(); } @NonNull @Override - public Compressor compressor() { + public Compressor getCompressor() { return compressorRef.get(); } @NonNull @Override - public FrameCodec frameCodec() { + public FrameCodec getFrameCodec() { return frameCodecRef.get(); } @NonNull @Override - public ProtocolVersionRegistry protocolVersionRegistry() { + public ProtocolVersionRegistry getProtocolVersionRegistry() { return protocolVersionRegistryRef.get(); } @NonNull @Override - public ConsistencyLevelRegistry consistencyLevelRegistry() { + public ConsistencyLevelRegistry getConsistencyLevelRegistry() { return consistencyLevelRegistryRef.get(); } @NonNull @Override - public WriteTypeRegistry writeTypeRegistry() { + public WriteTypeRegistry getWriteTypeRegistry() { return writeTypeRegistryRef.get(); } @NonNull @Override - public NettyOptions nettyOptions() { + public NettyOptions getNettyOptions() { return nettyOptionsRef.get(); } @NonNull @Override - public WriteCoalescer writeCoalescer() { + public WriteCoalescer getWriteCoalescer() { return writeCoalescerRef.get(); } @NonNull @Override - public Optional sslHandlerFactory() { + public Optional getSslHandlerFactory() { return sslHandlerFactoryRef.get(); } @NonNull @Override - public ChannelFactory channelFactory() { + public ChannelFactory getChannelFactory() { return channelFactoryRef.get(); } @NonNull @Override - public ChannelPoolFactory channelPoolFactory() { + public ChannelPoolFactory getChannelPoolFactory() { return channelPoolFactory; } @NonNull @Override - public TopologyMonitor topologyMonitor() { + public TopologyMonitor getTopologyMonitor() { return topologyMonitorRef.get(); } @NonNull @Override - public MetadataManager metadataManager() { + public MetadataManager getMetadataManager() { return metadataManagerRef.get(); } @NonNull @Override - public LoadBalancingPolicyWrapper loadBalancingPolicyWrapper() { + public LoadBalancingPolicyWrapper getLoadBalancingPolicyWrapper() { return loadBalancingPolicyWrapperRef.get(); } @NonNull @Override - public ControlConnection controlConnection() { + public ControlConnection getControlConnection() { return controlConnectionRef.get(); } @NonNull @Override - public RequestProcessorRegistry requestProcessorRegistry() { + public RequestProcessorRegistry getRequestProcessorRegistry() { return requestProcessorRegistryRef.get(); } @NonNull @Override - public SchemaQueriesFactory schemaQueriesFactory() { + public SchemaQueriesFactory getSchemaQueriesFactory() { return schemaQueriesFactoryRef.get(); } @NonNull @Override - public SchemaParserFactory schemaParserFactory() { + public SchemaParserFactory getSchemaParserFactory() { return schemaParserFactoryRef.get(); } @NonNull @Override - public TokenFactoryRegistry tokenFactoryRegistry() { + public TokenFactoryRegistry getTokenFactoryRegistry() { return tokenFactoryRegistryRef.get(); } @NonNull @Override - public ReplicationStrategyFactory replicationStrategyFactory() { + public ReplicationStrategyFactory getReplicationStrategyFactory() { return replicationStrategyFactoryRef.get(); } @NonNull @Override - public PoolManager poolManager() { + public PoolManager getPoolManager() { return poolManagerRef.get(); } @NonNull @Override - public MetricsFactory metricsFactory() { + public MetricsFactory getMetricsFactory() { return metricsFactoryRef.get(); } @NonNull @Override - public RequestThrottler requestThrottler() { + public RequestThrottler getRequestThrottler() { return requestThrottlerRef.get(); } @NonNull @Override - public NodeStateListener nodeStateListener() { + public NodeStateListener getNodeStateListener() { return nodeStateListenerRef.get(); } @NonNull @Override - public SchemaChangeListener schemaChangeListener() { + public SchemaChangeListener getSchemaChangeListener() { return schemaChangeListenerRef.get(); } @NonNull @Override - public RequestTracker requestTracker() { + public RequestTracker getRequestTracker() { return requestTrackerRef.get(); } @Nullable @Override - public Predicate nodeFilter(String profileName) { + public Predicate getNodeFilter(String profileName) { return nodeFiltersFromBuilder.get(profileName); } @Nullable @Override - public ClassLoader classLoader() { + public ClassLoader getClassLoader() { return classLoader; } @NonNull @Override - public CodecRegistry codecRegistry() { + public CodecRegistry getCodecRegistry() { return codecRegistry; } @NonNull @Override - public ProtocolVersion protocolVersion() { - return channelFactory().getProtocolVersion(); + public ProtocolVersion getProtocolVersion() { + return getChannelFactory().getProtocolVersion(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index c66db18582b..228dfbf8bc6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -47,7 +47,7 @@ public class DefaultNettyOptions implements NettyOptions { private final TimeUnit adminShutdownUnit; public DefaultNettyOptions(InternalDriverContext context) { - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); int ioGroupSize = config.getInt(DefaultDriverOption.NETTY_IO_SIZE); this.ioShutdownQuietPeriod = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_QUIET_PERIOD); this.ioShutdownTimeout = config.getInt(DefaultDriverOption.NETTY_IO_SHUTDOWN_TIMEOUT); @@ -64,14 +64,14 @@ public DefaultNettyOptions(InternalDriverContext context) { ThreadFactory ioThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(context.sessionName() + "-io-%d") + .setNameFormat(context.getSessionName() + "-io-%d") .build(); this.ioEventLoopGroup = new NioEventLoopGroup(ioGroupSize, ioThreadFactory); ThreadFactory adminThreadFactory = new ThreadFactoryBuilder() .setThreadFactory(safeFactory) - .setNameFormat(context.sessionName() + "-admin-%d") + .setNameFormat(context.getSessionName() + "-admin-%d") .build(); this.adminEventLoopGroup = new DefaultEventLoopGroup(adminGroupSize, adminThreadFactory); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index a1cb5942088..feae0df50b5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -48,70 +48,70 @@ public interface InternalDriverContext extends DriverContext { @NonNull - EventBus eventBus(); + EventBus getEventBus(); @NonNull - Compressor compressor(); + Compressor getCompressor(); @NonNull - FrameCodec frameCodec(); + FrameCodec getFrameCodec(); @NonNull - ProtocolVersionRegistry protocolVersionRegistry(); + ProtocolVersionRegistry getProtocolVersionRegistry(); @NonNull - ConsistencyLevelRegistry consistencyLevelRegistry(); + ConsistencyLevelRegistry getConsistencyLevelRegistry(); @NonNull - WriteTypeRegistry writeTypeRegistry(); + WriteTypeRegistry getWriteTypeRegistry(); @NonNull - NettyOptions nettyOptions(); + NettyOptions getNettyOptions(); @NonNull - WriteCoalescer writeCoalescer(); + WriteCoalescer getWriteCoalescer(); @NonNull - Optional sslHandlerFactory(); + Optional getSslHandlerFactory(); @NonNull - ChannelFactory channelFactory(); + ChannelFactory getChannelFactory(); @NonNull - ChannelPoolFactory channelPoolFactory(); + ChannelPoolFactory getChannelPoolFactory(); @NonNull - TopologyMonitor topologyMonitor(); + TopologyMonitor getTopologyMonitor(); @NonNull - MetadataManager metadataManager(); + MetadataManager getMetadataManager(); @NonNull - LoadBalancingPolicyWrapper loadBalancingPolicyWrapper(); + LoadBalancingPolicyWrapper getLoadBalancingPolicyWrapper(); @NonNull - ControlConnection controlConnection(); + ControlConnection getControlConnection(); @NonNull - RequestProcessorRegistry requestProcessorRegistry(); + RequestProcessorRegistry getRequestProcessorRegistry(); @NonNull - SchemaQueriesFactory schemaQueriesFactory(); + SchemaQueriesFactory getSchemaQueriesFactory(); @NonNull - SchemaParserFactory schemaParserFactory(); + SchemaParserFactory getSchemaParserFactory(); @NonNull - TokenFactoryRegistry tokenFactoryRegistry(); + TokenFactoryRegistry getTokenFactoryRegistry(); @NonNull - ReplicationStrategyFactory replicationStrategyFactory(); + ReplicationStrategyFactory getReplicationStrategyFactory(); @NonNull - PoolManager poolManager(); + PoolManager getPoolManager(); @NonNull - MetricsFactory metricsFactory(); + MetricsFactory getMetricsFactory(); /** * This is the filter from {@link SessionBuilder#withNodeFilter(String, Predicate)}. If the filter @@ -119,7 +119,7 @@ public interface InternalDriverContext extends DriverContext { * {@code null}. */ @Nullable - Predicate nodeFilter(String profileName); + Predicate getNodeFilter(String profileName); /** * The {@link ClassLoader} to use to reflectively load class names defined in configuration. If @@ -127,5 +127,5 @@ public interface InternalDriverContext extends DriverContext { * or {@link com.datastax.oss.driver.internal.core.util.Reflection}'s {@link ClassLoader}. */ @Nullable - ClassLoader classLoader(); + ClassLoader getClassLoader(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 08f691c67a1..1caf7ffee9c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -56,8 +56,8 @@ public interface NettyOptions { /** * The byte buffer allocator to use. This must always return the same instance. Note that this is - * also used by the default implementation of {@link InternalDriverContext#frameCodec()}, and the - * built-in {@link com.datastax.oss.protocol.internal.Compressor} implementations. + * also used by the default implementation of {@link InternalDriverContext#getFrameCodec()}, and + * the built-in {@link com.datastax.oss.protocol.internal.Compressor} implementations. */ ByteBufAllocator allocator(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index ec97c3d9040..b25e833db19 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -82,8 +82,8 @@ public class ControlConnection implements EventCallback, AsyncAutoCloseable { public ControlConnection(InternalDriverContext context) { this.context = context; - this.logPrefix = context.sessionName(); - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.logPrefix = context.getSessionName(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); } @@ -177,13 +177,13 @@ public void onEvent(Message eventMessage) { private void processTopologyChange(Event event) { TopologyChangeEvent tce = (TopologyChangeEvent) event; - InetSocketAddress address = context.addressTranslator().translate(tce.address); + InetSocketAddress address = context.getAddressTranslator().translate(tce.address); switch (tce.changeType) { case ProtocolConstants.TopologyChangeType.NEW_NODE: - context.eventBus().fire(TopologyEvent.suggestAdded(address)); + context.getEventBus().fire(TopologyEvent.suggestAdded(address)); break; case ProtocolConstants.TopologyChangeType.REMOVED_NODE: - context.eventBus().fire(TopologyEvent.suggestRemoved(address)); + context.getEventBus().fire(TopologyEvent.suggestRemoved(address)); break; default: LOG.warn("[{}] Unsupported topology change type: {}", logPrefix, tce.changeType); @@ -192,13 +192,13 @@ private void processTopologyChange(Event event) { private void processStatusChange(Event event) { StatusChangeEvent sce = (StatusChangeEvent) event; - InetSocketAddress address = context.addressTranslator().translate(sce.address); + InetSocketAddress address = context.getAddressTranslator().translate(sce.address); switch (sce.changeType) { case ProtocolConstants.StatusChangeType.UP: - context.eventBus().fire(TopologyEvent.suggestUp(address)); + context.getEventBus().fire(TopologyEvent.suggestUp(address)); break; case ProtocolConstants.StatusChangeType.DOWN: - context.eventBus().fire(TopologyEvent.suggestDown(address)); + context.getEventBus().fire(TopologyEvent.suggestDown(address)); break; default: LOG.warn("[{}] Unsupported status change type: {}", logPrefix, sce.changeType); @@ -207,7 +207,7 @@ private void processStatusChange(Event event) { private void processSchemaChange(Event event) { SchemaChangeEvent sce = (SchemaChangeEvent) event; - context.metadataManager().refreshSchema(sce.keyspace, false, false); + context.getMetadataManager().refreshSchema(sce.keyspace, false, false); } private class SingleThreaded { @@ -225,7 +225,7 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; - ReconnectionPolicy reconnectionPolicy = context.reconnectionPolicy(); + ReconnectionPolicy reconnectionPolicy = context.getReconnectionPolicy(); this.reconnection = new Reconnection( logPrefix, @@ -241,10 +241,10 @@ private SingleThreaded(InternalDriverContext context) { }); context - .eventBus() + .getEventBus() .register(DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); context - .eventBus() + .getEventBus() .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); } @@ -263,7 +263,7 @@ private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { .withOwnerLogPrefix(logPrefix + "|control") .build(); - Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); + Queue nodes = context.getLoadBalancingPolicyWrapper().newQueryPlan(); connect( nodes, @@ -287,7 +287,7 @@ private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { private CompletionStage reconnect() { assert adminExecutor.inEventLoop(); - Queue nodes = context.loadBalancingPolicyWrapper().newQueryPlan(); + Queue nodes = context.getLoadBalancingPolicyWrapper().newQueryPlan(); CompletableFuture result = new CompletableFuture<>(); connect( nodes, @@ -312,7 +312,7 @@ private void connect( } else { LOG.debug("[{}] Trying to establish a connection to {}", logPrefix, node); context - .channelFactory() + .getChannelFactory() .connect(node, channelOptions) .whenCompleteAsync( (channel, error) -> { @@ -331,7 +331,7 @@ private void connect( Map newErrors = (errors == null) ? new LinkedHashMap<>() : errors; newErrors.put(node, error); - context.eventBus().fire(ChannelEvent.controlConnectionFailed(node)); + context.getEventBus().fire(ChannelEvent.controlConnectionFailed(node)); connect(nodes, newErrors, onSuccess, onFailure); } } else if (closeWasCalled || initFuture.isCancelled()) { @@ -369,7 +369,7 @@ private void connect( previousChannel.forceClose(); } ControlConnection.this.channel = channel; - context.eventBus().fire(ChannelEvent.channelOpened(node)); + context.getEventBus().fire(ChannelEvent.channelOpened(node)); channel .closeFuture() .addListener( @@ -398,7 +398,7 @@ private void onSuccessfulReconnect() { // Always perform a full refresh (we don't know how long we were disconnected) context - .metadataManager() + .getMetadataManager() .refreshNodes() .whenComplete( (result, error) -> { @@ -409,8 +409,8 @@ private void onSuccessfulReconnect() { // A failed node list refresh at startup is not fatal, so this might be the // first successful refresh; make sure the LBP gets initialized (this is a no-op // if it was initialized already). - context.loadBalancingPolicyWrapper().init(); - context.metadataManager().refreshSchema(null, false, true); + context.getLoadBalancingPolicyWrapper().init(); + context.getMetadataManager().refreshSchema(null, false, true); } catch (Throwable t) { Loggers.warnWithException( LOG, "[{}] Unexpected error on control connection reconnect", logPrefix, t); @@ -423,7 +423,7 @@ private void onChannelClosed(DriverChannel channel, Node node) { assert adminExecutor.inEventLoop(); if (!closeWasCalled) { LOG.debug("[{}] Lost channel {}", logPrefix, channel); - context.eventBus().fire(ChannelEvent.channelClosed(node)); + context.getEventBus().fire(ChannelEvent.channelClosed(node)); reconnection.start(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 15a38f16c7a..55f094f8546 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -100,7 +100,7 @@ public static Message toMessage( statement.getConsistencyLevel() != null ? statement.getConsistencyLevel() : context - .consistencyLevelRegistry() + .getConsistencyLevelRegistry() .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); int pageSize = statement.getPageSize() > 0 @@ -110,15 +110,15 @@ public static Message toMessage( statement.getSerialConsistencyLevel() != null ? statement.getSerialConsistencyLevel() : context - .consistencyLevelRegistry() + .getConsistencyLevelRegistry() .fromName(config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)); long timestamp = statement.getTimestamp(); if (timestamp == Long.MIN_VALUE) { - timestamp = context.timestampGenerator().next(); + timestamp = context.getTimestampGenerator().next(); } - CodecRegistry codecRegistry = context.codecRegistry(); - ProtocolVersion protocolVersion = context.protocolVersion(); - ProtocolVersionRegistry registry = context.protocolVersionRegistry(); + CodecRegistry codecRegistry = context.getCodecRegistry(); + ProtocolVersion protocolVersion = context.getProtocolVersion(); + ProtocolVersionRegistry registry = context.getProtocolVersionRegistry(); CqlIdentifier keyspace = statement.getKeyspace(); if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; @@ -362,8 +362,8 @@ public static DefaultPreparedStatement toPreparedStatement( request.getConsistencyLevelForBoundStatements(), request.getSerialConsistencyLevelForBoundStatements(), request.areBoundStatementsTracing(), - context.codecRegistry(), - context.protocolVersion()); + context.getCodecRegistry(), + context.getProtocolVersion()); } public static ColumnDefinitions toColumnDefinitions( @@ -406,7 +406,7 @@ public static CoordinatorException toThrowable( Unavailable unavailable = (Unavailable) errorMessage; return new UnavailableException( node, - context.consistencyLevelRegistry().fromCode(unavailable.consistencyLevel), + context.getConsistencyLevelRegistry().fromCode(unavailable.consistencyLevel), unavailable.required, unavailable.alive); case ProtocolConstants.ErrorCode.OVERLOADED: @@ -419,15 +419,15 @@ public static CoordinatorException toThrowable( WriteTimeout writeTimeout = (WriteTimeout) errorMessage; return new WriteTimeoutException( node, - context.consistencyLevelRegistry().fromCode(writeTimeout.consistencyLevel), + context.getConsistencyLevelRegistry().fromCode(writeTimeout.consistencyLevel), writeTimeout.received, writeTimeout.blockFor, - context.writeTypeRegistry().fromName(writeTimeout.writeType)); + context.getWriteTypeRegistry().fromName(writeTimeout.writeType)); case ProtocolConstants.ErrorCode.READ_TIMEOUT: ReadTimeout readTimeout = (ReadTimeout) errorMessage; return new ReadTimeoutException( node, - context.consistencyLevelRegistry().fromCode(readTimeout.consistencyLevel), + context.getConsistencyLevelRegistry().fromCode(readTimeout.consistencyLevel), readTimeout.received, readTimeout.blockFor, readTimeout.dataPresent); @@ -435,7 +435,7 @@ public static CoordinatorException toThrowable( ReadFailure readFailure = (ReadFailure) errorMessage; return new ReadFailureException( node, - context.consistencyLevelRegistry().fromCode(readFailure.consistencyLevel), + context.getConsistencyLevelRegistry().fromCode(readFailure.consistencyLevel), readFailure.received, readFailure.blockFor, readFailure.numFailures, @@ -447,10 +447,10 @@ public static CoordinatorException toThrowable( WriteFailure writeFailure = (WriteFailure) errorMessage; return new WriteFailureException( node, - context.consistencyLevelRegistry().fromCode(writeFailure.consistencyLevel), + context.getConsistencyLevelRegistry().fromCode(writeFailure.consistencyLevel), writeFailure.received, writeFailure.blockFor, - context.writeTypeRegistry().fromName(writeFailure.writeType), + context.getWriteTypeRegistry().fromName(writeFailure.writeType), writeFailure.numFailures, writeFailure.reasonMap); case ProtocolConstants.ErrorCode.SYNTAX_ERROR: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index 7f944fd1377..e4b11b9a100 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -120,7 +120,7 @@ protected CqlPrepareHandlerBase( if (request.getExecutionProfile() != null) { this.executionProfile = request.getExecutionProfile(); } else { - DriverConfig config = context.config(); + DriverConfig config = context.getConfig(); String profileName = request.getExecutionProfileName(); this.executionProfile = (profileName == null || profileName.isEmpty()) @@ -129,9 +129,9 @@ protected CqlPrepareHandlerBase( } this.queryPlan = context - .loadBalancingPolicyWrapper() + .getLoadBalancingPolicyWrapper() .newQueryPlan(request, executionProfile.getName(), session); - this.retryPolicy = context.retryPolicy(executionProfile.getName()); + this.retryPolicy = context.getRetryPolicy(executionProfile.getName()); this.result = new CompletableFuture<>(); this.result.exceptionally( @@ -145,8 +145,8 @@ protected CqlPrepareHandlerBase( } return null; }); - ProtocolVersion protocolVersion = context.protocolVersion(); - ProtocolVersionRegistry registry = context.protocolVersionRegistry(); + ProtocolVersion protocolVersion = context.getProtocolVersion(); + ProtocolVersionRegistry registry = context.getProtocolVersionRegistry(); CqlIdentifier keyspace = request.getKeyspace(); if (keyspace != null && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { @@ -155,7 +155,7 @@ protected CqlPrepareHandlerBase( } this.message = new Prepare(request.getQuery(), (keyspace == null) ? null : keyspace.asInternal()); - this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); + this.scheduler = context.getNettyOptions().ioEventLoopGroup().next(); this.timeout = request.getTimeout() != null @@ -164,7 +164,7 @@ protected CqlPrepareHandlerBase( this.timeoutFuture = scheduleTimeout(timeout); this.prepareOnAllNodes = executionProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); - this.throttler = context.requestThrottler(); + this.throttler = context.getRequestThrottler(); this.throttler.register(this); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index db450bfa6f3..a85bfdb6138 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -146,7 +146,7 @@ protected CqlRequestHandlerBase( if (statement.getExecutionProfile() != null) { this.executionProfile = statement.getExecutionProfile(); } else { - DriverConfig config = context.config(); + DriverConfig config = context.getConfig(); String profileName = statement.getExecutionProfileName(); this.executionProfile = (profileName == null || profileName.isEmpty()) @@ -155,11 +155,11 @@ protected CqlRequestHandlerBase( } this.queryPlan = context - .loadBalancingPolicyWrapper() + .getLoadBalancingPolicyWrapper() .newQueryPlan(statement, executionProfile.getName(), session); - this.retryPolicy = context.retryPolicy(executionProfile.getName()); + this.retryPolicy = context.getRetryPolicy(executionProfile.getName()); this.speculativeExecutionPolicy = - context.speculativeExecutionPolicy(executionProfile.getName()); + context.getSpeculativeExecutionPolicy(executionProfile.getName()); Boolean statementIsIdempotent = statement.isIdempotent(); this.isIdempotent = (statementIsIdempotent == null) @@ -178,7 +178,7 @@ protected CqlRequestHandlerBase( return null; }); this.message = Conversions.toMessage(statement, executionProfile, context); - this.scheduler = context.nettyOptions().ioEventLoopGroup().next(); + this.scheduler = context.getNettyOptions().ioEventLoopGroup().next(); this.timeout = statement.getTimeout() != null @@ -191,7 +191,7 @@ protected CqlRequestHandlerBase( this.scheduledExecutions = isIdempotent ? new CopyOnWriteArrayList<>() : null; this.inFlightCallbacks = new CopyOnWriteArrayList<>(); - this.throttler = context.requestThrottler(); + this.throttler = context.getRequestThrottler(); this.throttler.register(this); } @@ -307,10 +307,10 @@ private void setFinalResult( long totalLatencyNanos = now - startTimeNanos; long nodeLatencyNanos = now - callback.start; context - .requestTracker() + .getRequestTracker() .onNodeSuccess(statement, nodeLatencyNanos, executionProfile, callback.node); context - .requestTracker() + .getRequestTracker() .onSuccess(statement, totalLatencyNanos, executionProfile, callback.node); session .getMetricUpdater() @@ -374,7 +374,7 @@ private void setFinalError(Throwable error, Node node, int execution) { if (result.completeExceptionally(error)) { cancelScheduledTasks(); long latencyNanos = System.nanoTime() - startTimeNanos; - context.requestTracker().onError(statement, error, latencyNanos, executionProfile, node); + context.getRequestTracker().onError(statement, error, latencyNanos, executionProfile, node); if (error instanceof DriverTimeoutException) { throttler.signalTimeout(this); session @@ -512,10 +512,10 @@ public void onResponse(Frame responseFrame) { if (responseMessage instanceof SchemaChange) { SchemaChange schemaChange = (SchemaChange) responseMessage; context - .topologyMonitor() + .getTopologyMonitor() .checkSchemaAgreement() .thenCombine( - context.metadataManager().refreshSchema(schemaChange.keyspace, false, false), + context.getMetadataManager().refreshSchema(schemaChange.keyspace, false, false), (schemaInAgreement, metadata) -> schemaInAgreement) .whenComplete( ((schemaInAgreement, error) -> @@ -762,7 +762,9 @@ public void cancel() { private void trackNodeError(Node node, Throwable error) { long latencyNanos = System.nanoTime() - this.start; - context.requestTracker().onNodeError(statement, error, latencyNanos, executionProfile, node); + context + .getRequestTracker() + .onNodeError(statement, error, latencyNanos, executionProfile, node); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java index ed2f11224c7..1b4db7968f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultRow.java @@ -101,13 +101,13 @@ public DataType getType(@NonNull String name) { @NonNull @Override public CodecRegistry codecRegistry() { - return attachmentPoint.codecRegistry(); + return attachmentPoint.getCodecRegistry(); } @NonNull @Override public ProtocolVersion protocolVersion() { - return attachmentPoint.protocolVersion(); + return attachmentPoint.getProtocolVersion(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index e2ed393af1c..45a78447995 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -58,11 +58,11 @@ class QueryTraceFetcher { ConsistencyLevel regularConsistency = context - .consistencyLevelRegistry() + .getConsistencyLevelRegistry() .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); ConsistencyLevel traceConsistency = context - .consistencyLevelRegistry() + .getConsistencyLevelRegistry() .fromName(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)); this.config = (traceConsistency.equals(regularConsistency)) @@ -71,7 +71,7 @@ class QueryTraceFetcher { this.maxAttempts = config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS); this.intervalNanos = config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); - this.scheduler = context.nettyOptions().adminEventExecutorGroup().next(); + this.scheduler = context.getNettyOptions().adminEventExecutorGroup().next(); querySession(maxAttempts); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index 3f887ef5250..132610886cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -48,8 +48,8 @@ public DefaultTupleValue(@NonNull TupleType type, @NonNull Object... values) { ValuesHelper.encodeValues( values, type.getComponentTypes(), - type.getAttachmentPoint().codecRegistry(), - type.getAttachmentPoint().protocolVersion())); + type.getAttachmentPoint().getCodecRegistry(), + type.getAttachmentPoint().getProtocolVersion())); } private DefaultTupleValue(TupleType type, ByteBuffer[] values) { @@ -90,13 +90,13 @@ public DataType getType(int i) { @NonNull @Override public CodecRegistry codecRegistry() { - return type.getAttachmentPoint().codecRegistry(); + return type.getAttachmentPoint().getCodecRegistry(); } @NonNull @Override public ProtocolVersion protocolVersion() { - return type.getAttachmentPoint().protocolVersion(); + return type.getAttachmentPoint().getProtocolVersion(); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index a59a512f200..e07e579ac67 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -50,8 +50,8 @@ public DefaultUdtValue(@NonNull UserDefinedType type, @NonNull Object... values) ValuesHelper.encodeValues( values, type.getFieldTypes(), - type.getAttachmentPoint().codecRegistry(), - type.getAttachmentPoint().protocolVersion())); + type.getAttachmentPoint().getCodecRegistry(), + type.getAttachmentPoint().getProtocolVersion())); } private DefaultUdtValue(UserDefinedType type, ByteBuffer[] values) { @@ -110,13 +110,13 @@ public UdtValue setBytesUnsafe(int i, @Nullable ByteBuffer v) { @NonNull @Override public CodecRegistry codecRegistry() { - return type.getAttachmentPoint().codecRegistry(); + return type.getAttachmentPoint().getCodecRegistry(); } @NonNull @Override public ProtocolVersion protocolVersion() { - return type.getAttachmentPoint().protocolVersion(); + return type.getAttachmentPoint().getProtocolVersion(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index d334e91dee3..218c5737116 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -87,7 +87,7 @@ public class DefaultLoadBalancingPolicy implements LoadBalancingPolicy { public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull String profileName) { this( - context.sessionName() + "|" + profileName, + context.getSessionName() + "|" + profileName, getLocalDcFromConfig(context, profileName), getFilterFromConfig(context, profileName), context, @@ -102,7 +102,7 @@ public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull Strin @NonNull DriverContext context, boolean isDefaultPolicy) { this.logPrefix = logPrefix; - this.metadataManager = ((InternalDriverContext) context).metadataManager(); + this.metadataManager = ((InternalDriverContext) context).getMetadataManager(); if (localDcFromConfig != null) { LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcFromConfig); this.localDc = localDcFromConfig; @@ -305,13 +305,14 @@ public void close() { } private static String getLocalDcFromConfig(DriverContext context, String profileName) { - DriverExecutionProfile config = context.config().getProfile(profileName); + DriverExecutionProfile config = context.getConfig().getProfile(profileName); return config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); } @SuppressWarnings("unchecked") private static Predicate getFilterFromConfig(DriverContext context, String profileName) { - Predicate filterFromBuilder = ((InternalDriverContext) context).nodeFilter(profileName); + Predicate filterFromBuilder = + ((InternalDriverContext) context).getNodeFilter(profileName); return (filterFromBuilder != null) ? filterFromBuilder : (Predicate) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 53b1a472d2a..4b7dda81111 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -41,7 +41,7 @@ public Result compute( return new Result(oldMetadata); } else { DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress(), context); - copyInfos(newNodeInfo, newNode, null, context.sessionName()); + copyInfos(newNodeInfo, newNode, null, context.getSessionName()); Map newNodes = ImmutableMap.builder() .putAll(oldNodes) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 469dc829631..9c22ed40709 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -126,8 +126,8 @@ protected TokenMap rebuildTokenMap( TokenFactory tokenFactory, InternalDriverContext context) { - String logPrefix = context.sessionName(); - ReplicationStrategyFactory replicationStrategyFactory = context.replicationStrategyFactory(); + String logPrefix = context.getSessionName(); + ReplicationStrategyFactory replicationStrategyFactory = context.getReplicationStrategyFactory(); if (!tokenMapEnabled) { LOG.debug("[{}] Token map is disabled, skipping", logPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index 700a4c3a8c0..b1693248cda 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -67,7 +67,7 @@ public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext conte this.extras = Collections.emptyMap(); // We leak a reference to a partially constructed object (this), but in practice this won't be a // problem because the node updater only needs the connect address to initialize. - this.metricUpdater = context.metricsFactory().newNodeUpdater(this); + this.metricUpdater = context.getMetricsFactory().newNodeUpdater(this); this.upSinceMillis = -1; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 2f9deb81684..9a65c0b5dac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -68,11 +68,11 @@ public class DefaultTopologyMonitor implements TopologyMonitor { @VisibleForTesting volatile int port = -1; public DefaultTopologyMonitor(InternalDriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); this.context = context; - this.controlConnection = context.controlConnection(); - this.addressTranslator = context.addressTranslator(); - DriverExecutionProfile config = context.config().getDefaultProfile(); + this.controlConnection = context.getControlConnection(); + this.addressTranslator = context.getAddressTranslator(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.timeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.reconnectOnInit = config.getBoolean(DefaultDriverOption.RECONNECT_ON_INIT); this.closeFuture = new CompletableFuture<>(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 8e56eb8f42d..61216af2a4a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -48,8 +48,8 @@ class FullNodeListRefresh extends NodesRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - String logPrefix = context.sessionName(); - TokenFactoryRegistry tokenFactoryRegistry = context.tokenFactoryRegistry(); + String logPrefix = context.getSessionName(); + TokenFactoryRegistry tokenFactoryRegistry = context.getTokenFactoryRegistry(); Map oldNodes = oldMetadata.getNodes(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java index 8d806562ab8..02b744ac770 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java @@ -40,7 +40,7 @@ class InitContactPointsRefresh implements MetadataRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - String logPrefix = context.sessionName(); + String logPrefix = context.getSessionName(); LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); ImmutableMap.Builder newNodes = ImmutableMap.builder(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index 3548b8cb488..351e83b2ff2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -106,8 +106,8 @@ public LoadBalancingPolicyWrapper( this.distances = new HashMap<>(); - this.logPrefix = context.sessionName(); - context.eventBus().register(NodeStateEvent.class, this::onNodeStateEvent); + this.logPrefix = context.getSessionName(); + context.getEventBus().register(NodeStateEvent.class, this::onNodeStateEvent); } public void init() { @@ -116,7 +116,7 @@ public void init() { // State events can happen concurrently with init, so we must record them and replay once the // policy is initialized. eventFilter.start(); - MetadataManager metadataManager = context.metadataManager(); + MetadataManager metadataManager = context.getMetadataManager(); Metadata metadata = metadataManager.getMetadata(); for (LoadBalancingPolicy policy : policies) { policy.init( @@ -148,7 +148,7 @@ public Queue newQueryPlan( // Retrieve nodes from the metadata (at this stage it's the contact points). The only time // when this can happen is during control connection initialization. List nodes = new ArrayList<>(); - nodes.addAll(context.metadataManager().getMetadata().getNodes().values()); + nodes.addAll(context.getMetadataManager().getMetadata().getNodes().values()); Collections.shuffle(nodes); return new ConcurrentLinkedQueue<>(nodes); case RUNNING: @@ -263,7 +263,7 @@ public void setDistance(@NonNull Node node, @NonNull NodeDistance suggestedDista LOG.debug("[{}] {} was {}, changing to {}", logPrefix, node, oldDistance, newDistance); DefaultNode defaultNode = (DefaultNode) node; defaultNode.distance = newDistance; - context.eventBus().fire(new DistanceEvent(newDistance, defaultNode)); + context.getEventBus().fire(new DistanceEvent(newDistance, defaultNode)); } else { LOG.debug("[{}] {} was already {}, ignoring", logPrefix, node, oldDistance); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index 39e8d45d71c..ce51374e9f1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -75,18 +75,18 @@ public MetadataManager(InternalDriverContext context) { protected MetadataManager(InternalDriverContext context, DefaultMetadata initialMetadata) { this.context = context; this.metadata = initialMetadata; - this.logPrefix = context.sessionName(); - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); + this.config = context.getConfig().getDefaultProfile(); this.singleThreaded = new SingleThreaded(context, config); - this.controlConnection = context.controlConnection(); + this.controlConnection = context.getControlConnection(); this.schemaEnabledInConfig = config.getBoolean(DefaultDriverOption.METADATA_SCHEMA_ENABLED); this.refreshedKeyspaces = config.getStringList( DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList()); this.tokenMapEnabled = config.getBoolean(DefaultDriverOption.METADATA_TOKEN_MAP_ENABLED); - context.eventBus().register(ConfigChangeEvent.class, this::onConfigChanged); + context.getEventBus().register(ConfigChangeEvent.class, this::onConfigChanged); } private void onConfigChanged(@SuppressWarnings("unused") ConfigChangeEvent event) { @@ -134,14 +134,14 @@ public Set getContactPoints() { public CompletionStage refreshNodes() { return context - .topologyMonitor() + .getTopologyMonitor() .refreshNodeList() .thenApplyAsync(singleThreaded::refreshNodes, adminExecutor); } public CompletionStage refreshNode(Node node) { return context - .topologyMonitor() + .getTopologyMonitor() .refreshNode(node) .thenApplyAsync( maybeInfo -> { @@ -164,7 +164,7 @@ public CompletionStage refreshNode(Node node) { public void addNode(InetSocketAddress address) { context - .topologyMonitor() + .getTopologyMonitor() .getNewNodeInfo(address) .whenCompleteAsync( (info, error) -> { @@ -270,8 +270,8 @@ private SingleThreaded(InternalDriverContext context, DriverExecutionProfile con this::startSchemaRequest, config.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW), config.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)); - this.schemaQueriesFactory = context.schemaQueriesFactory(); - this.schemaParserFactory = context.schemaParserFactory(); + this.schemaQueriesFactory = context.getSchemaQueriesFactory(); + this.schemaParserFactory = context.getSchemaParserFactory(); } private void initNodes( @@ -381,7 +381,7 @@ private void startSchemaRequest(CompletableFuture future) { currentSchemaRefresh = future; LOG.debug("[{}] Starting schema refresh", logPrefix); maybeInitControlConnection() - .thenCompose(v -> context.topologyMonitor().checkSchemaAgreement()) + .thenCompose(v -> context.getTopologyMonitor().checkSchemaAgreement()) // 1. Query system tables .thenCompose(b -> schemaQueriesFactory.newInstance(future).execute()) // 2. Parse the rows into metadata objects, put them in a MetadataRefresh @@ -464,7 +464,7 @@ Void apply(MetadataRefresh refresh) { refresh instanceof SchemaRefresh && !singleThreaded.firstSchemaRefreshFuture.isDone(); if (!singleThreaded.closeWasCalled && !isFirstSchemaRefresh) { for (Object event : result.events) { - context.eventBus().fire(event); + context.getEventBus().fire(event); } } return null; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index f498c68e3b0..528a40f3f84 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -54,9 +54,9 @@ public class NodeStateManager implements AsyncAutoCloseable { private final String logPrefix; public NodeStateManager(InternalDriverContext context) { - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); this.singleThreaded = new SingleThreaded(context); - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } /** @@ -96,9 +96,9 @@ private class SingleThreaded { private boolean closeWasCalled; private SingleThreaded(InternalDriverContext context) { - this.metadataManager = context.metadataManager(); + this.metadataManager = context.getMetadataManager(); - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.topologyEventDebouncer = new Debouncer<>( adminExecutor, @@ -107,7 +107,7 @@ private SingleThreaded(InternalDriverContext context) { config.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW), config.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)); - this.eventBus = context.eventBus(); + this.eventBus = context.getEventBus(); this.eventBus.register( ChannelEvent.class, RunOrSchedule.on(adminExecutor, this::onChannelEvent)); this.eventBus.register( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 1b13b4f5f3d..58f0c2c5133 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -41,7 +41,7 @@ public class RemoveNodeRefresh extends NodesRefresh { public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - String logPrefix = context.sessionName(); + String logPrefix = context.getSessionName(); Map oldNodes = oldMetadata.getNodes(); Node node = oldNodes.get(toRemove); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java index 633f9728402..787a60c8504 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java @@ -75,7 +75,7 @@ class SchemaAgreementChecker { this.context = context; this.port = port; this.logPrefix = logPrefix; - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.queryTimeout = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT); this.intervalNs = config.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL).toNanos(); @@ -128,7 +128,7 @@ private Set extractSchemaVersions(AdminResult controlNodeResult, AdminResu uuids.add(uuid); } - Map nodes = context.metadataManager().getMetadata().getNodes(); + Map nodes = context.getMetadataManager().getMetadata().getNodes(); for (AdminRow peerRow : peersResult) { InetSocketAddress connectAddress = getConnectAddress(peerRow); Node node = nodes.get(connectAddress); @@ -165,7 +165,7 @@ private InetSocketAddress getConnectAddress(AdminRow peerRow) { broadcastAddress); rpcAddress = broadcastAddress; } - return context.addressTranslator().translate(new InetSocketAddress(rpcAddress, port)); + return context.getAddressTranslator().translate(new InetSocketAddress(rpcAddress, port)); } private void completeOrReschedule(Set uuids, Throwable error) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java index d31460e34ff..1e74a1ee9a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParser.java @@ -79,7 +79,7 @@ public AggregateMetadata parseAggregate( DataType stateType = dataTypeParser.parse(keyspaceId, row.getString("state_type"), userDefinedTypes, context); - TypeCodec stateTypeCodec = context.codecRegistry().codecFor(stateType); + TypeCodec stateTypeCodec = context.getCodecRegistry().codecFor(stateType); String stateFuncSimpleName = row.getString("state_func"); FunctionSignature stateFuncSignature = @@ -108,7 +108,7 @@ public AggregateMetadata parseAggregate( initCond = (initCondBlob == null) ? null - : stateTypeCodec.decode(initCondBlob, context.protocolVersion()); + : stateTypeCodec.decode(initCondBlob, context.getProtocolVersion()); } return new DefaultAggregateMetadata( keyspaceId, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java index f305484cfda..04b6f69edec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/CassandraSchemaParser.java @@ -59,7 +59,7 @@ public class CassandraSchemaParser implements SchemaParser { public CassandraSchemaParser(SchemaRows rows, InternalDriverContext context) { this.rows = rows; - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); this.userDefinedTypeParser = new UserDefinedTypeParser(rows.dataTypeParser(), context); this.tableParser = new TableParser(rows, context); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java index 95696ea967a..d2470c0d48a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParser.java @@ -95,7 +95,7 @@ public DataType parse( LOG.warn( "[{}] Got o.a.c.db.marshal.FrozenType for something else than a collection, " + "this driver version might be too old for your version of Cassandra", - context.sessionName()); + context.getSessionName()); if (next.startsWith("org.apache.cassandra.db.marshal.UserType")) { ++parser.idx; // skipping '(' @@ -104,7 +104,7 @@ public DataType parse( parser.skipBlankAndComma(); String typeName = TypeCodecs.TEXT.decode( - Bytes.fromHexString("0x" + parser.readOne()), context.protocolVersion()); + Bytes.fromHexString("0x" + parser.readOne()), context.getProtocolVersion()); if (typeName == null) { throw new AssertionError("Type name cannot be null, this is a server bug"); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java index 6da4fa2637e..edd5a6bfe8f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/FunctionParser.java @@ -43,7 +43,7 @@ public class FunctionParser { public FunctionParser(DataTypeParser dataTypeParser, InternalDriverContext context) { this.dataTypeParser = dataTypeParser; this.context = context; - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } public FunctionMetadata parseFunction( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java index f59251f5123..85db273d12e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -39,7 +39,7 @@ public abstract class RelationParser { protected RelationParser(SchemaRows rows, InternalDriverContext context) { this.rows = rows; this.context = context; - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } protected Map parseOptions(AdminRow row) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index 4873b4cf2af..4da5c646b0b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -36,17 +36,17 @@ public class DefaultSchemaQueriesFactory implements SchemaQueriesFactory { public DefaultSchemaQueriesFactory(InternalDriverContext context) { this.context = context; - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } @Override public SchemaQueries newInstance(CompletableFuture refreshFuture) { - DriverChannel channel = context.controlConnection().channel(); + DriverChannel channel = context.getControlConnection().channel(); if (channel == null || channel.closeFuture().isDone()) { throw new IllegalStateException("Control channel not available, aborting schema refresh"); } @SuppressWarnings("SuspiciousMethodCalls") - Node node = context.metadataManager().getMetadata().getNodes().get(channel.remoteAddress()); + Node node = context.getMetadataManager().getMetadata().getNodes().get(channel.remoteAddress()); if (node == null) { throw new IllegalStateException( "Could not find control node metadata " @@ -69,7 +69,7 @@ protected SchemaQueries newInstance( } else { version = version.nextStable(); } - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); LOG.debug("[{}] Sending schema queries to {} with version {}", logPrefix, node, version); if (version.compareTo(Version.V2_2_0) < 0) { return new Cassandra21SchemaQueries(channel, refreshFuture, config, logPrefix); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java index 291e2bfe5e0..603d1af07bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultReplicationStrategyFactory.java @@ -26,7 +26,7 @@ public class DefaultReplicationStrategyFactory implements ReplicationStrategyFac private final String logPrefix; public DefaultReplicationStrategyFactory(InternalDriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java index e8b48184b0e..f7d83c9bdc8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java @@ -28,7 +28,7 @@ public class DefaultTokenFactoryRegistry implements TokenFactoryRegistry { private final String logPrefix; public DefaultTokenFactoryRegistry(InternalDriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java index 1b998cfdeef..c56b9e375bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java @@ -48,10 +48,10 @@ public class DropwizardMetricsFactory implements MetricsFactory { private final SessionMetricUpdater sessionUpdater; public DropwizardMetricsFactory(InternalDriverContext context) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); this.context = context; - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); Set enabledSessionMetrics = parseSessionMetricPaths(config.getStringList(DefaultDriverOption.METRICS_SESSION_ENABLED)); this.enabledNodeMetrics = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java index 071b4b0c8b8..016259da3b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java @@ -44,9 +44,9 @@ public DropwizardNodeMetricUpdater( MetricRegistry registry, InternalDriverContext context) { super(enabledMetrics, registry); - this.metricNamePrefix = buildPrefix(context.sessionName(), node.getConnectAddress()); + this.metricNamePrefix = buildPrefix(context.getSessionName(), node.getConnectAddress()); - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); if (enabledMetrics.contains(DefaultNodeMetric.OPEN_CONNECTIONS)) { this.registry.register( @@ -119,7 +119,7 @@ private void initializePoolGauge( buildFullName(metric, null), (Gauge) () -> { - ChannelPool pool = context.poolManager().getPools().get(node); + ChannelPool pool = context.getPoolManager().getPools().get(node); return (pool == null) ? 0 : reading.apply(pool); }); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java index 4c9b27f88c2..1241d2fcd60 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java @@ -41,7 +41,7 @@ public class DropwizardSessionMetricUpdater extends DropwizardMetricUpdater enabledMetrics, MetricRegistry registry, InternalDriverContext context) { super(enabledMetrics, registry); - this.metricNamePrefix = context.sessionName() + "."; + this.metricNamePrefix = context.getSessionName() + "."; if (enabledMetrics.contains(DefaultSessionMetric.CONNECTED_NODES)) { this.registry.register( @@ -49,7 +49,7 @@ public DropwizardSessionMetricUpdater( (Gauge) () -> { int count = 0; - for (Node node : context.metadataManager().getMetadata().getNodes().values()) { + for (Node node : context.getMetadataManager().getMetadata().getNodes().values()) { if (node.getOpenConnections() > 0) { count += 1; } @@ -60,18 +60,18 @@ public DropwizardSessionMetricUpdater( if (enabledMetrics.contains(DefaultSessionMetric.THROTTLING_QUEUE_SIZE)) { this.registry.register( buildFullName(DefaultSessionMetric.THROTTLING_QUEUE_SIZE, null), - buildQueueGauge(context.requestThrottler(), context.sessionName())); + buildQueueGauge(context.getRequestThrottler(), context.getSessionName())); } initializeHdrTimer( DefaultSessionMetric.CQL_REQUESTS, - context.config().getDefaultProfile(), + context.getConfig().getDefaultProfile(), DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_HIGHEST, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_DIGITS, DefaultDriverOption.METRICS_SESSION_CQL_REQUESTS_INTERVAL); initializeDefaultCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS, null); initializeHdrTimer( DefaultSessionMetric.THROTTLING_DELAY, - context.config().getDefaultProfile(), + context.getConfig().getDefaultProfile(), DefaultDriverOption.METRICS_SESSION_THROTTLING_HIGHEST, DefaultDriverOption.METRICS_SESSION_THROTTLING_DIGITS, DefaultDriverOption.METRICS_SESSION_THROTTLING_INTERVAL); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index bc1423b45d8..aa961ea3d03 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -107,7 +107,7 @@ private ChannelPool( String sessionLogPrefix) { this.node = node; this.initialKeyspaceName = keyspaceName; - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); this.sessionLogPrefix = sessionLogPrefix; this.logPrefix = sessionLogPrefix + "|" + node.getConnectAddress(); this.singleThreaded = new SingleThreaded(keyspaceName, distance, context); @@ -230,12 +230,12 @@ private class SingleThreaded { private SingleThreaded( CqlIdentifier keyspaceName, NodeDistance distance, InternalDriverContext context) { this.keyspaceName = keyspaceName; - this.config = context.config(); + this.config = context.getConfig(); this.distance = distance; this.wantedCount = getConfiguredSize(distance); - this.channelFactory = context.channelFactory(); - this.eventBus = context.eventBus(); - ReconnectionPolicy reconnectionPolicy = context.reconnectionPolicy(); + this.channelFactory = context.getChannelFactory(); + this.eventBus = context.getEventBus(); + ReconnectionPolicy reconnectionPolicy = context.getReconnectionPolicy(); this.reconnection = new Reconnection( logPrefix, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java index 1a0205118b3..e1bce12fc11 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/protocol/Lz4Compressor.java @@ -36,7 +36,7 @@ public class Lz4Compressor extends ByteBufCompressor { public Lz4Compressor(DriverContext context) { try { LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); - LOG.info("[{}] Using {}", context.sessionName(), lz4Factory.toString()); + LOG.info("[{}] Using {}", context.getSessionName(), lz4Factory.toString()); this.compressor = lz4Factory.fastCompressor(); this.decompressor = lz4Factory.fastDecompressor(); } catch (NoClassDefFoundError e) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java index f2242676d7a..c15cfb41baa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicy.java @@ -85,7 +85,7 @@ public class DefaultRetryPolicy implements RetryPolicy { public DefaultRetryPolicy( @SuppressWarnings("unused") DriverContext context, @SuppressWarnings("unused") String profileName) { - this.logPrefix = (context != null ? context.sessionName() : null) + "|" + profileName; + this.logPrefix = (context != null ? context.getSessionName() : null) + "|" + profileName; } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index e07e3ec65ad..cc0fd2b6a6c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -93,15 +93,15 @@ public static CompletionStage init( private final SessionMetricUpdater metricUpdater; private DefaultSession(InternalDriverContext context, Set contactPoints) { - LOG.debug("Creating new session {}", context.sessionName()); - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); + LOG.debug("Creating new session {}", context.getSessionName()); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); this.context = context; this.singleThreaded = new SingleThreaded(context, contactPoints); - this.metadataManager = context.metadataManager(); - this.processorRegistry = context.requestProcessorRegistry(); - this.poolManager = context.poolManager(); - this.logPrefix = context.sessionName(); - this.metricUpdater = context.metricsFactory().getSessionUpdater(); + this.metadataManager = context.getMetadataManager(); + this.processorRegistry = context.getRequestProcessorRegistry(); + this.poolManager = context.getPoolManager(); + this.logPrefix = context.getSessionName(); + this.metricUpdater = context.getMetricsFactory().getSessionUpdater(); } private CompletionStage init(CqlIdentifier keyspace) { @@ -112,7 +112,7 @@ private CompletionStage init(CqlIdentifier keyspace) { @NonNull @Override public String getName() { - return context.sessionName(); + return context.getSessionName(); } @NonNull @@ -141,7 +141,7 @@ public CompletionStage refreshSchemaAsync() { @NonNull @Override public CompletionStage checkSchemaAgreementAsync() { - return context.topologyMonitor().checkSchemaAgreement(); + return context.getTopologyMonitor().checkSchemaAgreement(); } @NonNull @@ -159,7 +159,7 @@ public Optional getKeyspace() { @NonNull @Override public Optional getMetrics() { - return context.metricsFactory().getMetrics(); + return context.getMetricsFactory().getMetrics(); } /** @@ -254,13 +254,14 @@ private SingleThreaded(InternalDriverContext context, Set con this.context = context; this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; - new SchemaListenerNotifier(context.schemaChangeListener(), context.eventBus(), adminExecutor); + new SchemaListenerNotifier( + context.getSchemaChangeListener(), context.getEventBus(), adminExecutor); context - .eventBus() + .getEventBus() .register( NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onNodeStateChanged)); CompletableFutures.propagateCancellation( - this.initFuture, context.topologyMonitor().initFuture()); + this.initFuture, context.getTopologyMonitor().initFuture()); } private void init(CqlIdentifier keyspace) { @@ -274,30 +275,30 @@ private void init(CqlIdentifier keyspace) { // Eagerly fetch user-facing policies right now, no need to start opening connections if // something is wrong in the configuration. try { - context.loadBalancingPolicies(); - context.retryPolicies(); - context.speculativeExecutionPolicies(); - context.reconnectionPolicy(); - context.addressTranslator(); - context.nodeStateListener(); - context.schemaChangeListener(); - context.requestTracker(); - context.requestThrottler(); - context.authProvider(); - context.sslHandlerFactory(); - context.timestampGenerator(); + context.getLoadBalancingPolicies(); + context.getRetryPolicies(); + context.getSpeculativeExecutionPolicies(); + context.getReconnectionPolicy(); + context.getAddressTranslator(); + context.getNodeStateListener(); + context.getSchemaChangeListener(); + context.getRequestTracker(); + context.getRequestThrottler(); + context.getAuthProvider(); + context.getSslHandlerFactory(); + context.getTimestampGenerator(); } catch (Throwable error) { initFuture.completeExceptionally(error); RunOrSchedule.on(adminExecutor, this::closePolicies); return; } - MetadataManager metadataManager = context.metadataManager(); + MetadataManager metadataManager = context.getMetadataManager(); metadataManager // Store contact points in the metadata right away, the control connection will need them // if it has to initialize (if the set is empty, 127.0.0.1 is used as a default). .addContactPoints(initialContactPoints) - .thenCompose(v -> context.topologyMonitor().init()) + .thenCompose(v -> context.getTopologyMonitor().init()) .thenCompose(v -> metadataManager.refreshNodes()) .thenAccept(v -> afterInitialNodeListRefresh(keyspace)) .exceptionally( @@ -311,13 +312,13 @@ private void init(CqlIdentifier keyspace) { private void afterInitialNodeListRefresh(CqlIdentifier keyspace) { try { boolean protocolWasForced = - context.config().getDefaultProfile().isDefined(DefaultDriverOption.PROTOCOL_VERSION); + context.getConfig().getDefaultProfile().isDefined(DefaultDriverOption.PROTOCOL_VERSION); boolean needSchemaRefresh = true; if (!protocolWasForced) { - ProtocolVersion currentVersion = context.protocolVersion(); + ProtocolVersion currentVersion = context.getProtocolVersion(); ProtocolVersion bestVersion = context - .protocolVersionRegistry() + .getProtocolVersionRegistry() .highestCommon(metadataManager.getMetadata().getNodes().values()); if (!currentVersion.equals(bestVersion)) { LOG.info( @@ -326,8 +327,8 @@ private void afterInitialNodeListRefresh(CqlIdentifier keyspace) { logPrefix, currentVersion, bestVersion); - context.channelFactory().setProtocolVersion(bestVersion); - ControlConnection controlConnection = context.controlConnection(); + context.getChannelFactory().setProtocolVersion(bestVersion); + ControlConnection controlConnection = context.getControlConnection(); // Might not have initialized yet if there is a custom TopologyMonitor if (controlConnection.isInit()) { controlConnection.reconnectNow(); @@ -351,8 +352,8 @@ private void afterInitialNodeListRefresh(CqlIdentifier keyspace) { private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { try { nodeStateManager.markInitialized(); - context.loadBalancingPolicyWrapper().init(); - context.configLoader().onDriverInit(context); + context.getLoadBalancingPolicyWrapper().init(); + context.getConfigLoader().onDriverInit(context); LOG.debug("[{}] Initialization complete, ready", logPrefix); poolManager .init(keyspace) @@ -372,13 +373,13 @@ private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { private void onNodeStateChanged(NodeStateEvent event) { assert adminExecutor.inEventLoop(); if (event.newState == null) { - context.nodeStateListener().onRemove(event.node); + context.getNodeStateListener().onRemove(event.node); } else if (event.oldState == null && event.newState == NodeState.UNKNOWN) { - context.nodeStateListener().onAdd(event.node); + context.getNodeStateListener().onAdd(event.node); } else if (event.newState == NodeState.UP) { - context.nodeStateListener().onUp(event.node); + context.getNodeStateListener().onUp(event.node); } else if (event.newState == NodeState.DOWN || event.newState == NodeState.FORCED_DOWN) { - context.nodeStateListener().onDown(event.node); + context.getNodeStateListener().onDown(event.node); } } @@ -433,7 +434,7 @@ private void onChildrenClosed(List> childrenCloseStages) { warnIfFailed(stage); } context - .nettyOptions() + .getNettyOptions() .onClose() .addListener( f -> { @@ -466,15 +467,15 @@ private void closePolicies() { List policies = new ArrayList<>(); for (Supplier supplier : ImmutableList.>of( - context::reconnectionPolicy, - context::loadBalancingPolicyWrapper, - context::addressTranslator, - context::configLoader, - context::nodeStateListener, - context::schemaChangeListener, - context::requestTracker, - context::requestThrottler, - context::timestampGenerator)) { + context::getReconnectionPolicy, + context::getLoadBalancingPolicyWrapper, + context::getAddressTranslator, + context::getConfigLoader, + context::getNodeStateListener, + context::getSchemaChangeListener, + context::getRequestTracker, + context::getRequestThrottler, + context::getTimestampGenerator)) { try { policies.add(supplier.get()); } catch (Throwable t) { @@ -482,22 +483,22 @@ private void closePolicies() { } } try { - context.authProvider().ifPresent(policies::add); + context.getAuthProvider().ifPresent(policies::add); } catch (Throwable t) { // ignore } try { - context.sslHandlerFactory().ifPresent(policies::add); + context.getSslHandlerFactory().ifPresent(policies::add); } catch (Throwable t) { // ignore } try { - policies.addAll(context.retryPolicies().values()); + policies.addAll(context.getRetryPolicies().values()); } catch (Throwable t) { // ignore } try { - policies.addAll(context.speculativeExecutionPolicies().values()); + policies.addAll(context.getSpeculativeExecutionPolicies().values()); } catch (Throwable t) { // ignore } @@ -520,12 +521,12 @@ private List internalComponentsToClose() { // Same as closePolicies(): make sure we don't trigger errors by accessing context components // that had failed to initialize: try { - components.add(context.topologyMonitor()); + components.add(context.getTopologyMonitor()); } catch (Throwable t) { // ignore } try { - components.add(context.controlConnection()); + components.add(context.getControlConnection()); } catch (Throwable t) { // ignore } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java index 60452db2f40..2d8fde37133 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -91,9 +91,9 @@ public class PoolManager implements AsyncAutoCloseable { private final SingleThreaded singleThreaded; public PoolManager(InternalDriverContext context) { - this.logPrefix = context.sessionName(); - this.adminExecutor = context.nettyOptions().adminEventExecutorGroup().next(); - this.config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); + this.config = context.getConfig().getDefaultProfile(); this.singleThreaded = new SingleThreaded(context); } @@ -179,19 +179,19 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; - this.channelPoolFactory = context.channelPoolFactory(); + this.channelPoolFactory = context.getChannelPoolFactory(); this.distanceListenerKey = context - .eventBus() + .getEventBus() .register( DistanceEvent.class, RunOrSchedule.on(adminExecutor, this::onDistanceEvent)); this.stateListenerKey = context - .eventBus() + .getEventBus() .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); this.topologyListenerKey = context - .eventBus() + .getEventBus() .register( TopologyEvent.class, RunOrSchedule.on(adminExecutor, this::onTopologyEvent)); } @@ -211,7 +211,7 @@ private void init(CqlIdentifier keyspace) { distanceEventFilter.start(); stateEventFilter.start(); - Collection nodes = context.metadataManager().getMetadata().getNodes().values(); + Collection nodes = context.getMetadataManager().getMetadata().getNodes().values(); List> poolStages = new ArrayList<>(nodes.size()); for (Node node : nodes) { NodeDistance distance = node.getDistance(); @@ -336,7 +336,7 @@ private void processStateEvent(NodeStateEvent event) { private void onTopologyEvent(TopologyEvent event) { assert adminExecutor.inEventLoop(); if (event.type == TopologyEvent.Type.SUGGEST_UP) { - Node node = context.metadataManager().getMetadata().getNodes().get(event.address); + Node node = context.getMetadataManager().getMetadata().getNodes().get(event.address); if (node.getDistance() != NodeDistance.IGNORED) { LOG.debug( "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); @@ -448,9 +448,9 @@ private void close() { LOG.debug("[{}] Starting shutdown", logPrefix); // Stop listening for events - context.eventBus().unregister(distanceListenerKey, DistanceEvent.class); - context.eventBus().unregister(stateListenerKey, NodeStateEvent.class); - context.eventBus().unregister(topologyListenerKey, TopologyEvent.class); + context.getEventBus().unregister(distanceListenerKey, DistanceEvent.class); + context.getEventBus().unregister(stateListenerKey, NodeStateEvent.class); + context.getEventBus().unregister(topologyListenerKey, TopologyEvent.class); List> closePoolStages = new ArrayList<>(pools.size()); for (ChannelPool pool : pools.values()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 5e2036eafa8..8c909c06b02 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -91,9 +91,9 @@ class ReprepareOnUp { this.channel = pool.next(); this.repreparePayloads = repreparePayloads; this.whenPrepared = whenPrepared; - this.throttler = context.requestThrottler(); + this.throttler = context.getRequestThrottler(); - DriverConfig config = context.config(); + DriverConfig config = context.getConfig(); this.checkSystemTable = config.getDefaultProfile().getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE); this.timeout = config.getDefaultProfile().getDuration(DefaultDriverOption.REPREPARE_TIMEOUT); @@ -102,7 +102,7 @@ class ReprepareOnUp { this.maxParallelism = config.getDefaultProfile().getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM); - this.metricUpdater = context.metricsFactory().getSessionUpdater(); + this.metricUpdater = context.getMetricsFactory().getSessionUpdater(); } void start() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java index 3ab2771c51e..8028bf88de9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -71,8 +71,8 @@ public class ConcurrencyLimitingRequestThrottler implements RequestThrottler { private boolean closed; public ConcurrencyLimitingRequestThrottler(DriverContext context) { - this.logPrefix = context.sessionName(); - DriverExecutionProfile config = context.config().getDefaultProfile(); + this.logPrefix = context.getSessionName(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.maxConcurrentRequests = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS); this.maxQueueSize = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java index 32337f89938..ad281c90769 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java @@ -87,10 +87,10 @@ public RateLimitingRequestThrottler(DriverContext context) { @VisibleForTesting RateLimitingRequestThrottler(DriverContext context, NanoClock clock) { - this.logPrefix = context.sessionName(); + this.logPrefix = context.getSessionName(); this.clock = clock; - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.maxRequestsPerSecond = config.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND); @@ -104,7 +104,7 @@ public RateLimitingRequestThrottler(DriverContext context) { this.storedPermits = maxRequestsPerSecond; this.scheduler = - ((InternalDriverContext) context).nettyOptions().adminEventExecutorGroup().next(); + ((InternalDriverContext) context).getNettyOptions().adminEventExecutorGroup().next(); LOG.debug( "[{}] Initializing with maxRequestsPerSecond = {}, maxQueueSize = {}, drainInterval = {}", diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java index daa1ee56b62..a33b0d33cc9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/specex/ConstantSpeculativeExecutionPolicy.java @@ -52,7 +52,7 @@ public class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionP private final long constantDelayMillis; public ConstantSpeculativeExecutionPolicy(DriverContext context, String profileName) { - DriverExecutionProfile config = context.config().getProfile(profileName); + DriverExecutionProfile config = context.getConfig().getProfile(profileName); this.maxExecutions = config.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX); if (this.maxExecutions < 1) { throw new IllegalArgumentException("Max must be at least 1"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index cd5e22b3865..3c5a7cb408d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -66,7 +66,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory { /** Builds a new instance from the driver configuration. */ public DefaultSslEngineFactory(DriverContext driverContext) { - DriverExecutionProfile config = driverContext.config().getDefaultProfile(); + DriverExecutionProfile config = driverContext.getConfig().getDefaultProfile(); try { this.sslContext = buildContext(config); } catch (Exception e) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java index da60654f7a7..9fa0bf482bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGenerator.java @@ -46,7 +46,7 @@ protected MonotonicTimestampGenerator(DriverContext context) { protected MonotonicTimestampGenerator(Clock clock, DriverContext context) { this.clock = clock; - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); this.warningThresholdMicros = config .getDuration( @@ -103,7 +103,7 @@ private void maybeLog(long currentTick, long last) { } private static Clock buildClock(DriverContext context) { - DriverExecutionProfile config = context.config().getDefaultProfile(); + DriverExecutionProfile config = context.getConfig().getDefaultProfile(); boolean forceJavaClock = config.getBoolean(DefaultDriverOption.TIMESTAMP_GENERATOR_FORCE_JAVA_CLOCK, false); return Clock.getInstance(forceJavaClock); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatter.java index bfaa5453cd1..fd49a2e92d9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatter.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatter.java @@ -241,7 +241,7 @@ protected int appendValues( } protected void appendValue(ByteBuffer raw, DataType type, int maxLength, StringBuilder builder) { - TypeCodec codec = context.codecRegistry().codecFor(type); + TypeCodec codec = context.getCodecRegistry().codecFor(type); if (type.equals(DataTypes.BLOB)) { // For very large buffers, apply the limit before converting into a string int maxBufferLength = Math.max((maxLength - 2) / 2, 0); @@ -249,19 +249,19 @@ protected void appendValue(ByteBuffer raw, DataType type, int maxLength, StringB if (bufferTooLarge) { raw = (ByteBuffer) raw.duplicate().limit(maxBufferLength); } - Object value = codec.decode(raw, context.protocolVersion()); + Object value = codec.decode(raw, context.getProtocolVersion()); append(codec.format(value), maxLength, builder); if (bufferTooLarge) { builder.append(TRUNCATED); } } else { - Object value = codec.decode(raw, context.protocolVersion()); + Object value = codec.decode(raw, context.getProtocolVersion()); append(codec.format(value), maxLength, builder); } } protected void appendValue(Object value, int maxLength, StringBuilder builder) { - TypeCodec codec = context.codecRegistry().codecFor(value); + TypeCodec codec = context.getCodecRegistry().codecFor(value); if (value instanceof ByteBuffer) { // For very large buffers, apply the limit before converting into a string ByteBuffer buffer = (ByteBuffer) value; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index 3a1fce01a27..8805edcd9ca 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -67,7 +67,7 @@ public class RequestLogger implements RequestTracker { private final RequestLogFormatter formatter; public RequestLogger(DriverContext context) { - this(context.sessionName(), new RequestLogFormatter(context)); + this(context.getSessionName(), new RequestLogFormatter(context)); } protected RequestLogger(String logPrefix, RequestLogFormatter formatter) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java index 3865f8033e6..15e34d75aab 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java @@ -135,7 +135,7 @@ public String format(@Nullable TupleValue value) { throw new IllegalArgumentException( String.format("Invalid tuple type, expected %s but got %s", cqlType, value.getType())); } - CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry(); + CodecRegistry registry = cqlType.getAttachmentPoint().getCodecRegistry(); StringBuilder sb = new StringBuilder("("); boolean first = true; @@ -176,7 +176,7 @@ public TupleValue parse(@Nullable String value) { return tuple; } - CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry(); + CodecRegistry registry = cqlType.getAttachmentPoint().getCodecRegistry(); int i = 0; while (position < value.length()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 0ec4f4b444a..910f298f2fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -135,7 +135,7 @@ public String format(@Nullable UdtValue value) { return "NULL"; } - CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry(); + CodecRegistry registry = cqlType.getAttachmentPoint().getCodecRegistry(); StringBuilder sb = new StringBuilder("{"); int size = cqlType.getFieldTypes().size(); @@ -180,7 +180,7 @@ public UdtValue parse(@Nullable String value) { return udt; } - CodecRegistry registry = cqlType.getAttachmentPoint().codecRegistry(); + CodecRegistry registry = cqlType.getAttachmentPoint().getCodecRegistry(); while (position < value.length()) { int n; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 57ce4cec189..10e648c8aa2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -128,7 +128,7 @@ public static Map buildFromConfigProfiles( // Find out how many distinct configurations we have ListMultimap profilesByConfig = MultimapBuilder.hashKeys().arrayListValues().build(); - for (DriverExecutionProfile profile : context.config().getProfiles().values()) { + for (DriverExecutionProfile profile : context.getConfig().getProfiles().values()) { profilesByConfig.put(profile.getComparisonKey(rootOption), profile.getName()); } @@ -167,8 +167,8 @@ public static Optional buildFromConfig( DriverExecutionProfile config = (profileName == null) - ? context.config().getDefaultProfile() - : context.config().getProfile(profileName); + ? context.getConfig().getDefaultProfile() + : context.getConfig().getProfile(profileName); String configPath = classNameOption.getPath(); LOG.debug("Creating a {} from config option {}", expectedSuperType.getSimpleName(), configPath); @@ -182,13 +182,13 @@ public static Optional buildFromConfig( Class clazz = null; if (className.contains(".")) { LOG.debug("Building from fully-qualified name {}", className); - clazz = loadClass(context.classLoader(), className); + clazz = loadClass(context.getClassLoader(), className); } else { LOG.debug("Building from unqualified name {}", className); for (String defaultPackage : defaultPackages) { String qualifiedClassName = defaultPackage + "." + className; LOG.debug("Trying with default package {}", qualifiedClassName); - clazz = loadClass(context.classLoader(), qualifiedClassName); + clazz = loadClass(context.getClassLoader(), qualifiedClassName); if (clazz != null) { break; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Sizes.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Sizes.java index 75ebbfb07d5..0b52a18e801 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Sizes.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Sizes.java @@ -60,7 +60,7 @@ public static int minimumStatementSize(Statement statement, DriverContext contex // These are options in the protocol inside a frame that are common to all Statements - size += QueryOptions.queryFlagsSize(context.protocolVersion().getCode()); + size += QueryOptions.queryFlagsSize(context.getProtocolVersion().getCode()); size += PrimitiveSizes.SHORT; // size of consistency level size += PrimitiveSizes.SHORT; // size of serial consistency level diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index b7e529a1227..5556e0ee676 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -654,7 +654,7 @@ datastax-java-driver { # an incompatible node joins the cluster later, connection will fail and the driver will force # it down (i.e. never try to connect to it again). # - # You can check the actual version at runtime with Cluster.getContext().protocolVersion(). + # You can check the actual version at runtime with Cluster.getContext().getProtocolVersion(). # # Required: no # Modifiable at runtime: no diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index 6e1cc6e3f8a..58658d2fa74 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -40,7 +40,7 @@ public class ConstantSpeculativeExecutionPolicyTest { @Before public void setup() { - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index c95b1b71e4d..798116c5e7d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -111,7 +111,7 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.config()).thenReturn(driverConfig); + Mockito.when(context.getConfig()).thenReturn(driverConfig); Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(defaultProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)) .thenReturn(false); @@ -123,19 +123,19 @@ public void setup() throws InterruptedException { Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); - Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); Mockito.when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); Mockito.when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); - Mockito.when(context.frameCodec()) + Mockito.when(context.getFrameCodec()) .thenReturn( FrameCodec.defaultClient( new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); - Mockito.when(context.sslHandlerFactory()).thenReturn(Optional.empty()); - Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.writeCoalescer()).thenReturn(new PassThroughWriteCoalescer(null)); - Mockito.when(context.compressor()).thenReturn(compressor); + Mockito.when(context.getSslHandlerFactory()).thenReturn(Optional.empty()); + Mockito.when(context.getEventBus()).thenReturn(eventBus); + Mockito.when(context.getWriteCoalescer()).thenReturn(new PassThroughWriteCoalescer(null)); + Mockito.when(context.getCompressor()).thenReturn(compressor); // Start local server ServerBootstrap serverBootstrap = @@ -232,7 +232,7 @@ ChannelInitializer initializer( @Override protected void initChannel(Channel channel) throws Exception { try { - DriverExecutionProfile defaultProfile = context.config().getDefaultProfile(); + DriverExecutionProfile defaultProfile = context.getConfig().getDefaultProfile(); long setKeyspaceTimeoutMillis = defaultProfile diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 50647478f45..7c334180177 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -75,15 +75,15 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { public void setup() { super.setup(); MockitoAnnotations.initMocks(this); - Mockito.when(internalDriverContext.config()).thenReturn(driverConfig); + Mockito.when(internalDriverContext.getConfig()).thenReturn(driverConfig); Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); - Mockito.when(internalDriverContext.protocolVersionRegistry()) + Mockito.when(internalDriverContext.getProtocolVersionRegistry()) .thenReturn(protocolVersionRegistry); - Mockito.when(internalDriverContext.compressor()).thenReturn(compressor); + Mockito.when(internalDriverContext.getCompressor()).thenReturn(compressor); Mockito.when(compressor.algorithm()).thenReturn(null); channel @@ -247,7 +247,7 @@ public void should_initialize_with_authentication() { MockAuthenticator authenticator = new MockAuthenticator(); Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); + Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -307,7 +307,7 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { heartbeatHandler)); AuthProvider authProvider = Mockito.mock(AuthProvider.class); - Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); + Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -343,7 +343,7 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa MockAuthenticator authenticator = new MockAuthenticator(); Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(internalDriverContext.authProvider()).thenReturn(Optional.of(authProvider)); + Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index 87b93ff3b92..d5b31898cf4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -54,20 +54,20 @@ public class DefaultDriverConfigLoaderTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(context.sessionName()).thenReturn("test"); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getSessionName()).thenReturn("test"); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); adminExecutor = new ScheduledTaskCapturingEventLoop(adminEventExecutorGroup); Mockito.when(adminEventExecutorGroup.next()).thenReturn(adminExecutor); eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.getEventBus()).thenReturn(eventBus); // The already loaded config in the context. // In real life, it's the object managed by the loader, but in this test it's simpler to mock // it. - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) .thenReturn(Duration.ofSeconds(12)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index ce27be05144..76f261af76c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -80,11 +80,11 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.channelFactory()).thenReturn(channelFactory); + Mockito.when(context.getEventBus()).thenReturn(eventBus); + Mockito.when(context.getChannelFactory()).thenReturn(channelFactory); channelFactoryFuture = new Exchanger<>(); Mockito.when(channelFactory.connect(any(Node.class), any(DriverChannelOptions.class))) @@ -95,26 +95,26 @@ public void setup() { return channelFuture; }); - Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); Mockito.when(reconnectionPolicy.newControlConnectionSchedule()) .thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); mockQueryPlan(node1, node2); Mockito.when(metadataManager.refreshNodes()) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); controlConnection = new ControlConnection(context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index f19a1a3c89a..b31e7c0426d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -126,7 +126,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = @@ -238,7 +238,7 @@ public void should_retry_initial_prepare_if_recoverable_error() { Mockito.when( harness .getContext() - .retryPolicy(anyString()) + .getRetryPolicy(anyString()) .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) .thenReturn(RetryDecision.RETRY_NEXT); @@ -277,7 +277,7 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { Mockito.when( harness .getContext() - .retryPolicy(anyString()) + .getRetryPolicy(anyString()) .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) .thenReturn(RetryDecision.RETHROW); @@ -315,7 +315,7 @@ public void should_fail_if_retry_policy_ignores_error() { // Make node1's error unrecoverable, will rethrow RetryPolicy mockRetryPolicy = - harness.getContext().retryPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME); Mockito.when( mockRetryPolicy.onErrorResponse( eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) @@ -356,7 +356,7 @@ public void should_propagate_custom_payload_on_single_node() { PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = new CqlPrepareAsyncHandler( @@ -387,7 +387,7 @@ public void should_propagate_custom_payload_on_all_nodes() { node2Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - DriverExecutionProfile config = harness.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); CompletionStage prepareFuture = new CqlPrepareAsyncHandler( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 86a1ca650c4..a7aa04a7479 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -94,7 +94,7 @@ public void should_always_try_next_node_if_bootstrapping( assertThat(executionInfo.getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(executionInfo.getWarnings()).isEmpty(); - Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy(anyString())); + Mockito.verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); }); } } @@ -121,7 +121,7 @@ public void should_always_rethrow_query_validation_error( assertThat(error) .isInstanceOf(InvalidQueryException.class) .hasMessage("mock message"); - Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy(anyString())); + Mockito.verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); Mockito.verify(nodeMetricUpdater1) .incrementCounter( @@ -148,7 +148,7 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { failureScenario.mockRetryPolicyDecision( - harness.getContext().retryPolicy(anyString()), RetryDecision.RETRY_NEXT); + harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") @@ -197,7 +197,7 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { failureScenario.mockRetryPolicyDecision( - harness.getContext().retryPolicy(anyString()), RetryDecision.RETRY_SAME); + harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") @@ -245,7 +245,7 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { failureScenario.mockRetryPolicyDecision( - harness.getContext().retryPolicy(anyString()), RetryDecision.IGNORE); + harness.getContext().getRetryPolicy(anyString()), RetryDecision.IGNORE); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") @@ -292,7 +292,7 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { failureScenario.mockRetryPolicyDecision( - harness.getContext().retryPolicy(anyString()), RetryDecision.RETHROW); + harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETHROW); CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") @@ -337,7 +337,7 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re if (shouldCallRetryPolicy) { failureScenario.mockRetryPolicyDecision( - harness.getContext().retryPolicy(anyString()), RetryDecision.RETHROW); + harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETHROW); } CompletionStage resultSetFuture = @@ -350,7 +350,8 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); // When non idempotent, the policy is bypassed completely: if (!shouldCallRetryPolicy) { - Mockito.verifyNoMoreInteractions(harness.getContext().retryPolicy(anyString())); + Mockito.verifyNoMoreInteractions( + harness.getContext().getRetryPolicy(anyString())); } Mockito.verify(nodeMetricUpdater1) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 3eb13a0b430..b4f678aaee7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -52,7 +52,7 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); @@ -79,7 +79,7 @@ public void should_schedule_speculative_executions( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; long secondExecutionDelay = 200L; Mockito.when( @@ -146,7 +146,7 @@ public void should_not_start_execution_if_result_complete( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -203,7 +203,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -234,7 +234,7 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -287,7 +287,7 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -344,7 +344,7 @@ public void should_retry_in_speculative_executions( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( @@ -393,7 +393,7 @@ public void should_stop_retrying_other_executions_if_result_complete( try (RequestHandlerTestHarness harness = harnessBuilder.build()) { SpeculativeExecutionPolicy speculativeExecutionPolicy = - harness.getContext().speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; Mockito.when( speculativeExecutionPolicy.nextExecution( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index d5f661006c1..60e31e8fbe4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -123,7 +123,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { Duration configuredTimeout = harness .getContext() - .config() + .getConfig() .getDefaultProfile() .getDuration(DefaultDriverOption.REQUEST_TIMEOUT); assertThat(scheduledTask.getInitialDelay(TimeUnit.NANOSECONDS)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 026fd4f077f..59061e689ee 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -56,8 +56,8 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(executionInfo.getStatement()).thenReturn((Statement) statement); - Mockito.when(context.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.protocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); + Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + Mockito.when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); } @Test(expected = IllegalStateException.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index de7f9fdbfc1..213ac26ccfe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -79,7 +79,7 @@ public class QueryTraceFetcherTest { @Before public void setup() { - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); Mockito.when(adminEventExecutorGroup.next()).thenReturn(eventExecutor); // Always execute scheduled tasks immediately: @@ -106,7 +106,7 @@ public void setup() { DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) .thenReturn(traceConfig); - Mockito.when(context.consistencyLevelRegistry()) + Mockito.when(context.getConsistencyLevelRegistry()) .thenReturn(new DefaultConsistencyLevelRegistry()); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index dccc20ea83f..ee921ca0835 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -95,7 +95,7 @@ private RequestHandlerTestHarness(Builder builder) { this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(eventLoopGroup); Mockito.when(eventLoopGroup.next()).thenReturn(schedulingEventLoop); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(defaultProfile.getName()).thenReturn(DriverExecutionProfile.DEFAULT_NAME); // TODO make configurable in the test, also handle profiles @@ -112,28 +112,28 @@ private RequestHandlerTestHarness(Builder builder) { .thenReturn(true); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when( loadBalancingPolicyWrapper.newQueryPlan( any(Request.class), anyString(), any(Session.class))) .thenReturn(builder.buildQueryPlan()); - Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.retryPolicy(anyString())).thenReturn(retryPolicy); + Mockito.when(context.getRetryPolicy(anyString())).thenReturn(retryPolicy); // Disable speculative executions by default Mockito.when( speculativeExecutionPolicy.nextExecution( any(Node.class), any(CqlIdentifier.class), any(Request.class), anyInt())) .thenReturn(-1L); - Mockito.when(context.speculativeExecutionPolicy(anyString())) + Mockito.when(context.getSpeculativeExecutionPolicy(anyString())) .thenReturn(speculativeExecutionPolicy); - Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); Mockito.when(timestampGenerator.next()).thenReturn(Long.MIN_VALUE); - Mockito.when(context.timestampGenerator()).thenReturn(timestampGenerator); + Mockito.when(context.getTimestampGenerator()).thenReturn(timestampGenerator); pools = builder.buildMockPools(); Mockito.when(session.getChannel(any(Node.class), anyString())) @@ -151,24 +151,25 @@ private RequestHandlerTestHarness(Builder builder) { Mockito.when(session.getMetadata()).thenReturn(DefaultMetadata.EMPTY); - Mockito.when(context.protocolVersionRegistry()).thenReturn(protocolVersionRegistry); + Mockito.when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); Mockito.when( protocolVersionRegistry.supports( any(ProtocolVersion.class), any(ProtocolFeature.class))) .thenReturn(true); if (builder.protocolVersion != null) { - Mockito.when(context.protocolVersion()).thenReturn(builder.protocolVersion); + Mockito.when(context.getProtocolVersion()).thenReturn(builder.protocolVersion); } - Mockito.when(context.consistencyLevelRegistry()) + Mockito.when(context.getConsistencyLevelRegistry()) .thenReturn(new DefaultConsistencyLevelRegistry()); - Mockito.when(context.writeTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); + Mockito.when(context.getWriteTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); - Mockito.when(context.requestThrottler()).thenReturn(new PassThroughRequestThrottler(context)); + Mockito.when(context.getRequestThrottler()) + .thenReturn(new PassThroughRequestThrottler(context)); - Mockito.when(context.requestTracker()).thenReturn(new NoopRequestTracker(context)); + Mockito.when(context.getRequestTracker()).thenReturn(new NoopRequestTracker(context)); } public DefaultSession getSession() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java index 83ca38065e5..6c0df155b1c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java @@ -81,13 +81,13 @@ public void setup() { Mockito.when(preparedStatement.getVariableDefinitions()).thenReturn(columnDefinitions); - Mockito.when(driverContext.protocolVersion()).thenReturn(DefaultProtocolVersion.V5); - Mockito.when(driverContext.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(driverContext.protocolVersionRegistry()) + Mockito.when(driverContext.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V5); + Mockito.when(driverContext.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + Mockito.when(driverContext.getProtocolVersionRegistry()) .thenReturn(new CassandraProtocolVersionRegistry(null)); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(driverContext.config()).thenReturn(config); - Mockito.when(driverContext.timestampGenerator()).thenReturn(timestampGenerator); + Mockito.when(driverContext.getConfig()).thenReturn(config); + Mockito.when(driverContext.getTimestampGenerator()).thenReturn(timestampGenerator); } private ColumnDefinition phonyColumnDef( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index c5ba1d9a193..1f533ec70f4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -58,8 +58,8 @@ protected abstract T newInstance( public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); intCodec = Mockito.spy(TypeCodecs.INT); doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index d19dba6aa64..7d2d1f7b345 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -61,7 +61,7 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin public void setup() { super.setup(); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); Mockito.when(metadata.getTokenMap()).thenAnswer(invocation -> Optional.of(this.tokenMap)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 3a2fbb22aa2..ccc7cdeea90 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -45,7 +45,7 @@ public class AddNodeRefreshTest { @Before public void setup() { - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index a4cdf18d4b3..300af568de9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -59,7 +59,7 @@ public class DefaultMetadataTokenMapTest { public void setup() { DefaultReplicationStrategyFactory replicationStrategyFactory = new DefaultReplicationStrategyFactory(context); - Mockito.when(context.replicationStrategyFactory()).thenReturn(replicationStrategyFactory); + Mockito.when(context.getReplicationStrategyFactory()).thenReturn(replicationStrategyFactory); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 897023f175a..5cbbd84a52d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -79,16 +79,16 @@ public void setup() { Mockito.when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); Mockito.when(channel.remoteAddress()).thenReturn(ADDRESS1); Mockito.when(controlConnection.channel()).thenReturn(channel); - Mockito.when(context.controlConnection()).thenReturn(controlConnection); + Mockito.when(context.getControlConnection()).thenReturn(controlConnection); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 5f14175b26c..66bec3f337e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -48,7 +48,7 @@ public class FullNodeListRefreshTest { @Before public void setup() { - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 6667290bc21..17a67028362 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -39,7 +39,7 @@ public class InitContactPointsRefreshTest { @Before public void setup() { - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 342712cef83..8a90ac1daaa 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -75,7 +75,7 @@ public class LoadBalancingPolicyWrapperTest { @Before public void setup() { - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); @@ -89,13 +89,13 @@ public void setup() { Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); Mockito.when(metadataManager.getContactPoints()).thenReturn(contactPointsMap.keySet()); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); defaultPolicysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); Mockito.when(policy1.newQueryPlan(null, null)).thenReturn(defaultPolicysQueryPlan); eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.getEventBus()).thenReturn(eventBus); wrapper = new LoadBalancingPolicyWrapper( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 868700efe9a..d6ca3a9ea26 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -78,22 +78,22 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); + Mockito.when(context.getTopologyMonitor()).thenReturn(topologyMonitor); Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW)) .thenReturn(Duration.ZERO); Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)) .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.schemaQueriesFactory()).thenReturn(schemaQueriesFactory); - Mockito.when(context.schemaParserFactory()).thenReturn(schemaParserFactory); + Mockito.when(context.getEventBus()).thenReturn(eventBus); + Mockito.when(context.getSchemaQueriesFactory()).thenReturn(schemaQueriesFactory); + Mockito.when(context.getSchemaParserFactory()).thenReturn(schemaParserFactory); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); metadataManager = new TestMetadataManager(context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 7316115b21a..58bd29020d4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -75,16 +75,16 @@ public void setup() { Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); this.eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.getEventBus()).thenReturn(eventBus); adminEventLoopGroup = new DefaultEventLoopGroup(1, new BlockingOperation.SafeThreadFactory()); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); ImmutableMap nodes = @@ -96,7 +96,7 @@ public void setup() { Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); Mockito.when(metadataManager.refreshNode(any(Node.class))) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); } @After diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 55bf0236bcd..863f462a8d2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -43,7 +43,7 @@ public class RemoveNodeRefreshTest { @Before public void setup() { - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index e493a34653a..5230b39cfe5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -86,15 +86,15 @@ public void setup() { Mockito.when(defaultConfig.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) .thenReturn(true); Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); + Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); Map nodes = ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2); Mockito.when(metadata.getNodes()).thenReturn(nodes); Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); Mockito.when(node2.getState()).thenReturn(NodeState.UP); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java index 292929f21b2..a3355adff4b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java @@ -57,8 +57,8 @@ public class AggregateParserTest extends SchemaParserTestBase { @Before public void setup() { - Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); - Mockito.when(context.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + Mockito.when(context.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java index c01fd606a6e..8f9e1bfd488 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java @@ -59,7 +59,7 @@ public void should_parse_legacy_keyspace_row() { @Test public void should_parse_keyspace_with_all_children() { // Needed to parse the aggregate - Mockito.when(context.codecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); SchemaRefresh refresh = (SchemaRefresh) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 39023773f7d..9593d60cc7b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -69,15 +69,15 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); this.eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); - Mockito.when(context.channelFactory()).thenReturn(channelFactory); + Mockito.when(context.getEventBus()).thenReturn(eventBus); + Mockito.when(context.getChannelFactory()).thenReturn(channelFactory); - Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); Mockito.when(reconnectionPolicy.newNodeSchedule(any(Node.class))) .thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index 31f6ccb9639..5775d393af4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -110,7 +110,7 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); // Config: Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) @@ -123,7 +123,7 @@ public void setup() { Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) .thenReturn(1); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); // Init sequence: Mockito.when(metadataManager.addContactPoints(anySet())) @@ -132,24 +132,24 @@ public void setup() { .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(metadataManager.firstSchemaRefreshFuture()) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.metadataManager()).thenReturn(metadataManager); + Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); Mockito.when(topologyMonitor.init()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.topologyMonitor()).thenReturn(topologyMonitor); + Mockito.when(context.getTopologyMonitor()).thenReturn(topologyMonitor); - Mockito.when(context.loadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.configLoader()).thenReturn(configLoader); + Mockito.when(context.getConfigLoader()).thenReturn(configLoader); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); // Runtime behavior: - Mockito.when(context.sessionName()).thenReturn("test"); + Mockito.when(context.getSessionName()).thenReturn("test"); - Mockito.when(context.channelPoolFactory()).thenReturn(channelPoolFactory); + Mockito.when(context.getChannelPoolFactory()).thenReturn(channelPoolFactory); eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.eventBus()).thenReturn(eventBus); + Mockito.when(context.getEventBus()).thenReturn(eventBus); node1 = mockLocalNode(1); node2 = mockLocalNode(2); @@ -163,18 +163,19 @@ public void setup() { Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); PoolManager poolManager = new PoolManager(context); - Mockito.when(context.poolManager()).thenReturn(poolManager); + Mockito.when(context.getPoolManager()).thenReturn(poolManager); // Shutdown sequence: - Mockito.when(context.reconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(context.retryPolicy(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(retryPolicy); - Mockito.when(context.speculativeExecutionPolicies()) + Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); + Mockito.when(context.getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME)) + .thenReturn(retryPolicy); + Mockito.when(context.getSpeculativeExecutionPolicies()) .thenReturn( ImmutableMap.of(DriverExecutionProfile.DEFAULT_NAME, speculativeExecutionPolicy)); - Mockito.when(context.addressTranslator()).thenReturn(addressTranslator); - Mockito.when(context.nodeStateListener()).thenReturn(nodeStateListener); - Mockito.when(context.schemaChangeListener()).thenReturn(schemaChangeListener); - Mockito.when(context.requestTracker()).thenReturn(requestTracker); + Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); + Mockito.when(context.getNodeStateListener()).thenReturn(nodeStateListener); + Mockito.when(context.getSchemaChangeListener()).thenReturn(schemaChangeListener); + Mockito.when(context.getRequestTracker()).thenReturn(requestTracker); Mockito.when(metadataManager.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(metadataManager.forceCloseAsync()) @@ -184,7 +185,7 @@ public void setup() { Mockito.when(topologyMonitor.forceCloseAsync()) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.controlConnection()).thenReturn(controlConnection); + Mockito.when(context.getControlConnection()).thenReturn(controlConnection); Mockito.when(controlConnection.closeAsync()) .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(controlConnection.forceCloseAsync()) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index 724bdc51077..df338b531c1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -87,9 +87,9 @@ public void setup() { Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(0); Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) .thenReturn(100); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(context.metricsFactory()).thenReturn(metricsFactory); + Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); Mockito.when(metricsFactory.getSessionUpdater()).thenReturn(metricUpdater); done = new CompletableFuture<>(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java index 909e8321cae..e5a660b8d1b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java @@ -45,7 +45,7 @@ public class ConcurrencyLimitingRequestThrottlerTest { @Before public void setup() { - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java index c797eeae491..19f4c7b5522 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java @@ -63,7 +63,7 @@ public class RateLimitingRequestThrottlerTest { @Before public void setup() { - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); Mockito.when( @@ -77,7 +77,7 @@ public void setup() { Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL)) .thenReturn(DRAIN_INTERVAL); - Mockito.when(context.nettyOptions()).thenReturn(nettyOptions); + Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminGroup); adminExecutor = new ScheduledTaskCapturingEventLoop(adminGroup); Mockito.when(adminGroup.next()).thenReturn(adminExecutor); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java index f95c6d9df50..25f6c73eb71 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java @@ -54,7 +54,7 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); // Disable warnings by default Mockito.when( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java index 4b86daf612f..e3d2c5ec41a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java @@ -57,8 +57,8 @@ public class RequestLogFormatterTest { @Before public void setup() { - Mockito.when(context.codecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.protocolVersion()).thenReturn(protocolVersion); + Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + Mockito.when(context.getProtocolVersion()).thenReturn(protocolVersion); formatter = new RequestLogFormatter(context); } @@ -283,7 +283,7 @@ private PreparedStatement mockPreparedStatement(String query, Map { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); intCodec = Mockito.spy(TypeCodecs.INT); doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 6efa0343852..faa16c50442 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -50,8 +50,8 @@ public class UdtCodecTest extends CodecTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.codecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.protocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); intCodec = Mockito.spy(TypeCodecs.INT); doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java index b31be019547..984ef1f2be3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java @@ -57,7 +57,7 @@ public void should_build_policies_per_profile() { + "}\n"; InternalDriverContext context = Mockito.mock(InternalDriverContext.class); TypesafeDriverConfig config = new TypesafeDriverConfig(ConfigFactory.parseString(configSource)); - Mockito.when(context.config()).thenReturn(config); + Mockito.when(context.getConfig()).thenReturn(config); Map policies = Reflection.buildFromConfigProfiles( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java index 19ac7d2b089..2705bf615a5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionInitialNegotiationIT.java @@ -42,7 +42,7 @@ public class ProtocolVersionInitialNegotiationIT { @Test public void should_downgrade_to_v3() { try (CqlSession session = SessionUtils.newSession(ccm)) { - assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); + assertThat(session.getContext().getProtocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } } @@ -59,7 +59,7 @@ public void should_fail_if_provided_version_isnt_supported() { .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4") .build(); try (CqlSession session = SessionUtils.newSession(ccm, loader)) { - assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); + assertThat(session.getContext().getProtocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); fail("Expected an AllNodesFailedException"); } catch (AllNodesFailedException anfe) { @@ -76,7 +76,7 @@ public void should_fail_if_provided_version_isnt_supported() { @Test public void should_not_downgrade_if_server_supports_latest_version() { try (CqlSession session = SessionUtils.newSession(ccm)) { - assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(4); + assertThat(session.getContext().getProtocolVersion().getCode()).isEqualTo(4); session.execute("select * from system.local"); } } @@ -89,7 +89,7 @@ public void should_use_explicitly_provided_protocol_version() { .withString(DefaultDriverOption.PROTOCOL_VERSION, "V3") .build(); try (CqlSession session = SessionUtils.newSession(ccm, loader)) { - assertThat(session.getContext().protocolVersion().getCode()).isEqualTo(3); + assertThat(session.getContext().getProtocolVersion().getCode()).isEqualTo(3); session.execute("select * from system.local"); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index f5120c81dfd..15c46f0c1e2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -63,11 +63,11 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); - assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V3); + assertThat(context.getProtocolVersion()).isEqualTo(DefaultProtocolVersion.V3); // Find out which node became the control node after the reconnection (not necessarily node 0) InetSocketAddress controlAddress = - (InetSocketAddress) context.controlConnection().channel().remoteAddress(); + (InetSocketAddress) context.getControlConnection().channel().remoteAddress(); BoundNode currentControlNode = null; for (BoundNode node : simulacron.getNodes()) { if (node.inetSocketAddress().equals(controlAddress)) { @@ -110,7 +110,7 @@ public void should_keep_current_if_supported_by_all_peers() { .build()) { InternalDriverContext context = (InternalDriverContext) session.getContext(); - assertThat(context.protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); + assertThat(context.getProtocolVersion()).isEqualTo(DefaultProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(4); assertThat(protocolQueries(contactPoint, 4)) .containsExactly( @@ -154,7 +154,7 @@ public void should_not_downgrade_and_force_down_old_nodes_if_version_forced() { .addContactPoint(contactPoint.inetSocketAddress()) .withConfigLoader(loader) .build()) { - assertThat(session.getContext().protocolVersion()).isEqualTo(DefaultProtocolVersion.V4); + assertThat(session.getContext().getProtocolVersion()).isEqualTo(DefaultProtocolVersion.V4); assertThat(queries(simulacron)).hasSize(4); assertThat(protocolQueries(contactPoint, 4)) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index 93df6017bf1..34fac5b1613 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -110,7 +110,7 @@ public void should_reload_configuration_when_event_fired() throws Exception { // Bump up request timeout to 10 seconds and trigger a manual reload. configSource.set("basic.request.timeout = 10s"); ((InternalDriverContext) session.getContext()) - .eventBus() + .getEventBus() .fire(ForceReloadConfigEvent.INSTANCE); waitForConfigChange(session, 500, TimeUnit.MILLISECONDS); @@ -198,7 +198,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio private void waitForConfigChange(CqlSession session, long timeout, TimeUnit unit) { CountDownLatch latch = new CountDownLatch(1); ((InternalDriverContext) session.getContext()) - .eventBus() + .getEventBus() .register(ConfigChangeEvent.class, (e) -> latch.countDown()); try { boolean success = latch.await(timeout, unit); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java index ae66fb98834..162e1a24d05 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -65,7 +65,7 @@ public class ChannelSocketOptionsIT { @Test public void should_report_socket_options() { CqlSession session = sessionRule.session(); - DriverExecutionProfile config = session.getContext().config().getDefaultProfile(); + DriverExecutionProfile config = session.getContext().getConfig().getDefaultProfile(); assertThat(config.getBoolean(SOCKET_TCP_NODELAY)).isTrue(); assertThat(config.getBoolean(SOCKET_KEEP_ALIVE)).isFalse(); assertThat(config.getBoolean(SOCKET_REUSE_ADDRESS)).isFalse(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 286a08a1195..be70d5f99ad 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -396,7 +396,7 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { DriverExecutionProfile mockProfile = session .getContext() - .config() + .getConfig() .getDefaultProfile() // Value doesn't matter, we just want a distinct profile .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(10)); @@ -497,8 +497,8 @@ private CqlSession sessionWithCustomCodec(CqlIntToStringCodec codec) { private boolean supportsPerRequestKeyspace(CqlSession session) { InternalDriverContext context = (InternalDriverContext) session.getContext(); - ProtocolVersionRegistry protocolVersionRegistry = context.protocolVersionRegistry(); + ProtocolVersionRegistry protocolVersionRegistry = context.getProtocolVersionRegistry(); return protocolVersionRegistry.supports( - context.protocolVersion(), ProtocolFeature.PER_REQUEST_KEYSPACE); + context.getProtocolVersion(), ProtocolFeature.PER_REQUEST_KEYSPACE); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 52a7f38272e..f15a307c1e3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -301,7 +301,7 @@ private static int nextKey() { @Test public void should_insert_non_primary_key_column_simple_statement_using_format( DataType dataType, K value, K expectedPrimitiveValue) { - TypeCodec codec = sessionRule.session().getContext().codecRegistry().codecFor(dataType); + TypeCodec codec = sessionRule.session().getContext().getCodecRegistry().codecFor(dataType); int key = nextKey(); String columnName = columnNameFor(dataType); @@ -435,7 +435,7 @@ private static > S setValue( int index, S bs, DataType dataType, Object value) { TypeCodec codec = sessionRule.session() != null - ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) + ? sessionRule.session().getContext().getCodecRegistry().codecFor(dataType) : null; // set to null if value is null instead of getting possible NPE when casting from null to @@ -527,7 +527,7 @@ private static > S setValue( String name, S bs, DataType dataType, Object value) { TypeCodec codec = sessionRule.session() != null - ? sessionRule.session().getContext().codecRegistry().codecFor(dataType) + ? sessionRule.session().getContext().getCodecRegistry().codecFor(dataType) : null; // set to null if value is null instead of getting possible NPE when casting from null to @@ -617,7 +617,8 @@ private static > S setValue( private void readValue( Statement select, DataType dataType, K value, K expectedPrimitiveValue) { - TypeCodec codec = sessionRule.session().getContext().codecRegistry().codecFor(dataType); + TypeCodec codec = + sessionRule.session().getContext().getCodecRegistry().codecFor(dataType); ResultSet result = sessionRule.session().execute(select); String columnName = columnNameFor(dataType); @@ -740,7 +741,7 @@ private void readValue( } // Decode directly using the codec - ProtocolVersion protocolVersion = sessionRule.session().getContext().protocolVersion(); + ProtocolVersion protocolVersion = sessionRule.session().getContext().getProtocolVersion(); assertThat(codec.decode(row.getBytesUnsafe(columnName), protocolVersion)).isEqualTo(value); assertThat(codec.decode(row.getBytesUnsafe(0), protocolVersion)).isEqualTo(value); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 76bdcb56008..67eee5a734f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -180,7 +180,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { if (replica.getDatacenter().equals(LOCAL_DC)) { localReplicas.add(replica); - context.eventBus().fire(TopologyEvent.forceDown(replica.getConnectAddress())); + context.getEventBus().fire(TopologyEvent.forceDown(replica.getConnectAddress())); ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isZero()) .becomesTrue(); } @@ -215,7 +215,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep } for (Node replica : localReplicas) { - context.eventBus().fire(TopologyEvent.forceUp(replica.getConnectAddress())); + context.getEventBus().fire(TopologyEvent.forceUp(replica.getConnectAddress())); ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isPositive()) .becomesTrue(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java index a7269ada90a..164fb2e18f4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java @@ -77,20 +77,20 @@ public void clear() { public static void setup() { // sanity checks DriverContext context = sessionRule.session().getContext(); - DriverConfig config = context.config(); + DriverConfig config = context.getConfig(); assertThat(config.getProfiles()).containsKeys("profile1", "profile2"); - assertThat(context.loadBalancingPolicies()) + assertThat(context.getLoadBalancingPolicies()) .hasSize(3) .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); DefaultLoadBalancingPolicy defaultPolicy = (DefaultLoadBalancingPolicy) - context.loadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); + context.getLoadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); DefaultLoadBalancingPolicy policy1 = - (DefaultLoadBalancingPolicy) context.loadBalancingPolicy("profile1"); + (DefaultLoadBalancingPolicy) context.getLoadBalancingPolicy("profile1"); DefaultLoadBalancingPolicy policy2 = - (DefaultLoadBalancingPolicy) context.loadBalancingPolicy("profile2"); + (DefaultLoadBalancingPolicy) context.getLoadBalancingPolicy("profile2"); assertThat(defaultPolicy).isSameAs(policy2).isNotSameAs(policy1); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index 502f52de155..dab87618c1e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -66,7 +66,7 @@ public void should_expose_node_metadata() { // Note: open connections and reconnection status are covered in NodeStateIT // Force the node down and back up to check that upSinceMillis gets updated - EventBus eventBus = ((InternalDriverContext) session.getContext()).eventBus(); + EventBus eventBus = ((InternalDriverContext) session.getContext()).getEventBus(); eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); ConditionChecker.checkThat(() -> node.getState() == NodeState.FORCED_DOWN).becomesTrue(); assertThat(node.getUpSinceMillis()).isEqualTo(-1); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 65d9f8b1197..404fc6b3c8f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -109,11 +109,11 @@ public void setup() { inOrder = Mockito.inOrder(nodeStateListener); driverContext = (InternalDriverContext) sessionRule.session().getContext(); - driverContext.eventBus().register(NodeStateEvent.class, stateEvents::add); + driverContext.getEventBus().register(NodeStateEvent.class, stateEvents::add); defaultLoadBalancingPolicy = (ConfigurableIgnoresPolicy) - driverContext.loadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); + driverContext.getLoadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); // Sanity check: the driver should have connected to simulacron ConditionChecker.checkThat( @@ -199,7 +199,7 @@ public void should_mark_control_node_down_when_control_connection_is_last_connec simulacronControlNode.rejectConnections(0, RejectScope.UNBIND); // Identify the control connection and close the two other ones - SocketAddress controlAddress = driverContext.controlConnection().channel().localAddress(); + SocketAddress controlAddress = driverContext.getControlConnection().channel().localAddress(); NodeConnectionReport report = simulacronControlNode.getConnections(); for (SocketAddress address : report.getConnections()) { if (!address.equals(controlAddress)) { @@ -265,7 +265,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .becomesTrue(); driverContext - .eventBus() + .getEventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> @@ -279,7 +279,9 @@ public void should_apply_up_and_down_topology_events_when_ignored() { .becomesTrue(); inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); - driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -298,7 +300,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { @Test public void should_ignore_down_topology_event_when_still_connected() throws InterruptedException { driverContext - .eventBus() + .getEventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting(); @@ -339,7 +341,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() localSimulacronNode.acceptConnections(); ((InternalDriverContext) session.getContext()) - .eventBus() + .getEventBus() .fire(TopologyEvent.suggestUp(localMetadataNode.getConnectAddress())); ConditionChecker.checkThat(() -> assertThat(localMetadataNode).isUp().isNotReconnecting()) @@ -354,7 +356,9 @@ public void should_force_immediate_reconnection_when_up_topology_event() @Test public void should_force_down_when_not_ignored() throws InterruptedException { - driverContext.eventBus().fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -367,18 +371,22 @@ public void should_force_down_when_not_ignored() throws InterruptedException { inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down - driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext - .eventBus() + .getEventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); // Should only come back up on a FORCE_UP event - driverContext.eventBus().fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting()) .as("Node forced back up") @@ -391,7 +399,9 @@ public void should_force_down_when_not_ignored() throws InterruptedException { public void should_force_down_when_ignored() throws InterruptedException { defaultLoadBalancingPolicy.ignore(metadataRegularNode); - driverContext.eventBus().fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -404,19 +414,23 @@ public void should_force_down_when_ignored() throws InterruptedException { inOrder.verify(nodeStateListener, timeout(500)).onDown(metadataRegularNode); // Should ignore up/down topology events while forced down - driverContext.eventBus().fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext - .eventBus() + .getEventBus() .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); // Should only come back up on a FORCE_UP event, will not reopen connections since it is still // ignored - driverContext.eventBus().fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + driverContext + .getEventBus() + .fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index e9a022ad970..a56ca55ffe9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -96,7 +96,7 @@ public void should_be_consistent_with_range_queries() { // Find the replica for a given partition key of ks1.foo. int key = 1; - ProtocolVersion protocolVersion = session().getContext().protocolVersion(); + ProtocolVersion protocolVersion = session().getContext().getProtocolVersion(); ByteBuffer serializedKey = TypeCodecs.INT.encodePrimitive(key, protocolVersion); Set replicas = tokenMap.getReplicas(KS1, serializedKey); assertThat(replicas).hasSize(1); @@ -335,7 +335,7 @@ public void should_create_token_from_partition_key() { Row row = session().execute("SELECT token(i) FROM foo WHERE i = 1").one(); Token expected = row.getToken(0); - ProtocolVersion protocolVersion = session().getContext().protocolVersion(); + ProtocolVersion protocolVersion = session().getContext().getProtocolVersion(); assertThat(tokenMap.newToken(TypeCodecs.INT.encodePrimitive(1, protocolVersion))) .isEqualTo(expected); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java index 28889580007..0b7adadbbc6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java @@ -102,16 +102,16 @@ public static void setup() { // sanity checks DriverContext context = sessionRule.session().getContext(); - DriverConfig config = context.config(); + DriverConfig config = context.getConfig(); assertThat(config.getProfiles()).containsKeys("profile1", "profile2"); - assertThat(context.retryPolicies()) + assertThat(context.getRetryPolicies()) .hasSize(3) .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); - RetryPolicy defaultPolicy = context.retryPolicy(DriverExecutionProfile.DEFAULT_NAME); - RetryPolicy policy1 = context.retryPolicy("profile1"); - RetryPolicy policy2 = context.retryPolicy("profile2"); + RetryPolicy defaultPolicy = context.getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME); + RetryPolicy policy1 = context.getRetryPolicy("profile1"); + RetryPolicy policy2 = context.getRetryPolicy("profile2"); assertThat(defaultPolicy) .isInstanceOf(DefaultRetryPolicy.class) .isSameAs(policy2) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index e18e29be01a..38216c7b71a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -49,7 +49,7 @@ * *

              Uses {@link DefaultGuavaSession} which is a specialized session implementation that uses * {@link GuavaDriverContext} which overrides {@link - * DefaultDriverContext#requestProcessorRegistry()} to provide its own {@link + * DefaultDriverContext#getRequestProcessorRegistry()} to provide its own {@link * com.datastax.oss.driver.internal.core.session.RequestProcessor} implementations for returning * {@link ListenableFuture}s rather than {@link java.util.concurrent.CompletionStage}s in async * method responses. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index af2f32d71ec..022895b9c7d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -419,17 +419,17 @@ private CqlSession buildSessionWithProfile( // validate profile data DriverContext context = session.getContext(); - DriverConfig driverConfig = context.config(); + DriverConfig driverConfig = context.getConfig(); assertThat(driverConfig.getProfiles()).containsKeys("profile1", "profile2"); - assertThat(context.speculativeExecutionPolicies()) + assertThat(context.getSpeculativeExecutionPolicies()) .hasSize(3) .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); SpeculativeExecutionPolicy defaultPolicy = - context.speculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); - SpeculativeExecutionPolicy policy1 = context.speculativeExecutionPolicy("profile1"); - SpeculativeExecutionPolicy policy2 = context.speculativeExecutionPolicy("profile2"); + context.getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); + SpeculativeExecutionPolicy policy1 = context.getSpeculativeExecutionPolicy("profile1"); + SpeculativeExecutionPolicy policy2 = context.getSpeculativeExecutionPolicy("profile2"); Class expectedDefaultPolicyClass = defaultMaxSpeculativeExecutions != -1 || defaultSpeculativeDelayMs != -1 ? ConstantSpeculativeExecutionPolicy.class diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java index 8c593fe5575..4499adc474b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestNodeLoggerExample.java @@ -27,7 +27,7 @@ public class RequestNodeLoggerExample extends RequestLogger { public RequestNodeLoggerExample(DriverContext context) { - super(context.sessionName(), new RequestLogFormatter(context)); + super(context.getSessionName(), new RequestLogFormatter(context)); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index 058fcbb6bde..e3450b6d799 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -39,7 +39,7 @@ import java.util.function.Predicate; /** - * A Custom {@link DefaultDriverContext} that overrides {@link #requestProcessorRegistry()} to + * A Custom {@link DefaultDriverContext} that overrides {@link #getRequestProcessorRegistry()} to * return a {@link RequestProcessorRegistry} that includes processors for returning guava futures. */ public class GuavaDriverContext extends DefaultDriverContext { @@ -75,7 +75,7 @@ public RequestProcessorRegistry buildRequestProcessorRegistry() { CqlRequestSyncProcessor cqlRequestSyncProcessor = new CqlRequestSyncProcessor(); return new RequestProcessorRegistry( - sessionName(), + getSessionName(), cqlRequestSyncProcessor, new CqlPrepareSyncProcessor(preparedStatementsCache), new GuavaRequestAsyncProcessor<>( diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 636c636442f..5e40202247b 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -132,7 +132,7 @@ You don't need the configuration API for everyday usage of the driver, but it ca The driver's context exposes a [DriverConfig] instance: ```java -DriverConfig config = session.getContext().config(); +DriverConfig config = session.getContext().getConfig(); DriverExecutionProfile defaultProfile = config.getDefaultProfile(); DriverExecutionProfile olapProfile = config.getProfile("olap"); @@ -156,7 +156,7 @@ To allow this, you start from an existing profile in the configuration and build that overrides a subset of options: ```java -DriverExecutionProfile defaultProfile = session.getContext().config().getDefaultProfile(); +DriverExecutionProfile defaultProfile = session.getContext().getConfig().getDefaultProfile(); DriverExecutionProfile dynamicProfile = defaultProfile.withString( DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.EACH_QUORUM.name()); @@ -337,7 +337,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; // Use responsibly. InternalDriverContext context = (InternalDriverContext) session.getContext(); -EventBus eventBus = context.eventBus(); +EventBus eventBus = context.getEventBus(); eventBus.fire(ForceReloadConfigEvent.INSTANCE); ``` @@ -462,7 +462,7 @@ datastax-java-driver { And access them from the code: ```java -DriverConfig config = session.getContext().config(); +DriverConfig config = session.getContext().getConfig(); config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL); config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); ``` diff --git a/manual/core/custom_codecs/README.md b/manual/core/custom_codecs/README.md index 855de62fcf4..5f979723e06 100644 --- a/manual/core/custom_codecs/README.md +++ b/manual/core/custom_codecs/README.md @@ -193,7 +193,7 @@ Cat cat = row.get(0, Cat.class); // throws CodecNotFoundException The driver stores all codecs (built-in and custom) in an internal [CodecRegistry]: ```java -CodecRegistry codecRegistry = session.getContext().codecRegistry(); +CodecRegistry getCodecRegistry = session.getContext().getCodecRegistry(); // Get the custom codec we registered earlier: TypeCodec cqlIntToString = codecRegistry.codecFor(DataTypes.INT, GenericType.STRING); diff --git a/manual/core/load_balancing/README.md b/manual/core/load_balancing/README.md index 4bf1aad1b20..9a25c33e454 100644 --- a/manual/core/load_balancing/README.md +++ b/manual/core/load_balancing/README.md @@ -146,8 +146,8 @@ statement = statement.setRoutingKeyspace("testKs"); // Set the routing key manually: serialize each partition key component to its target CQL type statement = statement.setRoutingKey( - TypeCodecs.INT.encodePrimitive(1, session.getContext().protocolVersion()), - TypeCodecs.INT.encodePrimitive(2016, session.getContext().protocolVersion())); + TypeCodecs.INT.encodePrimitive(1, session.getContext().getProtocolVersion()), + TypeCodecs.INT.encodePrimitive(2016, session.getContext().getProtocolVersion())); session.execute(statement); ``` diff --git a/manual/core/metadata/node/README.md b/manual/core/metadata/node/README.md index ff0843e8781..33ad36da3fc 100644 --- a/manual/core/metadata/node/README.md +++ b/manual/core/metadata/node/README.md @@ -53,8 +53,8 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; InternalDriverContext context = (InternalDriverContext) session.getContext(); -context.eventBus().fire(TopologyEvent.forceDown(node1.getConnectAddress())); -context.eventBus().fire(TopologyEvent.forceUp(node1.getConnectAddress())); +context.getEventBus().fire(TopologyEvent.forceDown(node1.getConnectAddress())); +context.getEventBus().fire(TopologyEvent.forceUp(node1.getConnectAddress())); ``` As shown by the imports above, forcing a node down requires the *internal* driver API, which is diff --git a/manual/core/metadata/token/README.md b/manual/core/metadata/token/README.md index 61f577687a1..6f489fcadf5 100644 --- a/manual/core/metadata/token/README.md +++ b/manual/core/metadata/token/README.md @@ -111,7 +111,7 @@ partitioner: ```java String pk = "johndoe@example.com"; // You need to manually encode the key as binary: -ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, session.getContext().protocolVersion()); +ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, session.getContext().getProtocolVersion()); Set nodes1 = tokenMap.getReplicas(CqlIdentifier.fromInternal("ks1"), encodedPk); // Assuming the key hashes to "1", it is in the ]12, 2] range diff --git a/manual/core/native_protocol/README.md b/manual/core/native_protocol/README.md index 68213c4602e..3fa92a94b5b 100644 --- a/manual/core/native_protocol/README.md +++ b/manual/core/native_protocol/README.md @@ -27,7 +27,7 @@ and must be enabled explicitly (negotiation will yield v4).* To find out which version you're currently using, use the following: ```java -ProtocolVersion currentVersion = session.getContext().protocolVersion(); +ProtocolVersion currentVersion = session.getContext().getProtocolVersion(); ``` The protocol version cannot be changed at runtime. However, you can force a particular version in diff --git a/manual/core/statements/simple/README.md b/manual/core/statements/simple/README.md index 21d7dd632e2..d3a864be485 100644 --- a/manual/core/statements/simple/README.md +++ b/manual/core/statements/simple/README.md @@ -157,9 +157,9 @@ In that situation, there is no way to hint at the correct type. Fortunately, you value manually as a workaround: ```java -TypeCodec codec = session.getContext().codecRegistry().codecFor(DataTypes.ASCII); +TypeCodec codec = session.getContext().getCodecRegistry().codecFor(DataTypes.ASCII); ByteBuffer bytes = - codec.encode("Touché sir, touché...", session.getContext().protocolVersion()); + codec.encode("Touché sir, touché...", session.getContext().getProtocolVersion()); session.execute( SimpleStatement.builder("INSERT INTO ascii_quotes (id, t) VALUES (?, ?)") diff --git a/manual/query_builder/term/README.md b/manual/query_builder/term/README.md index c4fd300cf04..0214e3361d8 100644 --- a/manual/query_builder/term/README.md +++ b/manual/query_builder/term/README.md @@ -27,7 +27,7 @@ your session): ```java MyCustomId myCustomId = ...; -CodecRegistry registry = session.getContext().codecRegistry(); +CodecRegistry registry = session.getContext().getCodecRegistry(); selectFrom("user").all().whereColumn("id").isEqualTo(literal(myCustomId, registry)); ``` diff --git a/pom.xml b/pom.xml index 016d5e72707..6e6a77524ae 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss @@ -567,6 +569,8 @@ limitations under the License.]]> revapi.json + + false diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java index 4d58db1fedc..c71f9a53ba3 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java @@ -392,9 +392,9 @@ public static Literal literal(@Nullable Object value) { * *

              This is an alternative to {@link #literal(Object)} for custom type mappings. The provided * registry should contain a codec that can format the value. Typically, this will be your - * session's registry, which is accessible via {@code session.getContext().codecRegistry()}. + * session's registry, which is accessible via {@code session.getContext().getCodecRegistry()}. * - * @see DriverContext#codecRegistry() + * @see DriverContext#getCodecRegistry() * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can * handle {@code value}. */ diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java index 55bb854307e..1891e8c724f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java @@ -759,9 +759,9 @@ default Select literal(@Nullable Object value) { * *

              This is an alternative to {@link #literal(Object)} for custom type mappings. The provided * registry should contain a codec that can format the value. Typically, this will be your - * session's registry, which is accessible via {@code session.getContext().codecRegistry()}. + * session's registry, which is accessible via {@code session.getContext().getCodecRegistry()}. * - * @see DriverContext#codecRegistry() + * @see DriverContext#getCodecRegistry() * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can * handle {@code value}. * @see QueryBuilderDsl#literal(Object, CodecRegistry) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index d2f73b94ca4..18d63830a0e 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -238,7 +238,7 @@ public static void dropKeyspace(Session session, CqlIdentifier keyspace) { public static DriverExecutionProfile slowProfile(Session session) { return session .getContext() - .config() + .getConfig() .getDefaultProfile() .withString(DefaultDriverOption.REQUEST_TIMEOUT, "30s"); } From 6608902901d942bb9482ebaf2337673c5af4a359 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 27 Jul 2018 15:09:03 -0700 Subject: [PATCH 537/742] Include external dependencies in Revapi check --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6e6a77524ae..92c913ad9fc 100644 --- a/pom.xml +++ b/pom.xml @@ -569,8 +569,6 @@ limitations under the License.]]> revapi.json - - false From 1bd9fb4cfab7034a8d32fc4fa17772a0401e725d Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 6 Aug 2018 20:44:34 +0200 Subject: [PATCH 538/742] Unwrap session before casting to DefaultSession --- .../api/core/connection/ChannelSocketOptionsIT.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java index 162e1a24d05..a3d7ef78f06 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -28,12 +28,14 @@ import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.session.SessionWrapper; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.RecvByteBufAllocator; @@ -64,7 +66,7 @@ public class ChannelSocketOptionsIT { @Test public void should_report_socket_options() { - CqlSession session = sessionRule.session(); + Session session = sessionRule.session(); DriverExecutionProfile config = session.getContext().getConfig().getDefaultProfile(); assertThat(config.getBoolean(SOCKET_TCP_NODELAY)).isTrue(); assertThat(config.getBoolean(SOCKET_KEEP_ALIVE)).isFalse(); @@ -73,7 +75,11 @@ public void should_report_socket_options() { assertThat(config.getInt(SOCKET_RECEIVE_BUFFER_SIZE)).isEqualTo(123456); assertThat(config.getInt(SOCKET_SEND_BUFFER_SIZE)).isEqualTo(123456); Node node = session.getMetadata().getNodes().values().iterator().next(); - DriverChannel channel = ((DefaultSession) session).getChannel(node, null); + if (session instanceof SessionWrapper) { + session = ((SessionWrapper) session).getDelegate(); + } + DriverChannel channel = ((DefaultSession) session).getChannel(node, "test"); + assertThat(channel).isNotNull(); assertThat(channel.config()).isInstanceOf(SocketChannelConfig.class); SocketChannelConfig socketConfig = (SocketChannelConfig) channel.config(); assertThat(socketConfig.isTcpNoDelay()).isTrue(); From 19a9e82b85ff0051b03aa9ab0c8e77a44610daad Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 6 Aug 2018 20:48:37 +0200 Subject: [PATCH 539/742] Only check C* version if CCM version in use is OSS Cassandra --- .../oss/driver/api/core/metadata/NodeMetadataIT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index dab87618c1e..8ca21fb7c05 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; @@ -55,7 +56,10 @@ public void should_expose_node_metadata() { .isEqualTo(node.getConnectAddress().getAddress()); assertThat(node.getDatacenter()).isEqualTo("dc1"); assertThat(node.getRack()).isEqualTo("r1"); - assertThat(node.getCassandraVersion()).isEqualTo(ccmRule.getCassandraVersion()); + if (!CcmBridge.DSE_ENABLEMENT) { + // CcmBridge does not report accurate C* versions for DSE, only approximated values + assertThat(node.getCassandraVersion()).isEqualTo(ccmRule.getCassandraVersion()); + } assertThat(node.getState()).isSameAs(NodeState.UP); assertThat(node.getDistance()).isSameAs(NodeDistance.LOCAL); assertThat(node.getHostId()).isNotNull(); From 80b26f8d7571b3be9711bdbc5f677c9f68d8b493 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 6 Aug 2018 16:52:55 +0200 Subject: [PATCH 540/742] Allow DropwizardMetricsFactory and DropwizardMetricsUpdater to be extended --- .../internal/core/metrics/DropwizardMetricUpdater.java | 8 ++++---- .../internal/core/metrics/DropwizardMetricsFactory.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java index d804e95d3e3..0c47637d780 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricUpdater.java @@ -18,8 +18,8 @@ import com.codahale.metrics.Metric; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; -import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.config.DriverOption; import java.time.Duration; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -90,9 +90,9 @@ protected void initializeDefaultCounter(MetricT metric, String profileName) { protected void initializeHdrTimer( MetricT metric, DriverExecutionProfile config, - DefaultDriverOption highestLatencyOption, - DefaultDriverOption significantDigitsOption, - DefaultDriverOption intervalOption) { + DriverOption highestLatencyOption, + DriverOption significantDigitsOption, + DriverOption intervalOption) { String profileName = config.getName(); if (isEnabled(metric, profileName)) { String fullName = buildFullName(metric, profileName); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java index c56b9e375bd..76e9cb8965a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardMetricsFactory.java @@ -88,7 +88,7 @@ public NodeMetricUpdater newNodeUpdater(Node node) { : new DropwizardNodeMetricUpdater(node, enabledNodeMetrics, registry, context); } - private Set parseSessionMetricPaths(List paths) { + protected Set parseSessionMetricPaths(List paths) { EnumSet result = EnumSet.noneOf(DefaultSessionMetric.class); for (String path : paths) { try { @@ -100,7 +100,7 @@ private Set parseSessionMetricPaths(List paths) { return Collections.unmodifiableSet(result); } - private Set parseNodeMetricPaths(List paths) { + protected Set parseNodeMetricPaths(List paths) { EnumSet result = EnumSet.noneOf(DefaultNodeMetric.class); for (String path : paths) { try { From ef2865d09fe24bf49c4111f192b9d8e860122474 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 7 Aug 2018 16:02:27 +0200 Subject: [PATCH 541/742] Fix minor typo in comment --- .../core/config/typesafe/TypesafeDriverExecutionProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java index 1ecdfd04ed2..31275a4acce 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverExecutionProfile.java @@ -367,7 +367,7 @@ private Set getDerivedProfiles() { } /** - * A profile that was copied from another profile programatically using {@code withXxx} methods. + * A profile that was copied from another profile programmatically using {@code withXxx} methods. */ @ThreadSafe static class Derived extends TypesafeDriverExecutionProfile { From d87f4942c58e36495bfc27e2d90072ad783157c8 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 7 Aug 2018 16:03:03 +0200 Subject: [PATCH 542/742] Fix misplaced entry in application.conf --- integration-tests/src/test/resources/application.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/resources/application.conf b/integration-tests/src/test/resources/application.conf index 275b0be511b..921a93fa6dd 100644 --- a/integration-tests/src/test/resources/application.conf +++ b/integration-tests/src/test/resources/application.conf @@ -13,8 +13,8 @@ datastax-java-driver { connection { init-query-timeout = 5 seconds set-keyspace-timeout = 5 seconds - heartbeat.timeout = 5 seconds } + heartbeat.timeout = 5 seconds control-connection.timeout = 5 seconds request { trace.interval = 1 second From 493048a234e6aa25e90884822a1f2d75820ff4dd Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Tue, 7 Aug 2018 11:29:36 -0500 Subject: [PATCH 543/742] JAVA-1927 fix issue with unused native port (#1066) * JAVA-1927 fix issue with unused native port --- .../internal/core/metadata/DefaultTopologyMonitor.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 9a65c0b5dac..366d753399d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; @@ -166,7 +167,7 @@ public CompletionStage> refreshNodeList() { peersV2Query.whenComplete( (r, t) -> { - if (t != null) { + if (t instanceof InvalidQueryException) { // The query to system.peers_v2 failed, we should not attempt this query in the // future. this.isSchemaV2 = false; @@ -317,7 +318,10 @@ private InetSocketAddress getNativeAddressFromPeers(AdminRow row) { if (nativeAddress == null) { return null; } - int row_port = port; - return addressTranslator.translate(new InetSocketAddress(nativeAddress, row_port)); + Integer rowPort = row.getInteger("native_port"); + if (rowPort == null || rowPort == 0) { + rowPort = port; + } + return addressTranslator.translate(new InetSocketAddress(nativeAddress, rowPort)); } } From 5f6203d36b9ec4b64b8d6a9994851246c7cd5aac Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 9 Aug 2018 11:28:26 -0500 Subject: [PATCH 544/742] Fix peers_v2 handling in DefaultTopologyMonitor --- .../core/metadata/DefaultTopologyMonitor.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 366d753399d..877beaf4528 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.control.ControlConnection; @@ -167,12 +168,18 @@ public CompletionStage> refreshNodeList() { peersV2Query.whenComplete( (r, t) -> { - if (t instanceof InvalidQueryException) { - // The query to system.peers_v2 failed, we should not attempt this query in the - // future. - this.isSchemaV2 = false; - CompletableFutures.completeFrom( - query(channel, "SELECT * FROM system.peers"), peersQuery); + if (t != null) { + if (t instanceof UnexpectedResponseException + && t.getCause() != null + && t.getCause() instanceof InvalidQueryException) { + // The query to system.peers_v2 failed, we should not attempt this query in the + // future. + this.isSchemaV2 = false; + CompletableFutures.completeFrom( + query(channel, "SELECT * FROM system.peers"), peersQuery); + } else { + peersQuery.completeExceptionally(t); + } } else { peersQuery.complete(r); } From ff6312cb0d21441346b24ce76359a5d587808b14 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 9 Aug 2018 11:44:25 -0500 Subject: [PATCH 545/742] Fix peers_v2 handling in DefaultTopologyMonitor --- .../core/metadata/DefaultTopologyMonitor.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 877beaf4528..7a7ac6633c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; @@ -30,6 +29,8 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.datastax.oss.protocol.internal.Message; +import com.datastax.oss.protocol.internal.ProtocolConstants; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -169,17 +170,21 @@ public CompletionStage> refreshNodeList() { peersV2Query.whenComplete( (r, t) -> { if (t != null) { - if (t instanceof UnexpectedResponseException - && t.getCause() != null - && t.getCause() instanceof InvalidQueryException) { - // The query to system.peers_v2 failed, we should not attempt this query in the - // future. - this.isSchemaV2 = false; - CompletableFutures.completeFrom( - query(channel, "SELECT * FROM system.peers"), peersQuery); - } else { - peersQuery.completeExceptionally(t); + if (t instanceof UnexpectedResponseException) { + UnexpectedResponseException e = (UnexpectedResponseException) t; + Message message = e.message; + if (message instanceof com.datastax.oss.protocol.internal.response.Error + && ((com.datastax.oss.protocol.internal.response.Error) message).code + == ProtocolConstants.ErrorCode.INVALID) { + // The query to system.peers_v2 failed, we should not attempt this query in the + // future. + this.isSchemaV2 = false; + CompletableFutures.completeFrom( + query(channel, "SELECT * FROM system.peers"), peersQuery); + return; + } } + peersQuery.completeExceptionally(t); } else { peersQuery.complete(r); } From f491e1221d4bb5c2dbbfc6202c38f01852260b18 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 9 Aug 2018 16:11:50 -0500 Subject: [PATCH 546/742] JAVA-1938: Make CassandraSchemaQueries classes public --- changelog/README.md | 1 + .../metadata/schema/queries/Cassandra21SchemaQueries.java | 4 ++-- .../metadata/schema/queries/Cassandra22SchemaQueries.java | 4 ++-- .../core/metadata/schema/queries/Cassandra3SchemaQueries.java | 4 ++-- .../core/metadata/schema/queries/Cassandra4SchemaQueries.java | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index f7e2e31316e..cb95901bafd 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [bug] JAVA-1938: Make CassandraSchemaQueries classes public - [improvement] JAVA-1925: Rename context getters - [improvement] JAVA-1544: Check API compatibility with Revapi - [new feature] JAVA-1900: Add support for virtual tables diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java index 05c12cdc4a3..556c9c58b6b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueries.java @@ -23,8 +23,8 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra21SchemaQueries extends CassandraSchemaQueries { - Cassandra21SchemaQueries( +public class Cassandra21SchemaQueries extends CassandraSchemaQueries { + public Cassandra21SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, DriverExecutionProfile config, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java index 936af222adb..130599b86e2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueries.java @@ -23,8 +23,8 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra22SchemaQueries extends CassandraSchemaQueries { - Cassandra22SchemaQueries( +public class Cassandra22SchemaQueries extends CassandraSchemaQueries { + public Cassandra22SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, DriverExecutionProfile config, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java index 91828977b2b..c2c97873624 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueries.java @@ -23,8 +23,8 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra3SchemaQueries extends CassandraSchemaQueries { - Cassandra3SchemaQueries( +public class Cassandra3SchemaQueries extends CassandraSchemaQueries { + public Cassandra3SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, DriverExecutionProfile config, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java index cba4195efe5..641a97119b9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra4SchemaQueries.java @@ -23,8 +23,8 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -class Cassandra4SchemaQueries extends Cassandra3SchemaQueries { - Cassandra4SchemaQueries( +public class Cassandra4SchemaQueries extends Cassandra3SchemaQueries { + public Cassandra4SchemaQueries( DriverChannel channel, CompletableFuture refreshFuture, DriverExecutionProfile config, From 96d6f34cd39fdd8612fa580e3bfb64caf7619e9a Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 9 Aug 2018 18:13:50 -0500 Subject: [PATCH 547/742] Upgrade to simulacron 0.8.4 Adds supports for peers_v2 table. With existing configuration this will just cause simulacron to return an INVALID error when querying peers_v2, which matches pre C* 4 versions. Updates SortingLoadBalancingPolicy to consider port when ordering nodes. --- pom.xml | 2 +- .../testinfra/loadbalancing/SortingLoadBalancingPolicy.java | 4 +++- .../oss/driver/api/testinfra/simulacron/SimulacronRule.java | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 92c913ad9fc..fcbecb1db8e 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 4.12 1.2.3 4.12.0 - 0.8.3 + 0.8.4 diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index af1c5be28f1..77de5f16968 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -67,7 +67,9 @@ public SortingLoadBalancingPolicy(DriverContext context, String profileName) { return b1 - b2; } } - return 0; + int port1 = node1.getBroadcastAddress().map(InetSocketAddress::getPort).orElse(0); + int port2 = node2.getBroadcastAddress().map(InetSocketAddress::getPort).orElse(0); + return port1 - port2; }); public SortingLoadBalancingPolicy() {} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index ea0db9251b5..86042052dac 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -19,9 +19,9 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; -import com.datastax.oss.simulacron.server.AddressResolver; import com.datastax.oss.simulacron.server.BoundCluster; import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.Inet4Resolver; import com.datastax.oss.simulacron.server.Server; import java.net.InetSocketAddress; import java.util.Set; @@ -31,7 +31,7 @@ public class SimulacronRule extends CassandraResourceRule { // TODO perhaps share server some other way public static final Server server = - Server.builder().withAddressResolver(new AddressResolver.Inet4Resolver(9043)).build(); + Server.builder().withAddressResolver(new Inet4Resolver(9043)).build(); private final ClusterSpec clusterSpec; private BoundCluster boundCluster; From 3a24c3f7309fbfe4ca942cc1a4b7311a1610ceb2 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 10 Aug 2018 12:04:34 +0200 Subject: [PATCH 548/742] JAVA-1939: Exclude logback-test.xml files from test jars Motivation: When logback-test.xml files get bundled in test jars, they appear on the classpath of client applications, creating Logback configuration conflicts with local configuration files. Modification: Exclude logback-test.xml files from test jars. Result: No more Logback configuration conflicts in client applications. --- core/pom.xml | 5 +++++ integration-tests/pom.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/core/pom.xml b/core/pom.xml index b98e13db5a3..b1844e6ce82 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -154,6 +154,11 @@ test-jar + + + logback-test.xml + + diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 75b410a4969..88426bb92c9 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -115,6 +115,11 @@ test-jar + + + logback-test.xml + + From 2362e1598eee81e1cb11f9130ad6e3f59725deb7 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 10 Aug 2018 12:05:06 +0200 Subject: [PATCH 549/742] Add missing @After annotation to teardown test method --- .../oss/driver/internal/core/retry/DefaultRetryPolicyIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index 1e0b995d42d..4c33157df85 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -58,6 +58,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Arrays; import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -111,6 +112,7 @@ public void setup() { simulacron.cluster().clearPrimes(true); } + @After public void teardown() { logger.detachAppender(appender); logger.setLevel(oldLevel); From 529b1734dda44e9655790eed73f60e5ce9cb5373 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 10 Aug 2018 12:07:28 +0200 Subject: [PATCH 550/742] Raise com.datastax.oss.driver logger level to ERROR At WARN level, some loggers can be very verbose, specially CqlPrepareHandlerBase. --- integration-tests/src/test/resources/logback-test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/resources/logback-test.xml b/integration-tests/src/test/resources/logback-test.xml index c2fe7cba13a..b74e0b63050 100644 --- a/integration-tests/src/test/resources/logback-test.xml +++ b/integration-tests/src/test/resources/logback-test.xml @@ -24,5 +24,5 @@ - + \ No newline at end of file From 941f5f0b07bc9fcc5edcb1db07df0e3fff8b9c8e Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 7 Aug 2018 14:20:31 +0200 Subject: [PATCH 551/742] Make ProtocolFeature an interface Motivation: ProtocolFeature is currently an enum and as such is not extensible; if custom protocol extensions need to support proprietary features, such features cannot be modeled as a ProtocolFeature instance. Modification: Make ProtocolFeature an interface, create enum DefaultProtocolFeature. Result: ProtocolFeature can now be implemented by other classes. --- .../oss/driver/api/core/ProtocolVersion.java | 4 +- .../CassandraProtocolVersionRegistry.java | 13 +++---- .../internal/core/DefaultProtocolFeature.java | 39 +++++++++++++++++++ .../driver/internal/core/ProtocolFeature.java | 30 +++++++------- .../driver/internal/core/cql/Conversions.java | 10 ++--- .../core/cql/CqlPrepareHandlerBase.java | 4 +- .../driver/api/core/cql/BoundStatementIT.java | 4 +- 7 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/DefaultProtocolFeature.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java index 9235439dd55..e39837cc090 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ProtocolVersion.java @@ -43,8 +43,8 @@ public interface ProtocolVersion { /** * Whether the protocol version is in a beta status. * - *

              Beta versions are intended for Cassandra development. They should be used in a regular - * application, beta features may break at any point. + *

              Beta versions are intended for Cassandra development. They should not be used in a regular + * application, as beta features may break at any point. */ boolean isBeta(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index d7d5b10c5d7..337868d9441 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -170,13 +170,12 @@ public ProtocolVersion highestCommon(Collection nodes) { @Override public boolean supports(ProtocolVersion version, ProtocolFeature feature) { - switch (feature) { - case UNSET_BOUND_VALUES: - return version.getCode() >= 4; - case PER_REQUEST_KEYSPACE: - return version.getCode() >= 5; - default: - throw new IllegalArgumentException("Unhandled protocol feature: " + feature); + if (DefaultProtocolFeature.UNSET_BOUND_VALUES.equals(feature)) { + return version.getCode() >= 4; + } else if (DefaultProtocolFeature.PER_REQUEST_KEYSPACE.equals(feature)) { + return version.getCode() >= 5; + } else { + throw new IllegalArgumentException("Unhandled protocol feature: " + feature); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultProtocolFeature.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultProtocolFeature.java new file mode 100644 index 00000000000..8d26d1d23f4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultProtocolFeature.java @@ -0,0 +1,39 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +/** + * Features that are commonly supported by most Apache Cassandra protocol versions. + * + * @see com.datastax.oss.driver.api.core.DefaultProtocolVersion + */ +public enum DefaultProtocolFeature implements ProtocolFeature { + + /** + * The ability to leave variables unset in prepared statements. + * + * @see CASSANDRA-7304 + */ + UNSET_BOUND_VALUES, + + /** + * The ability to override the keyspace on a per-request basis. + * + * @see CASSANDRA-10145 + */ + PER_REQUEST_KEYSPACE, + ; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java index d79069a40db..7f4e286ea17 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ProtocolFeature.java @@ -15,21 +15,17 @@ */ package com.datastax.oss.driver.internal.core; -/** Features of the native protocol that are only supported in specific versions. */ -public enum ProtocolFeature { +import com.datastax.oss.driver.api.core.ProtocolVersion; - /** - * The ability to leave variables unset in prepared statements. - * - * @see CASSANDRA-7304 - */ - UNSET_BOUND_VALUES, - - /** - * The ability to override the keyspace on a per-request basis. - * - * @see CASSANDRA-10145 - */ - PER_REQUEST_KEYSPACE, - ; -} +/** + * A marker interface for features of the native protocol that are only supported by specific + * {@linkplain ProtocolVersion versions}. + * + *

              The only reason to model this as an interface (as opposed to an enum type) is to accommodate + * for custom protocol extensions. If you're connecting to a standard Apache Cassandra cluster, all + * {@code ProtocolFeature}s are {@link DefaultProtocolFeature} instances. + * + * @see ProtocolVersionRegistry#supports(ProtocolVersion, ProtocolFeature) + * @see DefaultProtocolFeature + */ +public interface ProtocolFeature {} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 55f094f8546..c406aba52b0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -53,7 +53,7 @@ import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.DefaultProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; @@ -128,7 +128,7 @@ public static Message toMessage( "Can't have both positional and named values in a statement."); } if (keyspace != null - && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + && !registry.supports(protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { throw new IllegalArgumentException( "Can't use per-request keyspace with protocol " + protocolVersion); } @@ -146,7 +146,7 @@ public static Message toMessage( return new Query(simpleStatement.getQuery(), queryOptions); } else if (statement instanceof BoundStatement) { BoundStatement boundStatement = (BoundStatement) statement; - if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { + if (!registry.supports(protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(boundStatement); } boolean skipMetadata = @@ -171,11 +171,11 @@ public static Message toMessage( queryOptions); } else if (statement instanceof BatchStatement) { BatchStatement batchStatement = (BatchStatement) statement; - if (!registry.supports(protocolVersion, ProtocolFeature.UNSET_BOUND_VALUES)) { + if (!registry.supports(protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(batchStatement); } if (keyspace != null - && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + && !registry.supports(protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { throw new IllegalArgumentException( "Can't use per-request keyspace with protocol " + protocolVersion); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index e4b11b9a100..fd1e1f81c21 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -36,7 +36,7 @@ import com.datastax.oss.driver.api.core.servererrors.QueryValidationException; import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.api.core.session.throttling.Throttled; -import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.DefaultProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -149,7 +149,7 @@ protected CqlPrepareHandlerBase( ProtocolVersionRegistry registry = context.getProtocolVersionRegistry(); CqlIdentifier keyspace = request.getKeyspace(); if (keyspace != null - && !registry.supports(protocolVersion, ProtocolFeature.PER_REQUEST_KEYSPACE)) { + && !registry.supports(protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { throw new IllegalArgumentException( "Can't use per-request keyspace with protocol " + protocolVersion); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index be70d5f99ad..cfcf013b34f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -36,7 +36,7 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; -import com.datastax.oss.driver.internal.core.ProtocolFeature; +import com.datastax.oss.driver.internal.core.DefaultProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; @@ -499,6 +499,6 @@ private boolean supportsPerRequestKeyspace(CqlSession session) { InternalDriverContext context = (InternalDriverContext) session.getContext(); ProtocolVersionRegistry protocolVersionRegistry = context.getProtocolVersionRegistry(); return protocolVersionRegistry.supports( - context.getProtocolVersion(), ProtocolFeature.PER_REQUEST_KEYSPACE); + context.getProtocolVersion(), DefaultProtocolFeature.PER_REQUEST_KEYSPACE); } } From 0436d76a90097ee0cd07859edb00fe719c074e41 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 10 Aug 2018 14:19:32 +0200 Subject: [PATCH 552/742] Introduce overloaded method ArrayUtils.shuffleHead(T[], int, ThreadLocalRandom) Motivation: ArrayUtils.shuffleHead(T[], int) uses ThreadLocalRandom.nextInt(int) and is hard to test. In particular ArrayUtilsTest.should_shuffle_head() fails randomly because of the method's non-determinism. Modification: Extract ThreadLocalRandom.current() as a parameter, making it easier to mock. Keep original method and delegate to the new overloaded one. Result: ArrayUtilsTest.should_shuffle_head() is now deterministic. --- .../driver/internal/core/util/ArrayUtils.java | 22 +++++++++- .../internal/core/util/ArrayUtilsTest.java | 41 ++++++++----------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 2dc225bb195..7d2d543332e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.util; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ThreadLocalRandom; public class ArrayUtils { @@ -50,18 +51,35 @@ public static void bubbleDown(T[] elements, int sourceIndex, int targetIndex /** * Shuffles the first n elements of the array in-place. * + * @param elements the array to shuffle. + * @param n the number of elements to shuffle; must be {@code <= elements.length}. * @see Modern * Fisher-Yates shuffle */ - public static void shuffleHead(T[] elements, int n) { + public static void shuffleHead(@NonNull T[] elements, int n) { + shuffleHead(elements, n, ThreadLocalRandom.current()); + } + + /** + * Shuffles the first n elements of the array in-place. + * + * @param elements the array to shuffle. + * @param n the number of elements to shuffle; must be {@code <= elements.length}. + * @param random the {@link ThreadLocalRandom} instance to use. This is mainly intended to + * facilitate tests. + * @see Modern + * Fisher-Yates shuffle + */ + public static void shuffleHead( + @NonNull T[] elements, int n, @NonNull ThreadLocalRandom random) { if (n > elements.length) { throw new ArrayIndexOutOfBoundsException( String.format( "Can't shuffle the first %d elements, there are only %d", n, elements.length)); } if (n > 1) { - ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = n - 1; i > 0; i--) { int j = random.nextInt(i + 1); swap(elements, i, j); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java index bb67edb9c19..6cb06654781 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -17,11 +17,9 @@ import static com.datastax.oss.driver.Assertions.assertThat; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import org.junit.Test; +import org.mockito.Mockito; public class ArrayUtilsTest { @@ -83,25 +81,22 @@ public void should_not_bubble_down_when_target_index_lower() { @Test public void should_shuffle_head() { - // Testing for randomness is hard, so we call the method a large number of times, and check that - // we get all permutations with a decent distribution. - Map, Integer> counts = new HashMap<>(); - for (int i = 0; i < 6000; i++) { - String[] array = {"a", "b", "c", "d", "e"}; - ArrayUtils.shuffleHead(array, 3); - - // Tail elements should not move - assertThat(array[3]).isEqualTo("d"); - assertThat(array[4]).isEqualTo("e"); - - List permutation = ImmutableList.of(array[0], array[1], array[2]); - counts.merge(permutation, 1, (a, b) -> a + b); - } - - assertThat(counts).hasSize(6); - for (Integer count : counts.values()) { - assertThat(count).isBetween(900, 1100); - } + String[] array = {"a", "b", "c", "d", "e"}; + ThreadLocalRandom random = Mockito.mock(ThreadLocalRandom.class); + Mockito.when(random.nextInt(Mockito.anyInt())) + .thenAnswer( + (invocation) -> { + int i = invocation.getArgument(0); + // shifts elements by 1 to the right + return i - 2; + }); + ArrayUtils.shuffleHead(array, 3, random); + assertThat(array[0]).isEqualTo("c"); + assertThat(array[1]).isEqualTo("a"); + assertThat(array[2]).isEqualTo("b"); + // Tail elements should not move + assertThat(array[3]).isEqualTo("d"); + assertThat(array[4]).isEqualTo("e"); } @Test(expected = ArrayIndexOutOfBoundsException.class) From 3ab2cc858415b0c535113c21d1a2db591ec13f9f Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 10 Aug 2018 14:19:51 +0200 Subject: [PATCH 553/742] Annotate methods in ArrayUtils with nullability annotations --- .../oss/driver/internal/core/util/ArrayUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 7d2d543332e..ae2412ea7fc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -20,7 +20,7 @@ public class ArrayUtils { - public static void swap(T[] elements, int i, int j) { + public static void swap(@NonNull T[] elements, int i, int j) { if (i != j) { T tmp = elements[i]; elements[i] = elements[j]; @@ -32,7 +32,7 @@ public static void swap(T[] elements, int i, int j) { * Moves an element towards the beginning of the array, shifting all the intermediary elements to * the right (no-op if {@code targetIndex >= sourceIndex}). */ - public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) { + public static void bubbleUp(@NonNull T[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i > targetIndex; i--) { swap(elements, i, i - 1); } @@ -42,7 +42,7 @@ public static void bubbleUp(T[] elements, int sourceIndex, int targetIndex) * Moves an element towards the end of the array, shifting all the intermediary elements to the * left (no-op if {@code targetIndex <= sourceIndex}). */ - public static void bubbleDown(T[] elements, int sourceIndex, int targetIndex) { + public static void bubbleDown(@NonNull T[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i < targetIndex; i++) { swap(elements, i, i + 1); } @@ -88,7 +88,7 @@ public static void shuffleHead( } /** Rotates the elements in the specified range by the specified amount (round-robin). */ - public static void rotate(T[] elements, int startIndex, int length, int amount) { + public static void rotate(@NonNull T[] elements, int startIndex, int length, int amount) { if (length >= 2) { amount = amount % length; // Repeatedly shift by 1. This is not the most time-efficient but the array will typically be From 0ecfb88fd2d8b06eb1c459d9e19e2b00a8df9040 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Fri, 10 Aug 2018 11:52:55 -0500 Subject: [PATCH 554/742] JAVA-1940: Clean up test resources when CCM integration tests finish --- changelog/README.md | 1 + .../oss/driver/api/testinfra/ccm/CcmBridge.java | 17 ++++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index cb95901bafd..1a6d63f38a3 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1940: Clean up test resources when CCM integration tests finish - [bug] JAVA-1938: Make CassandraSchemaQueries classes public - [improvement] JAVA-1925: Rename context getters - [improvement] JAVA-1544: Check API compatibility with Revapi diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 20b10ea8e75..cefc04985cf 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -18,10 +18,10 @@ import static io.netty.util.internal.PlatformDependent.isWindows; import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.shaded.guava.common.io.Resources; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -314,16 +314,9 @@ public void close() { */ private static File createTempStore(String storePath) { File f = null; - try (InputStream trustStoreIs = CcmBridge.class.getResourceAsStream(storePath)) { - f = File.createTempFile("server", ".store"); - logger.debug("Created store file {} for {}.", f, storePath); - try (OutputStream trustStoreOs = new FileOutputStream(f)) { - byte[] buffer = new byte[1024]; - int len; - while ((len = trustStoreIs.read(buffer)) != -1) { - trustStoreOs.write(buffer, 0, len); - } - } + try (OutputStream os = new FileOutputStream(f = File.createTempFile("server", ".store"))) { + f.deleteOnExit(); + Resources.copy(CcmBridge.class.getResource(storePath), os); } catch (IOException e) { logger.warn("Failure to write keystore, SSL-enabled servers may fail to start.", e); } @@ -348,6 +341,8 @@ public static class Builder { private Builder() { try { this.configDirectory = Files.createTempDirectory("ccm"); + // mark the ccm temp directories for deletion when the JVM exits + this.configDirectory.toFile().deleteOnExit(); } catch (IOException e) { // change to unchecked for now. throw new RuntimeException(e); From 3db9882858a7ca6cef39fc531962d0fcf8588e22 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 20 Aug 2018 11:35:29 -0700 Subject: [PATCH 555/742] Fix minor formatting issue --- .../com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index cefc04985cf..23d7c0e941d 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -304,8 +304,6 @@ public void close() { /** * Extracts a keystore from the classpath into a temporary file. * - *

              - * *

              This is needed as the keystore could be part of a built test jar used by other projects, and * they need to be extracted to a file system so cassandra may use them. * From daf2f63cf08f994f66c1d808ebb540c755c54d85 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 20 Aug 2018 15:26:04 -0700 Subject: [PATCH 556/742] Suppress ErrorProne's GuardedBy error more precisely Use annotations on the relevant methods instead of desactivating it globally in the POM. --- .../session/throttling/ConcurrencyLimitingRequestThrottler.java | 1 + .../core/session/throttling/RateLimitingRequestThrottler.java | 1 + pom.xml | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java index 8028bf88de9..ebfc838f4ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -143,6 +143,7 @@ public void signalTimeout(@NonNull Throttled request) { } } + @SuppressWarnings("GuardedBy") // this method is only called with the lock held private void onRequestDone() { assert lock.isHeldByCurrentThread(); if (!closed) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java index ad281c90769..ab4035a4d46 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottler.java @@ -210,6 +210,7 @@ public void close() { } } + @SuppressWarnings("GuardedBy") // this method is only called with the lock held private int acquire(long currentTimeNanos, int wantedPermits) { assert lock.isHeldByCurrentThread() && !closed; diff --git a/pom.xml b/pom.xml index fcbecb1db8e..e372362ed18 100644 --- a/pom.xml +++ b/pom.xml @@ -315,8 +315,6 @@ 1.8 -Xep:FutureReturnValueIgnored:OFF - - -Xep:GuardedBy:OFF true true From 59dc84984d38d29055171ddab75e88778d3ec213 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Thu, 16 Aug 2018 18:32:05 +0200 Subject: [PATCH 557/742] JAVA-1916: Base TimestampCodec.parse on java.util.Date --- changelog/README.md | 1 + .../core/type/codec/TimestampCodec.java | 243 ++++++++++++++---- .../core/type/codec/TimestampCodecTest.java | 124 +++++++-- 3 files changed, 310 insertions(+), 58 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 1a6d63f38a3..5db0c4c8e4f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1916: Base TimestampCodec.parse on java.util.Date. - [improvement] JAVA-1940: Clean up test resources when CCM integration tests finish - [bug] JAVA-1938: Make CassandraSchemaQueries classes public - [improvement] JAVA-1925: Rename context getters diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java index a5a0b851084..aa7d147581f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodec.java @@ -15,8 +15,6 @@ */ package com.datastax.oss.driver.internal.core.type.codec; -import static java.lang.Long.parseLong; - import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -26,44 +24,186 @@ import com.datastax.oss.driver.internal.core.util.Strings; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import io.netty.util.concurrent.FastThreadLocal; import java.nio.ByteBuffer; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; import java.time.Instant; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; +import java.time.ZoneId; +import java.util.Date; +import java.util.TimeZone; import net.jcip.annotations.ThreadSafe; +/** + * A codec that handles Apache Cassandra(R)'s timestamp type and maps it to Java's {@link Instant}. + * + *

              Implementation notes: + * + *

                + *
              1. Because {@code Instant} uses a precision of nanoseconds, whereas the timestamp type uses a + * precision of milliseconds, truncation will happen for any excess precision information as + * though the amount in nanoseconds was subject to integer division by one million. + *
              2. For compatibility reasons, this codec uses the legacy {@link SimpleDateFormat} API + * internally when parsing and formatting, and converts from {@link Instant} to {@link Date} + * and vice versa. Specially when parsing, this may yield different results as compared to + * what the newer Java Time API parsers would have produced for the same input. + *
              3. Also, {@code Instant} can store points on the time-line further in the future and further + * in the past than {@code Date}. This codec will throw an exception when attempting to parse + * or format an {@code Instant} falling in this category. + *
              + * + *

              Accepted date-time formats

              + * + * The following patterns are valid CQL timestamp literal formats for Apache Cassandra(R) 3.0 and + * higher, and are thus all recognized when parsing: + * + *
                + *
              1. {@code yyyy-MM-dd'T'HH:mm} + *
              2. {@code yyyy-MM-dd'T'HH:mm:ss} + *
              3. {@code yyyy-MM-dd'T'HH:mm:ss.SSS} + *
              4. {@code yyyy-MM-dd'T'HH:mmX} + *
              5. {@code yyyy-MM-dd'T'HH:mmXX} + *
              6. {@code yyyy-MM-dd'T'HH:mmXXX} + *
              7. {@code yyyy-MM-dd'T'HH:mm:ssX} + *
              8. {@code yyyy-MM-dd'T'HH:mm:ssXX} + *
              9. {@code yyyy-MM-dd'T'HH:mm:ssXXX} + *
              10. {@code yyyy-MM-dd'T'HH:mm:ss.SSSX} + *
              11. {@code yyyy-MM-dd'T'HH:mm:ss.SSSXX} + *
              12. {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX} + *
              13. {@code yyyy-MM-dd'T'HH:mm z} + *
              14. {@code yyyy-MM-dd'T'HH:mm:ss z} + *
              15. {@code yyyy-MM-dd'T'HH:mm:ss.SSS z} + *
              16. {@code yyyy-MM-dd HH:mm} + *
              17. {@code yyyy-MM-dd HH:mm:ss} + *
              18. {@code yyyy-MM-dd HH:mm:ss.SSS} + *
              19. {@code yyyy-MM-dd HH:mmX} + *
              20. {@code yyyy-MM-dd HH:mmXX} + *
              21. {@code yyyy-MM-dd HH:mmXXX} + *
              22. {@code yyyy-MM-dd HH:mm:ssX} + *
              23. {@code yyyy-MM-dd HH:mm:ssXX} + *
              24. {@code yyyy-MM-dd HH:mm:ssXXX} + *
              25. {@code yyyy-MM-dd HH:mm:ss.SSSX} + *
              26. {@code yyyy-MM-dd HH:mm:ss.SSSXX} + *
              27. {@code yyyy-MM-dd HH:mm:ss.SSSXXX} + *
              28. {@code yyyy-MM-dd HH:mm z} + *
              29. {@code yyyy-MM-dd HH:mm:ss z} + *
              30. {@code yyyy-MM-dd HH:mm:ss.SSS z} + *
              31. {@code yyyy-MM-dd} + *
              32. {@code yyyy-MM-ddX} + *
              33. {@code yyyy-MM-ddXX} + *
              34. {@code yyyy-MM-ddXXX} + *
              35. {@code yyyy-MM-dd z} + *
              + * + * By default, when parsing, timestamp literals that do not include any time zone information will + * be interpreted using the system's {@linkplain TimeZone#getDefault() default time zone}. This is + * intended to mimic Apache Cassandra(R)'s own parsing behavior (see {@code + * org.apache.cassandra.serializers.TimestampSerializer}). The default time zone can be modified + * using the {@linkplain TimestampCodec#TimestampCodec(ZoneId) one-arg constructor} that takes a + * custom {@link ZoneId} as an argument. + * + *

              When formatting, the pattern used is always {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX} and the time + * zone is either the the system's default one, or the one that was provided when instantiating the + * codec. + */ @ThreadSafe public class TimestampCodec implements TypeCodec { - /** A {@link DateTimeFormatter} that parses (most) of the ISO formats accepted in CQL. */ - private static final DateTimeFormatter PARSER = - new java.time.format.DateTimeFormatterBuilder() - .parseCaseSensitive() - .parseStrict() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .optionalStart() - .appendLiteral('T') - .appendValue(ChronoField.HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(ChronoField.MINUTE_OF_HOUR, 2) - .optionalEnd() - .optionalStart() - .appendLiteral(':') - .appendValue(ChronoField.SECOND_OF_MINUTE, 2) - .optionalEnd() - .optionalStart() - .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) - .optionalEnd() - .optionalStart() - .appendZoneId() - .optionalEnd() - .toFormatter() - .withZone(ZoneOffset.UTC); + /** + * Patterns accepted by Apache Cassandra(R) 3.0 and higher when parsing CQL literals. + * + *

              Note that Cassandra's TimestampSerializer declares many more patterns but some of them are + * equivalent when parsing. + */ + private static final String[] DATE_STRING_PATTERNS = + new String[] { + // 1) date-time patterns separated by 'T' + // (declared first because none of the others are ISO compliant, but some of these are) + // 1.a) without time zone + "yyyy-MM-dd'T'HH:mm", + "yyyy-MM-dd'T'HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ss.SSS", + // 1.b) with ISO-8601 time zone + "yyyy-MM-dd'T'HH:mmX", + "yyyy-MM-dd'T'HH:mmXX", + "yyyy-MM-dd'T'HH:mmXXX", + "yyyy-MM-dd'T'HH:mm:ssX", + "yyyy-MM-dd'T'HH:mm:ssXX", + "yyyy-MM-dd'T'HH:mm:ssXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + // 1.c) with generic time zone + "yyyy-MM-dd'T'HH:mm z", + "yyyy-MM-dd'T'HH:mm:ss z", + "yyyy-MM-dd'T'HH:mm:ss.SSS z", + // 2) date-time patterns separated by whitespace + // 2.a) without time zone + "yyyy-MM-dd HH:mm", + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + // 2.b) with ISO-8601 time zone + "yyyy-MM-dd HH:mmX", + "yyyy-MM-dd HH:mmXX", + "yyyy-MM-dd HH:mmXXX", + "yyyy-MM-dd HH:mm:ssX", + "yyyy-MM-dd HH:mm:ssXX", + "yyyy-MM-dd HH:mm:ssXXX", + "yyyy-MM-dd HH:mm:ss.SSSX", + "yyyy-MM-dd HH:mm:ss.SSSXX", + "yyyy-MM-dd HH:mm:ss.SSSXXX", + // 2.c) with generic time zone + "yyyy-MM-dd HH:mm z", + "yyyy-MM-dd HH:mm:ss z", + "yyyy-MM-dd HH:mm:ss.SSS z", + // 3) date patterns without time + // 3.a) without time zone + "yyyy-MM-dd", + // 3.b) with ISO-8601 time zone + "yyyy-MM-ddX", + "yyyy-MM-ddXX", + "yyyy-MM-ddXXX", + // 3.c) with generic time zone + "yyyy-MM-dd z" + }; + + private final FastThreadLocal parser; - private static final DateTimeFormatter FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxxx").withZone(ZoneOffset.UTC); + private final FastThreadLocal formatter; + + /** + * Creates a new {@code TimestampCodec} that uses the system's {@linkplain ZoneId#systemDefault() + * default time zone} to parse timestamp literals that do not include any time zone information. + */ + public TimestampCodec() { + this(ZoneId.systemDefault()); + } + + /** + * Creates a new {@code TimestampCodec} that uses the given {@link ZoneId} to parse timestamp + * literals that do not include any time zone information. + */ + public TimestampCodec(ZoneId defaultZoneId) { + parser = + new FastThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + SimpleDateFormat parser = new SimpleDateFormat(); + parser.setLenient(false); + parser.setTimeZone(TimeZone.getTimeZone(defaultZoneId)); + return parser; + } + }; + formatter = + new FastThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + parser.setTimeZone(TimeZone.getTimeZone(defaultZoneId)); + return parser; + } + }; + } @NonNull @Override @@ -106,7 +246,7 @@ public Instant decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion proto @NonNull @Override public String format(@Nullable Instant value) { - return (value == null) ? "NULL" : Strings.quote(FORMATTER.format(value)); + return (value == null) ? "NULL" : Strings.quote(formatter.get().format(Date.from(value))); } @Nullable @@ -115,21 +255,38 @@ public Instant parse(@Nullable String value) { if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) { return null; } - // strip enclosing single quotes, if any - if (Strings.isQuoted(value)) { - value = Strings.unquote(value); - } - if (Strings.isLongLiteral(value)) { + String unquoted = Strings.unquote(value); + if (Strings.isLongLiteral(unquoted)) { + // Numeric literals may be quoted or not try { - return Instant.ofEpochMilli(parseLong(value)); + return Instant.ofEpochMilli(Long.parseLong(unquoted)); } catch (NumberFormatException e) { throw new IllegalArgumentException( String.format("Cannot parse timestamp value from \"%s\"", value)); } - } - try { - return Instant.from(PARSER.parse(value)); - } catch (DateTimeParseException e) { + } else { + // Alphanumeric literals must be quoted + if (!Strings.isQuoted(value)) { + throw new IllegalArgumentException( + String.format("Alphanumeric timestamp literal must be quoted: \"%s\"", value)); + } + SimpleDateFormat parser = this.parser.get(); + TimeZone timeZone = parser.getTimeZone(); + ParsePosition pos = new ParsePosition(0); + for (String pattern : DATE_STRING_PATTERNS) { + parser.applyPattern(pattern); + pos.setIndex(0); + try { + Date date = parser.parse(unquoted, pos); + if (date != null && pos.getIndex() == unquoted.length()) { + return date.toInstant(); + } + } finally { + // restore the parser's default time zone, it might have been modified by the call to + // parse() + parser.setTimeZone(timeZone); + } + } throw new IllegalArgumentException( String.format("Cannot parse timestamp value from \"%s\"", value)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java index d158d01c491..31e94f9abc0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java @@ -15,16 +15,29 @@ */ package com.datastax.oss.driver.internal.core.type.codec; +import static java.time.ZoneOffset.ofHours; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import org.junit.Test; +import org.junit.runner.RunWith; +@RunWith(DataProviderRunner.class) public class TimestampCodecTest extends CodecTestBase { public TimestampCodecTest() { - this.codec = TypeCodecs.TIMESTAMP; + // force a given timezone for reproducible results in should_format + codec = new TimestampCodec(ZoneOffset.UTC); } @Test @@ -53,28 +66,109 @@ public void should_fail_to_decode_if_too_many_bytes() { @Test public void should_format() { - // No need to test various values because the codec delegates directly to the JDK's formatter, + // No need to test various values because the codec delegates directly to SimpleDateFormat, // which we assume does its job correctly. - assertThat(format(Instant.EPOCH)).isEqualTo("'1970-01-01T00:00:00.000+00:00'"); + assertThat(format(Instant.EPOCH)).isEqualTo("'1970-01-01T00:00:00.000Z'"); + assertThat(format(Instant.parse("2018-08-16T15:59:34.123Z"))) + .isEqualTo("'2018-08-16T15:59:34.123Z'"); assertThat(format(null)).isEqualTo("NULL"); } + @DataProvider + public static Iterable timeZones() { + return Lists.newArrayList( + ZoneId.systemDefault(), + ZoneOffset.UTC, + ZoneOffset.ofHoursMinutes(3, 30), + ZoneId.of("Europe/Paris"), + ZoneId.of("GMT+7")); + } + @Test - public void should_parse() { - // Raw number - assertThat(parse("'0'")).isEqualTo(Instant.EPOCH); + @UseDataProvider("timeZones") + public void should_parse(ZoneId defaultTimeZone) { + TimestampCodec codec = new TimestampCodec(defaultTimeZone); + + // Raw numbers + assertThat(codec.parse("'0'")).isEqualTo(Instant.EPOCH); + assertThat(codec.parse("'-1'")).isEqualTo(Instant.EPOCH.minusMillis(1)); + assertThat(codec.parse("1534463100000")).isEqualTo(Instant.ofEpochMilli(1534463100000L)); + + // Date formats + Instant expected; + + // date without time, without time zone + expected = LocalDate.parse("2017-01-01").atStartOfDay().atZone(defaultTimeZone).toInstant(); + assertThat(codec.parse("'2017-01-01'")).isEqualTo(expected); + + // date without time, with time zone + expected = LocalDate.parse("2018-08-16").atStartOfDay().atZone(ofHours(2)).toInstant(); + assertThat(codec.parse("'2018-08-16+02'")).isEqualTo(expected); + assertThat(codec.parse("'2018-08-16+0200'")).isEqualTo(expected); + assertThat(codec.parse("'2018-08-16+02:00'")).isEqualTo(expected); + assertThat(codec.parse("'2018-08-16 CEST'")).isEqualTo(expected); + + // date with time, without time zone + expected = LocalDateTime.parse("2018-08-16T23:45").atZone(defaultTimeZone).toInstant(); + assertThat(codec.parse("'2018-08-16T23:45'")).isEqualTo(expected); + assertThat(codec.parse("'2018-08-16 23:45'")).isEqualTo(expected); - // Date format - assertThat(parse("'1970-01-01T00:00Z'")).isEqualTo(Instant.EPOCH); + // date with time + seconds, without time zone + expected = LocalDateTime.parse("2019-12-31T16:08:38").atZone(defaultTimeZone).toInstant(); + assertThat(codec.parse("'2019-12-31T16:08:38'")).isEqualTo(expected); + assertThat(codec.parse("'2019-12-31 16:08:38'")).isEqualTo(expected); - assertThat(parse("NULL")).isNull(); - assertThat(parse("null")).isNull(); - assertThat(parse("")).isNull(); - assertThat(parse(null)).isNull(); + // date with time + seconds + milliseconds, without time zone + expected = LocalDateTime.parse("1950-02-28T12:00:59.230").atZone(defaultTimeZone).toInstant(); + assertThat(codec.parse("'1950-02-28T12:00:59.230'")).isEqualTo(expected); + assertThat(codec.parse("'1950-02-28 12:00:59.230'")).isEqualTo(expected); + + // date with time, with time zone + expected = ZonedDateTime.parse("1973-06-23T23:59:00.000+01:00").toInstant(); + assertThat(codec.parse("'1973-06-23T23:59+01'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23T23:59+0100'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23T23:59+01:00'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23T23:59 CET'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23 23:59+01'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23 23:59+0100'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23 23:59+01:00'")).isEqualTo(expected); + assertThat(codec.parse("'1973-06-23 23:59 CET'")).isEqualTo(expected); + + // date with time + seconds, with time zone + expected = ZonedDateTime.parse("1980-01-01T23:59:59.000-08:00").toInstant(); + assertThat(codec.parse("'1980-01-01T23:59:59-08'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01T23:59:59-0800'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01T23:59:59-08:00'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01T23:59:59 PST'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01 23:59:59-08'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01 23:59:59-0800'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01 23:59:59-08:00'")).isEqualTo(expected); + assertThat(codec.parse("'1980-01-01 23:59:59 PST'")).isEqualTo(expected); + + // date with time + seconds + milliseconds, with time zone + expected = ZonedDateTime.parse("1999-12-31T23:59:59.999+00:00").toInstant(); + assertThat(codec.parse("'1999-12-31T23:59:59.999+00'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31T23:59:59.999+0000'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31T23:59:59.999+00:00'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31T23:59:59.999 UTC'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31 23:59:59.999+00'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31 23:59:59.999+0000'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31 23:59:59.999+00:00'")).isEqualTo(expected); + assertThat(codec.parse("'1999-12-31 23:59:59.999 UTC'")).isEqualTo(expected); + + assertThat(codec.parse("NULL")).isNull(); + assertThat(codec.parse("null")).isNull(); + assertThat(codec.parse("")).isNull(); + assertThat(codec.parse(null)).isNull(); } - @Test(expected = IllegalArgumentException.class) + @Test public void should_fail_to_parse_invalid_input() { - parse("not a timestamp"); + assertThatThrownBy(() -> parse("not a timestamp")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Alphanumeric timestamp literal must be quoted: \"not a timestamp\""); + assertThatThrownBy(() -> parse("'not a timestamp'")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse timestamp value from \"'not a timestamp'\""); } } From f32feeb6e75ee63831779d66ae5be6db517cd05d Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Thu, 19 Jul 2018 10:19:49 +0200 Subject: [PATCH 558/742] JAVA-1762: Improve shading and OSGi manifest generation --- core-shaded/pom.xml | 258 ++++++++++++++---- core-shaded/src/assembly/shaded-jar.xml | 44 +++ core/pom.xml | 124 ++------- .../com/datastax/oss/driver/Driver.properties | 2 +- integration-tests/pom.xml | 6 +- .../oss/driver/osgi/BundleOptions.java | 4 + .../datastax/oss/driver/osgi/OsgiBaseIT.java | 26 +- .../osgi/OsgiCustomLoadBalancingPolicyIT.java | 15 +- .../com/datastax/oss/driver/osgi/OsgiIT.java | 17 +- .../datastax/oss/driver/osgi/OsgiLz4IT.java | 12 +- .../oss/driver/osgi/OsgiShadedIT.java | 13 +- .../oss/driver/osgi/OsgiSnappyIT.java | 12 +- .../src/test/resources/logback-test.xml | 2 + pom.xml | 9 +- test-infra/pom.xml | 10 +- 15 files changed, 340 insertions(+), 214 deletions(-) create mode 100644 core-shaded/src/assembly/shaded-jar.xml diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index abc941c101a..aa9ee6188ea 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -16,7 +16,9 @@ limitations under the License. --> - + 4.0.0 @@ -26,27 +28,134 @@ java-driver-core-shaded - bundle - DataStax Java driver for Apache Cassandra(R) - core with netty shaded + DataStax Java driver for Apache Cassandra(R) - core with shaded deps + com.datastax.oss java-driver-core ${project.version} + + + com.datastax.oss + native-protocol + + + com.datastax.oss + java-driver-shaded-guava + + + com.typesafe + config + + + com.github.jnr + jnr-ffi + + + com.github.jnr + jnr-posix + + + org.xerial.snappy + snappy-java + true + + + org.lz4 + lz4-java + true + + + org.slf4j + slf4j-api + + + io.dropwizard.metrics + metrics-core + + + org.hdrhistogram + HdrHistogram + + + com.github.stephenc.jcip + jcip-annotations + true + + + com.github.spotbugs + spotbugs-annotations + true + + + - + + maven-shade-plugin + + + shade-core-dependencies + package + + shade + + + true + true + + + + com.datastax.oss:java-driver-core + io.netty:* + org.jctools:* + + + + + io.netty + com.datastax.oss.driver.shaded.netty + + + org.jctools + com.datastax.oss.driver.shaded.jctools + + + + + + maven-dependency-plugin - unpack-manifest - prepare-package + unpack-shaded-classes + package unpack @@ -54,69 +163,108 @@ com.datastax.oss - java-driver-core + java-driver-core-shaded ${project.version} - META-INF-shaded/MANIFEST.MF - ${project.build.directory} + jar + ${project.build.outputDirectory} + + + META-INF/maven/com.datastax.oss/java-driver-core/**, + META-INF/maven/io.netty/**, + META-INF/maven/org.jctools/** + - maven-shade-plugin + org.apache.felix + maven-bundle-plugin + true + generate-shaded-manifest package - shade + manifest - - - com.datastax.oss:java-driver-core - io.netty:* - - - - - io.netty - com.datastax.oss.driver.shaded.netty - - - - - - META-INF/MANIFEST.MF - META-INF-shaded/MANIFEST.MF - META-INF/maven/com.datastax.oss/java-driver-core/pom.xml - META-INF/maven/com.datastax.oss/java-driver-core/pom.properties - META-INF/maven/io.netty/netty-handler/pom.properties - META-INF/maven/io.netty/netty-handler/pom.xml - META-INF/maven/io.netty/netty-buffer/pom.properties - META-INF/maven/io.netty/netty-buffer/pom.xml - META-INF/maven/io.netty/netty-common/pom.properties - META-INF/maven/io.netty/netty-common/pom.xml - META-INF/maven/io.netty/netty-transport/pom.properties - META-INF/maven/io.netty/netty-transport/pom.xml - META-INF/maven/io.netty/netty-resolver/pom.properties - META-INF/maven/io.netty/netty-resolver/pom.xml - META-INF/maven/io.netty/netty-codec/pom.properties - META-INF/maven/io.netty/netty-codec/pom.xml - - META-INF/maven/org.jctools/jctools-core/pom.properties - META-INF/maven/org.jctools/jctools-core/pom.xml - - - - - META-INF/MANIFEST.MF - ${project.build.directory}/META-INF-shaded/MANIFEST.MF - - - - true + + com.datastax.oss.driver.core + + * + + + !com.datastax.oss.driver.shaded.netty.*, + !com.datastax.oss.driver.shaded.jctools.*, + !jnr.*, + !net.jcip.annotations.*, + !edu.umd.cs.findbugs.annotations.*, + !com.google.protobuf.*, + !com.jcraft.jzlib.*, + !com.ning.compress.*, + !lzma.sdk.*, + !net.jpountz.xxhash.*, + !org.bouncycastle.*, + !org.conscrypt.*, + !org.apache.commons.logging.*, + !org.apache.log4j.*, + !org.apache.logging.log4j.*, + !org.eclipse.jetty.*, + !org.jboss.marshalling.*, + !sun.misc.*, + !sun.security.*, + * + + + + com.datastax.oss.driver.api.core.*, + com.datastax.oss.driver.internal.core.*, + com.datastax.oss.driver.shaded.netty.*, + com.datastax.oss.driver.shaded.jctools.* + + + true + + + + + + maven-assembly-plugin + + + generate-final-shaded-jar + package + + single + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + src/assembly/shaded-jar.xml + + + false diff --git a/core-shaded/src/assembly/shaded-jar.xml b/core-shaded/src/assembly/shaded-jar.xml new file mode 100644 index 00000000000..3a735f36d2a --- /dev/null +++ b/core-shaded/src/assembly/shaded-jar.xml @@ -0,0 +1,44 @@ + + + shaded-jar + + jar + + false + + + + ${project.build.outputDirectory} + + + + + + + ${project.basedir}/dependency-reduced-pom.xml + META-INF/maven/com.datastax.oss/java-driver-core-shaded + pom.xml + + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index b1844e6ce82..beb85a7120f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 @@ -25,6 +27,7 @@ java-driver-core + bundle DataStax Java driver for Apache Cassandra(R) - core @@ -143,22 +146,12 @@ maven-jar-plugin - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - test-jar test-jar - - - logback-test.xml - - @@ -173,116 +166,39 @@ - org.apache.felix maven-bundle-plugin - - - com.datastax.oss.driver.core - - - - true - - + true - bundle-manifest - process-classes - manifest + bundle - ${project.build.outputDirectory}/META-INF - + com.datastax.oss.driver.core + * - + - !net.jcip.annotations,!jnr.*,!org.jctools.*,sun.misc;resolution:=optional,* + !net.jcip.annotations.*, + !edu.umd.cs.findbugs.annotations.*, + !jnr.*, + * - - com.datastax.oss.driver.*.core.*, - com.datastax.oss.driver.shaded.jctools.util;version="${project.version}";uses:="sun.misc", - com.datastax.oss.driver.shaded.jctools.queues;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues.spec", - com.datastax.oss.driver.shaded.jctools.queues.spec;version="${project.version}", - com.datastax.oss.driver.shaded.jctools.queues.atomic;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues,com.datastax.oss.driver.shaded.jctools.queues.spec", - com.datastax.oss.driver.shaded.jctools.maps;version="${project.version}" + com.datastax.oss.driver.*.core.* - - - bundle-manifest-shaded - process-classes - - manifest - - - ${project.build.directory}/classes/META-INF-shaded - - DataStax Java driver for Apache Cassandra(R) - core netty shaded - * - - - !net.jcip.annotations,!jnr.*,!org.jctools.*,sun.misc;resolution:=optional,!io.netty.*,javax.security.cert,* - - - com.datastax.oss.driver.*.core.*, - com.datastax.oss.driver.shaded.jctools.util;version="${project.version}";uses:="sun.misc", - com.datastax.oss.driver.shaded.jctools.queues;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues.spec", - com.datastax.oss.driver.shaded.jctools.queues.spec;version="${project.version}", - com.datastax.oss.driver.shaded.jctools.queues.atomic;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues,com.datastax.oss.driver.shaded.jctools.queues.spec", - com.datastax.oss.driver.shaded.jctools.maps;version="${project.version}" - - - - - - - - - maven-shade-plugin - - - package - - shade - - - - - org.jctools:jctools-core - - - - - org.jctools - com.datastax.oss.driver.shaded.jctools - - - - - - META-INF/maven/org.jctools/jctools-core/pom.properties - META-INF/maven/org.jctools/jctools-core/pom.xml - - - - true - - diff --git a/core/src/main/resources/com/datastax/oss/driver/Driver.properties b/core/src/main/resources/com/datastax/oss/driver/Driver.properties index 8e2dd8251b1..58725b68008 100644 --- a/core/src/main/resources/com/datastax/oss/driver/Driver.properties +++ b/core/src/main/resources/com/datastax/oss/driver/Driver.properties @@ -20,4 +20,4 @@ driver.groupId=${project.groupId} driver.artifactId=${project.artifactId} driver.version=${project.version} -driver.name=${project.parent.name} \ No newline at end of file +driver.name=DataStax Java driver for Apache Cassandra(R) diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 88426bb92c9..af39ea24754 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + 4.0.0 @@ -132,6 +134,7 @@ ${project.version} ${assertj.version} ${config.version} + ${jctools.version} ${commons-exec.version} ${guava.version} ${hdrhistogram.version} @@ -156,6 +159,7 @@ ${project.version} ${assertj.version} ${config.version} + ${jctools.version} ${commons-exec.version} ${guava.version} ${hdrhistogram.version} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java index 73490abf1ea..05f35d803ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java @@ -29,6 +29,9 @@ public class BundleOptions { public static CompositeOption baseOptions() { + // Note: the bundles below include Netty and JCTools; these bundles are not required by + // the shaded core driver bundle, but they need to be present in all cases because + // the test-infra bundle requires the (non-shaded) Netty bundle. return () -> options( nettyBundles(), @@ -38,6 +41,7 @@ public static CompositeOption baseOptions() { mavenBundle("org.slf4j", "slf4j-api", getVersion("slf4j.version")), mavenBundle("org.hdrhistogram", "HdrHistogram", getVersion("hdrhistogram.version")), mavenBundle("com.typesafe", "config", getVersion("config.version")), + mavenBundle("org.jctools", "jctools-core", getVersion("jctools.version")), mavenBundle( "com.datastax.oss", "native-protocol", getVersion("native-protocol.version")), logbackBundles(), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java index 32884e4ca0a..a9a7263373b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java @@ -16,11 +16,7 @@ package com.datastax.oss.driver.osgi; import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; -import static com.datastax.oss.driver.osgi.BundleOptions.baseOptions; -import static com.datastax.oss.driver.osgi.BundleOptions.driverCoreBundle; -import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; import static org.assertj.core.api.Assertions.assertThat; -import static org.ops4j.pax.exam.CoreOptions.options; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; @@ -29,15 +25,11 @@ import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; -import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; -import com.google.common.collect.ObjectArrays; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; -import org.ops4j.pax.exam.Configuration; -import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; import org.ops4j.pax.exam.spi.reactors.PerClass; @@ -54,31 +46,17 @@ public abstract class OsgiBaseIT { @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(1).build(); - /** @return Additional options that should be used in OSGi environment configuration. */ - public abstract Option[] additionalOptions(); - - @Configuration - public Option[] config() { - return ObjectArrays.concat( - options(driverCoreBundle(), driverQueryBuilderBundle(), baseOptions()), - additionalOptions(), - Option.class); - } - /** @return config loader to be used to create session. */ - public DriverConfigLoader configLoader() { - return SessionUtils.configLoaderBuilder().build(); - } + protected abstract DriverConfigLoader configLoader(); /** * A very simple test that ensures a session can be established and a query made when running in * an OSGi container. */ @Test - @SuppressWarnings("unchecked") public void should_connect_and_query() { SessionBuilder builder = - SessionUtils.baseBuilder() + CqlSession.builder() .addContactPoints(ccmRule.getContactPoints()) // use the driver's ClassLoader instead of the OSGI application thread's. .withClassLoader(CqlSession.class.getClassLoader()) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java index b151c21ad58..3bf3c948ab8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiCustomLoadBalancingPolicyIT.java @@ -15,10 +15,16 @@ */ package com.datastax.oss.driver.osgi; +import static com.datastax.oss.driver.osgi.BundleOptions.baseOptions; +import static com.datastax.oss.driver.osgi.BundleOptions.driverCoreBundle; +import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; +import static org.ops4j.pax.exam.CoreOptions.options; + import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; /** @@ -27,13 +33,14 @@ * DynamicImport-Package: *. */ public class OsgiCustomLoadBalancingPolicyIT extends OsgiBaseIT { - @Override - public Option[] additionalOptions() { - return new Option[0]; + + @Configuration + public Option[] config() { + return options(driverCoreBundle(), driverQueryBuilderBundle(), baseOptions()); } @Override - public DriverConfigLoader configLoader() { + protected DriverConfigLoader configLoader() { return SessionUtils.configLoaderBuilder() .withClass( DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, SortingLoadBalancingPolicy.class) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java index 7c59471df13..cfebc0d0a14 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiIT.java @@ -15,12 +15,25 @@ */ package com.datastax.oss.driver.osgi; +import static com.datastax.oss.driver.osgi.BundleOptions.baseOptions; +import static com.datastax.oss.driver.osgi.BundleOptions.driverCoreBundle; +import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; +import static org.ops4j.pax.exam.CoreOptions.options; + +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; public class OsgiIT extends OsgiBaseIT { + @Configuration + public Option[] config() { + return options(driverCoreBundle(), driverQueryBuilderBundle(), baseOptions()); + } + @Override - public Option[] additionalOptions() { - return new Option[0]; + protected DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder().build(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java index 4b2c74aebdf..e42b86d86d0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiLz4IT.java @@ -15,23 +15,27 @@ */ package com.datastax.oss.driver.osgi; +import static com.datastax.oss.driver.osgi.BundleOptions.baseOptions; +import static com.datastax.oss.driver.osgi.BundleOptions.driverCoreBundle; +import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; import static com.datastax.oss.driver.osgi.BundleOptions.lz4Bundle; import static org.ops4j.pax.exam.CoreOptions.options; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; public class OsgiLz4IT extends OsgiBaseIT { - @Override - public Option[] additionalOptions() { - return options(lz4Bundle()); + @Configuration + public Option[] config() { + return options(lz4Bundle(), driverCoreBundle(), driverQueryBuilderBundle(), baseOptions()); } @Override - public DriverConfigLoader configLoader() { + protected DriverConfigLoader configLoader() { return SessionUtils.configLoaderBuilder() .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, "lz4") .build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java index 9f069197706..54131d05b7c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiShadedIT.java @@ -20,23 +20,20 @@ import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; import static org.ops4j.pax.exam.CoreOptions.options; -import com.google.common.collect.ObjectArrays; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; public class OsgiShadedIT extends OsgiBaseIT { - @Override @Configuration public Option[] config() { - return ObjectArrays.concat( - options(driverCoreShadedBundle(), driverQueryBuilderBundle(), baseOptions()), - additionalOptions(), - Option.class); + return options(driverCoreShadedBundle(), driverQueryBuilderBundle(), baseOptions()); } @Override - public Option[] additionalOptions() { - return new Option[0]; + protected DriverConfigLoader configLoader() { + return SessionUtils.configLoaderBuilder().build(); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java index fcf56cfe5f3..9745e4df280 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiSnappyIT.java @@ -15,23 +15,27 @@ */ package com.datastax.oss.driver.osgi; +import static com.datastax.oss.driver.osgi.BundleOptions.baseOptions; +import static com.datastax.oss.driver.osgi.BundleOptions.driverCoreBundle; +import static com.datastax.oss.driver.osgi.BundleOptions.driverQueryBuilderBundle; import static com.datastax.oss.driver.osgi.BundleOptions.snappyBundle; import static org.ops4j.pax.exam.CoreOptions.options; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; public class OsgiSnappyIT extends OsgiBaseIT { - @Override - public Option[] additionalOptions() { - return options(snappyBundle()); + @Configuration + public Option[] config() { + return options(snappyBundle(), driverCoreBundle(), driverQueryBuilderBundle(), baseOptions()); } @Override - public DriverConfigLoader configLoader() { + protected DriverConfigLoader configLoader() { return SessionUtils.configLoaderBuilder() .withString(DefaultDriverOption.PROTOCOL_COMPRESSION, "snappy") .build(); diff --git a/integration-tests/src/test/resources/logback-test.xml b/integration-tests/src/test/resources/logback-test.xml index b74e0b63050..63a78b7735d 100644 --- a/integration-tests/src/test/resources/logback-test.xml +++ b/integration-tests/src/test/resources/logback-test.xml @@ -25,4 +25,6 @@ + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e372362ed18..ec89a46e88d 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ 1.3.3 25.1-jre 2.1.10 + 2.1.2 4.0.2 1.4.3 4.1.27.Final @@ -137,7 +138,7 @@ org.jctools jctools-core - 2.1.2 + ${jctools.version} com.github.stephenc.jcip @@ -197,7 +198,7 @@ org.apache.felix org.apache.felix.framework - 5.6.10 + 6.0.0 @@ -229,7 +230,7 @@ maven-shade-plugin - 3.0.0 + 3.1.1 maven-assembly-plugin @@ -289,7 +290,7 @@ org.apache.felix maven-bundle-plugin - 3.5.0 + 3.5.1 org.revapi diff --git a/test-infra/pom.xml b/test-infra/pom.xml index ffefd68fa98..8e261149965 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -66,9 +66,13 @@ com.datastax.oss.driver.testinfra - - com.datastax.oss.driver.*.testinfra.*,com.datastax.oss.driver.assertions,com.datastax.oss.driver.categories + + * + + com.datastax.oss.driver.*.testinfra.*, + com.datastax.oss.driver.assertions, + com.datastax.oss.driver.categories + From 14186af07a10ea1d9c1951f8bddd42ac00903493 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 21 Aug 2018 15:29:16 +0200 Subject: [PATCH 559/742] Remove the dependency to JCTools Motivation: JCTools is only used in DefaultWriteCoalescer. Our benchmarks did not show any performance improvement for MpscLinkedAtomicQueue versus ConcurrentLinkedQueue. Given that the WriteCoalescer is pluggable, users are still able to use JCTools in a custom WriteCoalescer implementation if they want to. Modifications: Remove all references to JCTools from Maven dependencies and OSGi directives. Result: No more dependency to JCTools in the OSS driver. --- core-shaded/pom.xml | 11 +---------- core/pom.xml | 4 ---- .../internal/core/channel/DefaultWriteCoalescer.java | 9 ++++----- integration-tests/pom.xml | 2 -- .../com/datastax/oss/driver/osgi/BundleOptions.java | 3 +-- pom.xml | 8 +------- 6 files changed, 7 insertions(+), 30 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index aa9ee6188ea..394084b187f 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -128,12 +128,10 @@ Include: - The core driver itself; it is not relocated but needs to be included. - All the dependencies we want to shade & relocate: currently - - all the Netty artifacts; - - JCTools. + - all the Netty artifacts. --> com.datastax.oss:java-driver-core io.netty:* - org.jctools:* @@ -141,10 +139,6 @@ io.netty com.datastax.oss.driver.shaded.netty - - org.jctools - com.datastax.oss.driver.shaded.jctools - @@ -176,7 +170,6 @@ META-INF/maven/com.datastax.oss/java-driver-core/**, META-INF/maven/io.netty/**, - META-INF/maven/org.jctools/** @@ -209,7 +202,6 @@ --> !com.datastax.oss.driver.shaded.netty.*, - !com.datastax.oss.driver.shaded.jctools.*, !jnr.*, !net.jcip.annotations.*, !edu.umd.cs.findbugs.annotations.*, @@ -238,7 +230,6 @@ com.datastax.oss.driver.api.core.*, com.datastax.oss.driver.internal.core.*, com.datastax.oss.driver.shaded.netty.*, - com.datastax.oss.driver.shaded.jctools.* true diff --git a/core/pom.xml b/core/pom.xml index beb85a7120f..9306e3359bc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -77,10 +77,6 @@ org.slf4j slf4j-api - - org.jctools - jctools-core - io.dropwizard.metrics metrics-core diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java index ecf8b9a6d5b..29bf2822617 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DefaultWriteCoalescer.java @@ -26,11 +26,11 @@ import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import net.jcip.annotations.ThreadSafe; -import org.jctools.queues.atomic.MpscLinkedAtomicQueue; /** * Default write coalescing strategy. @@ -55,9 +55,8 @@ public class DefaultWriteCoalescer implements WriteCoalescer { public DefaultWriteCoalescer(DriverContext context) { DriverExecutionProfile config = context.getConfig().getDefaultProfile(); - this.maxRunsWithNoWork = config.getInt(DefaultDriverOption.COALESCER_MAX_RUNS); - this.rescheduleIntervalNanos = - config.getDuration(DefaultDriverOption.COALESCER_INTERVAL).toNanos(); + maxRunsWithNoWork = config.getInt(DefaultDriverOption.COALESCER_MAX_RUNS); + rescheduleIntervalNanos = config.getDuration(DefaultDriverOption.COALESCER_INTERVAL).toNanos(); } @Override @@ -77,7 +76,7 @@ private class Flusher { private final EventLoop eventLoop; // These variables are accessed both from client threads and the event loop - private final Queue writes = new MpscLinkedAtomicQueue<>(); + private final Queue writes = new ConcurrentLinkedQueue<>(); private final AtomicBoolean running = new AtomicBoolean(); // These variables are accessed only from runOnEventLoop, they don't need to be thread-safe diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index af39ea24754..ca0f7c609b4 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -134,7 +134,6 @@ ${project.version} ${assertj.version} ${config.version} - ${jctools.version} ${commons-exec.version} ${guava.version} ${hdrhistogram.version} @@ -159,7 +158,6 @@ ${project.version} ${assertj.version} ${config.version} - ${jctools.version} ${commons-exec.version} ${guava.version} ${hdrhistogram.version} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java index 05f35d803ec..1a259d04466 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/BundleOptions.java @@ -29,7 +29,7 @@ public class BundleOptions { public static CompositeOption baseOptions() { - // Note: the bundles below include Netty and JCTools; these bundles are not required by + // Note: the bundles below include Netty; these bundles are not required by // the shaded core driver bundle, but they need to be present in all cases because // the test-infra bundle requires the (non-shaded) Netty bundle. return () -> @@ -41,7 +41,6 @@ public static CompositeOption baseOptions() { mavenBundle("org.slf4j", "slf4j-api", getVersion("slf4j.version")), mavenBundle("org.hdrhistogram", "HdrHistogram", getVersion("hdrhistogram.version")), mavenBundle("com.typesafe", "config", getVersion("config.version")), - mavenBundle("org.jctools", "jctools-core", getVersion("jctools.version")), mavenBundle( "com.datastax.oss", "native-protocol", getVersion("native-protocol.version")), logbackBundles(), diff --git a/pom.xml b/pom.xml index ec89a46e88d..7ab311a3d19 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,6 @@ 1.3.3 25.1-jre 2.1.10 - 2.1.2 4.0.2 1.4.3 4.1.27.Final @@ -135,11 +134,6 @@ HdrHistogram ${hdrhistogram.version} - - org.jctools - jctools-core - ${jctools.version} - com.github.stephenc.jcip jcip-annotations @@ -483,7 +477,7 @@ limitations under the License.]]> -preventleak com.datastax.oss.driver.internal - + -preventleak com.datastax.oss.driver.shaded From 1a646b73f3cab775a975b594f8d58a3f6fe3eaef Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 28 Aug 2018 16:25:58 -0700 Subject: [PATCH 566/742] Fix Revapi configuration issues - change exclusion pattern to also exclude root package - exclude native-protocol and Simulacron --- core/revapi.json | 5 +++-- query-builder/revapi.json | 7 ++++--- test-infra/revapi.json | 8 +++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 290004aaf83..db1d5ea5dca 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -7,8 +7,9 @@ "packages": { "regex": true, "exclude": [ - "com\\.datastax\\.oss\\.driver\\.internal\\..*", - "com\\.datastax\\.oss\\.driver\\.shaded\\..*" + "com\\.datastax\\.oss\\.protocol\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?" ] } } diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 7c69cf2450f..520787525f7 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -7,10 +7,11 @@ "packages": { "regex": true, "exclude": [ - "com\\.datastax\\.oss\\.driver\\.internal\\..*", - "com\\.datastax\\.oss\\.driver\\.shaded\\..*", + "com\\.datastax\\.oss\\.protocol\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", // Don't re-check sibling modules that this module depends on - "com\\.datastax\\.oss\\.driver\\.api\\.core\\..*" + "com\\.datastax\\.oss\\.driver\\.api\\.core(\\..+)?" ] } } diff --git a/test-infra/revapi.json b/test-infra/revapi.json index f1da1aa19e4..a4dc5b63664 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -7,10 +7,12 @@ "packages": { "regex": true, "exclude": [ - "com\\.datastax\\.oss\\.driver\\.internal\\..*", - "com\\.datastax\\.oss\\.driver\\.shaded\\..*", + "com\\.datastax\\.oss\\.protocol\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", + "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", + "com\\.datastax\\.oss\\.simulacron(\\..+)?", // Don't re-check sibling modules that this module depends on - "com\\.datastax\\.oss\\.driver\\.api\\.core\\..*" + "com\\.datastax\\.oss\\.driver\\.api\\.core(\\..+)?" ] } } From 575992a00e599b298e6872908a826f21b2906ab1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 31 Aug 2018 11:02:38 -0700 Subject: [PATCH 567/742] Include SERVER_ERROR in downgrade criteria from peers_v2 to peers --- .../core/metadata/DefaultTopologyMonitor.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 7a7ac6633c9..1b4112002ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -29,8 +29,8 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -170,15 +170,16 @@ public CompletionStage> refreshNodeList() { peersV2Query.whenComplete( (r, t) -> { if (t != null) { - if (t instanceof UnexpectedResponseException) { - UnexpectedResponseException e = (UnexpectedResponseException) t; - Message message = e.message; - if (message instanceof com.datastax.oss.protocol.internal.response.Error - && ((com.datastax.oss.protocol.internal.response.Error) message).code - == ProtocolConstants.ErrorCode.INVALID) { - // The query to system.peers_v2 failed, we should not attempt this query in the - // future. - this.isSchemaV2 = false; + // If system.peers_v2 does not exist, downgrade to system.peers + if (t instanceof UnexpectedResponseException + && ((UnexpectedResponseException) t).message instanceof Error) { + Error error = (Error) ((UnexpectedResponseException) t).message; + if (error.code == ProtocolConstants.ErrorCode.INVALID + // Also downgrade on server error with a specific error message (DSE 6.0.0 to + // 6.0.2 with search enabled) + || (error.code == ProtocolConstants.ErrorCode.SERVER_ERROR + && error.message.contains("Unknown keyspace/cf pair (system.peers_v2)"))) { + this.isSchemaV2 = false; // We should not attempt this query in the future. CompletableFutures.completeFrom( query(channel, "SELECT * FROM system.peers"), peersQuery); return; From 791656aa560d31d64155fd31eb8b35d715fdf7cd Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Fri, 24 Aug 2018 14:25:42 -0500 Subject: [PATCH 568/742] JAVA-1932: Send DRIVER_NAME and DRIVER_VERSION in Startup message --- changelog/README.md | 1 + .../core/channel/ProtocolInitHandler.java | 2 +- .../core/context/DefaultDriverContext.java | 17 +++ .../core/context/InternalDriverContext.java | 9 ++ .../core/context/StartupOptionsBuilder.java | 80 +++++++++++++ .../core/channel/ProtocolInitHandlerTest.java | 37 ------ .../context/StartupOptionsBuilderTest.java | 107 ++++++++++++++++++ 7 files changed, 215 insertions(+), 38 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java diff --git a/changelog/README.md b/changelog/README.md index 9c8ebb09c7f..1c09a4c0ca7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [new feature] JAVA-1932: Send Driver Name and Version in Startup message - [new feature] JAVA-1917: Add ability to set node on statement - [improvement] JAVA-1916: Base TimestampCodec.parse on java.util.Date. - [improvement] JAVA-1940: Clean up test resources when CCM integration tests finish diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index eda34429267..240e474709c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -142,7 +142,7 @@ String describe() { Message getRequest() { switch (step) { case STARTUP: - return new Startup(context.getCompressor().algorithm()); + return new Startup(context.getStartupOptions()); case GET_CLUSTER_NAME: return CLUSTER_NAME_QUERY; case SET_KEYSPACE: diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 89424a6f568..86f4fecd199 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -178,6 +178,8 @@ public class DefaultDriverContext implements InternalDriverContext { new LazyReference<>("metricsFactory", this::buildMetricsFactory, cycleDetector); private final LazyReference requestThrottlerRef = new LazyReference<>("requestThrottler", this::buildRequestThrottler, cycleDetector); + private final LazyReference> startupOptionsRef = + new LazyReference<>("startupOptions", this::buildStartupOptions, cycleDetector); private final LazyReference nodeStateListenerRef; private final LazyReference schemaChangeListenerRef; private final LazyReference requestTrackerRef; @@ -230,6 +232,15 @@ public DefaultDriverContext( this.classLoader = classLoader; } + /** + * Builds a map of options to send in a Startup message. + * + * @see #getStartupOptions() + */ + protected Map buildStartupOptions() { + return new StartupOptionsBuilder(this).build(); + } + protected Map buildLoadBalancingPolicies() { return Reflection.buildFromConfigProfiles( this, @@ -731,4 +742,10 @@ public CodecRegistry getCodecRegistry() { public ProtocolVersion getProtocolVersion() { return getChannelFactory().getProtocolVersion(); } + + @NonNull + @Override + public Map getStartupOptions() { + return startupOptionsRef.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index feae0df50b5..ee926a253fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -41,6 +41,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.buffer.ByteBuf; +import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -128,4 +129,12 @@ public interface InternalDriverContext extends DriverContext { */ @Nullable ClassLoader getClassLoader(); + + /** + * Retrieves the map of options to send in a Startup message. The returned map will be used to + * construct a {@link com.datastax.oss.protocol.internal.request.Startup} instance when + * initializing the native protocol handshake. + */ + @NonNull + Map getStartupOptions(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.java new file mode 100644 index 00000000000..49718b7df97 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.java @@ -0,0 +1,80 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.context; + +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.protocol.internal.request.Startup; +import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; +import java.util.Map; +import net.jcip.annotations.Immutable; + +@Immutable +public class StartupOptionsBuilder { + + public static final String DRIVER_NAME_KEY = "DRIVER_NAME"; + public static final String DRIVER_VERSION_KEY = "DRIVER_VERSION"; + + protected final InternalDriverContext context; + + public StartupOptionsBuilder(InternalDriverContext context) { + this.context = context; + } + + /** + * Builds a map of options to send in a Startup message. + * + *

              The default set of options are built here and include {@link + * com.datastax.oss.protocol.internal.request.Startup#COMPRESSION_KEY} (if the context passed in + * has a compressor/algorithm set), and the driver's {@link #DRIVER_NAME_KEY} and {@link + * #DRIVER_VERSION_KEY}. The {@link com.datastax.oss.protocol.internal.request.Startup} + * constructor will add {@link + * com.datastax.oss.protocol.internal.request.Startup#CQL_VERSION_KEY}. + * + * @return Map of Startup Options. + */ + public Map build() { + NullAllowingImmutableMap.Builder builder = NullAllowingImmutableMap.builder(3); + // add compression (if configured) and driver name and version + String compressionAlgorithm = context.getCompressor().algorithm(); + if (compressionAlgorithm != null && !compressionAlgorithm.trim().isEmpty()) { + builder.put(Startup.COMPRESSION_KEY, compressionAlgorithm.trim()); + } + return builder + .put(DRIVER_NAME_KEY, getDriverName()) + .put(DRIVER_VERSION_KEY, getDriverVersion()) + .build(); + } + + /** + * Returns this driver's name. + * + *

              By default, this method will pull from the bundled Driver.properties file. Subclasses should + * override this method if they need to report a different Driver name on Startup. + */ + protected String getDriverName() { + return Session.OSS_DRIVER_COORDINATES.getName(); + } + + /** + * Returns this driver's version. + * + *

              By default, this method will pull from the bundled Driver.properties file. Subclasses should + * override this method if they need to report a different Driver version on Startup. + */ + protected String getDriverVersion() { + return Session.OSS_DRIVER_COORDINATES.getVersion().toString(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 7c334180177..d1618dcb4be 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -30,7 +30,6 @@ import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.AuthResponse; @@ -44,7 +43,6 @@ import com.datastax.oss.protocol.internal.response.Ready; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import com.datastax.oss.protocol.internal.util.Bytes; -import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; import java.time.Duration; @@ -64,7 +62,6 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @Mock private DriverExecutionProfile defaultProfile; - @Mock private Compressor compressor; private ProtocolVersionRegistry protocolVersionRegistry = new CassandraProtocolVersionRegistry("test"); @@ -83,8 +80,6 @@ public void setup() { .thenReturn(Duration.ofSeconds(30)); Mockito.when(internalDriverContext.getProtocolVersionRegistry()) .thenReturn(protocolVersionRegistry); - Mockito.when(internalDriverContext.getCompressor()).thenReturn(compressor); - Mockito.when(compressor.algorithm()).thenReturn(null); channel .pipeline() @@ -120,8 +115,6 @@ public void should_initialize() { // It should send a STARTUP message Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.message).isInstanceOf(Startup.class); - Startup startup = (Startup) requestFrame.message; - assertThat(startup.options).doesNotContainKey("COMPRESSION"); assertThat(connectFuture).isNotDone(); // Simulate a READY response @@ -136,34 +129,6 @@ public void should_initialize() { assertThat(connectFuture).isSuccess(); } - @Test - public void should_initialize_with_compression() { - Mockito.when(compressor.algorithm()).thenReturn("lz4"); - channel - .pipeline() - .addLast( - "init", - new ProtocolInitHandler( - internalDriverContext, - DefaultProtocolVersion.V4, - null, - DriverChannelOptions.DEFAULT, - heartbeatHandler)); - - ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); - - Frame requestFrame = readOutboundFrame(); - assertThat(requestFrame.message).isInstanceOf(Startup.class); - Startup startup = (Startup) requestFrame.message; - - // STARTUP message should request compression - assertThat(startup.options).containsEntry("COMPRESSION", "lz4"); - - writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); - writeInboundFrame(readOutboundFrame(), TestResponses.clusterNameResponse("someClusterName")); - assertThat(connectFuture).isSuccess(); - } - @Test public void should_add_heartbeat_handler_to_pipeline_on_success() { ProtocolInitHandler protocolInitHandler = @@ -184,8 +149,6 @@ public void should_add_heartbeat_handler_to_pipeline_on_success() { // It should send a STARTUP message Frame requestFrame = readOutboundFrame(); assertThat(requestFrame.message).isInstanceOf(Startup.class); - Startup startup = (Startup) requestFrame.message; - assertThat(startup.options).doesNotContainKey("COMPRESSION"); assertThat(connectFuture).isNotDone(); // Simulate a READY response diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java new file mode 100644 index 00000000000..3868212179a --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java @@ -0,0 +1,107 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.context; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeStateListener; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; +import com.datastax.oss.driver.api.core.session.Session; +import com.datastax.oss.driver.api.core.tracker.RequestTracker; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import com.datastax.oss.driver.shaded.guava.common.collect.Maps; +import com.datastax.oss.protocol.internal.request.Startup; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +public class StartupOptionsBuilderTest { + + private DefaultDriverContext defaultDriverContext; + + // Mocks for instantiating the default driver context + @Mock private DriverConfigLoader configLoader; + private List> typeCodecs = Lists.newArrayList(); + @Mock private NodeStateListener nodeStateListener; + @Mock private SchemaChangeListener schemaChangeListener; + @Mock private RequestTracker requestTracker; + private Map> nodeFilters = Maps.newHashMap(); + @Mock private ClassLoader classLoader; + @Mock private DriverConfig driverConfig; + @Mock private DriverExecutionProfile defaultProfile; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + Mockito.when(configLoader.getInitialConfig()).thenReturn(driverConfig); + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + } + + private void buildDriverContext() { + defaultDriverContext = + new DefaultDriverContext( + configLoader, + typeCodecs, + nodeStateListener, + schemaChangeListener, + requestTracker, + nodeFilters, + classLoader); + } + + private void assertDefaultStartupOptions(Startup startup) { + assertThat(startup.options).containsEntry(Startup.CQL_VERSION_KEY, "3.0.0"); + assertThat(startup.options) + .containsEntry( + StartupOptionsBuilder.DRIVER_NAME_KEY, Session.OSS_DRIVER_COORDINATES.getName()); + assertThat(startup.options).containsKey(StartupOptionsBuilder.DRIVER_VERSION_KEY); + Version version = Version.parse(startup.options.get(StartupOptionsBuilder.DRIVER_VERSION_KEY)); + assertThat(version).isEqualByComparingTo(Session.OSS_DRIVER_COORDINATES.getVersion()); + } + + @Test + public void should_build_minimal_startup_options() { + buildDriverContext(); + Startup startup = new Startup(defaultDriverContext.getStartupOptions()); + assertThat(startup.options).doesNotContainKey(Startup.COMPRESSION_KEY); + assertDefaultStartupOptions(startup); + } + + @Test + public void should_build_startup_options_with_compression() { + Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_COMPRESSION)) + .thenReturn(Boolean.TRUE); + Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION)) + .thenReturn("lz4"); + buildDriverContext(); + Startup startup = new Startup(defaultDriverContext.getStartupOptions()); + // assert the compression option is present + assertThat(startup.options).containsEntry(Startup.COMPRESSION_KEY, "lz4"); + assertDefaultStartupOptions(startup); + } +} From aaefe1c54b0767ca43744a4e55d03e34a1027bb8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Sep 2018 09:38:18 -0700 Subject: [PATCH 569/742] Extract utility method resolveExecutionProfile() Co-authored-by: Kevin Gallardo --- .../driver/internal/core/cql/Conversions.java | 16 ++++++++++++++++ .../internal/core/cql/CqlPrepareHandlerBase.java | 13 +------------ .../internal/core/cql/CqlRequestHandlerBase.java | 13 +------------ 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index c406aba52b0..7f58b6c4af3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -20,7 +20,9 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchableStatement; @@ -51,6 +53,7 @@ import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteFailureException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.DefaultProtocolFeature; @@ -94,6 +97,19 @@ */ public class Conversions { + public static DriverExecutionProfile resolveExecutionProfile( + Request request, DriverContext context) { + if (request.getExecutionProfile() != null) { + return request.getExecutionProfile(); + } else { + DriverConfig config = context.getConfig(); + String profileName = request.getExecutionProfileName(); + return (profileName == null || profileName.isEmpty()) + ? config.getDefaultProfile() + : config.getProfile(profileName); + } + } + public static Message toMessage( Statement statement, DriverExecutionProfile config, InternalDriverContext context) { ConsistencyLevel consistency = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index fd1e1f81c21..fcd83937de3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -116,17 +115,7 @@ protected CqlPrepareHandlerBase( this.preparedStatementsCache = preparedStatementsCache; this.session = session; this.context = context; - - if (request.getExecutionProfile() != null) { - this.executionProfile = request.getExecutionProfile(); - } else { - DriverConfig config = context.getConfig(); - String profileName = request.getExecutionProfileName(); - this.executionProfile = - (profileName == null || profileName.isEmpty()) - ? config.getDefaultProfile() - : config.getProfile(profileName); - } + this.executionProfile = Conversions.resolveExecutionProfile(request, context); this.queryPlan = context .getLoadBalancingPolicyWrapper() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 1941d63d14f..a7596036655 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.DriverTimeoutException; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.FrameTooLongException; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -143,17 +142,7 @@ protected CqlRequestHandlerBase( this.session = session; this.keyspace = session.getKeyspace().orElse(null); this.context = context; - - if (statement.getExecutionProfile() != null) { - this.executionProfile = statement.getExecutionProfile(); - } else { - DriverConfig config = context.getConfig(); - String profileName = statement.getExecutionProfileName(); - this.executionProfile = - (profileName == null || profileName.isEmpty()) - ? config.getDefaultProfile() - : config.getProfile(profileName); - } + this.executionProfile = Conversions.resolveExecutionProfile(statement, context); if (this.statement.getNode() != null) { this.queryPlan = new QueryPlan(this.statement.getNode()); From 7116344344dd327976b4a8cbb23b4212795f0a66 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 12 Sep 2018 14:39:13 -0700 Subject: [PATCH 570/742] Document why we don't use parent.project.name in Driver.properties --- .../main/resources/com/datastax/oss/driver/Driver.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/resources/com/datastax/oss/driver/Driver.properties b/core/src/main/resources/com/datastax/oss/driver/Driver.properties index 58725b68008..a62ba0a538a 100644 --- a/core/src/main/resources/com/datastax/oss/driver/Driver.properties +++ b/core/src/main/resources/com/datastax/oss/driver/Driver.properties @@ -20,4 +20,7 @@ driver.groupId=${project.groupId} driver.artifactId=${project.artifactId} driver.version=${project.version} +# It would be better to use ${project.parent.name} here, but for some reason the bundle plugin +# prevents that from being resolved correctly (unlike the project-level properties above). +# The value is not likely to change, so we simply hard-code it: driver.name=DataStax Java driver for Apache Cassandra(R) From 813d6a6286f6da975661a4c7ec286e8ff740f18a Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Thu, 13 Sep 2018 09:59:52 -0500 Subject: [PATCH 571/742] JAVA-1946: Ignore protocol version in equals comparison for UdtValue/TupleValue --- changelog/README.md | 1 + .../internal/core/data/DefaultTupleValue.java | 4 --- .../internal/core/data/DefaultUdtValue.java | 8 ++---- .../core/data/AccessibleByIndexTestBase.java | 9 ++++++ .../core/data/DefaultTupleValueTest.java | 15 ++++++++++ .../core/data/DefaultUdtValueTest.java | 28 +++++++++++++++++++ 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 1c09a4c0ca7..2bf81ab7a97 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [bug] JAVA-1946: Ignore protocol version in equals comparison for UdtValue/TupleValue - [new feature] JAVA-1932: Send Driver Name and Version in Startup message - [new feature] JAVA-1917: Add ability to set node on statement - [improvement] JAVA-1916: Base TimestampCodec.parse on java.util.Date. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java index 132610886cb..9317a3f5a36 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValue.java @@ -123,10 +123,6 @@ public boolean equals(Object o) { } TupleValue that = (TupleValue) o; - if (!this.protocolVersion().equals(that.protocolVersion())) { - return false; - } - if (!type.equals(that.getType())) { return false; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java index e07e579ac67..5bed077a76d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValue.java @@ -130,10 +130,6 @@ public boolean equals(Object o) { } UdtValue that = (UdtValue) o; - if (!this.protocolVersion().equals(that.protocolVersion())) { - return false; - } - if (!type.equals(that.getType())) { return false; } @@ -144,11 +140,11 @@ public boolean equals(Object o) { DataType innerThatType = that.getType().getFieldTypes().get(i); Object thisValue = - that.codecRegistry() + this.codecRegistry() .codecFor(innerThisType) .decode(this.getBytesUnsafe(i), this.protocolVersion()); Object thatValue = - this.codecRegistry() + that.codecRegistry() .codecFor(innerThatType) .decode(that.getBytesUnsafe(i), that.protocolVersion()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index 1f533ec70f4..ca2d62134b0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.GettableByIndex; import com.datastax.oss.driver.api.core.data.SettableByIndex; @@ -49,6 +50,7 @@ protected abstract T newInstance( List dataTypes, List values, AttachmentPoint attachmentPoint); @Mock protected AttachmentPoint attachmentPoint; + @Mock protected AttachmentPoint v3AttachmentPoint; @Mock protected CodecRegistry codecRegistry; protected PrimitiveIntCodec intCodec; protected TypeCodec doubleCodec; @@ -61,6 +63,9 @@ public void setup() { Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + Mockito.when(v3AttachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + Mockito.when(v3AttachmentPoint.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V3); + intCodec = Mockito.spy(TypeCodecs.INT); doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); textCodec = Mockito.spy(TypeCodecs.TEXT); @@ -69,6 +74,10 @@ public void setup() { Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) .thenAnswer(i -> doubleCodec); Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + + Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(t -> textCodec); + Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(d -> doubleCodec); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index bf7052a8e87..57994165592 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -96,4 +96,19 @@ public void should_not_equate_instances_with_same_binary_representation_but_diff assertThat(tuple1).isNotEqualTo(tuple2); } + + @Test + public void should_equate_instances_with_different_protocol_versions() { + TupleType tupleType1 = DataTypes.tupleOf(DataTypes.TEXT); + tupleType1.attach(attachmentPoint); + + // use the V3 attachmentPoint for type2 + TupleType tupleType2 = DataTypes.tupleOf(DataTypes.TEXT); + tupleType2.attach(v3AttachmentPoint); + + TupleValue tuple1 = tupleType1.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x01")); + TupleValue tuple2 = tupleType2.newValue().setBytesUnsafe(0, Bytes.fromHexString("0x01")); + + assertThat(tuple1).isEqualTo(tuple2); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index a276d6f8b31..ce3cadf4227 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -125,4 +125,32 @@ public void should_format_to_string() { assertThat(udt.toString()).isEqualTo("{t:'foobar',i:NULL,d:3.14}"); } + + @Test + public void should_equate_instances_with_different_protocol_versions() { + + UserDefinedType type1 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("t"), DataTypes.TEXT) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("d"), DataTypes.DOUBLE) + .build(); + type1.attach(attachmentPoint); + + // create an idential type, but with a different attachment point + UserDefinedType type2 = + new UserDefinedTypeBuilder( + CqlIdentifier.fromInternal("ks"), CqlIdentifier.fromInternal("type")) + .withField(CqlIdentifier.fromInternal("t"), DataTypes.TEXT) + .withField(CqlIdentifier.fromInternal("i"), DataTypes.INT) + .withField(CqlIdentifier.fromInternal("d"), DataTypes.DOUBLE) + .build(); + type2.attach(v3AttachmentPoint); + UdtValue udt1 = + type1.newValue().setString("t", "some text string").setInt("i", 42).setDouble("d", 3.14); + UdtValue udt2 = + type2.newValue().setString("t", "some text string").setInt("i", 42).setDouble("d", 3.14); + assertThat(udt1).isEqualTo(udt2); + } } From a7e909e94b396f82fab2af31b139934b8a27d268 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Tue, 4 Sep 2018 09:25:51 -0500 Subject: [PATCH 572/742] JAVA-1956: Add statementsCount accessor to BatchStatementBuilder --- changelog/README.md | 1 + .../oss/driver/api/core/cql/BatchStatementBuilder.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 2bf81ab7a97..5e12fe224e3 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1956: Add statementsCount accessor to BatchStatementBuilder - [bug] JAVA-1946: Ignore protocol version in equals comparison for UdtValue/TupleValue - [new feature] JAVA-1932: Send Driver Name and Version in Startup message - [new feature] JAVA-1917: Add ability to set node on statement diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index cda75bb3443..f4ac0fefeb7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -149,4 +149,8 @@ public BatchStatement build() { timeout, node); } + + public int getStatementsCount() { + return this.statementsCount; + } } From 668075f5d84adeb4f80637b6fbdec40fa4bcb689 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 17 Sep 2018 18:37:08 +0200 Subject: [PATCH 573/742] JAVA-1939: Exclude logback-test.xml files from test jars This commit partially re-applies the changes from 3a24c3f73 accidentally reverted by f32feeb6e. --- core/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/pom.xml b/core/pom.xml index 9306e3359bc..eeea70aabed 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -148,6 +148,11 @@ test-jar + + + logback-test.xml + + From f394f748aa72daab9cdc0141d91c5f7ced7e1ee6 Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Fri, 14 Sep 2018 16:06:16 -0500 Subject: [PATCH 574/742] Allow for raw yaml when configuring DSE in CcmBridge --- .../driver/api/testinfra/ccm/CcmBridge.java | 36 ++++++++++++++++--- .../api/testinfra/ccm/CustomCcmRule.java | 5 +++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 981a87f2fbb..c36bbefc2c6 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -62,6 +62,7 @@ public class CcmBridge implements AutoCloseable { private final Map cassandraConfiguration; private final Map dseConfiguration; + private final List rawDseYaml; private final List createOptions; private final List dseWorkloads; @@ -127,6 +128,7 @@ private CcmBridge( String ipPrefix, Map cassandraConfiguration, Map dseConfiguration, + List dseConfigurationRawYaml, List createOptions, Collection jvmArgs, List dseWorkloads) { @@ -145,6 +147,7 @@ private CcmBridge( this.ipPrefix = ipPrefix; this.cassandraConfiguration = cassandraConfiguration; this.dseConfiguration = dseConfiguration; + this.rawDseYaml = dseConfigurationRawYaml; this.createOptions = createOptions; StringBuilder allJvmArgs = new StringBuilder(""); @@ -214,6 +217,9 @@ public void create() { for (Map.Entry conf : dseConfiguration.entrySet()) { execute("updatedseconf", String.format("%s:%s", conf.getKey(), conf.getValue())); } + for (String yaml : rawDseYaml) { + executeUnsanitized("updatedseconf", "-y", yaml); + } if (!dseWorkloads.isEmpty()) { execute("setworkload", String.join(",", dseWorkloads)); } @@ -259,9 +265,24 @@ synchronized void execute(String... args) { + String.join(" ", args) + " --config-dir=" + configDirectory.toFile().getAbsolutePath(); - logger.debug("Executing: " + command); + + execute(CommandLine.parse(command)); + } + + synchronized void executeUnsanitized(String... args) { + String command = "ccm "; CommandLine cli = CommandLine.parse(command); + for (String arg : args) { + cli.addArgument(arg, false); + } + cli.addArgument("--config-dir=" + configDirectory.toFile().getAbsolutePath()); + + execute(cli); + } + + private void execute(CommandLine cli) { + logger.debug("Executing: " + cli); ExecuteWatchdog watchDog = new ExecuteWatchdog(TimeUnit.MINUTES.toMillis(10)); try (LogOutputStream outStream = new LogOutputStream() { @@ -285,13 +306,13 @@ protected void processLine(String line, int logLevel) { int retValue = executor.execute(cli); if (retValue != 0) { logger.error( - "Non-zero exit code ({}) returned from executing ccm command: {}", retValue, command); + "Non-zero exit code ({}) returned from executing ccm command: {}", retValue, cli); } } catch (IOException ex) { if (watchDog.killedProcess()) { - throw new RuntimeException("The command '" + command + "' was killed after 10 minutes"); + throw new RuntimeException("The command '" + cli + "' was killed after 10 minutes"); } else { - throw new RuntimeException("The command '" + command + "' failed to execute", ex); + throw new RuntimeException("The command '" + cli + "' failed to execute", ex); } } } @@ -329,6 +350,7 @@ public static class Builder { private int[] nodes = {1}; private final Map cassandraConfiguration = new LinkedHashMap<>(); private final Map dseConfiguration = new LinkedHashMap<>(); + private final List dseRawYaml = new ArrayList<>(); private final List jvmArgs = new ArrayList<>(); private String ipPrefix = "127.0.0."; private final List createOptions = new ArrayList<>(); @@ -359,6 +381,11 @@ public Builder withDseConfiguration(String key, Object value) { return this; } + public Builder withDseConfiguration(String rawYaml) { + dseRawYaml.add(rawYaml); + return this; + } + public Builder withJvmArgs(String... jvmArgs) { Collections.addAll(this.jvmArgs, jvmArgs); return this; @@ -423,6 +450,7 @@ public CcmBridge build() { ipPrefix, cassandraConfiguration, dseConfiguration, + dseRawYaml, createOptions, jvmArgs, dseWorkloads); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index 898a49234a0..1e502238e99 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -77,6 +77,11 @@ public Builder withDseConfiguration(String key, Object value) { return this; } + public Builder withDseConfiguration(String rawYaml) { + bridgeBuilder.withDseConfiguration(rawYaml); + return this; + } + public Builder withDseWorkloads(String... workloads) { bridgeBuilder.withDseWorkloads(workloads); return this; From a4cf1aef29d91b91a46cb996f835417ce5c01161 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Tue, 18 Sep 2018 12:15:55 -0500 Subject: [PATCH 575/742] JAVA-1949: Improve error message when contact points are wrong --- changelog/README.md | 1 + .../api/core/AllNodesFailedException.java | 19 ++++++++++++++----- .../core/control/ControlConnection.java | 9 +++++++++ .../oss/driver/api/core/ConnectIT.java | 10 +++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 5e12fe224e3..fe10ba9221f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1949: Improve error message when contact points are wrong - [improvement] JAVA-1956: Add statementsCount accessor to BatchStatementBuilder - [bug] JAVA-1946: Ignore protocol version in equals comparison for UdtValue/TupleValue - [new feature] JAVA-1932: Send Driver Name and Version in Startup message diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java index 543fa9cb56a..a897c4d9e27 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AllNodesFailedException.java @@ -62,22 +62,25 @@ protected AllNodesFailedException( @NonNull String message, @Nullable ExecutionInfo executionInfo, @NonNull Map errors) { - super(message, null, null, true); + super(message, executionInfo, null, true); this.errors = errors; } private AllNodesFailedException(Map errors) { - this(buildMessage(errors), null, errors); + this( + buildMessage( + String.format("All %d node(s) tried for the query failed", errors.size()), errors), + null, + errors); } - private static String buildMessage(Map errors) { + private static String buildMessage(String baseMessage, Map errors) { int limit = Math.min(errors.size(), 3); String details = Joiner.on(", ").withKeyValueSeparator(": ").join(Iterables.limit(errors.entrySet(), limit)); return String.format( - "All %d node tried for the query failed (showing first %d, use getErrors() for more: %s)", - errors.size(), limit, details); + baseMessage + " (showing first %d, use getErrors() for more: %s)", limit, details); } /** The details of the individual error on each node. */ @@ -91,4 +94,10 @@ public Map getErrors() { public DriverException copy() { return new AllNodesFailedException(getMessage(), getExecutionInfo(), errors); } + + @NonNull + public AllNodesFailedException reword(String newMessage) { + return new AllNodesFailedException( + buildMessage(newMessage, errors), getExecutionInfo(), errors); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index b25e833db19..eee0254681b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -276,6 +276,15 @@ private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { if (reconnectOnFailure && !closeWasCalled) { reconnection.start(); } else { + // Special case for the initial connection: reword to a more user-friendly error + // message + if (error instanceof AllNodesFailedException) { + error = + ((AllNodesFailedException) error) + .reword( + "Could not reach any contact point, " + + "make sure you've provided valid addresses"); + } initFuture.completeExceptionally(error); } firstConnectionAttemptFuture.completeExceptionally(error); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 19262d11ad0..5753a3865d9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -32,8 +32,10 @@ import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; @Category(ParallelizableTests.class) public class ConnectIT { @@ -42,16 +44,22 @@ public class ConnectIT { public static SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + @Rule public ExpectedException thrown = ExpectedException.none(); + @Before public void setup() { simulacronRule.cluster().acceptConnections(); } - @Test(expected = AllNodesFailedException.class) + @Test public void should_fail_fast_if_contact_points_unreachable_and_reconnection_disabled() { // Given simulacronRule.cluster().rejectConnections(0, RejectScope.STOP); + thrown.expect(AllNodesFailedException.class); + thrown.expectMessage( + "Could not reach any contact point, make sure you've provided valid addresses"); + // When SessionUtils.newSession(simulacronRule); From eb4dcda235ecbf92714535a2a6e854535489e0c5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 19 Sep 2018 12:59:42 +0200 Subject: [PATCH 576/742] Return covariant future types from session async methods Motivation: We experienced problems by the past trying to extend classes and override methods returning CompletionStage. It is usually better to return CompletionStage since this can be overridden by CompletionStage thanks to type covariance. We modified lots of methods but for some reason we forgot a few ones in CqlSession and DefaultSession. Modifications: - Modify return types of methods in CqlSession and DefaultSession from CompletionStage to CompletionStage. Result: Methods in CqlSession and DefaultSession can now be overriden more easily. --- core/revapi.json | 75 +++++++++++++++++++ .../oss/driver/api/core/CqlSession.java | 11 +-- .../core/cql/DefaultAsyncResultSet.java | 2 +- .../internal/core/session/DefaultSession.java | 4 +- .../core/cql/DefaultAsyncResultSetTest.java | 7 +- .../core/cql/QueryTraceFetcherTest.java | 15 +++- .../api/core/connection/FrameLengthIT.java | 2 +- .../driver/api/core/cql/AsyncResultSetIT.java | 2 +- 8 files changed, 101 insertions(+), 17 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index db1d5ea5dca..79735fb0b89 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -522,6 +522,81 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", "elementKind": "method", "justification": "Add ability to query specific nodes for virtual tables" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", + "oldType": "java.util.concurrent.CompletionStage", + "newType": "java.util.concurrent.CompletionStage", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", + "classSimpleName": "CqlSession", + "methodName": "executeAsync", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Return covariant future types from session async methods" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", + "oldType": "java.util.concurrent.CompletionStage", + "newType": "java.util.concurrent.CompletionStage", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", + "classSimpleName": "CqlSession", + "methodName": "executeAsync", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Return covariant future types from session async methods" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", + "oldType": "java.util.concurrent.CompletionStage", + "newType": "java.util.concurrent.CompletionStage", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", + "classSimpleName": "CqlSession", + "methodName": "prepareAsync", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Return covariant future types from session async methods" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", + "oldType": "java.util.concurrent.CompletionStage", + "newType": "java.util.concurrent.CompletionStage", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", + "classSimpleName": "CqlSession", + "methodName": "prepareAsync", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Return covariant future types from session async methods" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", + "oldType": "java.util.concurrent.CompletionStage", + "newType": "java.util.concurrent.CompletionStage", + "package": "com.datastax.oss.driver.api.core", + "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", + "classSimpleName": "CqlSession", + "methodName": "prepareAsync", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "elementKind": "method", + "justification": "Return covariant future types from session async methods" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index 2314380dd84..b9784e3ff02 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -61,7 +61,7 @@ default ResultSet execute(@NonNull String query) { * generally before the result is available). */ @NonNull - default CompletionStage executeAsync(@NonNull Statement statement) { + default CompletionStage executeAsync(@NonNull Statement statement) { return Objects.requireNonNull( execute(statement, Statement.ASYNC), "The CQL processor should never return a null result"); } @@ -71,7 +71,7 @@ default CompletionStage executeAsync(@NonNull Statement state * generally before the result is available). */ @NonNull - default CompletionStage executeAsync(@NonNull String query) { + default CompletionStage executeAsync(@NonNull String query) { return executeAsync(SimpleStatement.newInstance(query)); } @@ -167,7 +167,8 @@ default PreparedStatement prepare(@NonNull PrepareRequest request) { * details. */ @NonNull - default CompletionStage prepareAsync(@NonNull SimpleStatement statement) { + default CompletionStage prepareAsync( + @NonNull SimpleStatement statement) { return Objects.requireNonNull( execute(new DefaultPrepareRequest(statement), PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); @@ -178,7 +179,7 @@ default CompletionStage prepareAsync(@NonNull SimpleStatement * sent, generally before the statement is prepared). */ @NonNull - default CompletionStage prepareAsync(@NonNull String query) { + default CompletionStage prepareAsync(@NonNull String query) { return Objects.requireNonNull( execute(new DefaultPrepareRequest(query), PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); @@ -194,7 +195,7 @@ default CompletionStage prepareAsync(@NonNull String query) { * with {@link PrepareRequest} directly. */ @NonNull - default CompletionStage prepareAsync(PrepareRequest request) { + default CompletionStage prepareAsync(PrepareRequest request) { return Objects.requireNonNull( execute(request, PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index b2630006b9a..64c2a42061e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -95,7 +95,7 @@ public boolean hasMorePages() { @NonNull @Override - public CompletionStage fetchNextPage() throws IllegalStateException { + public CompletionStage fetchNextPage() throws IllegalStateException { ByteBuffer nextState = executionInfo.getPagingState(); if (nextState == null) { throw new IllegalStateException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index cc0fd2b6a6c..278c3b8dc70 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -128,13 +128,13 @@ public boolean isSchemaMetadataEnabled() { @NonNull @Override - public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { + public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { return metadataManager.setSchemaEnabled(newValue); } @NonNull @Override - public CompletionStage refreshSchemaAsync() { + public CompletionStage refreshSchemaAsync() { return metadataManager.refreshSchema(null, true, true); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 59061e689ee..a2b01aa708f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -55,7 +55,7 @@ public class DefaultAsyncResultSetTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(executionInfo.getStatement()).thenReturn((Statement) statement); + Mockito.when(executionInfo.getStatement()).thenAnswer(invocation -> statement); Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); Mockito.when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); } @@ -85,14 +85,15 @@ public void should_invoke_session_to_fetch_next_page() { Mockito.when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); CompletableFuture mockResultFuture = new CompletableFuture<>(); - Mockito.when(session.executeAsync(Mockito.any(Statement.class))).thenReturn(mockResultFuture); + Mockito.when(session.executeAsync(Mockito.any(Statement.class))) + .thenAnswer(invocation -> mockResultFuture); // When DefaultAsyncResultSet resultSet = new DefaultAsyncResultSet( columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); assertThat(resultSet.hasMorePages()).isTrue(); - CompletionStage nextPageFuture = resultSet.fetchNextPage(); + CompletionStage nextPageFuture = resultSet.fetchNextPage(); // Then Mockito.verify(statement).copy(mockPagingState); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 213ac26ccfe..0008c326512 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -116,7 +116,8 @@ public void should_succeed_when_both_queries_succeed_immediately() { CompletionStage sessionRow = completeSessionRow(); CompletionStage eventRows = singlePageEventRows(); Mockito.when(session.executeAsync(any(SimpleStatement.class))) - .thenReturn(sessionRow, eventRows); + .thenAnswer(invocation -> sessionRow) + .thenAnswer(invocation -> eventRows); // When QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); @@ -167,7 +168,9 @@ public void should_succeed_when_events_query_is_paged() { CompletionStage eventRows1 = multiPageEventRows1(); CompletionStage eventRows2 = multiPageEventRows2(); Mockito.when(session.executeAsync(any(SimpleStatement.class))) - .thenReturn(sessionRow, eventRows1, eventRows2); + .thenAnswer(invocation -> sessionRow) + .thenAnswer(invocation -> eventRows1) + .thenAnswer(invocation -> eventRows2); // When QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); @@ -192,7 +195,9 @@ public void should_retry_when_session_row_is_incomplete() { CompletionStage sessionRow2 = completeSessionRow(); CompletionStage eventRows = singlePageEventRows(); Mockito.when(session.executeAsync(any(SimpleStatement.class))) - .thenReturn(sessionRow1, sessionRow2, eventRows); + .thenAnswer(invocation -> sessionRow1) + .thenAnswer(invocation -> sessionRow2) + .thenAnswer(invocation -> eventRows); // When QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); @@ -259,7 +264,9 @@ public void should_fail_when_session_query_still_incomplete_after_max_tries() { CompletionStage sessionRow2 = incompleteSessionRow(); CompletionStage sessionRow3 = incompleteSessionRow(); Mockito.when(session.executeAsync(any(SimpleStatement.class))) - .thenReturn(sessionRow1, sessionRow2, sessionRow3); + .thenAnswer(invocation -> sessionRow1) + .thenAnswer(invocation -> sessionRow2) + .thenAnswer(invocation -> sessionRow3); // When QueryTraceFetcher fetcher = new QueryTraceFetcher(TRACING_ID, session, context, config); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index af887bd1d1d..68af03548b0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -92,7 +92,7 @@ public void should_fail_if_request_exceeds_max_frame_length() { @Test public void should_fail_if_response_exceeds_max_frame_length() { - CompletionStage slowResultFuture = + CompletionStage slowResultFuture = sessionRule.session().executeAsync(SLOW_QUERY); try { sessionRule.session().execute(LARGE_QUERY); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index fb76d1d3f85..cc8d54c9ff1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -85,7 +85,7 @@ public static void setupSchema() { public void should_only_iterate_over_rows_in_current_page() throws Exception { // very basic test that just ensures that iterating over an AsyncResultSet only visits the first // page. - CompletionStage result = + CompletionStage result = sessionRule .session() .executeAsync( From a8c9dc1cf602afce8444ac2823c5e0cb3d984c3c Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 19 Sep 2018 09:39:51 +0200 Subject: [PATCH 577/742] Improve design of DefaultLoadBalancingPolicy event tests Motivation: Some of the tests in DefaultLoadBalancingPolicyEventsTest were poorly designed: their intent is to verify that the user filter is *not* consulted when a node is marked down or removed, but that wasn't clear enough. Modifications: - Remove methods should_remove_*_node_from_live_set_when_filtered. - Verify filter usage in should_remove_*_node_from_live_set instead. - Use the regular MockitoJUnitRunner runner. Result: Verifications around filter usage upon onDown / onRemove are now clear. --- .../DefaultLoadBalancingPolicyEventsTest.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index 1b3ec33c2a6..8ca7357abdc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -29,7 +29,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -@RunWith(MockitoJUnitRunner.Silent.class) +@RunWith(MockitoJUnitRunner.class) public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPolicyTestBase { private DefaultLoadBalancingPolicy policy; @@ -57,12 +57,8 @@ public void should_remove_down_node_from_live_set() { // Then assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); - } - - @Test - public void should_remove_down_node_from_live_set_when_filtered() { - Mockito.when(filter.test(node2)).thenReturn(true); - should_remove_down_node_from_live_set(); + // should have been called only once, during initialization, but not during onDown + Mockito.verify(filter).test(node2); } @Test @@ -73,12 +69,8 @@ public void should_remove_removed_node_from_live_set() { // Then assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); - } - - @Test - public void should_remove_removed_node_from_live_set_when_filtered() { - Mockito.when(filter.test(node2)).thenReturn(true); - should_remove_removed_node_from_live_set(); + // should have been called only once, during initialization, but not during onRemove + Mockito.verify(filter).test(node2); } @Test @@ -88,6 +80,7 @@ public void should_set_added_node_to_local() { // Then Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + Mockito.verify(filter).test(node3); // Not added to the live set yet, we're waiting for the pool to open assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } @@ -125,6 +118,7 @@ public void should_add_up_node_to_live_set() { // Then Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + Mockito.verify(filter).test(node3); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); } @@ -138,6 +132,7 @@ public void should_ignore_up_node_when_filtered() { // Then Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + Mockito.verify(filter).test(node3); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } From d9cdd9069fc1bdf553df4fc334e8c5b6e74d699a Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 14 Sep 2018 19:13:32 +0200 Subject: [PATCH 578/742] Clarify the contents of maps returned by DriverContext methods --- .../driver/api/core/context/DriverContext.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 2a26b9bc5ae..b4efb691494 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -54,7 +54,11 @@ public interface DriverContext extends AttachmentPoint { @NonNull DriverConfigLoader getConfigLoader(); - /** @return The driver's load balancing policies; may be empty but never {@code null}. */ + /** + * @return The driver's load balancing policies, keyed by profile name; the returned map is + * guaranteed to never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. + */ @NonNull Map getLoadBalancingPolicies(); @@ -71,7 +75,11 @@ default LoadBalancingPolicy getLoadBalancingPolicy(@NonNull String profileName) : getLoadBalancingPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } - /** @return The driver's load balancing policies; may be empty but never {@code null}. */ + /** + * @return The driver's retry policies, keyed by profile name; the returned map is guaranteed to + * never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. + */ @NonNull Map getRetryPolicies(); @@ -85,7 +93,11 @@ default RetryPolicy getRetryPolicy(@NonNull String profileName) { return (policy != null) ? policy : getRetryPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } - /** @return The driver's load balancing policies; may be empty but never {@code null}. */ + /** + * @return The driver's speculative execution policies, keyed by profile name; the returned map is + * guaranteed to never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. + */ @NonNull Map getSpeculativeExecutionPolicies(); From 16b17332b61b4a055ec0e3ba9e3a1b0bd54156ab Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 14 Sep 2018 19:13:54 +0200 Subject: [PATCH 579/742] Fix minor typo in LoadBalancingPolicy class javadocs --- .../oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index 62cb013cdbc..5b191fd3672 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -40,7 +40,7 @@ public interface LoadBalancingPolicy extends AutoCloseable { * @param nodes the nodes discovered by the driver when it connected to the cluster. When this * method is invoked, their state is guaranteed to be either {@link NodeState#UP} or {@link * NodeState#UNKNOWN}. Node states may be updated concurrently while this method executes, but - * if so you will receive a notification + * if so you will receive a notification. * @param distanceReporter an object that will be used by the policy to signal distance changes. * @param contactPoints the set of contact points that the driver was initialized with (see {@link * SessionBuilder#addContactPoints(Collection)}). This is provided for reference, in case the From 5ef9354afe2853479750a05d1bfb1b9644f639e5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 14 Sep 2018 19:31:11 +0200 Subject: [PATCH 580/742] Make RequestTracker methods default methods --- .../driver/api/core/tracker/RequestTracker.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java index ba90fc07ebd..abab882544b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java @@ -41,11 +41,11 @@ public interface RequestTracker extends AutoCloseable { * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. */ - void onSuccess( + default void onSuccess( @NonNull Request request, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, - @NonNull Node node); + @NonNull Node node) {} /** * Invoked each time a request fails. @@ -55,12 +55,12 @@ void onSuccess( * @param executionProfile the execution profile of this request. * @param node the node that returned the error response, or {@code null} if the error occurred */ - void onError( + default void onError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, - @Nullable Node node); + @Nullable Node node) {} /** * Invoked each time a request fails at the node level. Similar to {@link #onError(Request, @@ -71,12 +71,12 @@ void onError( * @param executionProfile the execution profile of this request. * @param node the node that returned the error response. */ - void onNodeError( + default void onNodeError( @NonNull Request request, @NonNull Throwable error, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, - @NonNull Node node); + @NonNull Node node) {} /** * Invoked each time a request succeeds at the node level. Similar to {@link #onSuccess(Request, @@ -87,9 +87,9 @@ void onNodeError( * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. */ - void onNodeSuccess( + default void onNodeSuccess( @NonNull Request request, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, - @NonNull Node node); + @NonNull Node node) {} } From f3c5e99be52711cf9399517a86852a517da97fa5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 17 Sep 2018 15:49:42 +0200 Subject: [PATCH 581/742] Add missing javadocs to methods in ChannelPool and ChannelSet --- .../driver/internal/core/pool/ChannelPool.java | 16 ++++++++++++++++ .../driver/internal/core/pool/ChannelSet.java | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index aa961ea3d03..15577254492 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -154,18 +154,34 @@ public int size() { return channels.size(); } + /** @return the number of available stream ids on all channels in the pool. */ public int getAvailableIds() { return channels.getAvailableIds(); } + /** + * @return the number of requests currently executing on all channels in this pool (including + * {@link #getOrphanedIds() orphaned ids}). + */ public int getInFlight() { return channels.getInFlight(); } + /** + * @return the number of stream ids for requests in all channels in this pool that have either + * timed out or been cancelled, but for which we can't release the stream id because a request + * might still come from the server. + */ public int getOrphanedIds() { return channels.getOrphanedIds(); } + /** + * Sets a new distance for the node this pool belongs to. This method returns immediately, the new + * distance will be set asynchronously. + * + * @param newDistance the new distance to set. + */ public void resize(NodeDistance newDistance) { RunOrSchedule.on(adminExecutor, () -> singleThreaded.resize(newDistance)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java index 93aee30a544..f6ba9e1176e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -98,6 +98,7 @@ DriverChannel next() { } } + /** @return the number of available stream ids on all channels in this channel set. */ int getAvailableIds() { int availableIds = 0; DriverChannel[] snapshot = this.channels; @@ -107,6 +108,10 @@ int getAvailableIds() { return availableIds; } + /** + * @return the number of requests currently executing on all channels in this channel set + * (including {@link #getOrphanedIds() orphaned ids}). + */ int getInFlight() { int inFlight = 0; DriverChannel[] snapshot = this.channels; @@ -116,6 +121,11 @@ int getInFlight() { return inFlight; } + /** + * @return the number of stream ids for requests in all channels in this channel set that have + * either timed out or been cancelled, but for which we can't release the stream id because a + * request might still come from the server. + */ int getOrphanedIds() { int orphanedIds = 0; DriverChannel[] snapshot = this.channels; From 161defdd6533afb8987d3de011ce4a07461bd676 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 17 Sep 2018 15:50:40 +0200 Subject: [PATCH 582/742] Make fields final in ChannelPool and ChannelSet --- .../datastax/oss/driver/internal/core/pool/ChannelPool.java | 4 ++-- .../datastax/oss/driver/internal/core/pool/ChannelSet.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index 15577254492..c4078a57398 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -235,9 +235,9 @@ private class SingleThreaded { private NodeDistance distance; private int wantedCount; - private CompletableFuture connectFuture = new CompletableFuture<>(); + private final CompletableFuture connectFuture = new CompletableFuture<>(); private boolean isConnecting; - private CompletableFuture closeFuture = new CompletableFuture<>(); + private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean isClosing; private CompletableFuture setKeyspaceFuture; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java index f6ba9e1176e..0f6144c77a4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelSet.java @@ -33,7 +33,7 @@ @ThreadSafe class ChannelSet implements Iterable { private volatile DriverChannel[] channels; - private ReentrantLock lock = new ReentrantLock(); // must be held when mutating the array + private final ReentrantLock lock = new ReentrantLock(); // must be held when mutating the array ChannelSet() { this.channels = new DriverChannel[] {}; From f76a2ed0de8961c12a8f6c35d36c4b18b1846710 Mon Sep 17 00:00:00 2001 From: Kevin Gallardo Date: Tue, 19 Jun 2018 13:55:38 -0400 Subject: [PATCH 583/742] Add a way to customize CcmBridge config in CcmRule --- .../oss/driver/api/testinfra/ccm/CcmRule.java | 24 ++++++++++++++++++- .../DefaultCcmBridgeBuilderCustomizer.java | 23 ++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index 9657250a521..a5169c0aef8 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -16,10 +16,13 @@ package com.datastax.oss.driver.api.testinfra.ccm; import com.datastax.oss.driver.categories.ParallelizableTests; +import java.lang.reflect.Method; import org.junit.AssumptionViolatedException; import org.junit.experimental.categories.Category; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A rule that creates a globally shared single node Ccm cluster that is only shut down after the @@ -35,7 +38,26 @@ public class CcmRule extends BaseCcmRule { private volatile boolean started = false; private CcmRule() { - super(CcmBridge.builder().build()); + super(configureCcmBridge(CcmBridge.builder()).build()); + } + + public static CcmBridge.Builder configureCcmBridge(CcmBridge.Builder builder) { + Logger logger = LoggerFactory.getLogger(CcmRule.class); + String customizerClass = + System.getProperty( + "ccmrule.bridgecustomizer", + "com.datastax.oss.driver.api.testinfra.ccm.DefaultCcmBridgeBuilderCustomizer"); + try { + Class clazz = Class.forName(customizerClass); + Method method = clazz.getMethod("configureBuilder", CcmBridge.Builder.class); + return (CcmBridge.Builder) method.invoke(null, builder); + } catch (Exception e) { + logger.warn( + "Could not find CcmRule customizer {}, will use the default CcmBridge.", + customizerClass, + e); + return builder; + } } @Override diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java new file mode 100644 index 00000000000..c9ed2faae67 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java @@ -0,0 +1,23 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.ccm; + +public class DefaultCcmBridgeBuilderCustomizer { + + public static CcmBridge.Builder configureBuilder(CcmBridge.Builder builder) { + return builder; + } +} From 2c7e91ea09e9cd1b79dd5d434657cea5fc02e2c9 Mon Sep 17 00:00:00 2001 From: Kevin Gallardo Date: Fri, 28 Sep 2018 11:44:46 -0700 Subject: [PATCH 584/742] Increase field visibility in RequestHandlerTestHarness This is useful if the class is extended in downstream projects. --- .../core/cql/RequestHandlerTestHarness.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index ee921ca0835..613de68555b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -76,20 +76,20 @@ public static Builder builder() { private final ScheduledTaskCapturingEventLoop schedulingEventLoop; private final Map pools; - @Mock private InternalDriverContext context; - @Mock private DefaultSession session; - @Mock private EventLoopGroup eventLoopGroup; - @Mock private NettyOptions nettyOptions; - @Mock private DriverConfig config; - @Mock private DriverExecutionProfile defaultProfile; - @Mock private LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; - @Mock private RetryPolicy retryPolicy; - @Mock private SpeculativeExecutionPolicy speculativeExecutionPolicy; - @Mock private TimestampGenerator timestampGenerator; - @Mock private ProtocolVersionRegistry protocolVersionRegistry; - @Mock private SessionMetricUpdater sessionMetricUpdater; - - private RequestHandlerTestHarness(Builder builder) { + @Mock protected InternalDriverContext context; + @Mock protected DefaultSession session; + @Mock protected EventLoopGroup eventLoopGroup; + @Mock protected NettyOptions nettyOptions; + @Mock protected DriverConfig config; + @Mock protected DriverExecutionProfile defaultProfile; + @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; + @Mock protected RetryPolicy retryPolicy; + @Mock protected SpeculativeExecutionPolicy speculativeExecutionPolicy; + @Mock protected TimestampGenerator timestampGenerator; + @Mock protected ProtocolVersionRegistry protocolVersionRegistry; + @Mock protected SessionMetricUpdater sessionMetricUpdater; + + protected RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(eventLoopGroup); From 2eeee93751889b8ca73b5b0e9bfa43bc4ea5be27 Mon Sep 17 00:00:00 2001 From: Kevin Gallardo Date: Wed, 20 Jun 2018 16:33:46 -0400 Subject: [PATCH 585/742] Add CcmBridge.dsetool() and reloadCore() --- .../datastax/oss/driver/api/testinfra/ccm/CcmBridge.java | 9 +++++++++ .../datastax/oss/driver/api/testinfra/ccm/CcmRule.java | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index c36bbefc2c6..809b0c41076 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -18,6 +18,7 @@ import static io.netty.util.internal.PlatformDependent.isWindows; import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.shaded.guava.common.base.Joiner; import com.datastax.oss.driver.shaded.guava.common.io.Resources; import java.io.File; import java.io.FileOutputStream; @@ -227,6 +228,14 @@ public void create() { } } + public void dsetool(int node, String... args) { + execute(String.format("node%d dsetool %s", node, Joiner.on(" ").join(args))); + } + + public void reloadCore(int node, String keyspace, String table, boolean reindex) { + dsetool(node, "reload_core", keyspace + "." + table, "reindex=" + reindex); + } + public void start() { if (started.compareAndSet(false, true)) { execute("start", jvmArgs, "--wait-for-binary-proto"); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index a5169c0aef8..eb12b6969e2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -97,6 +97,10 @@ public void evaluate() { return super.apply(base, description); } + public void reloadCore(int node, String keyspace, String table, boolean reindex) { + ccmBridge.reloadCore(node, keyspace, table, reindex); + } + public static CcmRule getInstance() { return INSTANCE; } From b618409536d0f8dcdc15c6c3868a92a94d435739 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 29 Aug 2018 15:41:08 -0700 Subject: [PATCH 586/742] Expose the config supplier in DefaultDriverConfigLoader --- .../core/config/typesafe/DefaultDriverConfigLoader.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index d19f9065968..a907666c0ae 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -83,6 +83,12 @@ public void onDriverInit(@NonNull DriverContext driverContext) { this.singleThreaded = new SingleThreaded((InternalDriverContext) driverContext); } + /** For internal use only, this leaks a Typesafe config type. */ + @NonNull + public Supplier getConfigSupplier() { + return configSupplier; + } + @Override public void close() { SingleThreaded singleThreaded = this.singleThreaded; From bc84386793ed7b78a4ee18117e0f132100322a01 Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Tue, 2 Oct 2018 17:56:19 -0500 Subject: [PATCH 587/742] JAVA-1948: Close session properly when LBP fails to initialize --- changelog/README.md | 1 + .../internal/core/session/DefaultSession.java | 6 ++++- .../oss/driver/api/core/ConnectIT.java | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index fe10ba9221f..39444bf3d46 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [bug] JAVA-1948: Close session properly when LBP fails to initialize - [improvement] JAVA-1949: Improve error message when contact points are wrong - [improvement] JAVA-1956: Add statementsCount accessor to BatchStatementBuilder - [bug] JAVA-1946: Ignore protocol version in equals comparison for UdtValue/TupleValue diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 278c3b8dc70..b03c12c41c3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -366,7 +366,11 @@ private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { } }); } catch (Throwable throwable) { - initFuture.completeExceptionally(throwable); + forceCloseAsync() + .whenComplete( + (v, error) -> { + initFuture.completeExceptionally(throwable); + }); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 5753a3865d9..aa88e55b487 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.api.core; +import static com.datastax.oss.driver.api.testinfra.utils.ConditionChecker.checkThat; +import static java.util.concurrent.TimeUnit.SECONDS; +import static junit.framework.TestCase.fail; import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -98,6 +101,30 @@ public void should_wait_for_contact_points_if_reconnection_enabled() throws Exce session.close(); } + /** + * Test for JAVA-1948. This ensures that when the LBP initialization fails that any connections + * are cleaned up appropriately. + */ + @Test + public void should_cleanup_on_lbp_init_failure() { + try { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .without(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) + .build(); + CqlSession.builder() + .addContactPoints(simulacronRule.getContactPoints()) + .withConfigLoader(loader) + .build(); + fail("Should have thrown a DriverException for no DC with explicit contact point"); + } catch (DriverException ignored) { + } + // One second should be plenty of time for connections to close server side + checkThat(() -> simulacronRule.cluster().getConnections().getConnections().isEmpty()) + .before(1, SECONDS) + .becomesTrue(); + } + @SuppressWarnings("unchecked") private CompletionStage newSessionAsync( SimulacronRule serverRule, DriverConfigLoader loader) { From 69a6a19b9503fbc42366d7293b9729835e47c37a Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 10 Oct 2018 15:02:43 +0200 Subject: [PATCH 588/742] JAVA-1988: Remove pre-fetching from ResultSet API --- changelog/README.md | 1 + core/revapi.json | 33 ++++++++ .../oss/driver/api/core/cql/ResultSet.java | 57 ++----------- .../internal/core/cql/MultiPageResultSet.java | 76 ++++------------- .../core/cql/SinglePageResultSet.java | 15 ---- .../internal/core/cql/ResultSetsTest.java | 81 ------------------- .../core/config/DriverExecutionProfileIT.java | 24 +++--- .../driver/api/core/cql/BatchStatementIT.java | 20 +++-- .../driver/api/core/cql/BoundStatementIT.java | 17 ++-- .../cql/PreparedStatementInvalidationIT.java | 14 ++-- .../api/core/cql/SimpleStatementIT.java | 32 +++++--- .../oss/driver/api/core/data/DataTypeIT.java | 5 +- .../type/codec/registry/CodecRegistryIT.java | 24 +++--- .../guava/internal/GuavaDriverContext.java | 2 +- .../guava/internal/KeyRequestProcessor.java | 25 +++--- .../datastax/oss/driver/osgi/OsgiBaseIT.java | 6 +- upgrade_guide/README.md | 21 +++-- 17 files changed, 173 insertions(+), 280 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 39444bf3d46..108c2609748 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1988: Remove pre-fetching from ResultSet API - [bug] JAVA-1948: Close session properly when LBP fails to initialize - [improvement] JAVA-1949: Improve error message when contact points are wrong - [improvement] JAVA-1956: Add statementsCount accessor to BatchStatementBuilder diff --git a/core/revapi.json b/core/revapi.json index 79735fb0b89..4f1c7c56c17 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -597,6 +597,39 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", "elementKind": "method", "justification": "Return covariant future types from session async methods" + }, + { + "code": "java.method.removed", + "old": "method void com.datastax.oss.driver.api.core.cql.ResultSet::fetchNextPage()", + "package": "com.datastax.oss.driver.api.core.cql", + "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", + "classSimpleName": "ResultSet", + "methodName": "fetchNextPage", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "JAVA-1988: remove pre-fetching from ResultSet API" + }, + { + "code": "java.method.removed", + "old": "method int com.datastax.oss.driver.api.core.cql.ResultSet::getAvailableWithoutFetching()", + "package": "com.datastax.oss.driver.api.core.cql", + "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", + "classSimpleName": "ResultSet", + "methodName": "getAvailableWithoutFetching", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "JAVA-1988: remove pre-fetching from ResultSet API" + }, + { + "code": "java.method.removed", + "old": "method boolean com.datastax.oss.driver.api.core.cql.ResultSet::isFullyFetched()", + "package": "com.datastax.oss.driver.api.core.cql", + "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", + "classSimpleName": "ResultSet", + "methodName": "isFullyFetched", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", + "elementKind": "method", + "justification": "JAVA-1988: remove pre-fetching from ResultSet API" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index e0e259e1d80..15dbc9a2a9d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -16,10 +16,9 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; -import com.datastax.oss.driver.shaded.guava.common.collect.Lists; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -98,59 +97,17 @@ default Row one() { */ @NonNull default List all() { - if (!iterator().hasNext()) { + Iterator iterator = iterator(); + if (!iterator.hasNext()) { return Collections.emptyList(); } - // We can't know the actual size in advance since more pages could be fetched, but we can at - // least allocate for what we already have. - List result = Lists.newArrayListWithExpectedSize(getAvailableWithoutFetching()); - Iterables.addAll(result, this); + List result = new ArrayList<>(); + while (iterator.hasNext()) { + result.add(iterator.next()); + } return result; } - /** - * Whether all pages have been fetched from the database. - * - *

              If this is {@code false}, it means that more blocking background queries will be triggered - * as iteration continues. - */ - boolean isFullyFetched(); - - /** - * The number of rows that can be returned from this result set before a blocking background query - * needs to be performed to retrieve more results. In other words, this is the number of rows - * remaining in the current page. - */ - int getAvailableWithoutFetching(); - - /** - * Forces the driver to fetch the next page of results, regardless of whether the current page is - * exhausted. - * - *

              If all pages have already been fetched ({@code isFullyFetched() == true}), this method has - * no effect. - * - *

              This can be used to pre-fetch the next page early to improve performance. For example, if - * you want to start fetching the next page as soon as you reach the last 100 rows of the current - * one, you can use: - * - *

              {@code
              -   * Iterator iterator = rs.iterator();
              -   * while (iterator.hasNext()) {
              -   *   if (rs.getAvailableWithoutFetching() == 100) {
              -   *     rs.fetchNextPage();
              -   *   }
              -   *   Row row = iterator.next();
              -   *   ... process the row ...
              -   * }
              -   * }
              - * - *

              Note that, contrary to version 3.x of the driver, this method deliberately avoids returning - * a future. If you want to iterate a multi-page result asynchronously with callbacks, use {@link - * AsyncResultSet}. - */ - void fetchNextPage(); - /** * If the query that produced this result was a conditional update, indicate whether it was * successfully applied. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index 34e5ce51423..2c7ba8e1fbe 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -20,14 +20,11 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; -import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.internal.core.util.CountingIterator; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.shaded.guava.common.collect.AbstractIterator; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.Iterator; import java.util.List; import net.jcip.annotations.NotThreadSafe; @@ -58,21 +55,6 @@ public List getExecutionInfos() { return executionInfos; } - @Override - public boolean isFullyFetched() { - return iterator.isFullyFetched(); - } - - @Override - public int getAvailableWithoutFetching() { - return iterator.remaining(); - } - - @Override - public void fetchNextPage() { - iterator.fetchNextPage(); - } - @NonNull @Override public Iterator iterator() { @@ -84,14 +66,12 @@ public boolean wasApplied() { return iterator.wasApplied(); } - private class RowIterator extends CountingIterator { - // The pages fetched so far. The first is the one we're currently iterating. - private Deque pages = new ArrayDeque<>(); + private class RowIterator extends AbstractIterator { + private AsyncResultSet currentPage; private Iterator currentRows; private RowIterator(AsyncResultSet firstPage) { - super(firstPage.remaining()); - this.pages.add(firstPage); + this.currentPage = firstPage; this.currentRows = firstPage.currentPage().iterator(); } @@ -102,47 +82,21 @@ protected Row computeNext() { } private void maybeMoveToNextPage() { - if (!currentRows.hasNext()) { - fetchNextPage(); - // We've just finished iterating the current page, remove it - pages.removeFirst(); - if (!pages.isEmpty()) { - AsyncResultSet nextPage = pages.getFirst(); - // The definitions can change from page to page if this result set was built from a bound - // 'SELECT *', and the schema was altered. - columnDefinitions = nextPage.getColumnDefinitions(); - currentRows = nextPage.currentPage().iterator(); - } - } - } - - private boolean isFullyFetched() { - return pages.isEmpty() || !pages.getLast().hasMorePages(); - } - - private void fetchNextPage() { - if (!pages.isEmpty()) { - AsyncResultSet lastPage = pages.getLast(); - if (lastPage.hasMorePages()) { - BlockingOperation.checkNotDriverThread(); - AsyncResultSet nextPage = - CompletableFutures.getUninterruptibly(pages.getLast().fetchNextPage()); - executionInfos.add(nextPage.getExecutionInfo()); - pages.offer(nextPage); - remaining += nextPage.remaining(); - } + if (!currentRows.hasNext() && currentPage.hasMorePages()) { + BlockingOperation.checkNotDriverThread(); + AsyncResultSet nextPage = + CompletableFutures.getUninterruptibly(currentPage.fetchNextPage()); + currentPage = nextPage; + currentRows = nextPage.currentPage().iterator(); + executionInfos.add(nextPage.getExecutionInfo()); + // The definitions can change from page to page if this result set was built from a bound + // 'SELECT *', and the schema was altered. + columnDefinitions = nextPage.getColumnDefinitions(); } } private boolean wasApplied() { - if (!columnDefinitions.contains("[applied]") - || !columnDefinitions.get("[applied]").getType().equals(DataTypes.BOOLEAN)) { - return true; - } else if (pages.isEmpty()) { - throw new IllegalStateException("This method must be called before consuming all the rows"); - } else { - return pages.getFirst().wasApplied(); - } + return currentPage.wasApplied(); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java index b2eeefa7e49..ca762a4b4d1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java @@ -54,21 +54,6 @@ public List getExecutionInfos() { return ImmutableList.of(onlyPage.getExecutionInfo()); } - @Override - public boolean isFullyFetched() { - return true; - } - - @Override - public int getAvailableWithoutFetching() { - return onlyPage.remaining(); - } - - @Override - public void fetchNextPage() { - // nothing to do - } - @NonNull @Override public Iterator iterator() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index 59e65c6c87a..e196481adab 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -46,19 +46,12 @@ public void should_create_result_set_from_single_page() { assertThat(resultSet.getColumnDefinitions()).isSameAs(page1.getColumnDefinitions()); assertThat(resultSet.getExecutionInfo()).isSameAs(page1.getExecutionInfo()); assertThat(resultSet.getExecutionInfos()).containsExactly(page1.getExecutionInfo()); - assertThat(resultSet.isFullyFetched()).isTrue(); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); Iterator iterator = resultSet.iterator(); assertNextRow(iterator, 0); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); - assertNextRow(iterator, 1); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); - assertNextRow(iterator, 2); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); assertThat(iterator.hasNext()).isFalse(); } @@ -77,41 +70,27 @@ public void should_create_result_set_from_multiple_pages() { ResultSet resultSet = ResultSets.newInstance(page1); // Then - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); assertThat(resultSet.iterator().hasNext()).isTrue(); assertThat(resultSet.getColumnDefinitions()).isSameAs(page1.getColumnDefinitions()); assertThat(resultSet.getExecutionInfo()).isSameAs(page1.getExecutionInfo()); assertThat(resultSet.getExecutionInfos()).containsExactly(page1.getExecutionInfo()); - assertThat(resultSet.isFullyFetched()).isFalse(); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); Iterator iterator = resultSet.iterator(); assertNextRow(iterator, 0); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); - assertNextRow(iterator, 1); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); - assertNextRow(iterator, 2); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); assertThat(iterator.hasNext()).isTrue(); // This should have triggered the fetch of page2 assertThat(resultSet.getExecutionInfo()).isEqualTo(page2.getExecutionInfo()); assertThat(resultSet.getExecutionInfos()) .containsExactly(page1.getExecutionInfo(), page2.getExecutionInfo()); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); assertNextRow(iterator, 3); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); - assertNextRow(iterator, 4); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); - assertNextRow(iterator, 5); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); assertThat(iterator.hasNext()).isTrue(); // This should have triggered the fetch of page3 @@ -119,70 +98,10 @@ public void should_create_result_set_from_multiple_pages() { assertThat(resultSet.getExecutionInfos()) .containsExactly( page1.getExecutionInfo(), page2.getExecutionInfo(), page3.getExecutionInfo()); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); - - assertNextRow(iterator, 6); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); - - assertNextRow(iterator, 7); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); - - assertNextRow(iterator, 8); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); - } - - @Test - public void should_fetch_multiple_pages_in_advance() { - // Given - AsyncResultSet page1 = mockPage(true, 0, 1, 2); - AsyncResultSet page2 = mockPage(true, 3, 4, 5); - AsyncResultSet page3 = mockPage(false, 6, 7, 8); - - complete(page1.fetchNextPage(), page2); - complete(page2.fetchNextPage(), page3); - - // When - ResultSet resultSet = ResultSets.newInstance(page1); - resultSet.fetchNextPage(); - - // Then - assertThat(resultSet.getExecutionInfo()).isEqualTo(page2.getExecutionInfo()); - assertThat(resultSet.getExecutionInfos()) - .containsExactly(page1.getExecutionInfo(), page2.getExecutionInfo()); - assertThat(resultSet.iterator().hasNext()).isTrue(); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(6); - - // When - resultSet.fetchNextPage(); - - // Then - assertThat(resultSet.getExecutionInfo()).isEqualTo(page3.getExecutionInfo()); - assertThat(resultSet.getExecutionInfos()) - .containsExactly( - page1.getExecutionInfo(), page2.getExecutionInfo(), page3.getExecutionInfo()); - assertThat(resultSet.iterator().hasNext()).isTrue(); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(9); - - Iterator iterator = resultSet.iterator(); - assertNextRow(iterator, 0); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(8); - assertNextRow(iterator, 1); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(7); - assertNextRow(iterator, 2); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(6); - assertNextRow(iterator, 3); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(5); - assertNextRow(iterator, 4); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(4); - assertNextRow(iterator, 5); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(3); assertNextRow(iterator, 6); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); assertNextRow(iterator, 7); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(1); assertNextRow(iterator, 8); - assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(0); } private void assertNextRow(Iterator iterator, int expectedValue) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index 9fa8d0b49da..98908a66e24 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -25,11 +25,11 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; import com.datastax.oss.driver.api.core.cql.DefaultBatchType; import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; @@ -37,10 +37,12 @@ import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import java.time.Duration; import java.util.Optional; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; @@ -222,20 +224,22 @@ public void should_use_profile_page_size() { String query = "SELECT * FROM test where k=0"; // Execute query without profile, should use global page size (100) - ResultSet result = session.execute(query); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); - result.fetchNextPage(); + CompletionStage future = session.executeAsync(query); + AsyncResultSet result = CompletableFutures.getUninterruptibly(future); + assertThat(result.remaining()).isEqualTo(100); + result = CompletableFutures.getUninterruptibly(result.fetchNextPage()); // next fetch should also be 100 pages. - assertThat(result.getAvailableWithoutFetching()).isEqualTo(200); + assertThat(result.remaining()).isEqualTo(100); // Execute query with profile, should use profile page size - result = - session.execute( + future = + session.executeAsync( SimpleStatement.builder(query).withExecutionProfileName("smallpages").build()); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + result = CompletableFutures.getUninterruptibly(future); + assertThat(result.remaining()).isEqualTo(10); // next fetch should also be 10 pages. - result.fetchNextPage(); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(20); + result = CompletableFutures.getUninterruptibly(result.fetchNextPage()); + assertThat(result.remaining()).isEqualTo(10); SessionUtils.dropKeyspace(session, keyspace, slowProfile); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index f7e3212c54a..88ce24928c6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Iterator; +import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -146,11 +147,12 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); + List rows = result.all(); + assertThat(rows).hasSize(100); - Iterator rows = result.iterator(); + Iterator iterator = rows.iterator(); for (int i = 0; i < batchCount; i++) { - Row row = rows.next(); + Row row = iterator.next(); assertThat(row.getString("k0")).isEqualTo(name.getMethodName()); assertThat(row.getInt("k1")).isEqualTo(i); // value should be from first insert (i + 1) if at row divisble by 20, otherwise second. @@ -269,9 +271,10 @@ public void should_execute_counter_batch() { String.format( "SELECT c from counter%d where k0 = '%s'", i, name.getMethodName())); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getLong("c")).isEqualTo(i); } } @@ -358,11 +361,12 @@ private void verifyBatchInsert() { ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(100); + List rows = result.all(); + assertThat(rows).hasSize(100); - Iterator rows = result.iterator(); + Iterator iterator = rows.iterator(); for (int i = 0; i < batchCount; i++) { - Row row = rows.next(); + Row row = iterator.next(); assertThat(row.getString("k0")).isEqualTo(name.getMethodName()); assertThat(row.getInt("k1")).isEqualTo(i); assertThat(row.getInt("v")).isEqualTo(i + 1); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index cfcf013b34f..2af90389dcc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -40,6 +40,7 @@ import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Execute; @@ -53,6 +54,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.junit.Before; @@ -72,7 +74,7 @@ public class BoundStatementIT { @ClassRule public static CcmRule ccm = CcmRule.getInstance(); - private static boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; + private static final boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; @ClassRule public static SessionRule sessionRule = @@ -242,10 +244,11 @@ public void should_use_page_size_from_simple_statement() { try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); PreparedStatement prepared = session.prepare(st); - ResultSet result = session.execute(prepared.bind()); + CompletionStage future = session.executeAsync(prepared.bind()); + AsyncResultSet result = CompletableFutures.getUninterruptibly(future); // Should have only fetched 10 (page size) rows. - assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + assertThat(result.remaining()).isEqualTo(10); } } @@ -256,10 +259,12 @@ public void should_use_page_size() { // overridden by bound statement. SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); PreparedStatement prepared = session.prepare(st); - ResultSet result = session.execute(prepared.bind().setPageSize(12)); + CompletionStage future = + session.executeAsync(prepared.bind().setPageSize(12)); + AsyncResultSet result = CompletableFutures.getUninterruptibly(future); - // Should have only fetched 10 (page size) rows. - assertThat(result.getAvailableWithoutFetching()).isEqualTo(12); + // Should have fetched 12 (page size) rows. + assertThat(result.remaining()).isEqualTo(12); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index 5e19b6c1bcc..260270ffb4c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -28,10 +28,12 @@ import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.util.Bytes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.time.Duration; +import java.util.concurrent.CompletionStage; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -120,15 +122,14 @@ public void should_update_metadata_when_schema_changed_across_pages() { ByteBuffer idBefore = ps.getResultMetadataId(); assertThat(ps.getResultSetDefinitions()).hasSize(3); - ResultSet rows = session.execute(ps.bind()); - assertThat(rows.isFullyFetched()).isFalse(); + CompletionStage future = session.executeAsync(ps.bind()); + AsyncResultSet rows = CompletableFutures.getUninterruptibly(future); assertThat(rows.getColumnDefinitions()).hasSize(3); assertThat(rows.getColumnDefinitions().contains("d")).isFalse(); // Consume the first page - int remaining = rows.getAvailableWithoutFetching(); - while (remaining-- > 0) { + for (Row row : rows.currentPage()) { try { - rows.one().getInt("d"); + row.getInt("d"); fail("expected an error"); } catch (ArrayIndexOutOfBoundsException e) { /*expected*/ @@ -144,7 +145,8 @@ public void should_update_metadata_when_schema_changed_across_pages() { // Then // this should trigger a background fetch of the second page, and therefore update the // definitions - for (Row row : rows) { + rows = CompletableFutures.getUninterruptibly(rows.fetchNextPage()); + for (Row row : rows.currentPage()) { assertThat(row.isNull("d")).isTrue(); } assertThat(rows.getColumnDefinitions()).hasSize(4); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 0e7bb80329d..f8f4aa94104 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -30,12 +30,14 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.cluster.QueryLog; import java.time.Duration; import java.util.List; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.BeforeClass; @@ -180,10 +182,11 @@ public void should_use_timestamp_when_set() { .build(); ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); // then the writetime should equal the timestamp provided. - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getLong("wv")).isEqualTo(timestamp); } @@ -217,9 +220,10 @@ public void should_use_positional_values() { .build(); ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getString("k")).isEqualTo(name.getMethodName()); assertThat(row.getInt("v")).isEqualTo(4); } @@ -243,9 +247,10 @@ public void should_allow_nulls_in_positional_values() { .build(); ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getString("k")).isEqualTo(name.getMethodName()); assertThat(row.getObject("v")).isNull(); } @@ -297,9 +302,10 @@ public void should_use_named_values() { .build(); ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getString("k")).isEqualTo(name.getMethodName()); assertThat(row.getInt("v")).isEqualTo(7); } @@ -323,9 +329,10 @@ public void should_allow_nulls_in_named_values() { .build(); ResultSet result = sessionRule.session().execute(select); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getString("k")).isEqualTo(name.getMethodName()); assertThat(row.getObject("v")).isNull(); } @@ -373,10 +380,11 @@ public void should_use_positional_value_with_case_sensitive_id() { @Test public void should_use_page_size() { Statement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); - ResultSet result = sessionRule.session().execute(st); + CompletionStage future = sessionRule.session().executeAsync(st); + AsyncResultSet result = CompletableFutures.getUninterruptibly(future); // Should have only fetched 10 (page size) rows. - assertThat(result.getAvailableWithoutFetching()).isEqualTo(10); + assertThat(result.remaining()).isEqualTo(10); } @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index f15a307c1e3..a457f2e28cc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -623,9 +623,10 @@ private void readValue( String columnName = columnNameFor(dataType); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.iterator().next(); + Row row = rows.iterator().next(); K expectedValue = expectedPrimitiveValue != null ? expectedPrimitiveValue : value; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 4547823f378..8f2cbe585b0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -40,6 +40,7 @@ import java.nio.ByteBuffer; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -149,10 +150,11 @@ public void should_throw_exception_if_no_codec_registered_for_type_get() { .addPositionalValue(name.getMethodName()) .build()); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); // should not be able to access int column as float as no codec is registered to handle that. - Row row = result.iterator().next(); + Row row = rows.iterator().next(); thrown.expect(CodecNotFoundException.class); @@ -186,11 +188,12 @@ public void should_be_able_to_register_and_use_custom_codec() { .addPositionalValue(name.getMethodName()) .build()); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); // should be able to retrieve value back as float, some precision is lost due to going from // int -> float. - Row row = result.iterator().next(); + Row row = rows.iterator().next(); assertThat(row.getFloat("v")).isEqualTo(3.0f); assertThat(row.getFloat(0)).isEqualTo(3.0f); } @@ -270,7 +273,7 @@ protected Optional decode(T value) { @Override protected T encode(Optional value) { - return value.isPresent() ? value.get() : null; + return value.orElse(null); } } @@ -339,18 +342,19 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() .addPositionalValues(name.getMethodName()) .build()); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(3); + List rows = result.all(); + assertThat(rows).hasSize(3); - Iterator rows = result.iterator(); + Iterator iterator = rows.iterator(); // row (at key 0) should have v0 - Row row = rows.next(); + Row row = iterator.next(); // should be able to retrieve value back as an optional map. assertThat(row.get(0, optionalMapCodec.getJavaType())).isEqualTo(v0Opt); // should be able to retrieve value back as map. assertThat(row.getMap(0, Integer.class, String.class)).isEqualTo(v0); // next row (at key 1) should be absent (null value). - row = rows.next(); + row = iterator.next(); // value should be null. assertThat(row.isNull(0)).isTrue(); // getting with codec should return Optional.empty() @@ -359,7 +363,7 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() assertThat(row.getMap(0, Integer.class, String.class)).isEmpty(); // next row (at key 2) should have v2 - row = rows.next(); + row = iterator.next(); // getting with codec should return with the correct type. assertThat(row.get(0, mapWithOptionalValueCodec.getJavaType())).isEqualTo(v2Map); // getting with map should return a map without optional value. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index e3450b6d799..40c2366f82a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -83,6 +83,6 @@ public RequestProcessorRegistry buildRequestProcessorRegistry() { new GuavaRequestAsyncProcessor<>( cqlPrepareAsyncProcessor, PrepareRequest.class, GuavaSession.ASYNC_PREPARED), // Register KeyRequestProcessor for handling KeyRequest and returning Integer. - new KeyRequestProcessor(cqlRequestSyncProcessor)); + new KeyRequestProcessor(cqlRequestAsyncProcessor)); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java index 8390f661589..6e88fa83b88 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java @@ -15,30 +15,32 @@ */ package com.datastax.oss.driver.example.guava.internal; -import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.RequestProcessorIT; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import java.util.concurrent.CompletionStage; /** * A request processor that takes a given {@link KeyRequest#getKey} and generates a query, delegates - * it to {@link CqlRequestSyncProcessor} to get the integer value of a row and return it as a + * it to {@link CqlRequestAsyncProcessor} to get the integer value of a row and return it as a * result. */ public class KeyRequestProcessor implements RequestProcessor { public static final GenericType INT_TYPE = GenericType.of(Integer.class); - private final CqlRequestSyncProcessor subProcessor; + private final CqlRequestAsyncProcessor subProcessor; - KeyRequestProcessor(CqlRequestSyncProcessor subProcessor) { + KeyRequestProcessor(CqlRequestAsyncProcessor subProcessor) { this.subProcessor = subProcessor; } @@ -57,27 +59,28 @@ public RequestHandler newHandler( SimpleStatement statement = SimpleStatement.newInstance( "select v1 from test where k = ? and v0 = ?", RequestProcessorIT.KEY, request.getKey()); - RequestHandler, ResultSet> subHandler = + RequestHandler, CompletionStage> subHandler = subProcessor.newHandler(statement, session, context, sessionLogPrefix); return new KeyRequestHandler(subHandler); } static class KeyRequestHandler implements RequestHandler { - private final RequestHandler, ResultSet> subHandler; + private final RequestHandler, CompletionStage> subHandler; - KeyRequestHandler(RequestHandler, ResultSet> subHandler) { + KeyRequestHandler(RequestHandler, CompletionStage> subHandler) { this.subHandler = subHandler; } @Override public Integer handle() { - ResultSet result = subHandler.handle(); + CompletionStage future = subHandler.handle(); + AsyncResultSet result = CompletableFutures.getUninterruptibly(future); // If not exactly 1 rows were found, return Integer.MIN_VALUE, otherwise return the value. - if (result.getAvailableWithoutFetching() != 1) { + if (result.remaining() != 1) { return Integer.MIN_VALUE; } else { - return result.iterator().next().getInt("v1"); + return result.currentPage().iterator().next().getInt("v1"); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java index a9a7263373b..16ee962c991 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.categories.IsolatedTests; +import java.util.List; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -64,9 +65,10 @@ public void should_connect_and_query() { try (CqlSession session = builder.build()) { ResultSet result = session.execute(selectFrom("system", "local").all().build()); - assertThat(result.getAvailableWithoutFetching()).isEqualTo(1); + List rows = result.all(); + assertThat(rows).hasSize(1); - Row row = result.one(); + Row row = rows.get(0); assertThat(row.getString("key")).isEqualTo("local"); } } diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 097a763f749..a26017e8b3e 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -94,13 +94,24 @@ In 3.x, both synchronous and asynchronous execution models shared a common resul implementation. This made asynchronous usage [notably error-prone][3.x async paging], because of the risk of accidentally triggering background synchronous fetches. -There are now two separate APIs: synchronous queries return a `ResultSet` which behaves like its 3.x -counterpart; asynchronous queries return a future of `AsyncResultSet`, a simplified type that only -contains the rows of the current page. When iterating asynchronously, you no longer need to stop the -iteration manually: just consume all the rows in the iterator, and then call `fetchNextPage` to -retrieve the next page asynchronously. +There are now two separate APIs: synchronous queries return a `ResultSet`; asynchronous queries +return a future of `AsyncResultSet`. + +`ResultSet` behaves much like its 3.x counterpart, except that background pre-fetching was +deliberately removed, in order to keep this interface simple and intuitive. This is why methods such +as `fetchMoreResults`, `getAvailableWithoutFetching` and `isFullyFetched` have disappeared. If you +were using synchronous iterations with background pre-fetching, you should now switch to fully +asynchronous iterations (see below). + +`AsyncResultSet` is a simplified type that only contains the rows of the current page. When +iterating asynchronously, you no longer need to stop the iteration manually: just consume all the +rows in the iterator, and then call `fetchNextPage` to retrieve the next page asynchronously. You +will find more information about asynchronous iterations in the manual pages about [asynchronous +programming][4.x async programming] and [paging][4.x paging]. [3.x async paging]: http://docs.datastax.com/en/developer/java-driver/3.2/manual/async/#async-paging +[4.x async programming]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/async/ +[4.x paging]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/paging/ #### Simplified request timeout From 75df423c74d83b08a4a450dda44be7ce84855411 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 15 Oct 2018 10:43:01 -0700 Subject: [PATCH 589/742] Log CCM error output at ERROR level --- .../com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 809b0c41076..54783a3d664 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -304,7 +304,7 @@ protected void processLine(String line, int logLevel) { new LogOutputStream() { @Override protected void processLine(String line, int logLevel) { - logger.warn("ccmerr> {}", line); + logger.error("ccmerr> {}", line); } }) { Executor executor = new DefaultExecutor(); From de127a4cdab2c5979625d8dd52e4420a2157e513 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 15 Oct 2018 11:56:29 -0700 Subject: [PATCH 590/742] Run CI in batch mode --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index dcafd823f29..79dd3b2c84e 100644 --- a/build.yaml +++ b/build.yaml @@ -10,7 +10,7 @@ cassandra: build: - type: maven version: 3.2.5 - goals: verify + goals: verify --batch-mode properties: | ccm.version=$CCM_CASSANDRA_VERSION - xunit: From a559b4ec04220d70b9ab9234c8d604a0866cab3e Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 15 Oct 2018 13:49:07 -0700 Subject: [PATCH 591/742] Fix error messages if DseRequirement not met --- .../datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java index 3b5df66aef0..42c754f0dba 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java @@ -115,7 +115,7 @@ public void evaluate() { if (!dseRequirement.min().isEmpty()) { Version minVersion = Version.parse(dseRequirement.min()); if (minVersion.compareTo(dseVersion) > 0) { - return buildErrorStatement(dseVersion, dseRequirement.description(), false, true); + return buildErrorStatement(minVersion, dseRequirement.description(), false, true); } } @@ -123,7 +123,7 @@ public void evaluate() { Version maxVersion = Version.parse(dseRequirement.max()); if (maxVersion.compareTo(ccmBridge.getCassandraVersion()) <= 0) { - return buildErrorStatement(dseVersion, dseRequirement.description(), true, true); + return buildErrorStatement(maxVersion, dseRequirement.description(), true, true); } } } From 070aa552d2fa78f39b41d54f3bf56a7c231f6209 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 8 Oct 2018 11:57:19 -0700 Subject: [PATCH 592/742] JAVA-1960: Ensure type parameters have meaningful names --- core/revapi.json | 2 +- .../api/core/cql/BatchableStatement.java | 5 +- .../oss/driver/api/core/cql/Bindable.java | 10 +-- .../oss/driver/api/core/cql/Statement.java | 40 +++++----- .../driver/api/core/cql/StatementBuilder.java | 43 ++++++----- .../driver/api/core/data/GettableById.java | 16 ++-- .../driver/api/core/data/GettableByIndex.java | 17 ++-- .../driver/api/core/data/GettableByName.java | 16 ++-- .../driver/api/core/data/SettableById.java | 77 ++++++++++--------- .../driver/api/core/data/SettableByIndex.java | 69 +++++++++-------- .../driver/api/core/data/SettableByName.java | 72 +++++++++-------- .../driver/api/core/type/codec/TypeCodec.java | 14 ++-- .../type/codec/registry/CodecRegistry.java | 16 ++-- .../internal/core/context/EventBus.java | 4 +- .../internal/core/type/codec/ListCodec.java | 28 +++---- .../internal/core/type/codec/MapCodec.java | 38 ++++----- .../internal/core/type/codec/SetCodec.java | 28 +++---- .../codec/registry/CachingCodecRegistry.java | 27 ++++--- .../driver/internal/core/util/ArrayUtils.java | 19 +++-- .../internal/core/util/CountingIterator.java | 14 ++-- .../internal/core/util/DirectedGraph.java | 28 +++---- .../driver/internal/core/util/Reflection.java | 20 ++--- .../core/util/concurrent/Debouncer.java | 18 ++--- .../util/concurrent/ReplayingEventFilter.java | 14 ++-- .../internal/querybuilder/DefaultLiteral.java | 14 ++-- 25 files changed, 344 insertions(+), 305 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 4f1c7c56c17..8cde48c19fa 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -514,7 +514,7 @@ }, { "code": "java.method.addedToInterface", - "new": "method T com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node)", "package": "com.datastax.oss.driver.api.core.cql", "classQualifiedName": "com.datastax.oss.driver.api.core.cql.Statement", "classSimpleName": "Statement", diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java index 98636805d62..5fb50fc5348 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchableStatement.java @@ -18,6 +18,7 @@ /** * A statement that can be added to a CQL batch. * - * @param the "self type" used for covariant returns in subtypes. + * @param the "self type" used for covariant returns in subtypes. */ -public interface BatchableStatement> extends Statement {} +public interface BatchableStatement> + extends Statement {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java index 51ec42e9f3e..0542f44b168 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java @@ -25,8 +25,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; /** A data container with the ability to unset values. */ -public interface Bindable> - extends GettableById, GettableByName, SettableById, SettableByName { +public interface Bindable> + extends GettableById, GettableByName, SettableById, SettableByName { /** * Whether the {@code i}th value has been set. * @@ -70,7 +70,7 @@ default boolean isSet(@NonNull String name) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T unset(int i) { + default SelfT unset(int i) { return setBytesUnsafe(i, ProtocolConstants.UNSET_VALUE); } @@ -81,7 +81,7 @@ default T unset(int i) { * @throws IndexOutOfBoundsException if the id is invalid. */ @NonNull - default T unset(@NonNull CqlIdentifier id) { + default SelfT unset(@NonNull CqlIdentifier id) { return setBytesUnsafe(id, ProtocolConstants.UNSET_VALUE); } @@ -92,7 +92,7 @@ default T unset(@NonNull CqlIdentifier id) { * @throws IndexOutOfBoundsException if the name is invalid. */ @NonNull - default T unset(@NonNull String name) { + default SelfT unset(@NonNull String name) { return setBytesUnsafe(name, ProtocolConstants.UNSET_VALUE); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 873064092a4..c5abf218957 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -41,9 +41,9 @@ /** * A request to execute a CQL query. * - * @param the "self type" used for covariant returns in subtypes. + * @param the "self type" used for covariant returns in subtypes. */ -public interface Statement> extends Request { +public interface Statement> extends Request { // Implementation note: "CqlRequest" would be a better name, but we keep "Statement" to match // previous driver versions. @@ -76,7 +76,7 @@ public interface Statement> extends Request { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull - T setExecutionProfileName(@Nullable String newConfigProfileName); + SelfT setExecutionProfileName(@Nullable String newConfigProfileName); /** * Sets the execution profile to use for this statement. @@ -85,7 +85,7 @@ public interface Statement> extends Request { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull - T setExecutionProfile(@Nullable DriverExecutionProfile newProfile); + SelfT setExecutionProfile(@Nullable DriverExecutionProfile newProfile); /** * Sets the keyspace to use for token-aware routing. @@ -95,7 +95,7 @@ public interface Statement> extends Request { * @param newRoutingKeyspace The keyspace to use, or {@code null} to disable token-aware routing. */ @NonNull - T setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeyspace); + SelfT setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeyspace); /** * Sets the {@link Node} that should handle this query. @@ -119,7 +119,7 @@ public interface Statement> extends Request { * delegate to the configured load balancing policy. */ @NonNull - T setNode(@Nullable Node node); + SelfT setNode(@Nullable Node node); /** * Shortcut for {@link #setRoutingKeyspace(CqlIdentifier) @@ -129,7 +129,7 @@ public interface Statement> extends Request { * routing. */ @NonNull - default T setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { + default SelfT setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { return setRoutingKeyspace( newRoutingKeyspaceName == null ? null : CqlIdentifier.fromCql(newRoutingKeyspaceName)); } @@ -142,7 +142,7 @@ default T setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { * @param newRoutingKey The routing key to use, or {@code null} to disable token-aware routing. */ @NonNull - T setRoutingKey(@Nullable ByteBuffer newRoutingKey); + SelfT setRoutingKey(@Nullable ByteBuffer newRoutingKey); /** * Sets the key to use for token-aware routing, when the partition key has multiple components. @@ -152,7 +152,7 @@ default T setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { * can be {@code null}. */ @NonNull - default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { + default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { return setRoutingKey(RoutingKey.compose(newRoutingKeyComponents)); } @@ -165,7 +165,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * routing. */ @NonNull - T setRoutingToken(@Nullable Token newRoutingToken); + SelfT setRoutingToken(@Nullable Token newRoutingToken); /** * Sets the custom payload to use for execution. @@ -179,7 +179,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * it's never modified after being set on the statement). */ @NonNull - T setCustomPayload(@NonNull Map newCustomPayload); + SelfT setCustomPayload(@NonNull Map newCustomPayload); /** * Sets the idempotence to use for execution. @@ -191,7 +191,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * use the default idempotence defined in the configuration. */ @NonNull - T setIdempotent(@Nullable Boolean newIdempotence); + SelfT setIdempotent(@Nullable Boolean newIdempotence); /** * Sets tracing for execution. @@ -200,7 +200,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull - T setTracing(boolean newTracing); + SelfT setTracing(boolean newTracing); /** * Returns the query timestamp, in microseconds, to send with the statement. @@ -224,7 +224,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see TimestampGenerator */ @NonNull - T setTimestamp(long newTimestamp); + SelfT setTimestamp(long newTimestamp); /** * Sets how long to wait for this request to complete. This is a global limit on the duration of a @@ -235,7 +235,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_TIMEOUT */ @NonNull - T setTimeout(@Nullable Duration newTimeout); + SelfT setTimeout(@Nullable Duration newTimeout); /** * Returns the paging state to send with the statement, or {@code null} if this statement has no @@ -265,7 +265,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * if you do so, you must override {@link #copy(ByteBuffer)}. */ @NonNull - T setPagingState(@Nullable ByteBuffer newPagingState); + SelfT setPagingState(@Nullable ByteBuffer newPagingState); /** * Returns the page size to use for the statement. @@ -285,7 +285,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_PAGE_SIZE */ @NonNull - T setPageSize(int newPageSize); + SelfT setPageSize(int newPageSize); /** * Returns the {@link ConsistencyLevel} to use for the statement. @@ -304,7 +304,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * defined in the configuration. * @see DefaultDriverOption#REQUEST_CONSISTENCY */ - T setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel); + SelfT setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel); /** * Returns the serial {@link ConsistencyLevel} to use for the statement. @@ -324,7 +324,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_SERIAL_CONSISTENCY */ @NonNull - T setSerialConsistencyLevel(@Nullable ConsistencyLevel newSerialConsistencyLevel); + SelfT setSerialConsistencyLevel(@Nullable ConsistencyLevel newSerialConsistencyLevel); /** Whether tracing information should be recorded for this statement. */ boolean isTracing(); @@ -351,7 +351,7 @@ default T setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * your own mutable implementation, make sure it returns a different instance. */ @NonNull - default T copy(@Nullable ByteBuffer newPagingState) { + default SelfT copy(@Nullable ByteBuffer newPagingState) { return setPagingState(newPagingState); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 6e4f239c765..b723fafc4b5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -36,10 +36,11 @@ * @see PreparedStatement#boundStatementBuilder(Object...) */ @NotThreadSafe -public abstract class StatementBuilder, S extends Statement> { +public abstract class StatementBuilder< + SelfT extends StatementBuilder, StatementT extends Statement> { @SuppressWarnings("unchecked") - private final T self = (T) this; + private final SelfT self = (SelfT) this; @Nullable protected String executionProfileName; @Nullable protected DriverExecutionProfile executionProfile; @@ -61,7 +62,7 @@ protected StatementBuilder() { // nothing to do } - protected StatementBuilder(S template) { + protected StatementBuilder(StatementT template) { this.executionProfileName = template.getExecutionProfileName(); this.executionProfile = template.getExecutionProfile(); this.routingKeyspace = template.getRoutingKeyspace(); @@ -85,14 +86,14 @@ protected StatementBuilder(S template) { /** @see Statement#setExecutionProfileName(String) */ @NonNull - public T withExecutionProfileName(@Nullable String executionProfileName) { + public SelfT withExecutionProfileName(@Nullable String executionProfileName) { this.executionProfileName = executionProfileName; return self; } /** @see Statement#setExecutionProfile(DriverExecutionProfile) */ @NonNull - public T withExecutionProfile(@Nullable DriverExecutionProfile executionProfile) { + public SelfT withExecutionProfile(@Nullable DriverExecutionProfile executionProfile) { this.executionProfile = executionProfile; this.executionProfileName = null; return self; @@ -100,7 +101,7 @@ public T withExecutionProfile(@Nullable DriverExecutionProfile executionProfile) /** @see Statement#setRoutingKeyspace(CqlIdentifier) */ @NonNull - public T withRoutingKeyspace(@Nullable CqlIdentifier routingKeyspace) { + public SelfT withRoutingKeyspace(@Nullable CqlIdentifier routingKeyspace) { this.routingKeyspace = routingKeyspace; return self; } @@ -110,28 +111,28 @@ public T withRoutingKeyspace(@Nullable CqlIdentifier routingKeyspace) { * withRoutingKeyspace(CqlIdentifier.fromCql(routingKeyspaceName))}. */ @NonNull - public T withRoutingKeyspace(@Nullable String routingKeyspaceName) { + public SelfT withRoutingKeyspace(@Nullable String routingKeyspaceName) { return withRoutingKeyspace( routingKeyspaceName == null ? null : CqlIdentifier.fromCql(routingKeyspaceName)); } /** @see Statement#setRoutingKey(ByteBuffer) */ @NonNull - public T withRoutingKey(@Nullable ByteBuffer routingKey) { + public SelfT withRoutingKey(@Nullable ByteBuffer routingKey) { this.routingKey = routingKey; return self; } /** @see Statement#setRoutingToken(Token) */ @NonNull - public T withRoutingToken(@Nullable Token routingToken) { + public SelfT withRoutingToken(@Nullable Token routingToken) { this.routingToken = routingToken; return self; } /** @see Statement#setCustomPayload(Map) */ @NonNull - public T addCustomPayload(@NonNull String key, @Nullable ByteBuffer value) { + public SelfT addCustomPayload(@NonNull String key, @Nullable ByteBuffer value) { if (customPayloadBuilder == null) { customPayloadBuilder = NullAllowingImmutableMap.builder(); } @@ -141,69 +142,69 @@ public T addCustomPayload(@NonNull String key, @Nullable ByteBuffer value) { /** @see Statement#setCustomPayload(Map) */ @NonNull - public T clearCustomPayload() { + public SelfT clearCustomPayload() { customPayloadBuilder = null; return self; } /** @see Statement#setIdempotent(Boolean) */ @NonNull - public T withIdempotence(@Nullable Boolean idempotent) { + public SelfT withIdempotence(@Nullable Boolean idempotent) { this.idempotent = idempotent; return self; } /** @see Statement#setTracing(boolean) */ @NonNull - public T withTracing() { + public SelfT withTracing() { this.tracing = true; return self; } /** @see Statement#setTimestamp(long) */ @NonNull - public T withTimestamp(long timestamp) { + public SelfT withTimestamp(long timestamp) { this.timestamp = timestamp; return self; } /** @see Statement#setPagingState(ByteBuffer) */ @NonNull - public T withPagingState(@Nullable ByteBuffer pagingState) { + public SelfT withPagingState(@Nullable ByteBuffer pagingState) { this.pagingState = pagingState; return self; } /** @see Statement#setPageSize(int) */ @NonNull - public T withPageSize(int pageSize) { + public SelfT withPageSize(int pageSize) { this.pageSize = pageSize; return self; } /** @see Statement#setConsistencyLevel(ConsistencyLevel) */ @NonNull - public T withConsistencyLevel(@Nullable ConsistencyLevel consistencyLevel) { + public SelfT withConsistencyLevel(@Nullable ConsistencyLevel consistencyLevel) { this.consistencyLevel = consistencyLevel; return self; } /** @see Statement#setSerialConsistencyLevel(ConsistencyLevel) */ @NonNull - public T withSerialConsistencyLevel(@Nullable ConsistencyLevel serialConsistencyLevel) { + public SelfT withSerialConsistencyLevel(@Nullable ConsistencyLevel serialConsistencyLevel) { this.serialConsistencyLevel = serialConsistencyLevel; return self; } /** @see Statement#setTimeout(Duration) */ @NonNull - public T withTimeout(@Nullable Duration timeout) { + public SelfT withTimeout(@Nullable Duration timeout) { this.timeout = timeout; return self; } /** @see Statement#setNode(Node) */ - public T withNode(@Nullable Node node) { + public SelfT withNode(@Nullable Node node) { this.node = node; return self; } @@ -216,5 +217,5 @@ protected Map buildCustomPayload() { } @NonNull - public abstract S build(); + public abstract StatementT build(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java index 45135b1639d..bf0ccfe1f2b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableById.java @@ -96,7 +96,7 @@ default boolean isNull(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @Nullable - default T get(@NonNull CqlIdentifier id, @NonNull TypeCodec codec) { + default ValueT get(@NonNull CqlIdentifier id, @NonNull TypeCodec codec) { return get(firstIndexOf(id), codec); } @@ -118,7 +118,7 @@ default T get(@NonNull CqlIdentifier id, @NonNull TypeCodec codec) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(@NonNull CqlIdentifier id, @NonNull GenericType targetType) { + default ValueT get(@NonNull CqlIdentifier id, @NonNull GenericType targetType) { return get(firstIndexOf(id), targetType); } @@ -139,7 +139,7 @@ default T get(@NonNull CqlIdentifier id, @NonNull GenericType targetType) * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(@NonNull CqlIdentifier id, @NonNull Class targetClass) { + default ValueT get(@NonNull CqlIdentifier id, @NonNull Class targetClass) { return get(firstIndexOf(id), targetClass); } @@ -551,7 +551,8 @@ default Token getToken(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @Nullable - default List getList(@NonNull CqlIdentifier id, @NonNull Class elementsClass) { + default List getList( + @NonNull CqlIdentifier id, @NonNull Class elementsClass) { return getList(firstIndexOf(id), elementsClass); } @@ -576,7 +577,8 @@ default List getList(@NonNull CqlIdentifier id, @NonNull Class element * @throws IllegalArgumentException if the id is invalid. */ @Nullable - default Set getSet(@NonNull CqlIdentifier id, @NonNull Class elementsClass) { + default Set getSet( + @NonNull CqlIdentifier id, @NonNull Class elementsClass) { return getSet(firstIndexOf(id), elementsClass); } @@ -601,8 +603,8 @@ default Set getSet(@NonNull CqlIdentifier id, @NonNull Class elementsC * @throws IllegalArgumentException if the id is invalid. */ @Nullable - default Map getMap( - @NonNull CqlIdentifier id, @NonNull Class keyClass, @NonNull Class valueClass) { + default Map getMap( + @NonNull CqlIdentifier id, @NonNull Class keyClass, @NonNull Class valueClass) { return getMap(firstIndexOf(id), keyClass, valueClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java index 3c7c4b1615c..177fd654507 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByIndex.java @@ -86,7 +86,7 @@ default boolean isNull(int i) { * @throws IndexOutOfBoundsException if the index is invalid. */ @Nullable - default T get(int i, TypeCodec codec) { + default ValueT get(int i, TypeCodec codec) { return codec.decode(getBytesUnsafe(i), protocolVersion()); } @@ -102,9 +102,9 @@ default T get(int i, TypeCodec codec) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(int i, GenericType targetType) { + default ValueT get(int i, GenericType targetType) { DataType cqlType = getType(i); - TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); return get(i, codec); } @@ -119,11 +119,11 @@ default T get(int i, GenericType targetType) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(int i, Class targetClass) { + default ValueT get(int i, Class targetClass) { // This is duplicated from the GenericType variant, because we want to give the codec registry // a chance to process the unwrapped class directly, if it can do so in a more efficient way. DataType cqlType = getType(i); - TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); return get(i, codec); } @@ -473,7 +473,7 @@ default Token getToken(int i) { * @throws IndexOutOfBoundsException if the index is invalid. */ @Nullable - default List getList(int i, @NonNull Class elementsClass) { + default List getList(int i, @NonNull Class elementsClass) { return get(i, GenericType.listOf(elementsClass)); } @@ -492,7 +492,7 @@ default List getList(int i, @NonNull Class elementsClass) { * @throws IndexOutOfBoundsException if the index is invalid. */ @Nullable - default Set getSet(int i, @NonNull Class elementsClass) { + default Set getSet(int i, @NonNull Class elementsClass) { return get(i, GenericType.setOf(elementsClass)); } @@ -511,7 +511,8 @@ default Set getSet(int i, @NonNull Class elementsClass) { * @throws IndexOutOfBoundsException if the index is invalid. */ @Nullable - default Map getMap(int i, @NonNull Class keyClass, @NonNull Class valueClass) { + default Map getMap( + int i, @NonNull Class keyClass, @NonNull Class valueClass) { return get(i, GenericType.mapOf(keyClass, valueClass)); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java index 4de5d9857bf..c1aca1576c6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/GettableByName.java @@ -95,7 +95,7 @@ default boolean isNull(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @Nullable - default T get(@NonNull String name, @NonNull TypeCodec codec) { + default ValueT get(@NonNull String name, @NonNull TypeCodec codec) { return get(firstIndexOf(name), codec); } @@ -118,7 +118,7 @@ default T get(@NonNull String name, @NonNull TypeCodec codec) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(@NonNull String name, @NonNull GenericType targetType) { + default ValueT get(@NonNull String name, @NonNull GenericType targetType) { return get(firstIndexOf(name), targetType); } @@ -140,7 +140,7 @@ default T get(@NonNull String name, @NonNull GenericType targetType) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @Nullable - default T get(@NonNull String name, @NonNull Class targetClass) { + default ValueT get(@NonNull String name, @NonNull Class targetClass) { return get(firstIndexOf(name), targetClass); } @@ -547,7 +547,8 @@ default Token getToken(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @Nullable - default List getList(@NonNull String name, @NonNull Class elementsClass) { + default List getList( + @NonNull String name, @NonNull Class elementsClass) { return getList(firstIndexOf(name), elementsClass); } @@ -572,7 +573,8 @@ default List getList(@NonNull String name, @NonNull Class elementsClas * @throws IllegalArgumentException if the name is invalid. */ @Nullable - default Set getSet(@NonNull String name, @NonNull Class elementsClass) { + default Set getSet( + @NonNull String name, @NonNull Class elementsClass) { return getSet(firstIndexOf(name), elementsClass); } @@ -597,8 +599,8 @@ default Set getSet(@NonNull String name, @NonNull Class elementsClass) * @throws IllegalArgumentException if the name is invalid. */ @Nullable - default Map getMap( - @NonNull String name, @NonNull Class keyClass, @NonNull Class valueClass) { + default Map getMap( + @NonNull String name, @NonNull Class keyClass, @NonNull Class valueClass) { return getMap(firstIndexOf(name), keyClass, valueClass); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index 754b3fce5ef..943c499e9cf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -36,8 +36,8 @@ import java.util.UUID; /** A data structure that provides methods to set its values via a CQL identifier. */ -public interface SettableById> - extends SettableByIndex, AccessibleById { +public interface SettableById> + extends SettableByIndex, AccessibleById { /** * Sets the raw binary representation of the value for the first occurrence of {@code id}. @@ -56,7 +56,7 @@ public interface SettableById> * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setBytesUnsafe(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { + default SelfT setBytesUnsafe(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { return setBytesUnsafe(firstIndexOf(id), v); } @@ -75,7 +75,7 @@ default DataType getType(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setToNull(@NonNull CqlIdentifier id) { + default SelfT setToNull(@NonNull CqlIdentifier id) { return setToNull(firstIndexOf(id)); } @@ -96,7 +96,8 @@ default T setToNull(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull TypeCodec codec) { + default SelfT set( + @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull TypeCodec codec) { return set(firstIndexOf(id), v, codec); } @@ -115,7 +116,8 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull TypeCodec T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull GenericType targetType) { + default SelfT set( + @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull GenericType targetType) { return set(firstIndexOf(id), v, targetType); } @@ -133,7 +135,8 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull GenericType * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull - default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull Class targetClass) { + default SelfT set( + @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull Class targetClass) { return set(firstIndexOf(id), v, targetClass); } @@ -151,7 +154,7 @@ default T set(@NonNull CqlIdentifier id, @Nullable V v, @NonNull Class ta * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setBoolean(@NonNull CqlIdentifier id, boolean v) { + default SelfT setBoolean(@NonNull CqlIdentifier id, boolean v) { return setBoolean(firstIndexOf(id), v); } @@ -169,7 +172,7 @@ default T setBoolean(@NonNull CqlIdentifier id, boolean v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setByte(@NonNull CqlIdentifier id, byte v) { + default SelfT setByte(@NonNull CqlIdentifier id, byte v) { return setByte(firstIndexOf(id), v); } @@ -187,7 +190,7 @@ default T setByte(@NonNull CqlIdentifier id, byte v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setDouble(@NonNull CqlIdentifier id, double v) { + default SelfT setDouble(@NonNull CqlIdentifier id, double v) { return setDouble(firstIndexOf(id), v); } @@ -205,7 +208,7 @@ default T setDouble(@NonNull CqlIdentifier id, double v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setFloat(@NonNull CqlIdentifier id, float v) { + default SelfT setFloat(@NonNull CqlIdentifier id, float v) { return setFloat(firstIndexOf(id), v); } @@ -223,7 +226,7 @@ default T setFloat(@NonNull CqlIdentifier id, float v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setInt(@NonNull CqlIdentifier id, int v) { + default SelfT setInt(@NonNull CqlIdentifier id, int v) { return setInt(firstIndexOf(id), v); } @@ -241,7 +244,7 @@ default T setInt(@NonNull CqlIdentifier id, int v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setLong(@NonNull CqlIdentifier id, long v) { + default SelfT setLong(@NonNull CqlIdentifier id, long v) { return setLong(firstIndexOf(id), v); } @@ -259,7 +262,7 @@ default T setLong(@NonNull CqlIdentifier id, long v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setShort(@NonNull CqlIdentifier id, short v) { + default SelfT setShort(@NonNull CqlIdentifier id, short v) { return setShort(firstIndexOf(id), v); } @@ -274,7 +277,7 @@ default T setShort(@NonNull CqlIdentifier id, short v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { + default SelfT setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { return setInstant(firstIndexOf(id), v); } @@ -289,7 +292,7 @@ default T setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { + default SelfT setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { return setLocalDate(firstIndexOf(id), v); } @@ -304,7 +307,7 @@ default T setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { + default SelfT setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { return setLocalTime(firstIndexOf(id), v); } @@ -319,7 +322,7 @@ default T setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { + default SelfT setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { return setByteBuffer(firstIndexOf(id), v); } @@ -334,7 +337,7 @@ default T setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setString(@NonNull CqlIdentifier id, @Nullable String v) { + default SelfT setString(@NonNull CqlIdentifier id, @Nullable String v) { return setString(firstIndexOf(id), v); } @@ -349,7 +352,7 @@ default T setString(@NonNull CqlIdentifier id, @Nullable String v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { + default SelfT setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { return setBigInteger(firstIndexOf(id), v); } @@ -364,7 +367,7 @@ default T setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { + default SelfT setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { return setBigDecimal(firstIndexOf(id), v); } @@ -379,7 +382,7 @@ default T setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { + default SelfT setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { return setUuid(firstIndexOf(id), v); } @@ -394,7 +397,7 @@ default T setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { + default SelfT setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { return setInetAddress(firstIndexOf(id), v); } @@ -409,7 +412,7 @@ default T setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { + default SelfT setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { return setCqlDuration(firstIndexOf(id), v); } @@ -426,7 +429,7 @@ default T setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { * @throws IllegalArgumentException if the index is invalid. */ @NonNull - default T setToken(@NonNull CqlIdentifier id, @NonNull Token v) { + default SelfT setToken(@NonNull CqlIdentifier id, @NonNull Token v) { return setToken(firstIndexOf(id), v); } @@ -444,8 +447,10 @@ default T setToken(@NonNull CqlIdentifier id, @NonNull Token v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setList( - @NonNull CqlIdentifier id, @Nullable List v, @NonNull Class elementsClass) { + default SelfT setList( + @NonNull CqlIdentifier id, + @Nullable List v, + @NonNull Class elementsClass) { return setList(firstIndexOf(id), v, elementsClass); } @@ -463,8 +468,10 @@ default T setList( * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setSet( - @NonNull CqlIdentifier id, @Nullable Set v, @NonNull Class elementsClass) { + default SelfT setSet( + @NonNull CqlIdentifier id, + @Nullable Set v, + @NonNull Class elementsClass) { return setSet(firstIndexOf(id), v, elementsClass); } @@ -482,11 +489,11 @@ default T setSet( * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setMap( + default SelfT setMap( @NonNull CqlIdentifier id, - @Nullable Map v, - @NonNull Class keyClass, - @NonNull Class valueClass) { + @Nullable Map v, + @NonNull Class keyClass, + @NonNull Class valueClass) { return setMap(firstIndexOf(id), v, keyClass, valueClass); } @@ -501,7 +508,7 @@ default T setMap( * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { + default SelfT setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { return setUdtValue(firstIndexOf(id), v); } @@ -516,7 +523,7 @@ default T setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull - default T setTupleValue(@NonNull CqlIdentifier id, @Nullable TupleValue v) { + default SelfT setTupleValue(@NonNull CqlIdentifier id, @Nullable TupleValue v) { return setTupleValue(firstIndexOf(id), v); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java index 03a39e58651..a3d3d79c084 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -45,7 +45,7 @@ import java.util.UUID; /** A data structure that provides methods to set its values via an integer index. */ -public interface SettableByIndex> extends AccessibleByIndex { +public interface SettableByIndex> extends AccessibleByIndex { /** * Sets the raw binary representation of the {@code i}th value. @@ -61,7 +61,7 @@ public interface SettableByIndex> extends Accessibl * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - T setBytesUnsafe(int i, @Nullable ByteBuffer v); + SelfT setBytesUnsafe(int i, @Nullable ByteBuffer v); /** * Sets the {@code i}th value to CQL {@code NULL}. @@ -69,7 +69,7 @@ public interface SettableByIndex> extends Accessibl * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setToNull(int i) { + default SelfT setToNull(int i) { return setBytesUnsafe(i, null); } @@ -86,7 +86,7 @@ default T setToNull(int i) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T set(int i, @Nullable V v, @NonNull TypeCodec codec) { + default SelfT set(int i, @Nullable ValueT v, @NonNull TypeCodec codec) { return setBytesUnsafe(i, codec.encode(v, protocolVersion())); } @@ -102,9 +102,9 @@ default T set(int i, @Nullable V v, @NonNull TypeCodec codec) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull - default T set(int i, @Nullable V v, @NonNull GenericType targetType) { + default SelfT set(int i, @Nullable ValueT v, @NonNull GenericType targetType) { DataType cqlType = getType(i); - TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); return set(i, v, codec); } @@ -119,11 +119,11 @@ default T set(int i, @Nullable V v, @NonNull GenericType targetType) { * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull - default T set(int i, @Nullable V v, @NonNull Class targetClass) { + default SelfT set(int i, @Nullable ValueT v, @NonNull Class targetClass) { // This is duplicated from the GenericType variant, because we want to give the codec registry // a chance to process the unwrapped class directly, if it can do so in a more efficient way. DataType cqlType = getType(i); - TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); + TypeCodec codec = codecRegistry().codecFor(cqlType, targetClass); return set(i, v, codec); } @@ -138,7 +138,7 @@ default T set(int i, @Nullable V v, @NonNull Class targetClass) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setBoolean(int i, boolean v) { + default SelfT setBoolean(int i, boolean v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Boolean.class); return (codec instanceof PrimitiveBooleanCodec) @@ -157,7 +157,7 @@ default T setBoolean(int i, boolean v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setByte(int i, byte v) { + default SelfT setByte(int i, byte v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Byte.class); return (codec instanceof PrimitiveByteCodec) @@ -176,7 +176,7 @@ default T setByte(int i, byte v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setDouble(int i, double v) { + default SelfT setDouble(int i, double v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Double.class); return (codec instanceof PrimitiveDoubleCodec) @@ -195,7 +195,7 @@ default T setDouble(int i, double v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setFloat(int i, float v) { + default SelfT setFloat(int i, float v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Float.class); return (codec instanceof PrimitiveFloatCodec) @@ -214,7 +214,7 @@ default T setFloat(int i, float v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setInt(int i, int v) { + default SelfT setInt(int i, int v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Integer.class); return (codec instanceof PrimitiveIntCodec) @@ -233,7 +233,7 @@ default T setInt(int i, int v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setLong(int i, long v) { + default SelfT setLong(int i, long v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Long.class); return (codec instanceof PrimitiveLongCodec) @@ -252,7 +252,7 @@ default T setLong(int i, long v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setShort(int i, short v) { + default SelfT setShort(int i, short v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Short.class); return (codec instanceof PrimitiveShortCodec) @@ -268,7 +268,7 @@ default T setShort(int i, short v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setInstant(int i, @Nullable Instant v) { + default SelfT setInstant(int i, @Nullable Instant v) { return set(i, v, Instant.class); } @@ -280,7 +280,7 @@ default T setInstant(int i, @Nullable Instant v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setLocalDate(int i, @Nullable LocalDate v) { + default SelfT setLocalDate(int i, @Nullable LocalDate v) { return set(i, v, LocalDate.class); } @@ -292,7 +292,7 @@ default T setLocalDate(int i, @Nullable LocalDate v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setLocalTime(int i, @Nullable LocalTime v) { + default SelfT setLocalTime(int i, @Nullable LocalTime v) { return set(i, v, LocalTime.class); } @@ -304,7 +304,7 @@ default T setLocalTime(int i, @Nullable LocalTime v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setByteBuffer(int i, @Nullable ByteBuffer v) { + default SelfT setByteBuffer(int i, @Nullable ByteBuffer v) { return set(i, v, ByteBuffer.class); } @@ -316,7 +316,7 @@ default T setByteBuffer(int i, @Nullable ByteBuffer v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setString(int i, @Nullable String v) { + default SelfT setString(int i, @Nullable String v) { return set(i, v, String.class); } @@ -328,7 +328,7 @@ default T setString(int i, @Nullable String v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setBigInteger(int i, @Nullable BigInteger v) { + default SelfT setBigInteger(int i, @Nullable BigInteger v) { return set(i, v, BigInteger.class); } @@ -340,7 +340,7 @@ default T setBigInteger(int i, @Nullable BigInteger v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setBigDecimal(int i, @Nullable BigDecimal v) { + default SelfT setBigDecimal(int i, @Nullable BigDecimal v) { return set(i, v, BigDecimal.class); } @@ -352,7 +352,7 @@ default T setBigDecimal(int i, @Nullable BigDecimal v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setUuid(int i, @Nullable UUID v) { + default SelfT setUuid(int i, @Nullable UUID v) { return set(i, v, UUID.class); } @@ -364,7 +364,7 @@ default T setUuid(int i, @Nullable UUID v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setInetAddress(int i, @Nullable InetAddress v) { + default SelfT setInetAddress(int i, @Nullable InetAddress v) { return set(i, v, InetAddress.class); } @@ -376,7 +376,7 @@ default T setInetAddress(int i, @Nullable InetAddress v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setCqlDuration(int i, @Nullable CqlDuration v) { + default SelfT setCqlDuration(int i, @Nullable CqlDuration v) { return set(i, v, CqlDuration.class); } @@ -390,7 +390,7 @@ default T setCqlDuration(int i, @Nullable CqlDuration v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setToken(int i, @NonNull Token v) { + default SelfT setToken(int i, @NonNull Token v) { // Simply enumerate all known implementations. This goes against the concept of TokenFactory, // but injecting the factory here is too much of a hassle. // The only issue is if someone uses a custom partitioner, but this is highly unlikely, and even @@ -417,7 +417,8 @@ default T setToken(int i, @NonNull Token v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setList(int i, @Nullable List v, @NonNull Class elementsClass) { + default SelfT setList( + int i, @Nullable List v, @NonNull Class elementsClass) { return set(i, v, GenericType.listOf(elementsClass)); } @@ -432,7 +433,8 @@ default T setList(int i, @Nullable List v, @NonNull Class elementsClas * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setSet(int i, @Nullable Set v, @NonNull Class elementsClass) { + default SelfT setSet( + int i, @Nullable Set v, @NonNull Class elementsClass) { return set(i, v, GenericType.setOf(elementsClass)); } @@ -447,8 +449,11 @@ default T setSet(int i, @Nullable Set v, @NonNull Class elementsClass) * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setMap( - int i, @Nullable Map v, @NonNull Class keyClass, @NonNull Class valueClass) { + default SelfT setMap( + int i, + @Nullable Map v, + @NonNull Class keyClass, + @NonNull Class valueClass) { return set(i, v, GenericType.mapOf(keyClass, valueClass)); } @@ -460,7 +465,7 @@ default T setMap( * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setUdtValue(int i, @Nullable UdtValue v) { + default SelfT setUdtValue(int i, @Nullable UdtValue v) { return set(i, v, UdtValue.class); } @@ -472,7 +477,7 @@ default T setUdtValue(int i, @Nullable UdtValue v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull - default T setTupleValue(int i, @Nullable TupleValue v) { + default SelfT setTupleValue(int i, @Nullable TupleValue v) { return set(i, v, TupleValue.class); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index 6bc9335dc52..bfdb656ef44 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -35,8 +35,8 @@ import java.util.UUID; /** A data structure that provides methods to set its values via a name. */ -public interface SettableByName> - extends SettableByIndex, AccessibleByName { +public interface SettableByName> + extends SettableByIndex, AccessibleByName { /** * Sets the raw binary representation of the value for the first occurrence of {@code name}. @@ -55,7 +55,7 @@ public interface SettableByName> * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setBytesUnsafe(@NonNull String name, @Nullable ByteBuffer v) { + default SelfT setBytesUnsafe(@NonNull String name, @Nullable ByteBuffer v) { return setBytesUnsafe(firstIndexOf(name), v); } @@ -74,7 +74,7 @@ default DataType getType(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setToNull(@NonNull String name) { + default SelfT setToNull(@NonNull String name) { return setToNull(firstIndexOf(name)); } @@ -95,7 +95,8 @@ default T setToNull(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T set(@NonNull String name, @Nullable V v, @NonNull TypeCodec codec) { + default SelfT set( + @NonNull String name, @Nullable ValueT v, @NonNull TypeCodec codec) { return set(firstIndexOf(name), v, codec); } @@ -114,7 +115,8 @@ default T set(@NonNull String name, @Nullable V v, @NonNull TypeCodec cod * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull - default T set(@NonNull String name, @Nullable V v, @NonNull GenericType targetType) { + default SelfT set( + @NonNull String name, @Nullable ValueT v, @NonNull GenericType targetType) { return set(firstIndexOf(name), v, targetType); } @@ -133,7 +135,8 @@ default T set(@NonNull String name, @Nullable V v, @NonNull GenericType t * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull - default T set(@NonNull String name, @Nullable V v, @NonNull Class targetClass) { + default SelfT set( + @NonNull String name, @Nullable ValueT v, @NonNull Class targetClass) { return set(firstIndexOf(name), v, targetClass); } @@ -151,7 +154,7 @@ default T set(@NonNull String name, @Nullable V v, @NonNull Class targetC * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setBoolean(@NonNull String name, boolean v) { + default SelfT setBoolean(@NonNull String name, boolean v) { return setBoolean(firstIndexOf(name), v); } @@ -169,7 +172,7 @@ default T setBoolean(@NonNull String name, boolean v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setByte(@NonNull String name, byte v) { + default SelfT setByte(@NonNull String name, byte v) { return setByte(firstIndexOf(name), v); } @@ -187,7 +190,7 @@ default T setByte(@NonNull String name, byte v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setDouble(@NonNull String name, double v) { + default SelfT setDouble(@NonNull String name, double v) { return setDouble(firstIndexOf(name), v); } @@ -205,7 +208,7 @@ default T setDouble(@NonNull String name, double v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setFloat(@NonNull String name, float v) { + default SelfT setFloat(@NonNull String name, float v) { return setFloat(firstIndexOf(name), v); } @@ -223,7 +226,7 @@ default T setFloat(@NonNull String name, float v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setInt(@NonNull String name, int v) { + default SelfT setInt(@NonNull String name, int v) { return setInt(firstIndexOf(name), v); } @@ -241,7 +244,7 @@ default T setInt(@NonNull String name, int v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setLong(@NonNull String name, long v) { + default SelfT setLong(@NonNull String name, long v) { return setLong(firstIndexOf(name), v); } @@ -259,7 +262,7 @@ default T setLong(@NonNull String name, long v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setShort(@NonNull String name, short v) { + default SelfT setShort(@NonNull String name, short v) { return setShort(firstIndexOf(name), v); } @@ -274,7 +277,7 @@ default T setShort(@NonNull String name, short v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setInstant(@NonNull String name, @Nullable Instant v) { + default SelfT setInstant(@NonNull String name, @Nullable Instant v) { return setInstant(firstIndexOf(name), v); } @@ -289,7 +292,7 @@ default T setInstant(@NonNull String name, @Nullable Instant v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setLocalDate(@NonNull String name, @Nullable LocalDate v) { + default SelfT setLocalDate(@NonNull String name, @Nullable LocalDate v) { return setLocalDate(firstIndexOf(name), v); } @@ -304,7 +307,7 @@ default T setLocalDate(@NonNull String name, @Nullable LocalDate v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setLocalTime(@NonNull String name, @Nullable LocalTime v) { + default SelfT setLocalTime(@NonNull String name, @Nullable LocalTime v) { return setLocalTime(firstIndexOf(name), v); } @@ -319,7 +322,7 @@ default T setLocalTime(@NonNull String name, @Nullable LocalTime v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { + default SelfT setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { return setByteBuffer(firstIndexOf(name), v); } @@ -334,7 +337,7 @@ default T setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setString(@NonNull String name, @Nullable String v) { + default SelfT setString(@NonNull String name, @Nullable String v) { return setString(firstIndexOf(name), v); } @@ -349,7 +352,7 @@ default T setString(@NonNull String name, @Nullable String v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setBigInteger(@NonNull String name, @Nullable BigInteger v) { + default SelfT setBigInteger(@NonNull String name, @Nullable BigInteger v) { return setBigInteger(firstIndexOf(name), v); } @@ -364,7 +367,7 @@ default T setBigInteger(@NonNull String name, @Nullable BigInteger v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { + default SelfT setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { return setBigDecimal(firstIndexOf(name), v); } @@ -379,7 +382,7 @@ default T setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setUuid(@NonNull String name, @Nullable UUID v) { + default SelfT setUuid(@NonNull String name, @Nullable UUID v) { return setUuid(firstIndexOf(name), v); } @@ -394,7 +397,7 @@ default T setUuid(@NonNull String name, @Nullable UUID v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setInetAddress(@NonNull String name, @Nullable InetAddress v) { + default SelfT setInetAddress(@NonNull String name, @Nullable InetAddress v) { return setInetAddress(firstIndexOf(name), v); } @@ -409,7 +412,7 @@ default T setInetAddress(@NonNull String name, @Nullable InetAddress v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { + default SelfT setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { return setCqlDuration(firstIndexOf(name), v); } @@ -426,7 +429,7 @@ default T setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setToken(@NonNull String name, @NonNull Token v) { + default SelfT setToken(@NonNull String name, @NonNull Token v) { return setToken(firstIndexOf(name), v); } @@ -444,8 +447,8 @@ default T setToken(@NonNull String name, @NonNull Token v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setList( - @NonNull String name, @Nullable List v, @NonNull Class elementsClass) { + default SelfT setList( + @NonNull String name, @Nullable List v, @NonNull Class elementsClass) { return setList(firstIndexOf(name), v, elementsClass); } @@ -463,7 +466,8 @@ default T setList( * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setSet(@NonNull String name, @Nullable Set v, @NonNull Class elementsClass) { + default SelfT setSet( + @NonNull String name, @Nullable Set v, @NonNull Class elementsClass) { return setSet(firstIndexOf(name), v, elementsClass); } @@ -481,11 +485,11 @@ default T setSet(@NonNull String name, @Nullable Set v, @NonNull Class * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setMap( + default SelfT setMap( @NonNull String name, - @Nullable Map v, - @NonNull Class keyClass, - @NonNull Class valueClass) { + @Nullable Map v, + @NonNull Class keyClass, + @NonNull Class valueClass) { return setMap(firstIndexOf(name), v, keyClass, valueClass); } @@ -501,7 +505,7 @@ default T setMap( * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setUdtValue(@NonNull String name, @Nullable UdtValue v) { + default SelfT setUdtValue(@NonNull String name, @Nullable UdtValue v) { return setUdtValue(firstIndexOf(name), v); } @@ -516,7 +520,7 @@ default T setUdtValue(@NonNull String name, @Nullable UdtValue v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull - default T setTupleValue(@NonNull String name, @Nullable TupleValue v) { + default SelfT setTupleValue(@NonNull String name, @Nullable TupleValue v) { return setTupleValue(firstIndexOf(name), v); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java index 82d2b7f57f7..8d7905b53dc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java @@ -58,10 +58,10 @@ * buffers prior to reading them. * */ -public interface TypeCodec { +public interface TypeCodec { @NonNull - GenericType getJavaType(); + GenericType getJavaType(); @NonNull DataType getCqlType(); @@ -109,7 +109,7 @@ default boolean accepts(@NonNull Class javaClass) { * subtype of the Java type that it has been created for. This is so because codec lookups by * arbitrary Java objects only make sense when attempting to encode, never when attempting to * decode, and indeed the {@linkplain #encode(Object, ProtocolVersion) encode} method is covariant - * with {@code T}. + * with {@code JavaTypeT}. * *

              It can only handle non-parameterized types; codecs handling parameterized types, such as * collection types, must override this method and perform some sort of "manual" inspection of the @@ -145,7 +145,7 @@ default boolean accepts(@NonNull DataType cqlType) { * */ @Nullable - ByteBuffer encode(@Nullable T value, @NonNull ProtocolVersion protocolVersion); + ByteBuffer encode(@Nullable JavaTypeT value, @NonNull ProtocolVersion protocolVersion); /** * Decodes a value from the binary format of the CQL type handled by this codec. @@ -167,7 +167,7 @@ default boolean accepts(@NonNull DataType cqlType) { * */ @Nullable - T decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion); + JavaTypeT decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion); /** * Formats the given value as a valid CQL literal according to the CQL type handled by this codec. @@ -191,7 +191,7 @@ default boolean accepts(@NonNull DataType cqlType) { * constant string (for example "XxxCodec.format not implemented"). */ @NonNull - String format(@Nullable T value); + String format(@Nullable JavaTypeT value); /** * Parse the given CQL literal into an instance of the Java type handled by this codec. @@ -210,5 +210,5 @@ default boolean accepts(@NonNull DataType cqlType) { * {@code null}. */ @Nullable - T parse(@Nullable String value); + JavaTypeT parse(@Nullable String value); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java index 09f57e8f56b..93456008bb6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java @@ -63,7 +63,8 @@ public interface CodecRegistry { * @throws CodecNotFoundException if there is no such codec. */ @NonNull - TypeCodec codecFor(@NonNull DataType cqlType, @NonNull GenericType javaType); + TypeCodec codecFor( + @NonNull DataType cqlType, @NonNull GenericType javaType); /** * Shortcut for {@link #codecFor(DataType, GenericType) codecFor(cqlType, @@ -75,7 +76,8 @@ public interface CodecRegistry { * @throws CodecNotFoundException if there is no such codec. */ @NonNull - default TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class javaType) { + default TypeCodec codecFor( + @NonNull DataType cqlType, @NonNull Class javaType) { return codecFor(cqlType, GenericType.of(javaType)); } @@ -93,7 +95,7 @@ default TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class j * @throws CodecNotFoundException if there is no such codec. */ @NonNull - TypeCodec codecFor(@NonNull DataType cqlType); + TypeCodec codecFor(@NonNull DataType cqlType); /** * Returns a codec to convert the given Java type to the CQL type deemed most appropriate to @@ -110,7 +112,7 @@ default TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class j * @throws CodecNotFoundException if there is no such codec. */ @NonNull - TypeCodec codecFor(@NonNull GenericType javaType); + TypeCodec codecFor(@NonNull GenericType javaType); /** * Shortcut for {@link #codecFor(GenericType) codecFor(GenericType.of(javaType))}. @@ -121,7 +123,7 @@ default TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class j * @throws CodecNotFoundException if there is no such codec. */ @NonNull - default TypeCodec codecFor(@NonNull Class javaType) { + default TypeCodec codecFor(@NonNull Class javaType) { return codecFor(GenericType.of(javaType)); } @@ -142,7 +144,7 @@ default TypeCodec codecFor(@NonNull Class javaType) { * @throws CodecNotFoundException if there is no such codec. */ @NonNull - TypeCodec codecFor(@NonNull DataType cqlType, @NonNull T value); + TypeCodec codecFor(@NonNull DataType cqlType, @NonNull JavaTypeT value); /** * Returns a codec to convert the given Java object to the CQL type deemed most appropriate to @@ -162,5 +164,5 @@ default TypeCodec codecFor(@NonNull Class javaType) { * @throws CodecNotFoundException if there is no such codec. */ @NonNull - TypeCodec codecFor(@NonNull T value); + TypeCodec codecFor(@NonNull JavaTypeT value); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java index 1f00ca4d1db..b61e1cf8149 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/EventBus.java @@ -55,7 +55,7 @@ public EventBus(String logPrefix) { * * @return a key that is needed to unregister later. */ - public Object register(Class eventClass, Consumer listener) { + public Object register(Class eventClass, Consumer listener) { LOG.debug("[{}] Registering {} for {}", logPrefix, listener, eventClass); listeners.put(eventClass, listener); // The reason for the key mechanism is that this will often be used with method references, @@ -69,7 +69,7 @@ public Object register(Class eventClass, Consumer listener) { * * @param key the key that was returned by {@link #register(Class, Consumer)} */ - public boolean unregister(Object key, Class eventClass) { + public boolean unregister(Object key, Class eventClass) { LOG.debug("[{}] Unregistering {} for {}", logPrefix, key, eventClass); return listeners.remove(eventClass, key); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java index 9075c533e58..dd4001e3930 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java @@ -29,13 +29,13 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class ListCodec implements TypeCodec> { +public class ListCodec implements TypeCodec> { private final DataType cqlType; - private final GenericType> javaType; - private final TypeCodec elementCodec; + private final GenericType> javaType; + private final TypeCodec elementCodec; - public ListCodec(DataType cqlType, TypeCodec elementCodec) { + public ListCodec(DataType cqlType, TypeCodec elementCodec) { this.cqlType = cqlType; this.javaType = GenericType.listOf(elementCodec.getJavaType()); this.elementCodec = elementCodec; @@ -44,7 +44,7 @@ public ListCodec(DataType cqlType, TypeCodec elementCodec) { @NonNull @Override - public GenericType> getJavaType() { + public GenericType> getJavaType() { return javaType; } @@ -67,7 +67,8 @@ public boolean accepts(@NonNull Object value) { @Nullable @Override - public ByteBuffer encode(@Nullable List value, @NonNull ProtocolVersion protocolVersion) { + public ByteBuffer encode( + @Nullable List value, @NonNull ProtocolVersion protocolVersion) { // An int indicating the number of elements in the list, followed by the elements. Each element // is a byte array representing the serialized value, preceded by an int indicating its size. if (value == null) { @@ -76,7 +77,7 @@ public ByteBuffer encode(@Nullable List value, @NonNull ProtocolVersion proto int i = 0; ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; int toAllocate = 4; // initialize with number of elements - for (T element : value) { + for (ElementT element : value) { if (element == null) { throw new NullPointerException("Collection elements cannot be null"); } @@ -105,13 +106,14 @@ public ByteBuffer encode(@Nullable List value, @NonNull ProtocolVersion proto @Nullable @Override - public List decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + public List decode( + @Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { if (bytes == null || bytes.remaining() == 0) { return new ArrayList<>(0); } else { ByteBuffer input = bytes.duplicate(); int size = input.getInt(); - List result = new ArrayList<>(size); + List result = new ArrayList<>(size); for (int i = 0; i < size; i++) { int elementSize = input.getInt(); ByteBuffer encodedElement = input.slice(); @@ -125,13 +127,13 @@ public List decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion proto @NonNull @Override - public String format(@Nullable List value) { + public String format(@Nullable List value) { if (value == null) { return "NULL"; } StringBuilder sb = new StringBuilder("["); boolean first = true; - for (T t : value) { + for (ElementT t : value) { if (first) { first = false; } else { @@ -145,7 +147,7 @@ public String format(@Nullable List value) { @Nullable @Override - public List parse(@Nullable String value) { + public List parse(@Nullable String value) { if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) return null; int idx = ParseUtils.skipSpaces(value, 0); @@ -161,7 +163,7 @@ public List parse(@Nullable String value) { return new ArrayList<>(0); } - List list = new ArrayList<>(); + List list = new ArrayList<>(); while (idx < value.length()) { int n; try { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java index 951129f72bc..4f330b3ab59 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/MapCodec.java @@ -28,14 +28,14 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class MapCodec implements TypeCodec> { +public class MapCodec implements TypeCodec> { private final DataType cqlType; - private final GenericType> javaType; - private final TypeCodec keyCodec; - private final TypeCodec valueCodec; + private final GenericType> javaType; + private final TypeCodec keyCodec; + private final TypeCodec valueCodec; - public MapCodec(DataType cqlType, TypeCodec keyCodec, TypeCodec valueCodec) { + public MapCodec(DataType cqlType, TypeCodec keyCodec, TypeCodec valueCodec) { this.cqlType = cqlType; this.keyCodec = keyCodec; this.valueCodec = valueCodec; @@ -44,7 +44,7 @@ public MapCodec(DataType cqlType, TypeCodec keyCodec, TypeCodec valueCodec @NonNull @Override - public GenericType> getJavaType() { + public GenericType> getJavaType() { return javaType; } @@ -70,7 +70,8 @@ public boolean accepts(@NonNull Object value) { @Override @Nullable - public ByteBuffer encode(@Nullable Map value, @NonNull ProtocolVersion protocolVersion) { + public ByteBuffer encode( + @Nullable Map value, @NonNull ProtocolVersion protocolVersion) { // An int indicating the number of key/value pairs in the map, followed by the pairs. Each pair // is a byte array representing the serialized key, preceded by an int indicating its size, // followed by the value in the same format. @@ -80,7 +81,7 @@ public ByteBuffer encode(@Nullable Map value, @NonNull ProtocolVersion pro int i = 0; ByteBuffer[] encodedElements = new ByteBuffer[value.size() * 2]; int toAllocate = 4; // initialize with number of elements - for (Map.Entry entry : value.entrySet()) { + for (Map.Entry entry : value.entrySet()) { if (entry.getKey() == null) { throw new NullPointerException("Map keys cannot be null"); } @@ -124,25 +125,26 @@ public ByteBuffer encode(@Nullable Map value, @NonNull ProtocolVersion pro @Nullable @Override - public Map decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + public Map decode( + @Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { if (bytes == null || bytes.remaining() == 0) { return new LinkedHashMap<>(0); } else { ByteBuffer input = bytes.duplicate(); int size = input.getInt(); - Map result = Maps.newLinkedHashMapWithExpectedSize(size); + Map result = Maps.newLinkedHashMapWithExpectedSize(size); for (int i = 0; i < size; i++) { int keySize = input.getInt(); ByteBuffer encodedKey = input.slice(); encodedKey.limit(keySize); input.position(input.position() + keySize); - K key = keyCodec.decode(encodedKey, protocolVersion); + KeyT key = keyCodec.decode(encodedKey, protocolVersion); int valueSize = input.getInt(); ByteBuffer encodedValue = input.slice(); encodedValue.limit(valueSize); input.position(input.position() + valueSize); - V value = valueCodec.decode(encodedValue, protocolVersion); + ValueT value = valueCodec.decode(encodedValue, protocolVersion); result.put(key, value); } @@ -152,14 +154,14 @@ public Map decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion pro @NonNull @Override - public String format(@Nullable Map value) { + public String format(@Nullable Map value) { if (value == null) { return "NULL"; } StringBuilder sb = new StringBuilder(); sb.append("{"); boolean first = true; - for (Map.Entry e : value.entrySet()) { + for (Map.Entry e : value.entrySet()) { if (first) { first = false; } else { @@ -175,7 +177,7 @@ public String format(@Nullable Map value) { @Nullable @Override - public Map parse(@Nullable String value) { + public Map parse(@Nullable String value) { if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) { return null; } @@ -194,7 +196,7 @@ public Map parse(@Nullable String value) { return new LinkedHashMap<>(0); } - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); while (idx < value.length()) { int n; try { @@ -207,7 +209,7 @@ public Map parse(@Nullable String value) { e); } - K k = keyCodec.parse(value.substring(idx, n)); + KeyT k = keyCodec.parse(value.substring(idx, n)); idx = n; idx = ParseUtils.skipSpaces(value, idx); @@ -229,7 +231,7 @@ public Map parse(@Nullable String value) { e); } - V v = valueCodec.parse(value.substring(idx, n)); + ValueT v = valueCodec.parse(value.substring(idx, n)); idx = n; map.put(k, v); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java index 0b355570ab2..7dc0c930c6e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java @@ -30,13 +30,13 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class SetCodec implements TypeCodec> { +public class SetCodec implements TypeCodec> { private final DataType cqlType; - private final GenericType> javaType; - private final TypeCodec elementCodec; + private final GenericType> javaType; + private final TypeCodec elementCodec; - public SetCodec(DataType cqlType, TypeCodec elementCodec) { + public SetCodec(DataType cqlType, TypeCodec elementCodec) { this.cqlType = cqlType; this.javaType = GenericType.setOf(elementCodec.getJavaType()); this.elementCodec = elementCodec; @@ -45,7 +45,7 @@ public SetCodec(DataType cqlType, TypeCodec elementCodec) { @NonNull @Override - public GenericType> getJavaType() { + public GenericType> getJavaType() { return javaType; } @@ -68,7 +68,8 @@ public boolean accepts(@NonNull Object value) { @Nullable @Override - public ByteBuffer encode(@Nullable Set value, @NonNull ProtocolVersion protocolVersion) { + public ByteBuffer encode( + @Nullable Set value, @NonNull ProtocolVersion protocolVersion) { // An int indicating the number of elements in the set, followed by the elements. Each element // is a byte array representing the serialized value, preceded by an int indicating its size. if (value == null) { @@ -77,7 +78,7 @@ public ByteBuffer encode(@Nullable Set value, @NonNull ProtocolVersion protoc int i = 0; ByteBuffer[] encodedElements = new ByteBuffer[value.size()]; int toAllocate = 4; // initialize with number of elements - for (T element : value) { + for (ElementT element : value) { if (element == null) { throw new NullPointerException("Collection elements cannot be null"); } @@ -106,13 +107,14 @@ public ByteBuffer encode(@Nullable Set value, @NonNull ProtocolVersion protoc @Nullable @Override - public Set decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + public Set decode( + @Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { if (bytes == null || bytes.remaining() == 0) { return new LinkedHashSet<>(0); } else { ByteBuffer input = bytes.duplicate(); int size = input.getInt(); - Set result = Sets.newLinkedHashSetWithExpectedSize(size); + Set result = Sets.newLinkedHashSetWithExpectedSize(size); for (int i = 0; i < size; i++) { int elementSize = input.getInt(); ByteBuffer encodedElement = input.slice(); @@ -126,13 +128,13 @@ public Set decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protoc @NonNull @Override - public String format(@Nullable Set value) { + public String format(@Nullable Set value) { if (value == null) { return "NULL"; } StringBuilder sb = new StringBuilder("{"); boolean first = true; - for (T t : value) { + for (ElementT t : value) { if (first) { first = false; } else { @@ -146,7 +148,7 @@ public String format(@Nullable Set value) { @Nullable @Override - public Set parse(@Nullable String value) { + public Set parse(@Nullable String value) { if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) return null; int idx = ParseUtils.skipSpaces(value, 0); @@ -162,7 +164,7 @@ public Set parse(@Nullable String value) { return new LinkedHashSet<>(0); } - Set set = new LinkedHashSet<>(); + Set set = new LinkedHashSet<>(); while (idx < value.length()) { int n; try { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index a7dbb63e51e..31dbb15a9af 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -89,13 +89,14 @@ protected abstract TypeCodec getCachedCodec( @NonNull @Override - public TypeCodec codecFor(@NonNull DataType cqlType, @NonNull GenericType javaType) { + public TypeCodec codecFor( + @NonNull DataType cqlType, @NonNull GenericType javaType) { return codecFor(cqlType, javaType, false); } // Not exposed publicly, (isJavaCovariant=true) is only used for internal recursion - protected TypeCodec codecFor( - DataType cqlType, GenericType javaType, boolean isJavaCovariant) { + protected TypeCodec codecFor( + DataType cqlType, GenericType javaType, boolean isJavaCovariant) { LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = primitiveCodecsByCode.get(cqlType.getProtocolCode()); if (primitiveCodec != null && matches(primitiveCodec, javaType, isJavaCovariant)) { @@ -113,7 +114,8 @@ protected TypeCodec codecFor( @NonNull @Override - public TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class javaType) { + public TypeCodec codecFor( + @NonNull DataType cqlType, @NonNull Class javaType) { LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = primitiveCodecsByCode.get(cqlType.getProtocolCode()); if (primitiveCodec != null && primitiveCodec.getJavaType().__getToken().getType() == javaType) { @@ -131,7 +133,7 @@ public TypeCodec codecFor(@NonNull DataType cqlType, @NonNull Class ja @NonNull @Override - public TypeCodec codecFor(@NonNull DataType cqlType) { + public TypeCodec codecFor(@NonNull DataType cqlType) { LOG.trace("[{}] Looking up codec for CQL type {}", logPrefix, cqlType); TypeCodec primitiveCodec = primitiveCodecsByCode.get(cqlType.getProtocolCode()); if (primitiveCodec != null) { @@ -149,7 +151,8 @@ public TypeCodec codecFor(@NonNull DataType cqlType) { @NonNull @Override - public TypeCodec codecFor(@NonNull DataType cqlType, @NonNull T value) { + public TypeCodec codecFor( + @NonNull DataType cqlType, @NonNull JavaTypeT value) { Preconditions.checkNotNull(cqlType); Preconditions.checkNotNull(value); LOG.trace("[{}] Looking up codec for CQL type {} and object {}", logPrefix, cqlType, value); @@ -179,7 +182,7 @@ public TypeCodec codecFor(@NonNull DataType cqlType, @NonNull T value) { @NonNull @Override - public TypeCodec codecFor(@NonNull T value) { + public TypeCodec codecFor(@NonNull JavaTypeT value) { Preconditions.checkNotNull(value); LOG.trace("[{}] Looking up codec for object {}", logPrefix, value); @@ -209,12 +212,13 @@ public TypeCodec codecFor(@NonNull T value) { @NonNull @Override - public TypeCodec codecFor(@NonNull GenericType javaType) { + public TypeCodec codecFor(@NonNull GenericType javaType) { return codecFor(javaType, false); } // Not exposed publicly, (isJavaCovariant=true) is only used for internal recursion - protected TypeCodec codecFor(GenericType javaType, boolean isJavaCovariant) { + protected TypeCodec codecFor( + GenericType javaType, boolean isJavaCovariant) { LOG.trace( "[{}] Looking up codec for Java type {} (covariant = {})", logPrefix, @@ -403,9 +407,10 @@ private static IntMap sortByProtocolCode(TypeCodec[] codecs) { } // We call this after validating the types, so we know the cast will never fail. - private static TypeCodec uncheckedCast(TypeCodec codec) { + private static TypeCodec uncheckedCast( + TypeCodec codec) { @SuppressWarnings("unchecked") - TypeCodec result = (TypeCodec) codec; + TypeCodec result = (TypeCodec) codec; return result; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index ae2412ea7fc..25597e190c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -20,9 +20,9 @@ public class ArrayUtils { - public static void swap(@NonNull T[] elements, int i, int j) { + public static void swap(@NonNull ElementT[] elements, int i, int j) { if (i != j) { - T tmp = elements[i]; + ElementT tmp = elements[i]; elements[i] = elements[j]; elements[j] = tmp; } @@ -32,7 +32,8 @@ public static void swap(@NonNull T[] elements, int i, int j) { * Moves an element towards the beginning of the array, shifting all the intermediary elements to * the right (no-op if {@code targetIndex >= sourceIndex}). */ - public static void bubbleUp(@NonNull T[] elements, int sourceIndex, int targetIndex) { + public static void bubbleUp( + @NonNull ElementT[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i > targetIndex; i--) { swap(elements, i, i - 1); } @@ -42,7 +43,8 @@ public static void bubbleUp(@NonNull T[] elements, int sourceIndex, int targ * Moves an element towards the end of the array, shifting all the intermediary elements to the * left (no-op if {@code targetIndex <= sourceIndex}). */ - public static void bubbleDown(@NonNull T[] elements, int sourceIndex, int targetIndex) { + public static void bubbleDown( + @NonNull ElementT[] elements, int sourceIndex, int targetIndex) { for (int i = sourceIndex; i < targetIndex; i++) { swap(elements, i, i + 1); } @@ -57,7 +59,7 @@ public static void bubbleDown(@NonNull T[] elements, int sourceIndex, int ta * href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm">Modern * Fisher-Yates shuffle */ - public static void shuffleHead(@NonNull T[] elements, int n) { + public static void shuffleHead(@NonNull ElementT[] elements, int n) { shuffleHead(elements, n, ThreadLocalRandom.current()); } @@ -72,8 +74,8 @@ public static void shuffleHead(@NonNull T[] elements, int n) { * href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm">Modern * Fisher-Yates shuffle */ - public static void shuffleHead( - @NonNull T[] elements, int n, @NonNull ThreadLocalRandom random) { + public static void shuffleHead( + @NonNull ElementT[] elements, int n, @NonNull ThreadLocalRandom random) { if (n > elements.length) { throw new ArrayIndexOutOfBoundsException( String.format( @@ -88,7 +90,8 @@ public static void shuffleHead( } /** Rotates the elements in the specified range by the specified amount (round-robin). */ - public static void rotate(@NonNull T[] elements, int startIndex, int length, int amount) { + public static void rotate( + @NonNull ElementT[] elements, int startIndex, int length, int amount) { if (length >= 2) { amount = amount % length; // Repeatedly shift by 1. This is not the most time-efficient but the array will typically be diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java index 60a04fd917f..a65808e7b2a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/CountingIterator.java @@ -25,7 +25,7 @@ * elements get returned. */ @NotThreadSafe -public abstract class CountingIterator implements Iterator { +public abstract class CountingIterator implements Iterator { protected int remaining; @@ -64,11 +64,11 @@ private enum State { } private State state = State.NOT_READY; - private T next; + private ElementT next; - protected abstract T computeNext(); + protected abstract ElementT computeNext(); - protected final T endOfData() { + protected final ElementT endOfData() { state = State.DONE; return null; } @@ -97,19 +97,19 @@ private boolean tryToComputeNext() { } @Override - public final T next() { + public final ElementT next() { if (!hasNext()) { throw new NoSuchElementException(); } state = State.NOT_READY; - T result = next; + ElementT result = next; next = null; // Added to original Guava code: decrement counter when we return an element remaining -= 1; return result; } - public final T peek() { + public final ElementT peek() { if (!hasNext()) { throw new NoSuchElementException(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java index 03f40b9e0b9..6f75d759451 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/DirectedGraph.java @@ -31,26 +31,26 @@ /** A basic directed graph implementation to perform topological sorts. */ @NotThreadSafe -public class DirectedGraph { +public class DirectedGraph { // We need to keep track of the predecessor count. For simplicity, use a map to store it // alongside the vertices. - private final Map vertices; - private final Multimap adjacencyList; + private final Map vertices; + private final Multimap adjacencyList; private boolean wasSorted; - public DirectedGraph(Collection vertices) { + public DirectedGraph(Collection vertices) { this.vertices = Maps.newLinkedHashMapWithExpectedSize(vertices.size()); this.adjacencyList = LinkedHashMultimap.create(); - for (V vertex : vertices) { + for (VertexT vertex : vertices) { this.vertices.put(vertex, 0); } } @VisibleForTesting @SafeVarargs - DirectedGraph(V... vertices) { + DirectedGraph(VertexT... vertices) { this(Arrays.asList(vertices)); } @@ -58,30 +58,30 @@ public DirectedGraph(Collection vertices) { * this assumes that {@code from} and {@code to} were part of the vertices passed to the * constructor */ - public void addEdge(V from, V to) { + public void addEdge(VertexT from, VertexT to) { Preconditions.checkArgument(vertices.containsKey(from) && vertices.containsKey(to)); adjacencyList.put(from, to); vertices.put(to, vertices.get(to) + 1); } /** one-time use only, calling this multiple times on the same graph won't work */ - public List topologicalSort() { + public List topologicalSort() { Preconditions.checkState(!wasSorted); wasSorted = true; - Queue queue = new ArrayDeque<>(); + Queue queue = new ArrayDeque<>(); - for (Map.Entry entry : vertices.entrySet()) { + for (Map.Entry entry : vertices.entrySet()) { if (entry.getValue() == 0) { queue.add(entry.getKey()); } } - List result = Lists.newArrayList(); + List result = Lists.newArrayList(); while (!queue.isEmpty()) { - V vertex = queue.remove(); + VertexT vertex = queue.remove(); result.add(vertex); - for (V successor : adjacencyList.get(vertex)) { + for (VertexT successor : adjacencyList.get(vertex)) { if (decrementAndGetCount(successor) == 0) { queue.add(successor); } @@ -95,7 +95,7 @@ public List topologicalSort() { return result; } - private int decrementAndGetCount(V vertex) { + private int decrementAndGetCount(VertexT vertex) { Integer count = vertices.get(vertex); count = count - 1; vertices.put(vertex, count); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java index 10e648c8aa2..d57e23c3982 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/Reflection.java @@ -83,10 +83,10 @@ public static Class loadClass(ClassLoader classLoader, String className) { * @return the new instance, or empty if {@code classNameOption} is not defined in the * configuration. */ - public static Optional buildFromConfig( + public static Optional buildFromConfig( InternalDriverContext context, DriverOption classNameOption, - Class expectedSuperType, + Class expectedSuperType, String... defaultPackages) { return buildFromConfig(context, null, classNameOption, expectedSuperType, defaultPackages); } @@ -119,10 +119,10 @@ public static Optional buildFromConfig( * @return the policy instances by profile name. If multiple profiles share the same * configuration, a single instance will be shared by all their entries. */ - public static Map buildFromConfigProfiles( + public static Map buildFromConfigProfiles( InternalDriverContext context, DriverOption rootOption, - Class expectedSuperType, + Class expectedSuperType, String... defaultPackages) { // Find out how many distinct configurations we have @@ -133,11 +133,11 @@ public static Map buildFromConfigProfiles( } // Instantiate each distinct configuration, and associate it with the corresponding profiles - ImmutableMap.Builder result = ImmutableMap.builder(); + ImmutableMap.Builder result = ImmutableMap.builder(); for (Collection profiles : profilesByConfig.asMap().values()) { // Since all profiles use the same config, we can use any of them String profileName = profiles.iterator().next(); - T policy = + ComponentT policy = buildFromConfig( context, profileName, classOption(rootOption), expectedSuperType, defaultPackages) .orElseThrow( @@ -158,11 +158,11 @@ context, profileName, classOption(rootOption), expectedSuperType, defaultPackage * one-arg constructor. If not null, this is a per-profile policy, look for a two-arg * constructor. */ - public static Optional buildFromConfig( + public static Optional buildFromConfig( InternalDriverContext context, String profileName, DriverOption classNameOption, - Class expectedSuperType, + Class expectedSuperType, String... defaultPackages) { DriverExecutionProfile config = @@ -205,7 +205,7 @@ public static Optional buildFromConfig( configPath, expectedSuperType.getName()); - Constructor constructor; + Constructor constructor; Class[] argumentTypes = (profileName == null) ? new Class[] {DriverContext.class} @@ -221,7 +221,7 @@ public static Optional buildFromConfig( } try { @SuppressWarnings("JavaReflectionInvocation") - T instance = + ComponentT instance = (profileName == null) ? constructor.newInstance(context) : constructor.newInstance(context, profileName); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java index bd364b89761..ded770a3d48 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Debouncer.java @@ -37,20 +37,20 @@ * window is reset, and the next flush will now contain both events. If the window keeps getting * reset, the debouncer will flush after a given number of accumulated events. * - * @param the type of event. - * @param the resulting type after the events of a batch have been coalesced. + * @param the type of the incoming events. + * @param the resulting type after the events of a batch have been coalesced. */ @NotThreadSafe // must be confined to adminExecutor -public class Debouncer { +public class Debouncer { private static final Logger LOG = LoggerFactory.getLogger(Debouncer.class); private final EventExecutor adminExecutor; - private final Consumer onFlush; + private final Consumer onFlush; private final Duration window; private final long maxEvents; - private final Function, R> coalescer; + private final Function, CoalescedT> coalescer; - private List currentBatch = new ArrayList<>(); + private List currentBatch = new ArrayList<>(); private ScheduledFuture nextFlush; private boolean stopped; @@ -65,8 +65,8 @@ public class Debouncer { */ public Debouncer( EventExecutor adminExecutor, - Function, R> coalescer, - Consumer onFlush, + Function, CoalescedT> coalescer, + Consumer onFlush, Duration window, long maxEvents) { this.coalescer = coalescer; @@ -78,7 +78,7 @@ public Debouncer( } /** This must be called on eventExecutor too. */ - public void receive(T element) { + public void receive(IncomingT element) { assert adminExecutor.inEventLoop(); if (stopped) { return; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java index a84b63ed558..5d6fd62918a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/ReplayingEventFilter.java @@ -38,7 +38,7 @@ * */ @ThreadSafe -public class ReplayingEventFilter { +public class ReplayingEventFilter { private enum State { NEW, @@ -46,7 +46,7 @@ private enum State { READY } - private final Consumer consumer; + private final Consumer consumer; // Exceptionally, we use a lock: it will rarely be contended, and if so for only a short period. private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); @@ -55,9 +55,9 @@ private enum State { private State state; @GuardedBy("stateLock") - private List recordedEvents; + private List recordedEvents; - public ReplayingEventFilter(Consumer consumer) { + public ReplayingEventFilter(Consumer consumer) { this.consumer = consumer; this.state = State.NEW; this.recordedEvents = new CopyOnWriteArrayList<>(); @@ -76,7 +76,7 @@ public void markReady() { stateLock.writeLock().lock(); try { state = State.READY; - for (T event : recordedEvents) { + for (EventT event : recordedEvents) { consumer.accept(event); } } finally { @@ -84,7 +84,7 @@ public void markReady() { } } - public void accept(T event) { + public void accept(EventT event) { stateLock.readLock().lock(); try { switch (state) { @@ -103,7 +103,7 @@ public void accept(T event) { } @VisibleForTesting - public List recordedEvents() { + public List recordedEvents() { stateLock.readLock().lock(); try { return ImmutableList.copyOf(recordedEvents); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java index 77dcd67b6b2..e6c08f8e063 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java @@ -25,18 +25,18 @@ import net.jcip.annotations.Immutable; @Immutable -public class DefaultLiteral implements Literal { +public class DefaultLiteral implements Literal { - private final T value; - private final TypeCodec codec; + private final ValueT value; + private final TypeCodec codec; private final CqlIdentifier alias; - public DefaultLiteral(@Nullable T value, @Nullable TypeCodec codec) { + public DefaultLiteral(@Nullable ValueT value, @Nullable TypeCodec codec) { this(value, codec, null); } public DefaultLiteral( - @Nullable T value, @Nullable TypeCodec codec, @Nullable CqlIdentifier alias) { + @Nullable ValueT value, @Nullable TypeCodec codec, @Nullable CqlIdentifier alias) { Preconditions.checkArgument( value == null || codec != null, "Must provide a codec if the value is not null"); this.value = value; @@ -68,12 +68,12 @@ public Selector as(@NonNull CqlIdentifier alias) { } @Nullable - public T getValue() { + public ValueT getValue() { return value; } @Nullable - public TypeCodec getCodec() { + public TypeCodec getCodec() { return codec; } From 9949467d404642d8b135b798d905d4d5b887c678 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 10:02:54 -0700 Subject: [PATCH 593/742] JAVA-1989: Add BatchStatement.newInstance(BatchType, Iterable) --- changelog/README.md | 1 + .../driver/api/core/cql/BatchStatement.java | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 108c2609748..6351ea68614 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [improvement] JAVA-1989: Add BatchStatement.newInstance(BatchType, Iterable) - [improvement] JAVA-1988: Remove pre-fetching from ResultSet API - [bug] JAVA-1948: Close session properly when LBP fails to initialize - [improvement] JAVA-1949: Improve error message when contact points are wrong diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index 13d30c5e394..d41b8d42b23 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -64,6 +64,34 @@ static BatchStatement newInstance(@NonNull BatchType batchType) { null); } + /** + * Creates an instance of the default implementation for the given batch type, containing the + * given statements. + */ + @NonNull + static BatchStatement newInstance( + @NonNull BatchType batchType, @NonNull Iterable> statements) { + return new DefaultBatchStatement( + batchType, + ImmutableList.copyOf(statements), + null, + null, + null, + null, + null, + null, + Collections.emptyMap(), + false, + false, + Long.MIN_VALUE, + null, + Integer.MIN_VALUE, + null, + null, + null, + null); + } + /** * Creates an instance of the default implementation for the given batch type, containing the * given statements. From 9a68d912acb2b3ef341d01134c3246ebd6073567 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 8 Oct 2018 15:04:08 +0200 Subject: [PATCH 594/742] JAVA-1919: Provide a timestamp <=> ZonedDateTime codec --- changelog/README.md | 1 + .../api/core/type/codec/TypeCodecs.java | 49 +++++ .../api/core/type/reflect/GenericType.java | 2 + .../core/type/codec/ZonedTimestampCodec.java | 126 +++++++++++++ .../type/codec/ZonedTimestampCodecTest.java | 167 ++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodec.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java diff --git a/changelog/README.md b/changelog/README.md index 6351ea68614..e008c29a72c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta2 (in progress) +- [new feature] JAVA-1919: Provide a timestamp <=> ZonedDateTime codec - [improvement] JAVA-1989: Add BatchStatement.newInstance(BatchType, Iterable) - [improvement] JAVA-1988: Remove pre-fetching from ResultSet API - [bug] JAVA-1948: Close session properly when LBP fails to initialize diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java index a6d6fa0f357..ac421f2a046 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.java @@ -48,6 +48,7 @@ import com.datastax.oss.driver.internal.core.type.codec.UdtCodec; import com.datastax.oss.driver.internal.core.type.codec.UuidCodec; import com.datastax.oss.driver.internal.core.type.codec.VarIntCodec; +import com.datastax.oss.driver.internal.core.type.codec.ZonedTimestampCodec; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import edu.umd.cs.findbugs.annotations.NonNull; @@ -58,6 +59,9 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.Set; @@ -75,6 +79,35 @@ public class TypeCodecs { public static final PrimitiveLongCodec BIGINT = new BigIntCodec(); public static final PrimitiveShortCodec SMALLINT = new SmallIntCodec(); public static final TypeCodec TIMESTAMP = new TimestampCodec(); + + /** + * A codec that handles Apache Cassandra(R)'s timestamp type and maps it to Java's {@link + * ZonedDateTime}, using the system's {@linkplain ZoneId#systemDefault() default time zone} as its + * source of time zone information. + * + *

              Note that Apache Cassandra(R)'s timestamp type does not store any time zone; this codec is + * provided merely as a convenience for users that need to deal with zoned timestamps in their + * applications. + * + * @see #ZONED_TIMESTAMP_UTC + * @see #zonedTimestampAt(ZoneId) + */ + public static final TypeCodec ZONED_TIMESTAMP_SYSTEM = new ZonedTimestampCodec(); + + /** + * A codec that handles Apache Cassandra(R)'s timestamp type and maps it to Java's {@link + * ZonedDateTime}, using {@link ZoneOffset#UTC} as its source of time zone information. + * + *

              Note that Apache Cassandra(R)'s timestamp type does not store any time zone; this codec is + * provided merely as a convenience for users that need to deal with zoned timestamps in their + * applications. + * + * @see #ZONED_TIMESTAMP_SYSTEM + * @see #zonedTimestampAt(ZoneId) + */ + public static final TypeCodec ZONED_TIMESTAMP_UTC = + new ZonedTimestampCodec(ZoneOffset.UTC); + public static final TypeCodec DATE = new DateCodec(); public static final TypeCodec TIME = new TimeCodec(); public static final TypeCodec BLOB = new BlobCodec(); @@ -119,4 +152,20 @@ public static TypeCodec tupleOf(@NonNull TupleType cqlType) { public static TypeCodec udtOf(@NonNull UserDefinedType cqlType) { return new UdtCodec(cqlType); } + + /** + * Returns a codec that handles Apache Cassandra(R)'s timestamp type and maps it to Java's {@link + * ZonedDateTime}, using the supplied {@link ZoneId} as its source of time zone information. + * + *

              Note that Apache Cassandra(R)'s timestamp type does not store any time zone; the codecs + * created by this method are provided merely as a convenience for users that need to deal with + * zoned timestamps in their applications. + * + * @see #ZONED_TIMESTAMP_SYSTEM + * @see #ZONED_TIMESTAMP_UTC + */ + @NonNull + public static TypeCodec zonedTimestampAt(@NonNull ZoneId timeZone) { + return new ZonedTimestampCodec(timeZone); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index ce14e2052bf..daa269862c3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -34,6 +34,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.Set; @@ -90,6 +91,7 @@ public class GenericType { public static final GenericType LONG = of(Long.class); public static final GenericType SHORT = of(Short.class); public static final GenericType INSTANT = of(Instant.class); + public static final GenericType ZONED_DATE_TIME = of(ZonedDateTime.class); public static final GenericType LOCAL_DATE = of(LocalDate.class); public static final GenericType LOCAL_TIME = of(LocalTime.class); public static final GenericType BYTE_BUFFER = of(ByteBuffer.class); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodec.java new file mode 100644 index 00000000000..16649fd8daa --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodec.java @@ -0,0 +1,126 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.type.codec; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import net.jcip.annotations.ThreadSafe; + +/** + * A codec that handles Apache Cassandra(R)'s timestamp type and maps it to Java's {@link + * ZonedDateTime}, using the {@link ZoneId} supplied at instantiation. + * + *

              Note that Apache Cassandra(R)'s timestamp type does not store any time zone; this codec is + * provided merely as a convenience for users that need to deal with zoned timestamps in their + * applications. + * + *

              This codec shares its logic with {@link TimestampCodec}. See the javadocs of this codec for + * important remarks about implementation notes and accepted timestamp formats. + * + * @see TimestampCodec + */ +@ThreadSafe +public class ZonedTimestampCodec implements TypeCodec { + + private final TypeCodec instantCodec; + private final ZoneId timeZone; + + /** + * Creates a new {@code ZonedTimestampCodec} that converts CQL timestamps into {@link + * ZonedDateTime} instances using the system's {@linkplain ZoneId#systemDefault() default time + * zone} as their time zone. The supplied {@code timeZone} will also be used to parse CQL + * timestamp literals that do not include any time zone information. + */ + public ZonedTimestampCodec() { + this(ZoneId.systemDefault()); + } + + /** + * Creates a new {@code ZonedTimestampCodec} that converts CQL timestamps into {@link + * ZonedDateTime} instances using the given {@link ZoneId} as their time zone. The supplied {@code + * timeZone} will also be used to parse CQL timestamp literals that do not include any time zone + * information. + */ + public ZonedTimestampCodec(ZoneId timeZone) { + instantCodec = new TimestampCodec(timeZone); + this.timeZone = timeZone; + } + + @NonNull + @Override + public GenericType getJavaType() { + return GenericType.ZONED_DATE_TIME; + } + + @NonNull + @Override + public DataType getCqlType() { + return DataTypes.TIMESTAMP; + } + + @Override + public boolean accepts(@NonNull Object value) { + return value instanceof ZonedDateTime; + } + + @Override + public boolean accepts(@NonNull Class javaClass) { + return javaClass == ZonedDateTime.class; + } + + @Nullable + @Override + public ByteBuffer encode( + @Nullable ZonedDateTime value, @NonNull ProtocolVersion protocolVersion) { + return instantCodec.encode(value != null ? value.toInstant() : null, protocolVersion); + } + + @Nullable + @Override + public ZonedDateTime decode( + @Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + Instant instant = instantCodec.decode(bytes, protocolVersion); + if (instant == null) { + return null; + } + return instant.atZone(timeZone); + } + + @NonNull + @Override + public String format(@Nullable ZonedDateTime value) { + return instantCodec.format(value != null ? value.toInstant() : null); + } + + @Nullable + @Override + public ZonedDateTime parse(@Nullable String value) { + Instant instant = instantCodec.parse(value); + if (instant == null) { + return null; + } + return instant.atZone(timeZone); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java new file mode 100644 index 00000000000..77a09ccead3 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java @@ -0,0 +1,167 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.type.codec; + +import static java.time.ZoneOffset.ofHours; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DataProviderRunner.class) +public class ZonedTimestampCodecTest extends CodecTestBase { + + @Test + @UseDataProvider(value = "timeZones", location = TimestampCodecTest.class) + public void should_encode(ZoneId timeZone) { + codec = TypeCodecs.zonedTimestampAt(timeZone); + assertThat(encode(Instant.EPOCH.atZone(timeZone))).isEqualTo("0x0000000000000000"); + assertThat(encode(Instant.ofEpochMilli(128).atZone(timeZone))).isEqualTo("0x0000000000000080"); + assertThat(encode(null)).isNull(); + } + + @Test + public void should_decode() { + codec = TypeCodecs.ZONED_TIMESTAMP_UTC; + assertThat(decode("0x0000000000000000").toInstant().toEpochMilli()).isEqualTo(0); + assertThat(decode("0x0000000000000080").toInstant().toEpochMilli()).isEqualTo(128); + assertThat(decode(null)).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_decode_if_not_enough_bytes() { + codec = TypeCodecs.ZONED_TIMESTAMP_SYSTEM; + decode("0x0000"); + } + + @Test(expected = IllegalArgumentException.class) + public void should_fail_to_decode_if_too_many_bytes() { + codec = TypeCodecs.ZONED_TIMESTAMP_SYSTEM; + decode("0x0000000000000000" + "0000"); + } + + @Test + public void should_format() { + codec = TypeCodecs.zonedTimestampAt(ZoneOffset.ofHours(2)); + // No need to test various values because the codec delegates directly to SimpleDateFormat, + // which we assume does its job correctly. + assertThat(format(Instant.EPOCH.atZone(ZoneOffset.UTC))) + .isEqualTo("'1970-01-01T02:00:00.000+02:00'"); + assertThat(format(ZonedDateTime.parse("2018-08-16T15:59:34.123Z"))) + .isEqualTo("'2018-08-16T17:59:34.123+02:00'"); + assertThat(format(null)).isEqualTo("NULL"); + } + + @Test + @UseDataProvider(value = "timeZones", location = TimestampCodecTest.class) + public void should_parse(ZoneId timeZone) { + codec = TypeCodecs.zonedTimestampAt(timeZone); + + // Raw numbers + assertThat(parse("'0'")).isEqualTo(Instant.EPOCH.atZone(timeZone)); + assertThat(parse("'-1'")).isEqualTo(Instant.EPOCH.minusMillis(1).atZone(timeZone)); + assertThat(parse("1534463100000")) + .isEqualTo(Instant.ofEpochMilli(1534463100000L).atZone(timeZone)); + + // Date formats + ZonedDateTime expected; + + // date without time, without time zone + expected = LocalDate.parse("2017-01-01").atStartOfDay().atZone(timeZone); + assertThat(parse("'2017-01-01'")).isEqualTo(expected); + + // date without time, with time zone + expected = LocalDate.parse("2018-08-16").atStartOfDay().atZone(ofHours(2)); + assertThat(parse("'2018-08-16+02'")).isEqualTo(expected); + assertThat(parse("'2018-08-16+0200'")).isEqualTo(expected); + assertThat(parse("'2018-08-16+02:00'")).isEqualTo(expected); + assertThat(parse("'2018-08-16 CEST'")).isEqualTo(expected); + + // date with time, without time zone + expected = LocalDateTime.parse("2018-08-16T23:45").atZone(timeZone); + assertThat(parse("'2018-08-16T23:45'")).isEqualTo(expected); + assertThat(parse("'2018-08-16 23:45'")).isEqualTo(expected); + + // date with time + seconds, without time zone + expected = LocalDateTime.parse("2019-12-31T16:08:38").atZone(timeZone); + assertThat(parse("'2019-12-31T16:08:38'")).isEqualTo(expected); + assertThat(parse("'2019-12-31 16:08:38'")).isEqualTo(expected); + + // date with time + seconds + milliseconds, without time zone + expected = LocalDateTime.parse("1950-02-28T12:00:59.230").atZone(timeZone); + assertThat(parse("'1950-02-28T12:00:59.230'")).isEqualTo(expected); + assertThat(parse("'1950-02-28 12:00:59.230'")).isEqualTo(expected); + + // date with time, with time zone + expected = ZonedDateTime.parse("1973-06-23T23:59:00.000+01:00"); + assertThat(parse("'1973-06-23T23:59+01'")).isEqualTo(expected); + assertThat(parse("'1973-06-23T23:59+0100'")).isEqualTo(expected); + assertThat(parse("'1973-06-23T23:59+01:00'")).isEqualTo(expected); + assertThat(parse("'1973-06-23T23:59 CET'")).isEqualTo(expected); + assertThat(parse("'1973-06-23 23:59+01'")).isEqualTo(expected); + assertThat(parse("'1973-06-23 23:59+0100'")).isEqualTo(expected); + assertThat(parse("'1973-06-23 23:59+01:00'")).isEqualTo(expected); + assertThat(parse("'1973-06-23 23:59 CET'")).isEqualTo(expected); + + // date with time + seconds, with time zone + expected = ZonedDateTime.parse("1980-01-01T23:59:59.000-08:00"); + assertThat(parse("'1980-01-01T23:59:59-08'")).isEqualTo(expected); + assertThat(parse("'1980-01-01T23:59:59-0800'")).isEqualTo(expected); + assertThat(parse("'1980-01-01T23:59:59-08:00'")).isEqualTo(expected); + assertThat(parse("'1980-01-01T23:59:59 PST'")).isEqualTo(expected); + assertThat(parse("'1980-01-01 23:59:59-08'")).isEqualTo(expected); + assertThat(parse("'1980-01-01 23:59:59-0800'")).isEqualTo(expected); + assertThat(parse("'1980-01-01 23:59:59-08:00'")).isEqualTo(expected); + assertThat(parse("'1980-01-01 23:59:59 PST'")).isEqualTo(expected); + + // date with time + seconds + milliseconds, with time zone + expected = ZonedDateTime.parse("1999-12-31T23:59:59.999+00:00"); + assertThat(parse("'1999-12-31T23:59:59.999+00'")).isEqualTo(expected); + assertThat(parse("'1999-12-31T23:59:59.999+0000'")).isEqualTo(expected); + assertThat(parse("'1999-12-31T23:59:59.999+00:00'")).isEqualTo(expected); + assertThat(parse("'1999-12-31T23:59:59.999 UTC'")).isEqualTo(expected); + assertThat(parse("'1999-12-31 23:59:59.999+00'")).isEqualTo(expected); + assertThat(parse("'1999-12-31 23:59:59.999+0000'")).isEqualTo(expected); + assertThat(parse("'1999-12-31 23:59:59.999+00:00'")).isEqualTo(expected); + assertThat(parse("'1999-12-31 23:59:59.999 UTC'")).isEqualTo(expected); + + assertThat(parse("NULL")).isNull(); + assertThat(parse("null")).isNull(); + assertThat(parse("")).isNull(); + assertThat(parse(null)).isNull(); + } + + @Test + public void should_fail_to_parse_invalid_input() { + codec = new ZonedTimestampCodec(); + assertThatThrownBy(() -> parse("not a timestamp")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Alphanumeric timestamp literal must be quoted: \"not a timestamp\""); + assertThatThrownBy(() -> parse("'not a timestamp'")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse timestamp value from \"'not a timestamp'\""); + } +} From 9028caaa780c5dfaf9d7d576f4472415fef6eca9 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 16:26:24 -0700 Subject: [PATCH 595/742] Bump native-protocol to 1.4.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 868f1752cdd..c30a84d994b 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 25.1-jre 2.1.10 4.0.2 - 1.4.4-SNAPSHOT + 1.4.4 4.1.27.Final 1.7.25 From e5d58b03c08a43756abaeafccd8fd8518e9a50aa Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 16:30:28 -0700 Subject: [PATCH 596/742] Update version in docs --- README.md | 2 +- changelog/README.md | 2 +- manual/core/README.md | 2 +- manual/core/compression/README.md | 2 +- manual/core/integration/README.md | 16 ++++++++-------- manual/core/shaded_jar/README.md | 6 +++--- manual/query_builder/README.md | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8f913e1879f..b9089777e3a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-beta1](https://github.com/datastax/java-driver/tree/4.0.0-beta1).* +[4.0.0-beta2](https://github.com/datastax/java-driver/tree/4.0.0-beta2).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and [DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query diff --git a/changelog/README.md b/changelog/README.md index e008c29a72c..deef10be1c7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-beta2 (in progress) +### 4.0.0-beta2 - [new feature] JAVA-1919: Provide a timestamp <=> ZonedDateTime codec - [improvement] JAVA-1989: Add BatchStatement.newInstance(BatchType, Iterable) diff --git a/manual/core/README.md b/manual/core/README.md index 886fd68b0ed..dcdf7b52eff 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 ``` diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index 2180c52782f..c6aea1c0b1a 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -65,4 +65,4 @@ Dependency: Always double-check the exact Snappy version needed; you can find it in the driver's [parent POM]. -[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta1%7Cpom \ No newline at end of file +[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta2%7Cpom \ No newline at end of file diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 4df5a73097b..7f6e5708069 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -39,7 +39,7 @@ dependencies, and tell Maven that we're going to use Java 8: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 ch.qos.logback @@ -144,7 +144,7 @@ You should see output similar to: [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3.1:java (default-cli) @ yourapp --- -11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta1 +11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta2 11:39:45.648 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision 11:39:45.649 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 3.11.2 @@ -176,7 +176,7 @@ repositories { } dependencies { - compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta1' + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta2' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } ``` @@ -260,7 +260,7 @@ In that case, you can exclude the dependency: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 com.typesafe @@ -288,7 +288,7 @@ are not available on your platform, you can exclude the following dependencies: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 com.github.jnr @@ -322,7 +322,7 @@ and never call [Session.getMetrics] anywhere in your application, you can remove com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 io.dropwizard.metrics @@ -343,7 +343,7 @@ If all of these metrics are disabled, you can remove the dependency: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 org.hdrhistogram @@ -382,7 +382,7 @@ you, the workaround is to redeclare them explicitly in your application: com.datastax.oss java-driver-core - 4.0.0-beta1 + 4.0.0-beta2 com.github.stephenc.jcip diff --git a/manual/core/shaded_jar/README.md b/manual/core/shaded_jar/README.md index 180e220c744..975e86631b5 100644 --- a/manual/core/shaded_jar/README.md +++ b/manual/core/shaded_jar/README.md @@ -12,7 +12,7 @@ package name: com.datastax.oss java-driver-core-shaded - 4.0.0-beta1 + 4.0.0-beta2 ``` @@ -23,12 +23,12 @@ dependency to the non-shaded JAR: com.datastax.oss java-driver-core-shaded - 4.0.0-beta1 + 4.0.0-beta2 com.datastax.oss java-driver-query-builder - 4.0.0-beta1 + 4.0.0-beta2 com.datastax.oss diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index c3a52e45984..8ed3de9969e 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -14,7 +14,7 @@ To use it in your application, add the following dependency: com.datastax.oss java-driver-query-builder - 4.0.0-beta1 + 4.0.0-beta2 ``` From 34a7900365d3a28184541c2cc8b620767b21d126 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 17:53:52 -0700 Subject: [PATCH 597/742] Update version in RevApi config file --- core/revapi.json | 54 ++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 8cde48c19fa..716e60b4633 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -22,7 +22,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata", "classSimpleName": "KeyspaceMetadata", "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Adding virtual tables" }, @@ -37,7 +37,7 @@ "classSimpleName": "RelationMetadata", "methodName": "getId", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Adding virtual tables" }, @@ -76,7 +76,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", "classSimpleName": "TableMetadata", "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Adding virtual tables" }, @@ -145,7 +145,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getAddressTranslator", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -156,7 +156,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getAuthProvider", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -167,7 +167,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getConfig", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -178,7 +178,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getConfigLoader", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -189,7 +189,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getLoadBalancingPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -200,7 +200,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getNodeStateListener", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -211,7 +211,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getReconnectionPolicy", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -222,7 +222,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getRequestThrottler", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -233,7 +233,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getRequestTracker", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -244,7 +244,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getRetryPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -255,7 +255,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getSchemaChangeListener", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -266,7 +266,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getSessionName", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -277,7 +277,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getSpeculativeExecutionPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -288,7 +288,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getSslEngineFactory", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -299,7 +299,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", "classSimpleName": "DriverContext", "methodName": "getTimestampGenerator", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -475,7 +475,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", "classSimpleName": "AttachmentPoint", "methodName": "getCodecRegistry", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -486,7 +486,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", "classSimpleName": "AttachmentPoint", "methodName": "getProtocolVersion", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Renamed context getters for uniformity" }, @@ -508,7 +508,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.session.Request", "classSimpleName": "Request", "methodName": "getNode", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Add ability to query specific nodes for virtual tables" }, @@ -519,7 +519,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.cql.Statement", "classSimpleName": "Statement", "methodName": "setNode", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Add ability to query specific nodes for virtual tables" }, @@ -534,7 +534,7 @@ "classSimpleName": "CqlSession", "methodName": "executeAsync", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Return covariant future types from session async methods" }, @@ -549,7 +549,7 @@ "classSimpleName": "CqlSession", "methodName": "executeAsync", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Return covariant future types from session async methods" }, @@ -564,7 +564,7 @@ "classSimpleName": "CqlSession", "methodName": "prepareAsync", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Return covariant future types from session async methods" }, @@ -579,7 +579,7 @@ "classSimpleName": "CqlSession", "methodName": "prepareAsync", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Return covariant future types from session async methods" }, @@ -594,7 +594,7 @@ "classSimpleName": "CqlSession", "methodName": "prepareAsync", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "elementKind": "method", "justification": "Return covariant future types from session async methods" }, From 29c68c6a466bb59b9e5593d602aa0d47cdbabe31 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 17:58:44 -0700 Subject: [PATCH 598/742] [maven-release-plugin] prepare release 4.0.0-beta2 --- core-shaded/pom.xml | 6 ++---- core/pom.xml | 6 ++---- distribution/pom.xml | 2 +- integration-tests/pom.xml | 6 ++---- pom.xml | 8 +++----- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 12 insertions(+), 20 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 394084b187f..f920f4816d4 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -16,15 +16,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index eeea70aabed..e97161b263a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index 39f25716830..46c57e7b887 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index ca0f7c609b4..d6c079fe6c4 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index c30a84d994b..eee9966804a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,12 @@ limitations under the License. --> - + 4.0.0 com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 pom DataStax Java driver for Apache Cassandra(R) @@ -607,7 +605,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-beta2 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index a5bd124ac9a..c1cbf824097 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 8e261149965..ac07fa4450a 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2-SNAPSHOT + 4.0.0-beta2 java-driver-test-infra From 1e10cdcd8044a48c630d739baadc0aafeb802732 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 18:00:12 -0700 Subject: [PATCH 599/742] [maven-release-plugin] prepare for next development iteration --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index f920f4816d4..087bf2d6d63 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index e97161b263a..e1e38a77bef 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index 46c57e7b887..ea68776a9a7 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index d6c079fe6c4..91df7c247c5 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index eee9966804a..ab33ff2690f 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT pom DataStax Java driver for Apache Cassandra(R) @@ -605,7 +605,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-beta2 + HEAD diff --git a/query-builder/pom.xml b/query-builder/pom.xml index c1cbf824097..7e6da61d1a6 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index ac07fa4450a..7dc4d5d7d32 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta2 + 4.0.0-beta3-SNAPSHOT java-driver-test-infra From 8f09dcb07b4f036d35deb357bdda132b04764c25 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 16 Oct 2018 18:19:59 -0700 Subject: [PATCH 600/742] Generate empty javadoc JAR for the shaded core module Motivation: It looks like Maven Central has become less permissive, we used to deploy the shaded core without a javadocs JAR but that's not accepted anymore. Modifications: Generate a javadocs JAR during the build. For now it's a dummy, empty JAR, users can refer to the javadocs of the unshaded one. One downside is that this won't work in IDEs; if there's a strong demand, we can look into generating the actual javadocs later. Result: The driver can be deployed into Maven central again. --- core-shaded/pom.xml | 16 ++++++++++++++++ core-shaded/src/main/javadoc/README.txt | 3 +++ 2 files changed, 19 insertions(+) create mode 100644 core-shaded/src/main/javadoc/README.txt diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index f920f4816d4..fd277efeb9a 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -142,6 +142,22 @@ + + maven-jar-plugin + 3.0.2 + + + empty-javadoc-jar + + jar + + + javadoc + ${basedir}/src/main/javadoc + + + + maven-dependency-plugin diff --git a/core-shaded/src/main/javadoc/README.txt b/core-shaded/src/main/javadoc/README.txt new file mode 100644 index 00000000000..8ec8f952d69 --- /dev/null +++ b/core-shaded/src/main/javadoc/README.txt @@ -0,0 +1,3 @@ +This empty JAR is generated for compliance with Maven Central rules. + +Please refer to the javadocs of the unshaded artifact (com.datastax.oss:java-driver-core). \ No newline at end of file From b0bb2136b5f9e52d98c17c0ef22cf8cb40b1a33f Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 17 Oct 2018 09:56:14 -0700 Subject: [PATCH 601/742] Prepare changelog for next iteration --- changelog/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index deef10be1c7..2cf0d4029e7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,6 +2,8 @@ +### 4.0.0-beta3 (in progress) + ### 4.0.0-beta2 - [new feature] JAVA-1919: Provide a timestamp <=> ZonedDateTime codec From 24e27dd9415456338510d2cf18ac9a6c5428e677 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 22 Oct 2018 11:10:55 -0700 Subject: [PATCH 602/742] JAVA-2001: Handle zero timeout in admin requests --- changelog/README.md | 2 ++ .../core/adminrequest/AdminRequestHandler.java | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 2cf0d4029e7..261a25faa6a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2001: Handle zero timeout in admin requests + ### 4.0.0-beta2 - [new feature] JAVA-1919: Provide a timestamp <=> ZonedDateTime codec diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index f710240dec0..4f112f24974 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -108,9 +108,13 @@ public CompletionStage start() { private void onWriteComplete(Future future) { if (future.isSuccess()) { LOG.debug("[{}] Successfully wrote {}, waiting for response", logPrefix, this); - timeoutFuture = - channel.eventLoop().schedule(this::fireTimeout, timeout.toNanos(), TimeUnit.NANOSECONDS); - timeoutFuture.addListener(UncaughtExceptions::log); + if (timeout.toNanos() > 0) { + timeoutFuture = + channel + .eventLoop() + .schedule(this::fireTimeout, timeout.toNanos(), TimeUnit.NANOSECONDS); + timeoutFuture.addListener(UncaughtExceptions::log); + } } else { setFinalError(future.cause()); } From 0d305a34f25c14f9a53ef1def8bd7c37c552ea59 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 24 Oct 2018 15:38:33 -0700 Subject: [PATCH 603/742] JAVA-2007: Make driver threads extend FastThreadLocalThread --- changelog/README.md | 1 + .../internal/core/util/concurrent/BlockingOperation.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 261a25faa6a..f7624f4dd4c 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2007: Make driver threads extend FastThreadLocalThread - [bug] JAVA-2001: Handle zero timeout in admin requests ### 4.0.0-beta2 diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java index 4161c11e1f5..7797594b7b9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/BlockingOperation.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.util.concurrent; import edu.umd.cs.findbugs.annotations.NonNull; +import io.netty.util.concurrent.FastThreadLocalThread; import java.util.concurrent.ThreadFactory; /** @@ -58,7 +59,7 @@ public Thread newThread(@NonNull Runnable r) { } } - private static class InternalThread extends Thread { + private static class InternalThread extends FastThreadLocalThread { private InternalThread(Runnable runnable) { super(runnable); } From 1accded3aa32aa3cd5c23cf5d52df8ae318dd05e Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 26 Oct 2018 14:42:23 +0200 Subject: [PATCH 604/742] Fix minor typo in javadocs --- .../datastax/oss/driver/api/core/retry/RetryPolicy.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java index 8465556e479..29359c1607d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -124,10 +124,10 @@ RetryDecision onUnavailable( * heartbeat failure); or if there was an unexpected error while decoding the response (this can * only be a driver bug). * - *

              Note that this method will only be invoked for {@link Request#isIdempotent()} idempotent} - * requests: when execution was aborted before getting a response, it is impossible to determine - * with 100% certainty whether a mutation was applied or not, so a write is never safe to retry; - * the driver will rethrow the error directly, without invoking the retry policy. + *

              Note that this method will only be invoked for {@linkplain Request#isIdempotent() + * idempotent} requests: when execution was aborted before getting a response, it is impossible to + * determine with 100% certainty whether a mutation was applied or not, so a write is never safe + * to retry; the driver will rethrow the error directly, without invoking the retry policy. * * @param request the request that was aborted. * @param error the error. From 0fb742ac8ee29144e249dee264c499baf01455e5 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Nov 2018 09:16:14 -0700 Subject: [PATCH 605/742] Avoid unnecessary conversion to ConsistencyLevel in QueryTraceFetcher --- .../driver/internal/core/cql/QueryTraceFetcher.java | 13 +++---------- .../internal/core/cql/QueryTraceFetcherTest.java | 4 ---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index 45a78447995..ef746460570 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -15,7 +15,6 @@ */ package com.datastax.oss.driver.internal.core.cql; -import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -56,18 +55,12 @@ class QueryTraceFetcher { this.tracingId = tracingId; this.session = session; - ConsistencyLevel regularConsistency = - context - .getConsistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); - ConsistencyLevel traceConsistency = - context - .getConsistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)); + String regularConsistency = config.getString(DefaultDriverOption.REQUEST_CONSISTENCY); + String traceConsistency = config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY); this.config = (traceConsistency.equals(regularConsistency)) ? config - : config.withString(DefaultDriverOption.REQUEST_CONSISTENCY, traceConsistency.name()); + : config.withString(DefaultDriverOption.REQUEST_CONSISTENCY, traceConsistency); this.maxAttempts = config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS); this.intervalNanos = config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL).toNanos(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index 0008c326512..df7b2afa16c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -32,7 +32,6 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.TraceEvent; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.datastax.oss.driver.internal.core.DefaultConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -105,9 +104,6 @@ public void setup() { config.withString( DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) .thenReturn(traceConfig); - - Mockito.when(context.getConsistencyLevelRegistry()) - .thenReturn(new DefaultConsistencyLevelRegistry()); } @Test From 39bf5e363e79d0184b5897d65197202391967bae Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 26 Oct 2018 10:43:20 -0700 Subject: [PATCH 606/742] JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched() --- changelog/README.md | 1 + core/revapi.json | 22 ++++++++++++ .../oss/driver/api/core/cql/ResultSet.java | 35 +++++++++++++++---- .../internal/core/cql/MultiPageResultSet.java | 20 +++++++++-- .../core/cql/SinglePageResultSet.java | 10 ++++++ 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index f7624f4dd4c..230958b3656 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched() - [improvement] JAVA-2007: Make driver threads extend FastThreadLocalThread - [bug] JAVA-2001: Handle zero timeout in admin requests diff --git a/core/revapi.json b/core/revapi.json index 716e60b4633..c2cf05235fa 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -630,6 +630,28 @@ "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", "elementKind": "method", "justification": "JAVA-1988: remove pre-fetching from ResultSet API" + }, + { + "code": "java.method.addedToInterface", + "new": "method int com.datastax.oss.driver.api.core.cql.ResultSet::getAvailableWithoutFetching()", + "package": "com.datastax.oss.driver.api.core.cql", + "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", + "classSimpleName": "ResultSet", + "methodName": "getAvailableWithoutFetching", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" + }, + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.cql.ResultSet::isFullyFetched()", + "package": "com.datastax.oss.driver.api.core.cql", + "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", + "classSimpleName": "ResultSet", + "methodName": "isFullyFetched", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index 15dbc9a2a9d..c9b13189716 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -16,9 +16,10 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -97,17 +98,37 @@ default Row one() { */ @NonNull default List all() { - Iterator iterator = iterator(); - if (!iterator.hasNext()) { + if (!iterator().hasNext()) { return Collections.emptyList(); } - List result = new ArrayList<>(); - while (iterator.hasNext()) { - result.add(iterator.next()); - } + // We can't know the actual size in advance since more pages could be fetched, but we can at + // least allocate for what we already have. + List result = Lists.newArrayListWithExpectedSize(getAvailableWithoutFetching()); + Iterables.addAll(result, this); return result; } + /** + * Whether all pages have been fetched from the database. + * + *

              If this is {@code false}, it means that more blocking background queries will be triggered + * as iteration continues. + */ + boolean isFullyFetched(); + + /** + * The number of rows that can be returned from this result set before a blocking background query + * needs to be performed to retrieve more results. In other words, this is the number of rows + * remaining in the current page. + * + *

              This is useful if you use the paging state to pause the iteration and resume it later: after + * you've retrieved the state ({@link ExecutionInfo#getPagingState() + * getExecutionInfo().getPagingState()}), call this method and iterate the remaining rows; that + * way you're not leaving a gap between the last row and the position you'll restart from when you + * reinject the state in a new query. + */ + int getAvailableWithoutFetching(); + /** * If the query that produced this result was a conditional update, indicate whether it was * successfully applied. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java index 2c7ba8e1fbe..e80b442726d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/MultiPageResultSet.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.util.CountingIterator; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.datastax.oss.driver.shaded.guava.common.collect.AbstractIterator; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.Iterator; @@ -55,6 +55,16 @@ public List getExecutionInfos() { return executionInfos; } + @Override + public boolean isFullyFetched() { + return iterator.isFullyFetched(); + } + + @Override + public int getAvailableWithoutFetching() { + return iterator.remaining(); + } + @NonNull @Override public Iterator iterator() { @@ -66,11 +76,12 @@ public boolean wasApplied() { return iterator.wasApplied(); } - private class RowIterator extends AbstractIterator { + private class RowIterator extends CountingIterator { private AsyncResultSet currentPage; private Iterator currentRows; private RowIterator(AsyncResultSet firstPage) { + super(firstPage.remaining()); this.currentPage = firstPage; this.currentRows = firstPage.currentPage().iterator(); } @@ -87,6 +98,7 @@ private void maybeMoveToNextPage() { AsyncResultSet nextPage = CompletableFutures.getUninterruptibly(currentPage.fetchNextPage()); currentPage = nextPage; + remaining += nextPage.remaining(); currentRows = nextPage.currentPage().iterator(); executionInfos.add(nextPage.getExecutionInfo()); // The definitions can change from page to page if this result set was built from a bound @@ -95,6 +107,10 @@ private void maybeMoveToNextPage() { } } + private boolean isFullyFetched() { + return !currentPage.hasMorePages(); + } + private boolean wasApplied() { return currentPage.wasApplied(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java index ca762a4b4d1..5d5e4047e42 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/SinglePageResultSet.java @@ -54,6 +54,16 @@ public List getExecutionInfos() { return ImmutableList.of(onlyPage.getExecutionInfo()); } + @Override + public boolean isFullyFetched() { + return true; + } + + @Override + public int getAvailableWithoutFetching() { + return onlyPage.remaining(); + } + @NonNull @Override public Iterator iterator() { From c983c7570821d2eaa41b44966c7b7b06b7e0f3f5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 22 Oct 2018 12:37:58 +0200 Subject: [PATCH 607/742] JAVA-2002: Reimplement TypeCodec.accepts to improve performance --- changelog/README.md | 1 + .../driver/api/core/type/codec/TypeCodec.java | 49 +++++++++++++------ .../codec/registry/CachingCodecRegistry.java | 2 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 230958b3656..0c26114fdc6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2002: Reimplement TypeCodec.accepts to improve performance - [improvement] JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched() - [improvement] JAVA-2007: Make driver threads extend FastThreadLocalThread - [bug] JAVA-2001: Handle zero timeout in admin requests diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java index 8d7905b53dc..c90b77e6383 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java @@ -82,19 +82,39 @@ default boolean accepts(@NonNull GenericType javaType) { } /** - * Whether this codec is capable of processing the given Java type. + * Whether this codec is capable of processing the given Java class. * - *

              The default implementation wraps the class in a generic type and calls {@link - * #accepts(GenericType)}, therefore it is invariant as well. + *

              This implementation simply compares the given class (or its wrapper type if it is a + * primitive type) against this codec's runtime (raw) class; it is invariant with respect + * to the passed argument (through the usage of {@link Class#equals(Object)} and it's strongly + * recommended not to modify this behavior. This means that a codec will only ever return + * {@code true} for the exact runtime (raw) Java class that it has been created for. * *

              Implementors are encouraged to override this method if there is a more efficient way. In - * particular, if the codec targets a non-generic class, the check can be done with {@code - * Class#isAssignableFrom}. Or even better, with {@code ==} if that class also happens to be - * final. + * particular, if the codec targets a final class, the check can be done with a simple {@code ==}. */ default boolean accepts(@NonNull Class javaClass) { Preconditions.checkNotNull(javaClass); - return accepts(GenericType.of(javaClass)); + if (javaClass.isPrimitive()) { + if (javaClass == Boolean.TYPE) { + javaClass = Boolean.class; + } else if (javaClass == Character.TYPE) { + javaClass = Character.class; + } else if (javaClass == Byte.TYPE) { + javaClass = Byte.class; + } else if (javaClass == Short.TYPE) { + javaClass = Short.class; + } else if (javaClass == Integer.TYPE) { + javaClass = Integer.class; + } else if (javaClass == Long.TYPE) { + javaClass = Long.class; + } else if (javaClass == Float.TYPE) { + javaClass = Float.class; + } else if (javaClass == Double.TYPE) { + javaClass = Double.class; + } + } + return getJavaType().getRawType().equals(javaClass); } /** @@ -104,12 +124,13 @@ default boolean accepts(@NonNull Class javaClass) { * #accepts(GenericType)} which is capable of handling generic types. * *

              Contrary to other {@code accept} methods, this method's default implementation is - * covariant with respect to the passed argument and it's strongly recommended not to - * modify this behavior. This means that, by default, a codec will accept any - * subtype of the Java type that it has been created for. This is so because codec lookups by - * arbitrary Java objects only make sense when attempting to encode, never when attempting to - * decode, and indeed the {@linkplain #encode(Object, ProtocolVersion) encode} method is covariant - * with {@code JavaTypeT}. + * covariant with respect to the passed argument (through the usage of {@link + * Class#isAssignableFrom(Class)}) and it's strongly recommended not to modify this + * behavior. This means that, by default, a codec will accept any subtype of the + * Java type that it has been created for. This is so because codec lookups by arbitrary Java + * objects only make sense when attempting to encode, never when attempting to decode, and indeed + * the {@linkplain #encode(Object, ProtocolVersion) encode} method is covariant with {@code + * JavaTypeT}. * *

              It can only handle non-parameterized types; codecs handling parameterized types, such as * collection types, must override this method and perform some sort of "manual" inspection of the @@ -124,7 +145,7 @@ default boolean accepts(@NonNull Class javaClass) { */ default boolean accepts(@NonNull Object value) { Preconditions.checkNotNull(value); - return getJavaType().isSupertypeOf(GenericType.of(value.getClass())); + return getJavaType().getRawType().isAssignableFrom(value.getClass()); } /** Whether this codec is capable of processing the given CQL type. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java index 31dbb15a9af..740591cf3a7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java @@ -118,7 +118,7 @@ public TypeCodec codecFor( @NonNull DataType cqlType, @NonNull Class javaType) { LOG.trace("[{}] Looking up codec for {} <-> {}", logPrefix, cqlType, javaType); TypeCodec primitiveCodec = primitiveCodecsByCode.get(cqlType.getProtocolCode()); - if (primitiveCodec != null && primitiveCodec.getJavaType().__getToken().getType() == javaType) { + if (primitiveCodec != null && primitiveCodec.accepts(javaType)) { LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec); return uncheckedCast(primitiveCodec); } From 2c1b499a9ba5d2a3582bd3c3c81ba355307baec5 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 23 Oct 2018 15:25:19 +0200 Subject: [PATCH 608/742] Accept both wrapper and non-wrapper types in primitive codecs --- .../oss/driver/internal/core/type/codec/BigIntCodec.java | 2 +- .../oss/driver/internal/core/type/codec/BooleanCodec.java | 2 +- .../oss/driver/internal/core/type/codec/DoubleCodec.java | 2 +- .../oss/driver/internal/core/type/codec/FloatCodec.java | 2 +- .../datastax/oss/driver/internal/core/type/codec/IntCodec.java | 2 +- .../oss/driver/internal/core/type/codec/SmallIntCodec.java | 2 +- .../oss/driver/internal/core/type/codec/TinyIntCodec.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java index 92ca0619f93..7ba465543bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodec.java @@ -46,7 +46,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Long.class; + return javaClass == Long.class || javaClass == long.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java index 2ffe1451aa4..8768311f2ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodec.java @@ -50,7 +50,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Boolean.class; + return javaClass == Boolean.class || javaClass == boolean.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java index 18921c2bbdb..91071c0d847 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodec.java @@ -46,7 +46,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Double.class; + return javaClass == Double.class || javaClass == double.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java index c311089ecb7..a3ff38a2b83 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodec.java @@ -46,7 +46,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Float.class; + return javaClass == Float.class || javaClass == float.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java index be8f46bc9c4..702c9a40d2d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/IntCodec.java @@ -47,7 +47,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Integer.class; + return javaClass == Integer.class || javaClass == int.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java index 7a7a67608d2..d8ec3c2d414 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodec.java @@ -46,7 +46,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Short.class; + return javaClass == Short.class || javaClass == short.class; } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java index a235a2f41dd..27241aa7833 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodec.java @@ -46,7 +46,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return javaClass == Byte.class; + return javaClass == Byte.class || javaClass == byte.class; } @Nullable From dd03d4f3ed9d3794960a3468619bb22edbcb5025 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 23 Oct 2018 15:26:43 +0200 Subject: [PATCH 609/742] Enforce invariance in overridings of TypeCodec.accepts(Class) --- .../oss/driver/internal/core/type/codec/BlobCodec.java | 2 +- .../oss/driver/internal/core/type/codec/CustomCodec.java | 2 +- .../oss/driver/internal/core/type/codec/InetCodec.java | 2 +- .../oss/driver/internal/core/type/codec/TupleCodec.java | 9 ++++++--- .../oss/driver/internal/core/type/codec/UdtCodec.java | 8 +++++--- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java index c8bb12db740..4aeed77b00b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodec.java @@ -47,7 +47,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return ByteBuffer.class.isAssignableFrom(javaClass); + return ByteBuffer.class.equals(javaClass); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java index f4746ff1747..b1e1d204bfd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodec.java @@ -54,7 +54,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return ByteBuffer.class.isAssignableFrom(javaClass); + return ByteBuffer.class.equals(javaClass); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java index 18c5c620342..efc7c254d21 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/InetCodec.java @@ -50,7 +50,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return InetAddress.class.isAssignableFrom(javaClass); + return InetAddress.class.equals(javaClass); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java index 15e34d75aab..2b5e3e4a1a4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java @@ -56,7 +56,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return TupleValue.class.isAssignableFrom(javaClass); + return TupleValue.class.equals(javaClass); } @Nullable @@ -200,12 +200,15 @@ public TupleValue parse(@Nullable String value) { i += 1; position = ParseUtils.skipSpaces(value, position); - if (value.charAt(position) == ')') return tuple; - if (value.charAt(position) != ',') + if (value.charAt(position) == ')') { + return tuple; + } + if (value.charAt(position) != ',') { throw new IllegalArgumentException( String.format( "Cannot parse tuple value from \"%s\", at character %d expecting ',' but got '%c'", value, position, value.charAt(position))); + } ++position; // skip ',' position = ParseUtils.skipSpaces(value, position); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 910f298f2fa..04b08330e9e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -57,7 +57,7 @@ public boolean accepts(@NonNull Object value) { @Override public boolean accepts(@NonNull Class javaClass) { - return UdtValue.class.isAssignableFrom(javaClass); + return UdtValue.class.equals(javaClass); } @Nullable @@ -196,16 +196,18 @@ public UdtValue parse(@Nullable String value) { CqlIdentifier id = CqlIdentifier.fromInternal(value.substring(position, n)); position = n; - if (!cqlType.contains(id)) + if (!cqlType.contains(id)) { throw new IllegalArgumentException( String.format("Unknown field %s in value \"%s\"", id, value)); + } position = ParseUtils.skipSpaces(value, position); - if (value.charAt(position++) != ':') + if (value.charAt(position++) != ':') { throw new IllegalArgumentException( String.format( "Cannot parse UDT value from \"%s\", at character %d expecting ':' but got '%c'", value, position, value.charAt(position))); + } position = ParseUtils.skipSpaces(value, position); try { From 28e920f02d53fb440a02665335d97c9a15543950 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 23 Oct 2018 15:27:27 +0200 Subject: [PATCH 610/742] Add tests for TypeCodec.accepts --- .../core/type/codec/BigIntCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/BlobCodecTest.java | 21 +++++++++++++++++ .../core/type/codec/BooleanCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/CounterCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/CqlDurationCodecTest.java | 19 +++++++++++++++ .../core/type/codec/CustomCodecTest.java | 21 +++++++++++++++++ .../core/type/codec/DateCodecTest.java | 19 +++++++++++++++ .../core/type/codec/DecimalCodecTest.java | 19 +++++++++++++++ .../core/type/codec/DoubleCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/FloatCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/InetCodecTest.java | 21 +++++++++++++++++ .../core/type/codec/IntCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/SmallIntCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/StringCodecTest.java | 19 +++++++++++++++ .../core/type/codec/TimeCodecTest.java | 19 +++++++++++++++ .../core/type/codec/TimeUuidCodecTest.java | 23 +++++++++++++++++-- .../core/type/codec/TimestampCodecTest.java | 19 +++++++++++++++ .../core/type/codec/TinyIntCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/TupleCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/UdtCodecTest.java | 22 ++++++++++++++++++ .../core/type/codec/UuidCodecTest.java | 21 ++++++++++++++++- .../core/type/codec/VarintCodecTest.java | 19 +++++++++++++++ .../type/codec/ZonedTimestampCodecTest.java | 22 ++++++++++++++++++ 23 files changed, 479 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java index 1f1107727c8..a2d7fd91ee0 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BigIntCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class BigIntCodecTest extends CodecTestBase { @@ -68,4 +69,25 @@ public void should_fail_to_parse_invalid_input() { public void should_fail_to_parse_if_out_of_range() { parse(Long.toString(Long.MAX_VALUE) + "0"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Long.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(long.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Long.class)).isTrue(); + assertThat(codec.accepts(long.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(123L)).isTrue(); + assertThat(codec.accepts(Long.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java index dfba69f2596..c0448e38dbd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BlobCodecTest.java @@ -19,8 +19,10 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; import org.junit.Test; public class BlobCodecTest extends CodecTestBase { @@ -82,4 +84,23 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a blob"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(ByteBuffer.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(MappedByteBuffer.class))) + .isFalse(); // covariance not allowed + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(ByteBuffer.class)).isTrue(); + assertThat(codec.accepts(MappedByteBuffer.class)).isFalse(); // covariance not allowed + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(BUFFER)).isTrue(); + assertThat(codec.accepts(MappedByteBuffer.allocate(0))).isTrue(); // covariance allowed + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java index b23c9ee098f..9433984011f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/BooleanCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class BooleanCodecTest extends CodecTestBase { @@ -67,4 +68,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("maybe"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Boolean.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(boolean.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Boolean.class)).isTrue(); + assertThat(codec.accepts(boolean.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(true)).isTrue(); + assertThat(codec.accepts(Boolean.TRUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java index b413e9e50bf..70dbd91c305 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CounterCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class CounterCodecTest extends CodecTestBase { @@ -68,4 +69,25 @@ public void should_fail_to_parse_invalid_input() { public void should_fail_to_parse_if_out_of_range() { parse(Long.toString(Long.MAX_VALUE) + "0"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Long.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(long.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Long.class)).isTrue(); + assertThat(codec.accepts(long.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(123L)).isTrue(); + assertThat(codec.accepts(Long.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java index 606c670560b..3a8e36d8c42 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CqlDurationCodecTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class CqlDurationCodecTest extends CodecTestBase { @@ -72,4 +73,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a duration"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(CqlDuration.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(CqlDuration.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(DURATION)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java index 834ee426b42..545b03c1f4f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/CustomCodecTest.java @@ -20,8 +20,10 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; import org.junit.Test; public class CustomCodecTest extends CodecTestBase { @@ -83,4 +85,23 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a blob"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(ByteBuffer.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(MappedByteBuffer.class))) + .isFalse(); // covariance not allowed + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(ByteBuffer.class)).isTrue(); + assertThat(codec.accepts(MappedByteBuffer.class)).isFalse(); // covariance not allowed + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(BUFFER)).isTrue(); + assertThat(codec.accepts(MappedByteBuffer.allocate(0))).isTrue(); // covariance allowed + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java index 3f8bfc32bbe..55fcf4c78a9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DateCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.time.LocalDate; import org.junit.Test; @@ -86,4 +87,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a date"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(LocalDate.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(LocalDate.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(EPOCH)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java index 349a8c54899..b0c7e2bec79 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DecimalCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigDecimal; import org.junit.Test; @@ -78,4 +79,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a decimal"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(BigDecimal.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(BigDecimal.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(BigDecimal.ONE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java index 4fa761c24dc..2c72249b597 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/DoubleCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class DoubleCodecTest extends CodecTestBase { @@ -65,4 +66,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a double"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Double.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(double.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Double.class)).isTrue(); + assertThat(codec.accepts(double.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(123.45d)).isTrue(); + assertThat(codec.accepts(Double.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java index 7b2a92d6f18..6864e3a788a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/FloatCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class FloatCodecTest extends CodecTestBase { @@ -65,4 +66,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a float"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Float.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(float.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Float.class)).isTrue(); + assertThat(codec.accepts(float.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(123.45f)).isTrue(); + assertThat(codec.accepts(Float.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java index 82976687c87..e47c74ba8c1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/InetCodecTest.java @@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.fail; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.shaded.guava.common.base.Strings; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.Test; @@ -94,4 +96,23 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not an address"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(InetAddress.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Inet4Address.class))) + .isFalse(); // covariance not allowed + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(InetAddress.class)).isTrue(); + assertThat(codec.accepts(Inet4Address.class)).isFalse(); // covariance not allowed + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(V4_ADDRESS)).isTrue(); // covariance allowed + assertThat(codec.accepts(V6_ADDRESS)).isTrue(); // covariance allowed + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java index 154c2dfe939..931934e3f55 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/IntCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class IntCodecTest extends CodecTestBase { @@ -70,4 +71,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not an int"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Integer.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(int.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Long.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Integer.class)).isTrue(); + assertThat(codec.accepts(int.class)).isTrue(); + assertThat(codec.accepts(Long.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(123)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Long.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java index 8057607a424..75c436e0475 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SmallIntCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class SmallIntCodecTest extends CodecTestBase { @@ -70,4 +71,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a smallint"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Short.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(short.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Short.class)).isTrue(); + assertThat(codec.accepts(short.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(((short) 123))).isTrue(); + assertThat(codec.accepts(Short.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java index 28cab6892d3..77f33c1ae93 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/StringCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class StringCodecTest extends CodecTestBase { @@ -59,4 +60,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a string"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(String.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(String.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts("hello")).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java index 234396ab89e..6c346b145aa 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -78,4 +79,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a time"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(LocalTime.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(LocalTime.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(LocalTime.MIDNIGHT)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java index 809fdc59a51..ae89ce5d9a8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimeUuidCodecTest.java @@ -18,13 +18,14 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.util.UUID; import org.junit.Test; public class TimeUuidCodecTest extends CodecTestBase { - public static final UUID TIME_BASED = new UUID(6342305776366260711L, -5736720392086604862L); - public static final UUID NOT_TIME_BASED = new UUID(2, 1); + private static final UUID TIME_BASED = new UUID(6342305776366260711L, -5736720392086604862L); + private static final UUID NOT_TIME_BASED = new UUID(2, 1); public TimeUuidCodecTest() { this.codec = TypeCodecs.TIMEUUID; @@ -53,4 +54,22 @@ public void should_format_time_uuid() { public void should_not_format_non_time_uuid() { format(NOT_TIME_BASED); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(UUID.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(UUID.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(TIME_BASED)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java index 31e94f9abc0..97a57c2fabc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TimestampCodecTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.shaded.guava.common.collect.Lists; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -171,4 +172,22 @@ public void should_fail_to_parse_invalid_input() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Cannot parse timestamp value from \"'not a timestamp'\""); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Instant.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Instant.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(Instant.EPOCH)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java index 80a291f5853..ae31be9dc42 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TinyIntCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import org.junit.Test; public class TinyIntCodecTest extends CodecTestBase { @@ -65,4 +66,25 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a tinyint"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(Byte.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(byte.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(Byte.class)).isTrue(); + assertThat(codec.accepts(byte.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(((byte) 123))).isTrue(); + assertThat(codec.accepts(Byte.MIN_VALUE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index 4c505c2eb2e..af53d295260 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -26,6 +26,8 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.data.DefaultTupleValue; import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.util.Bytes; @@ -162,4 +164,24 @@ public void should_parse_tuple() { public void should_fail_to_parse_invalid_input() { parse("not a tuple"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(TupleValue.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(DefaultTupleValue.class))) + .isFalse(); // covariance not allowed + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(TupleValue.class)).isTrue(); + assertThat(codec.accepts(DefaultTupleValue.class)).isFalse(); // covariance not allowed + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(tupleType.newValue())).isTrue(); + assertThat(codec.accepts(new DefaultTupleValue(tupleType))).isTrue(); // covariance allowed + assertThat(codec.accepts("not a tuple")).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index faa16c50442..88ea987d7ef 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -27,6 +27,8 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.data.DefaultUdtValue; import com.datastax.oss.driver.internal.core.type.DefaultUserDefinedType; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.util.Bytes; @@ -171,4 +173,24 @@ public void should_parse_udt() { public void should_fail_to_parse_invalid_input() { parse("not a udt"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(UdtValue.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(DefaultUdtValue.class))) + .isFalse(); // covariance not allowed + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(UdtValue.class)).isTrue(); + assertThat(codec.accepts(DefaultUdtValue.class)).isFalse(); // covariance not allowed + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(userType.newValue())).isTrue(); + assertThat(codec.accepts(new DefaultUdtValue(userType))).isTrue(); // covariance allowed + assertThat(codec.accepts("not a udt")).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java index ebe5f004c5a..16baada6810 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UuidCodecTest.java @@ -18,11 +18,12 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.util.UUID; import org.junit.Test; public class UuidCodecTest extends CodecTestBase { - private final UUID MOCK_UUID = new UUID(2L, 1L); + private static final UUID MOCK_UUID = new UUID(2L, 1L); public UuidCodecTest() { this.codec = TypeCodecs.UUID; @@ -72,4 +73,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a uuid"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(UUID.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(UUID.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(MOCK_UUID)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java index 0aed7a0ddc7..e52dd93919c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VarintCodecTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import java.math.BigInteger; import org.junit.Test; @@ -61,4 +62,22 @@ public void should_parse() { public void should_fail_to_parse_invalid_input() { parse("not a varint"); } + + @Test + public void should_accept_generic_type() { + assertThat(codec.accepts(GenericType.of(BigInteger.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + assertThat(codec.accepts(BigInteger.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + assertThat(codec.accepts(BigInteger.ONE)).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java index 77a09ccead3..5fb73d0ec76 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ZonedTimestampCodecTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.time.Instant; @@ -164,4 +165,25 @@ public void should_fail_to_parse_invalid_input() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Cannot parse timestamp value from \"'not a timestamp'\""); } + + @Test + public void should_accept_generic_type() { + codec = new ZonedTimestampCodec(); + assertThat(codec.accepts(GenericType.of(ZonedDateTime.class))).isTrue(); + assertThat(codec.accepts(GenericType.of(Integer.class))).isFalse(); + } + + @Test + public void should_accept_raw_type() { + codec = new ZonedTimestampCodec(); + assertThat(codec.accepts(ZonedDateTime.class)).isTrue(); + assertThat(codec.accepts(Integer.class)).isFalse(); + } + + @Test + public void should_accept_object() { + codec = new ZonedTimestampCodec(); + assertThat(codec.accepts(ZonedDateTime.now())).isTrue(); + assertThat(codec.accepts(Integer.MIN_VALUE)).isFalse(); + } } From 8cb77c493c3ac30b9bdeff1195e28df0b1ba7c52 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 23 Oct 2018 14:57:08 -0700 Subject: [PATCH 611/742] JAVA-2000: Fix ConcurrentModificationException during channel shutdown Motivation: When a channel is shut down abruptly we iterate the inFlight map in abortAllInFlight() to fail all pending queries. But in some cases, failing a query will also indirectly mutate the map, causing a ConcurrentModificationException. We ran into this particular scenario: - the channel initializes and the write of the initial STARTUP query is scheduled (but the write future is not complete yet) - during the actual write task, an IOException is thrown ("connection reset by peer"). exceptionCaught() is called and invokes abortAllInFlight(). - abortAllInFlight() calls onFailure() on the ProtocolInitHandler.InitRequest corresponding to the STARTUP request. That calls ConnectInitHandler.setConnectFailure(), which closes the channel. - closing the channel fails the write future of the STARTUP query, which invokes the write listener synchronously. The listener calls release() which removes the callback from inFlight. - the initial iteration resumes and finds out that the map was modified. Similar issues could happen if one of the aborted requests is a SetKeyspaceRequest that calls abortAllInFlight() recursively. Modifications: - don't iterate inFlight directly, make a copy to avoid concurrent modifications. - clear it immediately so that recursive invocations of abortAllInFlight() have no effect. - ensure release() is lenient if the callback is not in inFlight. For clarity, also change it to never return the callback, the caller has to retrieve it itself (as already done in channelRead). Result: The initial call to abortInFlight() clear inFlight and fails the STARTUP query. When the write listener invokes release(), that's a no-op because the callback is not in the map anymore. Co-authored-by: Greg Bestland --- changelog/README.md | 1 + .../core/channel/InFlightHandler.java | 59 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 0c26114fdc6..4d5e940de8b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2000: Fix ConcurrentModificationException during channel shutdown - [improvement] JAVA-2002: Reimplement TypeCodec.accepts to improve performance - [improvement] JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched() - [improvement] JAVA-2007: Make driver threads extend FastThreadLocalThread diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java index 48525797e78..d6d69306871 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/InFlightHandler.java @@ -25,9 +25,9 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel.SetKeyspaceEvent; import com.datastax.oss.driver.internal.core.protocol.FrameDecodingException; import com.datastax.oss.driver.internal.core.util.Loggers; -import com.datastax.oss.driver.shaded.guava.common.base.MoreObjects; import com.datastax.oss.driver.shaded.guava.common.collect.BiMap; import com.datastax.oss.driver.shaded.guava.common.collect.HashBiMap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.request.Query; @@ -40,6 +40,7 @@ import io.netty.util.concurrent.Promise; import java.util.HashMap; import java.util.Map; +import java.util.Set; import net.jcip.annotations.NotThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -273,13 +274,16 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable exception) thro LOG.debug("[{}] Error while decoding response on stream id {}", logPrefix, streamId); if (streamId >= 0) { // We know which request matches the failing response, fail that one only - ResponseCallback responseCallback = release(streamId, ctx); - try { - responseCallback.onFailure(exception.getCause()); - } catch (Throwable t) { - Loggers.warnWithException( - LOG, "[{}] Unexpected error while invoking failure handler", logPrefix, t); + ResponseCallback responseCallback = inFlight.get(streamId); + if (responseCallback != null) { + try { + responseCallback.onFailure(exception.getCause()); + } catch (Throwable t) { + Loggers.warnWithException( + LOG, "[{}] Unexpected error while invoking failure handler", logPrefix, t); + } } + release(streamId, ctx); } else { Loggers.warnWithException( LOG, @@ -325,19 +329,21 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); } - private ResponseCallback release(int streamId, ChannelHandlerContext ctx) { + private void release(int streamId, ChannelHandlerContext ctx) { LOG.trace("[{}] Releasing stream id {}", logPrefix, streamId); - ResponseCallback responseCallback = - MoreObjects.firstNonNull(inFlight.remove(streamId), orphaned.remove(streamId)); - orphanedSize = orphaned.size(); - streamIds.release(streamId); - // If we're in the middle of an orderly close and this was the last request, actually close - // the channel now - if (closingGracefully && inFlight.isEmpty()) { - LOG.debug("[{}] Done handling the last pending query, closing channel", logPrefix); - ctx.channel().close(); + if (inFlight.remove(streamId) != null) { + // If we're in the middle of an orderly close and this was the last request, actually close + // the channel now + if (closingGracefully && inFlight.isEmpty()) { + LOG.debug("[{}] Done handling the last pending query, closing channel", logPrefix); + ctx.channel().close(); + } + } else if (orphaned.remove(streamId) != null) { + orphanedSize = orphaned.size(); } - return responseCallback; + // Note: it's possible that the callback is in neither map, if we get here after a call to + // abortAllInFlight that already cleared the map (see JAVA-2000) + streamIds.release(streamId); } private void abortAllInFlight(DriverException cause) { @@ -349,14 +355,19 @@ private void abortAllInFlight(DriverException cause) { * loop) */ private void abortAllInFlight(DriverException cause, ResponseCallback ignore) { - for (ResponseCallback responseCallback : inFlight.values()) { - if (responseCallback != ignore) { - responseCallback.onFailure(cause); + if (!inFlight.isEmpty()) { + // Clear the map now and iterate on a copy, in case one of the onFailure calls below recurses + // back into this method + Set toAbort = ImmutableSet.copyOf(inFlight.values()); + inFlight.clear(); + for (ResponseCallback responseCallback : toAbort) { + if (responseCallback != ignore) { + responseCallback.onFailure(cause); + } } + // It's not necessary to release the stream ids, since we always call this method right before + // closing the channel } - inFlight.clear(); - // It's not necessary to release the stream ids, since we always call this method right before - // closing the channel } int getAvailableIds() { From 2d41ed03a4b9ae5e9ab0625ac9f87888190929af Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 5 Nov 2018 16:06:18 -0800 Subject: [PATCH 612/742] Fix nullability annotations on query builder range selectors --- .../driver/api/querybuilder/select/OngoingSelection.java | 6 +++--- .../oss/driver/api/querybuilder/select/Selector.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java index 1891e8c724f..f4f5ed47c1e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java @@ -348,7 +348,7 @@ default Select element(@NonNull String collectionName, @NonNull Term index) { * @see Selector#range(Selector, Term, Term) */ @NonNull - default Select range(@NonNull Selector collection, @NonNull Term left, @NonNull Term right) { + default Select range(@NonNull Selector collection, @Nullable Term left, @Nullable Term right) { return selector(Selector.range(collection, left, right)); } @@ -362,7 +362,7 @@ default Select range(@NonNull Selector collection, @NonNull Term left, @NonNull */ @NonNull default Select range( - @NonNull CqlIdentifier collectionId, @NonNull Term left, @NonNull Term right) { + @NonNull CqlIdentifier collectionId, @Nullable Term left, @Nullable Term right) { return range(Selector.column(collectionId), left, right); } @@ -373,7 +373,7 @@ default Select range( * @see Selector#range(String, Term, Term) */ @NonNull - default Select range(@NonNull String collectionName, @NonNull Term left, @NonNull Term right) { + default Select range(@NonNull String collectionName, @Nullable Term left, @Nullable Term right) { return range(CqlIdentifier.fromCql(collectionName), left, right); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java index d80f5c8c258..b0a6ae30588 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java @@ -238,7 +238,7 @@ static Selector element(@NonNull String collectionName, @NonNull Term index) { * only left-bound. */ @NonNull - static Selector range(@NonNull Selector collection, @NonNull Term left, @NonNull Term right) { + static Selector range(@NonNull Selector collection, @Nullable Term left, @Nullable Term right) { return new RangeSelector(collection, left, right); } @@ -250,7 +250,7 @@ static Selector range(@NonNull Selector collection, @NonNull Term left, @NonNull */ @NonNull static Selector range( - @NonNull CqlIdentifier collectionId, @NonNull Term left, @NonNull Term right) { + @NonNull CqlIdentifier collectionId, @Nullable Term left, @Nullable Term right) { return range(column(collectionId), left, right); } @@ -259,7 +259,7 @@ static Selector range( * range(CqlIdentifier.fromCql(collectionName), left, right)}. */ @NonNull - static Selector range(@NonNull String collectionName, @NonNull Term left, @NonNull Term right) { + static Selector range(@NonNull String collectionName, @Nullable Term left, @Nullable Term right) { return range(CqlIdentifier.fromCql(collectionName), left, right); } From d4bc1762d02394c9fd5950e6cb48b6cf7f1f2933 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 5 Nov 2018 17:01:31 -0800 Subject: [PATCH 613/742] Fix revapi ignores for 2d41ed03a --- query-builder/revapi.json | 92 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 520787525f7..d9fa8c894e9 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -15,7 +15,97 @@ ] } } - } + }, + "ignore": [ + { + "regex": true, + "code": "java\\.annotation\\.removed", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.querybuilder.*", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.added", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", + "package": "com.datastax.oss.driver.api.querybuilder.*", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.removed", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.querybuilder.*", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.added", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", + "package": "com.datastax.oss.driver.api.querybuilder.*", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.removed", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.querybuilder.select", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.added", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", + "package": "com.datastax.oss.driver.api.querybuilder.select", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.removed", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", + "package": "com.datastax.oss.driver.api.querybuilder.select", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "regex": true, + "code": "java\\.annotation\\.added", + "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", + "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", + "package": "com.datastax.oss.driver.api.querybuilder.select", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "parameter", + "justification": "Fix nullability annotations on query builder range selectors" + } + ] } } From f1697b9857bb08b7c067bbb44b5ef77550f95143 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 6 Nov 2018 15:04:49 -0800 Subject: [PATCH 614/742] JAVA-1982: Mention in RetryPolicy javadocs that methods should not block --- .../com/datastax/oss/driver/api/core/retry/RetryPolicy.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java index 29359c1607d..e36658c9d8e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryPolicy.java @@ -40,6 +40,12 @@ *

              For each request, the driver gets a "query plan" (a list of coordinators to try) from the * {@link LoadBalancingPolicy}, and tries each node in sequence. This policy is invoked if the * request to that node fails. + * + *

              The methods of this interface are invoked on I/O threads, therefore implementations should + * never block. In particular, don't call {@link Thread#sleep(long)} to retry after a delay: + * this would prevent asynchronous processing of other requests, and very negatively impact + * throughput. If the application needs to back off and retry later, this should be implemented in + * client code, not in this policy. */ public interface RetryPolicy extends AutoCloseable { From 1134c709f64a0d15664713f375e5d0d3850c4522 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 24 Sep 2018 15:38:30 -0700 Subject: [PATCH 615/742] JAVA-1978: Add a config option to keep contact points unresolved --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 2 + .../api/core/session/SessionBuilder.java | 10 +++- .../driver/internal/core/ContactPoints.java | 55 +++++++++++++------ .../internal/core/channel/ChannelFactory.java | 2 +- .../internal/core/channel/DriverChannel.java | 20 ++++++- .../core/control/ControlConnection.java | 4 +- .../core/metadata/DefaultTopologyMonitor.java | 8 +-- .../queries/DefaultSchemaQueriesFactory.java | 4 +- core/src/main/resources/reference.conf | 25 +++++++++ .../core/channel/DriverChannelTest.java | 4 +- .../control/ControlConnectionTestBase.java | 2 +- .../metadata/DefaultTopologyMonitorTest.java | 2 +- .../core/ProtocolVersionMixedClusterIT.java | 2 +- 14 files changed, 104 insertions(+), 37 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 4d5e940de8b..5697b12f9fa 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-1978: Add a config option to keep contact points unresolved - [bug] JAVA-2000: Fix ConcurrentModificationException during channel shutdown - [improvement] JAVA-2002: Reimplement TypeCodec.accepts to improve performance - [improvement] JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched() diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index a9800278f3a..86ce1d79afc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -166,6 +166,8 @@ public enum DefaultDriverOption implements DriverOption { COALESCER_MAX_RUNS("advanced.coalescer.max-runs-with-no-work"), COALESCER_INTERVAL("advanced.coalescer.reschedule-interval"), + + RESOLVE_CONTACT_POINTS("advanced.resolve-contact-points"), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 25fc35862fe..babaa845b3b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -123,8 +123,10 @@ protected DriverConfigLoader defaultConfigLoader() { * they will be merged. If both are absent, the driver will default to 127.0.0.1:9042. * *

              Contrary to the configuration, DNS names with multiple A-records will not be handled here. - * If you need that, call {@link java.net.InetAddress#getAllByName(String)} before calling this - * method. + * If you need that, extract them manually with {@link java.net.InetAddress#getAllByName(String)} + * before calling this method. Similarly, if you need connect addresses to stay unresolved, make + * sure you pass unresolved instances here (see {@code advanced.resolve-addresses} in the + * configuration for more explanations). */ @NonNull public SelfT addContactPoints(@NonNull Collection contactPoints) { @@ -292,9 +294,11 @@ protected final CompletionStage buildDefaultSessionAsync() { DriverExecutionProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); List configContactPoints = defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); + boolean resolveAddresses = + defaultConfig.getBoolean(DefaultDriverOption.RESOLVE_CONTACT_POINTS, true); Set contactPoints = - ContactPoints.merge(programmaticContactPoints, configContactPoints); + ContactPoints.merge(programmaticContactPoints, configContactPoints, resolveAddresses); if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) { keyspace = diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java index ced31d6ae88..e88dbcd7edc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -34,11 +34,26 @@ public class ContactPoints { private static final Logger LOG = LoggerFactory.getLogger(ContactPoints.class); public static Set merge( - Set programmaticContactPoints, List configContactPoints) { + Set programmaticContactPoints, + List configContactPoints, + boolean resolve) { + + if (!resolve) { + for (InetSocketAddress address : programmaticContactPoints) { + if (!address.isUnresolved()) { + LOG.warn( + "You configured the driver to not resolve addresses," + + " but at least one of your programmatic contact points is resolved ({})," + + " this is probably a mistake", + address); + break; + } + } + } Set result = Sets.newHashSet(programmaticContactPoints); for (String spec : configContactPoints) { - for (InetSocketAddress address : extract(spec)) { + for (InetSocketAddress address : extract(spec, resolve)) { boolean wasNew = result.add(address); if (!wasNew) { LOG.warn("Duplicate contact point {}", address); @@ -48,7 +63,7 @@ public static Set merge( return ImmutableSet.copyOf(result); } - private static Set extract(String spec) { + private static Set extract(String spec, boolean resolve) { List hostAndPort = Splitter.on(":").splitToList(spec); if (hostAndPort.size() != 2) { LOG.warn("Ignoring invalid contact point {} (expecting host:port)", spec); @@ -65,22 +80,26 @@ private static Set extract(String spec) { hostAndPort.get(1)); return Collections.emptySet(); } - try { - InetAddress[] inetAddresses = InetAddress.getAllByName(host); - if (inetAddresses.length > 1) { - LOG.info( - "Contact point {} resolves to multiple addresses, will use them all ({})", - spec, - Arrays.deepToString(inetAddresses)); - } - Set result = new HashSet<>(); - for (InetAddress inetAddress : inetAddresses) { - result.add(new InetSocketAddress(inetAddress, port)); + if (!resolve) { + return ImmutableSet.of(InetSocketAddress.createUnresolved(host, port)); + } else { + try { + InetAddress[] inetAddresses = InetAddress.getAllByName(host); + if (inetAddresses.length > 1) { + LOG.info( + "Contact point {} resolves to multiple addresses, will use them all ({})", + spec, + Arrays.deepToString(inetAddresses)); + } + Set result = new HashSet<>(); + for (InetAddress inetAddress : inetAddresses) { + result.add(new InetSocketAddress(inetAddress, port)); + } + return result; + } catch (UnknownHostException e) { + LOG.warn("Ignoring invalid contact point {} (unknown host {})", spec, host); + return Collections.emptySet(); } - return result; - } catch (UnknownHostException e) { - LOG.warn("Ignoring invalid contact point {} (unknown host {})", spec, host); - return Collections.emptySet(); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 7b55efc8b2e..f38ea39113b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -187,7 +187,7 @@ private void connect( if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); DriverChannel driverChannel = - new DriverChannel(channel, context.getWriteCoalescer(), currentVersion); + new DriverChannel(address, channel, context.getWriteCoalescer(), currentVersion); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 0b5eb577ef4..7830d170992 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -46,6 +46,7 @@ public class DriverChannel { @SuppressWarnings("RedundantStringConstructorCall") static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); + private final SocketAddress connectAddress; private final Channel channel; private final InFlightHandler inFlightHandler; private final WriteCoalescer writeCoalescer; @@ -53,7 +54,12 @@ public class DriverChannel { private final AtomicBoolean closing = new AtomicBoolean(); private final AtomicBoolean forceClosing = new AtomicBoolean(); - DriverChannel(Channel channel, WriteCoalescer writeCoalescer, ProtocolVersion protocolVersion) { + DriverChannel( + SocketAddress connectAddress, + Channel channel, + WriteCoalescer writeCoalescer, + ProtocolVersion protocolVersion) { + this.connectAddress = connectAddress; this.channel = channel; this.inFlightHandler = channel.pipeline().get(InFlightHandler.class); this.writeCoalescer = writeCoalescer; @@ -147,8 +153,16 @@ public ProtocolVersion protocolVersion() { return protocolVersion; } - public SocketAddress remoteAddress() { - return channel.remoteAddress(); + /** + * The address that was used to establish the connection. + * + *

              Note that it might be unresolved, and therefore not equal to the underlying Netty channel's + * remote address. + * + *

              See {@code advanced.resolve-contact-points} in the configuration. + */ + public SocketAddress connectAddress() { + return connectAddress; } public SocketAddress localAddress() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index eee0254681b..b5ad2235c64 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -450,7 +450,7 @@ private void onDistanceEvent(DistanceEvent event) { if (event.distance == NodeDistance.IGNORED && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.remoteAddress())) { + && event.node.getConnectAddress().equals(channel.connectAddress())) { LOG.debug( "[{}] Control node {} became IGNORED, reconnecting to a different node", logPrefix, @@ -465,7 +465,7 @@ private void onStateEvent(NodeStateEvent event) { if ((event.newState == null /*(removed)*/ || event.newState == NodeState.FORCED_DOWN) && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.remoteAddress())) { + && event.node.getConnectAddress().equals(channel.connectAddress())) { LOG.debug( "[{}] Control node {} was removed or forced down, reconnecting to a different node", logPrefix, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 1b4112002ac..4801e1e12db 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -104,7 +104,7 @@ public CompletionStage> refreshNode(Node node) { } LOG.debug("[{}] Refreshing info for {}", logPrefix, node); DriverChannel channel = controlConnection.channel(); - if (node.getConnectAddress().equals(channel.remoteAddress())) { + if (node.getConnectAddress().equals(channel.connectAddress())) { // refreshNode is called for nodes that just came up. If the control node just came up, it // means the control connection just reconnected, which means we did a full node refresh. So // we don't need to process this call. @@ -159,7 +159,7 @@ public CompletionStage> refreshNodeList() { // This cast always succeeds in production. The only way it could fail is in a test that uses a // local channel, and we don't have such tests at the moment. - InetSocketAddress controlAddress = (InetSocketAddress) channel.remoteAddress(); + InetSocketAddress controlAddress = (InetSocketAddress) channel.connectAddress(); savePort(channel); @@ -318,8 +318,8 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con // nodes. As a consequence, the port is not stored in system tables. // We save it the first time we get a control connection channel. private void savePort(DriverChannel channel) { - if (port < 0 && channel.remoteAddress() instanceof InetSocketAddress) { - port = ((InetSocketAddress) channel.remoteAddress()).getPort(); + if (port < 0 && channel.connectAddress() instanceof InetSocketAddress) { + port = ((InetSocketAddress) channel.connectAddress()).getPort(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index 4da5c646b0b..aeed089327d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -46,11 +46,11 @@ public SchemaQueries newInstance(CompletableFuture refreshFuture) { throw new IllegalStateException("Control channel not available, aborting schema refresh"); } @SuppressWarnings("SuspiciousMethodCalls") - Node node = context.getMetadataManager().getMetadata().getNodes().get(channel.remoteAddress()); + Node node = context.getMetadataManager().getMetadata().getNodes().get(channel.connectAddress()); if (node == null) { throw new IllegalStateException( "Could not find control node metadata " - + channel.remoteAddress() + + channel.connectAddress() + ", aborting schema refresh"); } return newInstance(node, channel, refreshFuture); diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 5556e0ee676..0c29fddec3f 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -637,6 +637,31 @@ datastax-java-driver { class = PassThroughAddressTranslator } + # Whether to resolve the addresses passed to `basic.contact-points`. + # + # If this is true, addresses are created with `InetSocketAddress(String, int)`: the host name will + # be resolved the first time, and the driver will use the resolved IP address for all subsequent + # connection attempts. + # + # If this is false, addresses are created with `InetSocketAddress.createUnresolved()`: the host + # name will be resolved again every time the driver opens a new connection. This is useful for + # containerized environments where DNS records are more likely to change over time (note that the + # JVM and OS have their own DNS caching mechanisms, so you might need additional configuration + # beyond the driver). + # + # This option only applies to the contact points specified in the configuration. It has no effect + # on: + # - programmatic contact points passed to SessionBuilder.addContactPoints: these addresses are + # built outside of the driver, so it is your responsibility to provide unresolved instances. + # - dynamically discovered peers: the driver relies on Cassandra system tables, which expose raw + # IP addresses. Use a custom address translator to convert them to unresolved addresses (if + # you're in a containerized environment, you probably already need address translation anyway). + # + # Required: no (defaults to true) + # Modifiable at runtime: no + # Overridable in a profile: no + advanced.resolve-contact-points = true + advanced.protocol { # The native protocol version to use. # diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index b581ad4efc2..1c4c30a6fe3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -60,7 +60,9 @@ public void setup() { null, "test")); writeCoalescer = new MockWriteCoalescer(); - driverChannel = new DriverChannel(channel, writeCoalescer, DefaultProtocolVersion.V3); + driverChannel = + new DriverChannel( + channel.remoteAddress(), channel, writeCoalescer, DefaultProtocolVersion.V3); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 76f261af76c..cf2496f32dc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -155,7 +155,7 @@ protected DriverChannel newMockDriverChannel(int id) { }); Mockito.when(driverChannel.closeFuture()).thenReturn(closeFuture); Mockito.when(driverChannel.toString()).thenReturn("channel" + id); - Mockito.when(driverChannel.remoteAddress()) + Mockito.when(driverChannel.connectAddress()) .thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); return driverChannel; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 5cbbd84a52d..ffcf23f8360 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -84,7 +84,7 @@ public void setup() { addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); - Mockito.when(channel.remoteAddress()).thenReturn(ADDRESS1); + Mockito.when(channel.connectAddress()).thenReturn(ADDRESS1); Mockito.when(controlConnection.channel()).thenReturn(channel); Mockito.when(context.getControlConnection()).thenReturn(controlConnection); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 15c46f0c1e2..4a55ac7a82d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -67,7 +67,7 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { // Find out which node became the control node after the reconnection (not necessarily node 0) InetSocketAddress controlAddress = - (InetSocketAddress) context.getControlConnection().channel().remoteAddress(); + (InetSocketAddress) context.getControlConnection().channel().connectAddress(); BoundNode currentControlNode = null; for (BoundNode node : simulacron.getNodes()) { if (node.inetSocketAddress().equals(controlAddress)) { From c5f24418f4148f858c177ebfcca51c206ed3014a Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Oct 2018 15:14:22 -0700 Subject: [PATCH 616/742] JAVA-2010: Make dependencies to annotations required again --- changelog/README.md | 1 + core-shaded/pom.xml | 2 -- core/pom.xml | 2 -- distribution/pom.xml | 16 --------- manual/core/integration/README.md | 56 +++++++++++++++++-------------- query-builder/pom.xml | 2 -- 6 files changed, 32 insertions(+), 47 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 5697b12f9fa..bc060cf1a81 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2010: Make dependencies to annotations required again - [improvement] JAVA-1978: Add a config option to keep contact points unresolved - [bug] JAVA-2000: Fix ConcurrentModificationException during channel shutdown - [improvement] JAVA-2002: Reimplement TypeCodec.accepts to improve performance diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index f75db6f1d1e..8fd37dbc1be 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -89,12 +89,10 @@ com.github.stephenc.jcip jcip-annotations - true com.github.spotbugs spotbugs-annotations - true diff --git a/core/pom.xml b/core/pom.xml index e1e38a77bef..c9650be03f7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -86,12 +86,10 @@ com.github.stephenc.jcip jcip-annotations - true com.github.spotbugs spotbugs-annotations - true ch.qos.logback diff --git a/distribution/pom.xml b/distribution/pom.xml index ea68776a9a7..a80259b2d33 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -45,18 +45,6 @@ java-driver-query-builder ${project.version} - - - com.github.stephenc.jcip - jcip-annotations - - - com.github.spotbugs - spotbugs-annotations - @@ -120,10 +108,6 @@ true - - com.github.stephenc.jcip:jcip-annotations - com.github.spotbugs:spotbugs-annotations - DataStax Java driver for Apache Cassandra® ${project.version} API DataStax Java driver for Apache Cassandra(R) ${project.version} API diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 7f6e5708069..34696ae6566 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -362,40 +362,45 @@ The driver team uses annotations to document certain aspects of the code: * nullability with [SpotBugs](https://spotbugs.github.io/) annotations `@Nullable` and `@NonNull`. This is mostly used during development; while these annotations are retained in class files, they -serve no purpose at runtime. Whether to make them optional dependencies is up for debate: - -* if they are required, it's two additional JARs that every client has to pull in (admittedly they - are quite small); -* if they are optional, the bytecode will reference missing classes. This is not a blocker, but it - can manifest to the end user in a few ways: - - * if you navigate to the driver's sources in your IDE, the missing annotations will be - highlighted as errors (note however that modern IDEs such as IntelliJ IDEA can analyze - nullability annotations even if they are missing from the classpath); - * this produces compiler warnings (see [this discussion][guava] of a similar issue for Google's - Guava library). - -The Java driver team has decided to make the dependencies optional. If that creates any problem for -you, the workaround is to redeclare them explicitly in your application: +serve no purpose at runtime. If you want to minimize the number of JARs in your classpath, you can +exclude them: ```xml com.datastax.oss java-driver-core 4.0.0-beta2 + + + com.github.stephenc.jcip + jcip-annotations + + + com.github.spotbugs + spotbugs-annotations + + - - com.github.stephenc.jcip - jcip-annotations - 1.0-1 - - - com.github.spotbugs - spotbugs-annotations - 3.1.3 - ``` +However, there is one case when excluding those dependencies won't work: if you use [annotation +processing] in your build, the Java compiler scans the entire classpath -- including the driver's +classes -- and tries to load all declared annotations. If it can't find the class for an annotation, +you'll get a compiler error: + +``` +error: cannot access ThreadSafe + class file for net.jcip.annotations.ThreadSafe not found +1 error +``` + +The workaround is to keep the dependencies. + +Sometimes annotation scanning can be triggered involuntarily, if one of your dependencies declares +a processor via the service provider mechanism (check the `META-INF/services` directory in the +JARs). If you are sure that you don't need any annotation processing, you can compile with the +`-proc:none` option and still exclude the dependencies. + #### Mandatory dependencies The remaining core driver dependencies are the only ones that are truly mandatory: @@ -412,6 +417,7 @@ The remaining core driver dependencies are the only ones that are truly mandator [gradle_init]: https://guides.gradle.org/creating-new-gradle-builds/ [downloads]: http://downloads.datastax.com/java-driver/ [guava]: https://github.com/google/guava/issues/2721 +[annotation processing]: https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html#sthref65 [Session.getMetrics]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/Session.html#getMetrics-- [SessionBuilder.addContactPoint]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/session/SessionBuilder.html#addContactPoint-java.net.InetSocketAddress- diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 7e6da61d1a6..d9f815613b5 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -42,12 +42,10 @@ com.github.stephenc.jcip jcip-annotations - true com.github.spotbugs spotbugs-annotations - true junit From f12f0fbc6ff804af08b44f42da937b61af367c95 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 1 Nov 2018 15:05:28 -0700 Subject: [PATCH 617/742] Fix title level in manual --- manual/core/integration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 34696ae6566..90c8a834d53 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -245,7 +245,7 @@ It is a required dependency, but we provide a a [shaded JAR](../shaded_jar/) tha different Java package; this is useful to avoid dependency hell if you already use Netty in another part of your application. -### Typesafe config +#### Typesafe config [Typesafe config](https://lightbend.github.io/config/) is used for our file-based [configuration](../configuration/). From ba8c09220b17ffa87fd19580255ba6a8d9906268 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 31 Oct 2018 15:15:58 -0700 Subject: [PATCH 618/742] JAVA-2017: Slightly optimize conversion methods on the hot path --- changelog/README.md | 1 + .../core/ConsistencyLevelRegistry.java | 4 +- .../core/DefaultConsistencyLevelRegistry.java | 20 +++- .../driver/internal/core/cql/Conversions.java | 113 ++++++++---------- 4 files changed, 71 insertions(+), 67 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index bc060cf1a81..1f5c6ed0330 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2017: Slightly optimize conversion methods on the hot path - [improvement] JAVA-2010: Make dependencies to annotations required again - [improvement] JAVA-1978: Add a config option to keep contact points unresolved - [bug] JAVA-2000: Fix ConcurrentModificationException during channel shutdown diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java index ca1c93fb1ea..c9353df9b55 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ConsistencyLevelRegistry.java @@ -25,9 +25,9 @@ */ public interface ConsistencyLevelRegistry { - ConsistencyLevel fromCode(int code); + ConsistencyLevel codeToLevel(int code); - ConsistencyLevel fromName(String name); + int nameToCode(String name); /** @return all the values known to this driver instance. */ Iterable getValues(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java index 9ab929cd1fd..ba833674292 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/DefaultConsistencyLevelRegistry.java @@ -18,26 +18,36 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import net.jcip.annotations.ThreadSafe; @ThreadSafe public class DefaultConsistencyLevelRegistry implements ConsistencyLevelRegistry { - private static final ImmutableList values = + private static final ImmutableList VALUES = ImmutableList.builder().add(DefaultConsistencyLevel.values()).build(); + private static final ImmutableMap NAME_TO_CODE; + + static { + ImmutableMap.Builder nameToCodeBuilder = ImmutableMap.builder(); + for (DefaultConsistencyLevel consistencyLevel : DefaultConsistencyLevel.values()) { + nameToCodeBuilder.put(consistencyLevel.name(), consistencyLevel.getProtocolCode()); + } + NAME_TO_CODE = nameToCodeBuilder.build(); + } @Override - public ConsistencyLevel fromCode(int code) { + public ConsistencyLevel codeToLevel(int code) { return DefaultConsistencyLevel.fromCode(code); } @Override - public ConsistencyLevel fromName(String name) { - return DefaultConsistencyLevel.valueOf(name); + public int nameToCode(String name) { + return NAME_TO_CODE.get(name); } @Override public Iterable getValues() { - return values; + return VALUES; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 7f58b6c4af3..e5ece85c8a0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -56,6 +56,7 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.ConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.DefaultProtocolFeature; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -63,6 +64,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.primitives.Ints; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Batch; @@ -112,70 +114,74 @@ public static DriverExecutionProfile resolveExecutionProfile( public static Message toMessage( Statement statement, DriverExecutionProfile config, InternalDriverContext context) { - ConsistencyLevel consistency = - statement.getConsistencyLevel() != null - ? statement.getConsistencyLevel() - : context - .getConsistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)); - int pageSize = - statement.getPageSize() > 0 - ? statement.getPageSize() - : config.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE); - ConsistencyLevel serialConsistency = - statement.getSerialConsistencyLevel() != null - ? statement.getSerialConsistencyLevel() - : context - .getConsistencyLevelRegistry() - .fromName(config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)); + ConsistencyLevelRegistry consistencyLevelRegistry = context.getConsistencyLevelRegistry(); + ConsistencyLevel consistency = statement.getConsistencyLevel(); + int consistencyCode = + (consistency == null) + ? consistencyLevelRegistry.nameToCode( + config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + : consistency.getProtocolCode(); + int pageSize = statement.getPageSize(); + if (pageSize <= 0) { + pageSize = config.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE); + } + ConsistencyLevel serialConsistency = statement.getSerialConsistencyLevel(); + int serialConsistencyCode = + (serialConsistency == null) + ? consistencyLevelRegistry.nameToCode( + config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) + : serialConsistency.getProtocolCode(); long timestamp = statement.getTimestamp(); if (timestamp == Long.MIN_VALUE) { timestamp = context.getTimestampGenerator().next(); } CodecRegistry codecRegistry = context.getCodecRegistry(); ProtocolVersion protocolVersion = context.getProtocolVersion(); - ProtocolVersionRegistry registry = context.getProtocolVersionRegistry(); + ProtocolVersionRegistry protocolVersionRegistry = context.getProtocolVersionRegistry(); CqlIdentifier keyspace = statement.getKeyspace(); if (statement instanceof SimpleStatement) { SimpleStatement simpleStatement = (SimpleStatement) statement; - if (!simpleStatement.getPositionalValues().isEmpty() - && !simpleStatement.getNamedValues().isEmpty()) { + List positionalValues = simpleStatement.getPositionalValues(); + Map namedValues = simpleStatement.getNamedValues(); + if (!positionalValues.isEmpty() && !namedValues.isEmpty()) { throw new IllegalArgumentException( "Can't have both positional and named values in a statement."); } if (keyspace != null - && !registry.supports(protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { + && !protocolVersionRegistry.supports( + protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { throw new IllegalArgumentException( "Can't use per-request keyspace with protocol " + protocolVersion); } QueryOptions queryOptions = new QueryOptions( - consistency.getProtocolCode(), - encode(simpleStatement.getPositionalValues(), codecRegistry, protocolVersion), - encode(simpleStatement.getNamedValues(), codecRegistry, protocolVersion), + consistencyCode, + encode(positionalValues, codecRegistry, protocolVersion), + encode(namedValues, codecRegistry, protocolVersion), false, pageSize, statement.getPagingState(), - serialConsistency.getProtocolCode(), + serialConsistencyCode, timestamp, (keyspace == null) ? null : keyspace.asInternal()); return new Query(simpleStatement.getQuery(), queryOptions); } else if (statement instanceof BoundStatement) { BoundStatement boundStatement = (BoundStatement) statement; - if (!registry.supports(protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { + if (!protocolVersionRegistry.supports( + protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(boundStatement); } boolean skipMetadata = boundStatement.getPreparedStatement().getResultSetDefinitions().size() > 0; QueryOptions queryOptions = new QueryOptions( - consistency.getProtocolCode(), + consistencyCode, boundStatement.getValues(), Collections.emptyMap(), skipMetadata, pageSize, statement.getPagingState(), - serialConsistency.getProtocolCode(), + serialConsistencyCode, timestamp, null); PreparedStatement preparedStatement = boundStatement.getPreparedStatement(); @@ -187,11 +193,13 @@ public static Message toMessage( queryOptions); } else if (statement instanceof BatchStatement) { BatchStatement batchStatement = (BatchStatement) statement; - if (!registry.supports(protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { + if (!protocolVersionRegistry.supports( + protocolVersion, DefaultProtocolFeature.UNSET_BOUND_VALUES)) { ensureAllSet(batchStatement); } if (keyspace != null - && !registry.supports(protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { + && !protocolVersionRegistry.supports( + protocolVersion, DefaultProtocolFeature.PER_REQUEST_KEYSPACE)) { throw new IllegalArgumentException( "Can't use per-request keyspace with protocol " + protocolVersion); } @@ -222,8 +230,8 @@ public static Message toMessage( batchStatement.getBatchType().getProtocolCode(), queriesOrIds, values, - consistency.getProtocolCode(), - serialConsistency.getProtocolCode(), + consistencyCode, + serialConsistencyCode, timestamp, (keyspace == null) ? null : keyspace.asInternal()); } else { @@ -237,16 +245,12 @@ public static List encode( if (values.isEmpty()) { return Collections.emptyList(); } else { - NullAllowingImmutableList.Builder encodedValues = - NullAllowingImmutableList.builder(values.size()); + ByteBuffer[] encodedValues = new ByteBuffer[values.size()]; + int i = 0; for (Object value : values) { - if (value == null) { - encodedValues.add(null); - } else { - encodedValues.add(encode(value, codecRegistry, protocolVersion)); - } + encodedValues[i] = (value == null) ? null : encode(value, codecRegistry, protocolVersion); } - return encodedValues.build(); + return NullAllowingImmutableList.of(encodedValues); } } @@ -358,7 +362,7 @@ public static DefaultPreparedStatement toPreparedStatement( ByteBuffer.wrap(response.preparedQueryId).asReadOnlyBuffer(), request.getQuery(), toColumnDefinitions(response.variablesMetadata, context), - asList(response.variablesMetadata.pkIndices), + Ints.asList(response.variablesMetadata.pkIndices), (response.resultMetadataId == null) ? null : ByteBuffer.wrap(response.resultMetadataId).asReadOnlyBuffer(), @@ -384,23 +388,12 @@ public static DefaultPreparedStatement toPreparedStatement( public static ColumnDefinitions toColumnDefinitions( RowsMetadata metadata, InternalDriverContext context) { - ImmutableList.Builder definitions = ImmutableList.builder(); + ColumnDefinition[] values = new ColumnDefinition[metadata.columnSpecs.size()]; + int i = 0; for (ColumnSpec columnSpec : metadata.columnSpecs) { - definitions.add(new DefaultColumnDefinition(columnSpec, context)); - } - return DefaultColumnDefinitions.valueOf(definitions.build()); - } - - public static List asList(int[] pkIndices) { - if (pkIndices == null || pkIndices.length == 0) { - return Collections.emptyList(); - } else { - ImmutableList.Builder builder = ImmutableList.builder(); - for (int pkIndex : pkIndices) { - builder.add(pkIndex); - } - return builder.build(); + values[i++] = new DefaultColumnDefinition(columnSpec, context); } + return DefaultColumnDefinitions.valueOf(ImmutableList.copyOf(values)); } public static CoordinatorException toThrowable( @@ -422,7 +415,7 @@ public static CoordinatorException toThrowable( Unavailable unavailable = (Unavailable) errorMessage; return new UnavailableException( node, - context.getConsistencyLevelRegistry().fromCode(unavailable.consistencyLevel), + context.getConsistencyLevelRegistry().codeToLevel(unavailable.consistencyLevel), unavailable.required, unavailable.alive); case ProtocolConstants.ErrorCode.OVERLOADED: @@ -435,7 +428,7 @@ public static CoordinatorException toThrowable( WriteTimeout writeTimeout = (WriteTimeout) errorMessage; return new WriteTimeoutException( node, - context.getConsistencyLevelRegistry().fromCode(writeTimeout.consistencyLevel), + context.getConsistencyLevelRegistry().codeToLevel(writeTimeout.consistencyLevel), writeTimeout.received, writeTimeout.blockFor, context.getWriteTypeRegistry().fromName(writeTimeout.writeType)); @@ -443,7 +436,7 @@ public static CoordinatorException toThrowable( ReadTimeout readTimeout = (ReadTimeout) errorMessage; return new ReadTimeoutException( node, - context.getConsistencyLevelRegistry().fromCode(readTimeout.consistencyLevel), + context.getConsistencyLevelRegistry().codeToLevel(readTimeout.consistencyLevel), readTimeout.received, readTimeout.blockFor, readTimeout.dataPresent); @@ -451,7 +444,7 @@ public static CoordinatorException toThrowable( ReadFailure readFailure = (ReadFailure) errorMessage; return new ReadFailureException( node, - context.getConsistencyLevelRegistry().fromCode(readFailure.consistencyLevel), + context.getConsistencyLevelRegistry().codeToLevel(readFailure.consistencyLevel), readFailure.received, readFailure.blockFor, readFailure.numFailures, @@ -463,7 +456,7 @@ public static CoordinatorException toThrowable( WriteFailure writeFailure = (WriteFailure) errorMessage; return new WriteFailureException( node, - context.getConsistencyLevelRegistry().fromCode(writeFailure.consistencyLevel), + context.getConsistencyLevelRegistry().codeToLevel(writeFailure.consistencyLevel), writeFailure.received, writeFailure.blockFor, context.getWriteTypeRegistry().fromName(writeFailure.writeType), From 75b9d1ff5e9ba7d159cdd8aff32caa91e8f03fb2 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Nov 2018 17:45:52 -0800 Subject: [PATCH 619/742] JAVA-2026: Make CqlDuration implement TemporalAmount --- changelog/README.md | 1 + .../oss/driver/api/core/data/CqlDuration.java | 62 ++++++++++++++++++- .../driver/api/core/data/CqlDurationTest.java | 35 +++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 1f5c6ed0330..714d11d038f 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2026: Make CqlDuration implement TemporalAmount - [improvement] JAVA-2017: Slightly optimize conversion methods on the hot path - [improvement] JAVA-2010: Make dependencies to annotations required again - [improvement] JAVA-1978: Add a config option to keep contact points unresolved diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java index 7614d103b19..1db7e1d8d4f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java @@ -18,7 +18,16 @@ import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.base.Objects; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Duration; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAmount; +import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.jcip.annotations.Immutable; @@ -32,7 +41,7 @@ * in time, regardless of the calendar). */ @Immutable -public final class CqlDuration { +public final class CqlDuration implements TemporalAmount { @VisibleForTesting static final long NANOS_PER_MICRO = 1000L; @VisibleForTesting static final long NANOS_PER_MILLI = 1000 * NANOS_PER_MICRO; @@ -62,6 +71,9 @@ public final class CqlDuration { private static final Pattern ISO8601_ALTERNATIVE_PATTERN = Pattern.compile("P(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})"); + private static final ImmutableList TEMPORAL_UNITS = + ImmutableList.of(ChronoUnit.MONTHS, ChronoUnit.DAYS, ChronoUnit.NANOS); + private final int months; private final int days; private final long nanoseconds; @@ -290,6 +302,54 @@ public long getNanoseconds() { return nanoseconds; } + /** + * {@inheritDoc} + * + *

              This implementation converts the months and days components to a {@link Period}, and the + * nanosecond component to a {@link Duration}, and adds those two amounts to the temporal object. + * Therefore the chronology of the temporal must be either the ISO chronology or null. + * + * @see Period#addTo(Temporal) + * @see Duration#addTo(Temporal) + */ + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(Period.of(0, months, days)).plus(Duration.ofNanos(nanoseconds)); + } + + /** + * {@inheritDoc} + * + *

              This implementation converts the months and days components to a {@link Period}, and the + * nanosecond component to a {@link Duration}, and subtracts those two amounts to the temporal + * object. Therefore the chronology of the temporal must be either the ISO chronology or null. + * + * @see Period#subtractFrom(Temporal) + * @see Duration#subtractFrom(Temporal) + */ + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(Period.of(0, months, days)).minus(Duration.ofNanos(nanoseconds)); + } + + @Override + public long get(TemporalUnit unit) { + if (unit == ChronoUnit.MONTHS) { + return months; + } else if (unit == ChronoUnit.DAYS) { + return days; + } else if (unit == ChronoUnit.NANOS) { + return nanoseconds; + } else { + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); + } + } + + @Override + public List getUnits() { + return TEMPORAL_UNITS; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java index c983735c739..f41498b1e19 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java @@ -16,8 +16,12 @@ package com.datastax.oss.driver.api.core.data; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import org.junit.Test; public class CqlDurationTest { @@ -128,4 +132,35 @@ private void assertInvalidDuration(String duration, String expectedErrorMessage) assertThat(e.getMessage()).isEqualTo(expectedErrorMessage); } } + + @Test + public void should_get_by_unit() { + CqlDuration duration = CqlDuration.from("3mo2d15s"); + assertThat(duration.get(ChronoUnit.MONTHS)).isEqualTo(3); + assertThat(duration.get(ChronoUnit.DAYS)).isEqualTo(2); + assertThat(duration.get(ChronoUnit.NANOS)).isEqualTo(15 * CqlDuration.NANOS_PER_SECOND); + assertThatThrownBy(() -> duration.get(ChronoUnit.YEARS)) + .isInstanceOf(UnsupportedTemporalTypeException.class); + } + + @Test + public void should_add_to_temporal() { + ZonedDateTime dateTime = ZonedDateTime.parse("2018-10-04T00:00-07:00[America/Los_Angeles]"); + assertThat(dateTime.plus(CqlDuration.from("1mo"))) + .isEqualTo("2018-11-04T00:00-07:00[America/Los_Angeles]"); + assertThat(dateTime.plus(CqlDuration.from("1mo1h10s"))) + .isEqualTo("2018-11-04T01:00:10-07:00[America/Los_Angeles]"); + // 11-04 2:00 is daylight saving time end + assertThat(dateTime.plus(CqlDuration.from("1mo3h"))) + .isEqualTo("2018-11-04T02:00-08:00[America/Los_Angeles]"); + } + + @Test + public void should_subtract_from_temporal() { + ZonedDateTime dateTime = ZonedDateTime.parse("2018-10-04T00:00-07:00[America/Los_Angeles]"); + assertThat(dateTime.minus(CqlDuration.from("2mo"))) + .isEqualTo("2018-08-04T00:00-07:00[America/Los_Angeles]"); + assertThat(dateTime.minus(CqlDuration.from("1h15s15ns"))) + .isEqualTo("2018-10-03T22:59:44.999999985-07:00[America/Los_Angeles]"); + } } From 164fbc4331fdfc961b7c98167e8311a86536acc8 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Nov 2018 17:48:38 -0800 Subject: [PATCH 620/742] Rename test methods to follow conventions --- .../oss/driver/api/core/data/CqlDurationTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java index f41498b1e19..a880f4a8579 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java @@ -27,7 +27,7 @@ public class CqlDurationTest { @Test - public void testFromStringWithStandardPattern() { + public void should_parse_from_string_with_standard_pattern() { assertThat(CqlDuration.from("1y2mo")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); assertThat(CqlDuration.from("-1y2mo")).isEqualTo(CqlDuration.newInstance(-14, 0, 0)); assertThat(CqlDuration.from("1Y2MO")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); @@ -59,7 +59,7 @@ public void testFromStringWithStandardPattern() { } @Test - public void testFromStringWithIso8601Pattern() { + public void should_parse_from_string_with_iso8601_pattern() { assertThat(CqlDuration.from("P1Y2D")).isEqualTo(CqlDuration.newInstance(12, 2, 0)); assertThat(CqlDuration.from("P1Y2M")).isEqualTo(CqlDuration.newInstance(14, 0, 0)); assertThat(CqlDuration.from("P2W")).isEqualTo(CqlDuration.newInstance(0, 14, 0)); @@ -82,7 +82,7 @@ public void testFromStringWithIso8601Pattern() { } @Test - public void testFromStringWithIso8601AlternativePattern() { + public void should_parse_from_string_with_iso8601_alternative_pattern() { assertThat(CqlDuration.from("P0001-00-02T00:00:00")) .isEqualTo(CqlDuration.newInstance(12, 2, 0)); assertThat(CqlDuration.from("P0001-02-00T00:00:00")) @@ -108,7 +108,7 @@ public void testFromStringWithIso8601AlternativePattern() { } @Test - public void testInvalidDurations() { + public void should_fail_to_parse_invalid_durations() { assertInvalidDuration( Long.MAX_VALUE + "d", "Invalid duration. The total number of days must be less or equal to 2147483647"); From f83f483cd99d660379fac9ccf58961387cdb7ecf Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Nov 2018 16:17:38 -0800 Subject: [PATCH 621/742] JAVA-1945: Document corner cases around UDT and tuple attachment --- changelog/README.md | 1 + manual/core/detachable_types/README.md | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 714d11d038f..5d42b2e7d32 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-1945: Document corner cases around UDT and tuple attachment - [improvement] JAVA-2026: Make CqlDuration implement TemporalAmount - [improvement] JAVA-2017: Slightly optimize conversion methods on the hot path - [improvement] JAVA-2010: Make dependencies to annotations required again diff --git a/manual/core/detachable_types/README.md b/manual/core/detachable_types/README.md index 41fbfc89136..8c17cd74cfc 100644 --- a/manual/core/detachable_types/README.md +++ b/manual/core/detachable_types/README.md @@ -87,6 +87,34 @@ TupleValue tupleValue = tupleType.newValue().setString(0, "foo"); When you pass a detached type to the session (for example by executing a request with a tuple value based on a detached tuple type), it will automatically be reattached. +### Sharing data across sessions + +If you're reading data from one session and writing it into another, you should take a few extra +precautions: + +* if you use custom codecs, they should obviously be registered with both sessions; + +* if the protocol version is different, you should avoid sharing UDT and tuple types; keep a + separate set of definitions for each session, and copy the values field by field: + + ```java + Row row = session1.execute("SELECT QUERY...").one(); + UdtValue user1 = row.getUdtValue("user"); + + // Don't pass user1 to session2: create a new copy from userType2 instead + UserDefinedType userType2 = + session2.getMetadata().getKeyspace("ks").flatMap(ks -> ks.getUserDefinedType("user")).get(); + UdtValue user2 = userType2.newValue(); + user2.setString("first_name", user1.getString("first_name")); + user2.setString("last_name", user1.getString("last_name")); + + session2.execute(SimpleStatement.newInstance("INSERT QUERY...", user2)); + ``` + + This will ensure that UDT definition are not accidentally reattached to the wrong session, and + use the correct protocol version to encode values. + + ### Bottom line You only need to worry about detachable types if you serialize driver rows or data types, or if you From 0871cd46277653a1065d44b576fa3f233830ba81 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 2 Nov 2018 15:56:53 -0700 Subject: [PATCH 622/742] JAVA-1914: Optimize use of System.nanoTime in CqlRequestHandlerBase --- changelog/README.md | 1 + .../core/cql/CqlRequestHandlerBase.java | 149 +++++++++++------- .../core/cql/CqlRequestHandlerRetryTest.java | 12 ++ ...equestHandlerSpeculativeExecutionTest.java | 2 + .../core/cql/CqlRequestHandlerTestBase.java | 7 + .../cql/CqlRequestHandlerTrackerTest.java | 114 ++++++++++++++ .../core/cql/RequestHandlerTestHarness.java | 3 + 7 files changed, 230 insertions(+), 58 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java diff --git a/changelog/README.md b/changelog/README.md index 5d42b2e7d32..9bf7d652701 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-1914: Optimize use of System.nanoTime in CqlRequestHandlerBase - [improvement] JAVA-1945: Document corner cases around UDT and tuple attachment - [improvement] JAVA-2026: Make CqlDuration implement TemporalAmount - [improvement] JAVA-2017: Slightly optimize conversion methods on the hot path diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index a7596036655..252c2abb7ce 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -42,6 +42,7 @@ import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.api.core.session.throttling.Throttled; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; +import com.datastax.oss.driver.api.core.tracker.RequestTracker; import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -49,8 +50,10 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; +import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; +import com.datastax.oss.driver.internal.core.tracker.NoopRequestTracker; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.collection.QueryPlan; import com.datastax.oss.protocol.internal.Frame; @@ -90,6 +93,7 @@ public abstract class CqlRequestHandlerBase implements Throttled { private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandlerBase.class); + private static final long NANOTIME_NOT_MEASURED_YET = -1; private final long startTimeNanos; private final String logPrefix; @@ -123,6 +127,8 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; private final RequestThrottler throttler; + private final RequestTracker requestTracker; + private final SessionMetricUpdater sessionMetricUpdater; // The errors on the nodes that were already tried (lazily initialized on the first error). // We don't use a map because nodes can appear multiple times. @@ -186,20 +192,24 @@ protected CqlRequestHandlerBase( this.scheduledExecutions = isIdempotent ? new CopyOnWriteArrayList<>() : null; this.inFlightCallbacks = new CopyOnWriteArrayList<>(); + this.requestTracker = context.getRequestTracker(); + this.sessionMetricUpdater = session.getMetricUpdater(); + this.throttler = context.getRequestThrottler(); this.throttler.register(this); } @Override public void onThrottleReady(boolean wasDelayed) { - if (wasDelayed) { - session - .getMetricUpdater() - .updateTimer( - DefaultSessionMetric.THROTTLING_DELAY, - executionProfile.getName(), - System.nanoTime() - startTimeNanos, - TimeUnit.NANOSECONDS); + if (wasDelayed + // avoid call to nanoTime() if metric is disabled: + && sessionMetricUpdater.isEnabled( + DefaultSessionMetric.THROTTLING_DELAY, executionProfile.getName())) { + sessionMetricUpdater.updateTimer( + DefaultSessionMetric.THROTTLING_DELAY, + executionProfile.getName(), + System.nanoTime() - startTimeNanos, + TimeUnit.NANOSECONDS); } sendRequest(null, 0, 0, true); } @@ -298,22 +308,30 @@ private void setFinalResult( if (result.complete(resultSet)) { cancelScheduledTasks(); throttler.signalSuccess(this); - long now = System.nanoTime(); - long totalLatencyNanos = now - startTimeNanos; - long nodeLatencyNanos = now - callback.start; - context - .getRequestTracker() - .onNodeSuccess(statement, nodeLatencyNanos, executionProfile, callback.node); - context - .getRequestTracker() - .onSuccess(statement, totalLatencyNanos, executionProfile, callback.node); - session - .getMetricUpdater() - .updateTimer( - DefaultSessionMetric.CQL_REQUESTS, - executionProfile.getName(), - totalLatencyNanos, - TimeUnit.NANOSECONDS); + + // Only call nanoTime() if we're actually going to use it + long completionTimeNanos = NANOTIME_NOT_MEASURED_YET, + totalLatencyNanos = NANOTIME_NOT_MEASURED_YET; + if (!(requestTracker instanceof NoopRequestTracker)) { + completionTimeNanos = System.nanoTime(); + totalLatencyNanos = completionTimeNanos - startTimeNanos; + long nodeLatencyNanos = completionTimeNanos - callback.nodeStartTimeNanos; + requestTracker.onNodeSuccess( + statement, nodeLatencyNanos, executionProfile, callback.node); + requestTracker.onSuccess(statement, totalLatencyNanos, executionProfile, callback.node); + } + if (sessionMetricUpdater.isEnabled( + DefaultSessionMetric.CQL_REQUESTS, executionProfile.getName())) { + if (completionTimeNanos == NANOTIME_NOT_MEASURED_YET) { + completionTimeNanos = System.nanoTime(); + totalLatencyNanos = completionTimeNanos - startTimeNanos; + } + sessionMetricUpdater.updateTimer( + DefaultSessionMetric.CQL_REQUESTS, + executionProfile.getName(), + totalLatencyNanos, + TimeUnit.NANOSECONDS); + } } } catch (Throwable error) { setFinalError(error, callback.node, -1); @@ -343,9 +361,8 @@ private ExecutionInfo buildExecutionInfo( @Override public void onThrottleFailure(@NonNull RequestThrottlingException error) { - session - .getMetricUpdater() - .incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, executionProfile.getName()); + sessionMetricUpdater.incrementCounter( + DefaultSessionMetric.THROTTLING_ERRORS, executionProfile.getName()); setFinalError(error, null, -1); } @@ -368,13 +385,14 @@ private void setFinalError(Throwable error, Node node, int execution) { } if (result.completeExceptionally(error)) { cancelScheduledTasks(); - long latencyNanos = System.nanoTime() - startTimeNanos; - context.getRequestTracker().onError(statement, error, latencyNanos, executionProfile, node); + if (!(requestTracker instanceof NoopRequestTracker)) { + long latencyNanos = System.nanoTime() - startTimeNanos; + requestTracker.onError(statement, error, latencyNanos, executionProfile, node); + } if (error instanceof DriverTimeoutException) { throttler.signalTimeout(this); - session - .getMetricUpdater() - .incrementCounter(DefaultSessionMetric.CQL_CLIENT_TIMEOUTS, executionProfile.getName()); + sessionMetricUpdater.incrementCounter( + DefaultSessionMetric.CQL_CLIENT_TIMEOUTS, executionProfile.getName()); } else if (!(error instanceof RequestThrottlingException)) { throttler.signalError(this, error); } @@ -389,7 +407,7 @@ private void setFinalError(Throwable error, Node node, int execution) { private class NodeResponseCallback implements ResponseCallback, GenericFutureListener> { - private final long start = System.nanoTime(); + private final long nodeStartTimeNanos = System.nanoTime(); private final Node node; private final DriverChannel channel; // The identifier of the current execution (0 for the initial execution, 1 for the first @@ -423,7 +441,7 @@ public void operationComplete(Future future) throws Exception { Throwable error = future.cause(); if (error instanceof EncoderException && error.getCause() instanceof FrameTooLongException) { - trackNodeError(node, error.getCause()); + trackNodeError(node, error.getCause(), NANOTIME_NOT_MEASURED_YET); setFinalError(error.getCause(), node, execution); } else { LOG.trace( @@ -432,7 +450,7 @@ public void operationComplete(Future future) throws Exception { channel, error); recordError(node, error); - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); ((DefaultNode) node) .getMetricUpdater() .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS, executionProfile.getName()); @@ -491,13 +509,17 @@ public void operationComplete(Future future) throws Exception { @Override public void onResponse(Frame responseFrame) { - ((DefaultNode) node) - .getMetricUpdater() - .updateTimer( - DefaultNodeMetric.CQL_MESSAGES, - executionProfile.getName(), - System.nanoTime() - start, - TimeUnit.NANOSECONDS); + long nodeResponseTimeNanos = NANOTIME_NOT_MEASURED_YET; + NodeMetricUpdater nodeMetricUpdater = ((DefaultNode) node).getMetricUpdater(); + if (nodeMetricUpdater.isEnabled(DefaultNodeMetric.CQL_MESSAGES, executionProfile.getName())) { + nodeResponseTimeNanos = System.nanoTime(); + long nodeLatency = System.nanoTime() - nodeStartTimeNanos; + nodeMetricUpdater.updateTimer( + DefaultNodeMetric.CQL_MESSAGES, + executionProfile.getName(), + nodeLatency, + TimeUnit.NANOSECONDS); + } inFlightCallbacks.remove(this); if (result.isDone()) { return; @@ -527,12 +549,15 @@ public void onResponse(Frame responseFrame) { LOG.trace("[{}] Got error response, processing", logPrefix); processErrorResponse((Error) responseMessage); } else { - trackNodeError(node, new IllegalStateException("Unexpected response " + responseMessage)); + trackNodeError( + node, + new IllegalStateException("Unexpected response " + responseMessage), + nodeResponseTimeNanos); setFinalError( new IllegalStateException("Unexpected response " + responseMessage), node, execution); } } catch (Throwable t) { - trackNodeError(node, t); + trackNodeError(node, t, nodeResponseTimeNanos); setFinalError(t, node, execution); } } @@ -556,7 +581,7 @@ private void processErrorResponse(Error errorMessage) { repreparePayload.customPayload, timeout, throttler, - session.getMetricUpdater(), + sessionMetricUpdater, logPrefix, "Reprepare " + reprepareMessage.toString()); reprepareHandler @@ -575,18 +600,17 @@ private void processErrorResponse(Error errorMessage) { || prepareError instanceof FunctionFailureException || prepareError instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error on reprepare, rethrowing", logPrefix); - trackNodeError(node, prepareError); + trackNodeError(node, prepareError, NANOTIME_NOT_MEASURED_YET); setFinalError(prepareError, node, execution); return null; } } } else if (exception instanceof RequestThrottlingException) { - trackNodeError(node, exception); setFinalError(exception, node, execution); return null; } recordError(node, exception); - trackNodeError(node, exception); + trackNodeError(node, exception, NANOTIME_NOT_MEASURED_YET); LOG.trace("[{}] Reprepare failed, trying next node", logPrefix); sendRequest(null, execution, retryCount, false); } else { @@ -602,14 +626,14 @@ private void processErrorResponse(Error errorMessage) { if (error instanceof BootstrappingException) { LOG.trace("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); sendRequest(null, execution, retryCount, false); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { LOG.trace("[{}] Unrecoverable error, rethrowing", logPrefix); metricUpdater.incrementCounter(DefaultNodeMetric.OTHER_ERRORS, executionProfile.getName()); - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); setFinalError(error, node, execution); } else { RetryDecision decision; @@ -683,16 +707,16 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { switch (decision) { case RETRY_SAME: recordError(node, error); - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); sendRequest(node, execution, retryCount + 1, false); break; case RETRY_NEXT: recordError(node, error); - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); sendRequest(null, execution, retryCount + 1, false); break; case RETHROW: - trackNodeError(node, error); + trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); setFinalError(error, node, execution); break; case IGNORE: @@ -755,11 +779,20 @@ public void cancel() { } } - private void trackNodeError(Node node, Throwable error) { - long latencyNanos = System.nanoTime() - this.start; - context - .getRequestTracker() - .onNodeError(statement, error, latencyNanos, executionProfile, node); + /** + * @param nodeResponseTimeNanos the time we received the response, if it's already been + * measured. If {@link #NANOTIME_NOT_MEASURED_YET}, it hasn't and we need to measure it now + * (this is to avoid unnecessary calls to System.nanoTime) + */ + private void trackNodeError(Node node, Throwable error, long nodeResponseTimeNanos) { + if (requestTracker instanceof NoopRequestTracker) { + return; + } + if (nodeResponseTimeNanos == NANOTIME_NOT_MEASURED_YET) { + nodeResponseTimeNanos = System.nanoTime(); + } + long latencyNanos = nodeResponseTimeNanos - this.nodeStartTimeNanos; + requestTracker.onNodeError(statement, error, latencyNanos, executionProfile, node); } @Override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index a7aa04a7479..eca062cdaac 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -126,6 +126,8 @@ public void should_always_rethrow_query_validation_error( Mockito.verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.OTHER_ERRORS, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), @@ -175,6 +177,8 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), @@ -224,6 +228,8 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(2)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(2)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), @@ -270,6 +276,8 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( failureScenario.ignoreMetric, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), @@ -306,6 +314,8 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( Mockito.verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), @@ -357,6 +367,8 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re Mockito.verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); + Mockito.verify(nodeMetricUpdater1, atMost(1)) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index b4f678aaee7..3f4d3a98e1d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -184,6 +184,8 @@ public void should_not_start_execution_if_result_complete( firstExecutionTask.run(); node2Behavior.verifyNoWrite(); + Mockito.verify(nodeMetricUpdater1) + .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); Mockito.verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 2854c3d171e..88c29c79d47 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -15,9 +15,13 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; + import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.metrics.NodeMetric; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; @@ -68,8 +72,11 @@ public void setup() { MockitoAnnotations.initMocks(this); Mockito.when(node1.getMetricUpdater()).thenReturn(nodeMetricUpdater1); + Mockito.when(nodeMetricUpdater1.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); Mockito.when(node2.getMetricUpdater()).thenReturn(nodeMetricUpdater2); + Mockito.when(nodeMetricUpdater2.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); Mockito.when(node3.getMetricUpdater()).thenReturn(nodeMetricUpdater3); + Mockito.when(nodeMetricUpdater3.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); } protected static Frame defaultFrameOf(Message responseMessage) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java new file mode 100644 index 00000000000..1e73600e480 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java @@ -0,0 +1,114 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; + +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; +import com.datastax.oss.driver.api.core.tracker.RequestTracker; +import com.datastax.oss.driver.internal.core.tracker.NoopRequestTracker; +import com.datastax.oss.protocol.internal.ProtocolConstants; +import com.datastax.oss.protocol.internal.response.Error; +import java.util.concurrent.CompletionStage; +import org.junit.Test; +import org.mockito.Mockito; + +public class CqlRequestHandlerTrackerTest extends CqlRequestHandlerTestBase { + + @Test + public void should_invoke_request_tracker() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withDefaultIdempotence(true) + .withResponse( + node1, + defaultFrameOf( + new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))) + .withResponse(node2, defaultFrameOf(singleRow())) + .build()) { + + RequestTracker requestTracker = Mockito.mock(RequestTracker.class); + Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); + + CompletionStage resultSetFuture = + new CqlRequestAsyncHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") + .handle(); + + assertThatStage(resultSetFuture) + .isSuccess( + resultSet -> { + Mockito.verify(requestTracker) + .onNodeError( + eq(UNDEFINED_IDEMPOTENCE_STATEMENT), + any(BootstrappingException.class), + anyLong(), + any(DriverExecutionProfile.class), + eq(node1)); + Mockito.verify(requestTracker) + .onNodeSuccess( + eq(UNDEFINED_IDEMPOTENCE_STATEMENT), + anyLong(), + any(DriverExecutionProfile.class), + eq(node2)); + Mockito.verify(requestTracker) + .onSuccess( + eq(UNDEFINED_IDEMPOTENCE_STATEMENT), + anyLong(), + any(DriverExecutionProfile.class), + eq(node2)); + Mockito.verifyNoMoreInteractions(requestTracker); + }); + } + } + + @Test + public void should_not_invoke_noop_request_tracker() { + try (RequestHandlerTestHarness harness = + RequestHandlerTestHarness.builder() + .withDefaultIdempotence(true) + .withResponse( + node1, + defaultFrameOf( + new Error(ProtocolConstants.ErrorCode.IS_BOOTSTRAPPING, "mock message"))) + .withResponse(node2, defaultFrameOf(singleRow())) + .build()) { + + RequestTracker requestTracker = spy(new NoopRequestTracker(harness.getContext())); + Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); + + CompletionStage resultSetFuture = + new CqlRequestAsyncHandler( + UNDEFINED_IDEMPOTENCE_STATEMENT, + harness.getSession(), + harness.getContext(), + "test") + .handle(); + + assertThatStage(resultSetFuture) + .isSuccess(resultSet -> Mockito.verifyNoMoreInteractions(requestTracker)); + } + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 613de68555b..f2423682ae3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.api.core.retry.RetryPolicy; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; @@ -148,6 +149,8 @@ protected RequestHandlerTestHarness(Builder builder) { .thenReturn(CompletableFuture.completedFuture(null)); Mockito.when(session.getMetricUpdater()).thenReturn(sessionMetricUpdater); + Mockito.when(sessionMetricUpdater.isEnabled(any(SessionMetric.class), anyString())) + .thenReturn(true); Mockito.when(session.getMetadata()).thenReturn(DefaultMetadata.EMPTY); From bcf5e1238c9cb4aa9246b3fcb0da4f9b45dd8093 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 6 Nov 2018 10:58:06 -0800 Subject: [PATCH 623/742] JAVA-1995: Use rule chains to enforce JUnit rule order Motivation: Currently we don't control the execution order of the rules in our integration tests. But in order to open a session, we need Cassandra to be running, so we call cassandraResource.setUp() in Sessionrule.before(). The problem is when this is coupled with a CassandraRequirement that isn't met: cassandraResource.setUp() does not re-check the requirements, so SessionRule forces the initialization of a cluster for a test that's going to be ignored. Modifications: Use RuleChain to ensure that the server rule (CCM or Simulacron) always initializes before the SessionRule. Result: No need to force the initialization of the server rule anymore. Additionally, error handling in SessionRule.after() is simplified, since the server rule is guaranteed to still be running. --- .../driver/api/core/ConnectKeyspaceIT.java | 8 +++++-- .../core/compression/DirectCompressionIT.java | 9 +++++--- .../core/compression/HeapCompressionIT.java | 9 +++++--- .../connection/ChannelSocketOptionsIT.java | 10 +++++---- .../api/core/connection/FrameLengthIT.java | 10 +++++---- .../driver/api/core/cql/AsyncResultSetIT.java | 9 +++++--- .../driver/api/core/cql/BatchStatementIT.java | 8 +++++-- .../driver/api/core/cql/BoundStatementIT.java | 9 +++++--- .../api/core/cql/PerRequestKeyspaceIT.java | 8 +++++-- .../cql/PreparedStatementInvalidationIT.java | 9 +++++--- .../oss/driver/api/core/cql/QueryTraceIT.java | 9 +++++--- .../api/core/cql/SimpleStatementIT.java | 19 ++++++++++------- .../oss/driver/api/core/data/DataTypeIT.java | 8 +++++-- .../DefaultLoadBalancingPolicyIT.java | 9 +++++--- .../core/loadbalancing/NodeTargetingIT.java | 8 +++++-- .../PerProfileLoadBalancingPolicyIT.java | 8 +++++-- .../api/core/metadata/ByteOrderedTokenIT.java | 10 +++++---- .../metadata/ByteOrderedTokenVnodesIT.java | 10 +++++---- .../driver/api/core/metadata/DescribeIT.java | 9 +++++--- .../api/core/metadata/Murmur3TokenIT.java | 9 +++++--- .../core/metadata/Murmur3TokenVnodesIT.java | 10 +++++---- .../api/core/metadata/NodeMetadataIT.java | 9 +++++--- .../driver/api/core/metadata/NodeStateIT.java | 8 +++++-- .../api/core/metadata/RandomTokenIT.java | 10 +++++---- .../core/metadata/RandomTokenVnodesIT.java | 10 +++++---- .../api/core/metadata/SchemaChangesIT.java | 9 +++++--- .../driver/api/core/metadata/SchemaIT.java | 8 +++++-- .../core/retry/PerProfileRetryPolicyIT.java | 9 +++++--- .../driver/api/core/session/ExceptionIT.java | 10 +++++---- .../api/core/session/RequestProcessorIT.java | 8 +++++-- .../api/core/tracker/RequestLoggerIT.java | 21 ++++++++++++------- .../type/codec/registry/CodecRegistryIT.java | 8 +++++-- .../api/testinfra/CassandraResourceRule.java | 14 ++++++++++--- .../api/testinfra/session/SessionRule.java | 12 +---------- 34 files changed, 217 insertions(+), 117 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java index ec5f92ba9c5..32347d57e0b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectKeyspaceIT.java @@ -27,12 +27,16 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class ConnectKeyspaceIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); + private static SessionRule sessionRule = SessionRule.builder(ccm).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Test public void should_connect_to_existing_keyspace() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java index 7ba32a43024..e788e5352f8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/DirectCompressionIT.java @@ -34,14 +34,15 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class DirectCompressionIT { - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + private static CcmRule ccmRule = CcmRule.getInstance(); - @ClassRule - public static SessionRule schemaSessionRule = + private static SessionRule schemaSessionRule = SessionRule.builder(ccmRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -49,6 +50,8 @@ public class DirectCompressionIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(schemaSessionRule); + @BeforeClass public static void setup() { schemaSessionRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java index ea490631d9e..809b0083ac3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/compression/HeapCompressionIT.java @@ -34,6 +34,8 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(IsolatedTests.class) public class HeapCompressionIT { @@ -43,10 +45,9 @@ public class HeapCompressionIT { System.setProperty("io.netty.noUnsafe", "true"); } - @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().build(); + private static CustomCcmRule ccmRule = CustomCcmRule.builder().build(); - @ClassRule - public static SessionRule schemaSessionRule = + private static SessionRule schemaSessionRule = SessionRule.builder(ccmRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -54,6 +55,8 @@ public class HeapCompressionIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(schemaSessionRule); + @BeforeClass public static void setup() { schemaSessionRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java index a3d7ef78f06..72ae2fdad61 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/ChannelSocketOptionsIT.java @@ -43,12 +43,13 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class ChannelSocketOptionsIT { - public static @ClassRule SimulacronRule simulacron = - new SimulacronRule(ClusterSpec.builder().withNodes(1)); + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); private static DriverConfigLoader loader = SessionUtils.configLoaderBuilder() @@ -60,10 +61,11 @@ public class ChannelSocketOptionsIT { .withInt(DefaultDriverOption.SOCKET_SEND_BUFFER_SIZE, 123456) .build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(simulacron).withConfigLoader(loader).build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + @Test public void should_report_socket_options() { Session session = sessionRule.session(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 68af03548b0..5e599af39ba 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -46,11 +46,12 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class FrameLengthIT { - public static @ClassRule SimulacronRule simulacron = - new SimulacronRule(ClusterSpec.builder().withNodes(1)); + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); private static DriverConfigLoader loader = SessionUtils.configLoaderBuilder() @@ -60,10 +61,11 @@ public class FrameLengthIT { .withBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH, "100 kilobytes") .build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(simulacron).withConfigLoader(loader).build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + private static final SimpleStatement LARGE_QUERY = SimpleStatement.newInstance("select * from foo").setIdempotent(true); private static final SimpleStatement SLOW_QUERY = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index cc8d54c9ff1..f32823557dd 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -31,6 +31,8 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class AsyncResultSetIT { @@ -40,10 +42,9 @@ public class AsyncResultSetIT { private static final String PARTITION_KEY1 = "part"; private static final String PARTITION_KEY2 = "part2"; - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccm) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -51,6 +52,8 @@ public class AsyncResultSetIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); + @BeforeClass public static void setupSchema() { // create table and load data across two partitions so we can test paging across tokens. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 88ce24928c6..1b932d72550 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -32,14 +32,18 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class BatchStatementIT { - @Rule public CcmRule ccm = CcmRule.getInstance(); + private CcmRule ccm = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = SessionRule.builder(ccm).build(); + private SessionRule sessionRule = SessionRule.builder(ccm).build(); + + @Rule public TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public TestName name = new TestName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 2af90389dcc..2497ce9d955 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -64,7 +64,9 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class BoundStatementIT { @@ -72,12 +74,11 @@ public class BoundStatementIT { @ClassRule public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); private static final boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccm) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -85,6 +86,8 @@ public class BoundStatementIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); + @Rule public TestName name = new TestName(); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index 0f567de5f29..be492d4f691 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -30,7 +30,9 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; /** * Note: at the time of writing, this test exercises features of an unreleased Cassandra version. To @@ -43,9 +45,11 @@ @Category(ParallelizableTests.class) public class PerRequestKeyspaceIT { - @Rule public CcmRule ccmRule = CcmRule.getInstance(); + private CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + private SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public TestName nameRule = new TestName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java index 260270ffb4c..a51525b6307 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java @@ -39,6 +39,8 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; /** * Note: at the time of writing, some of these tests exercises features of an unreleased Cassandra @@ -51,10 +53,9 @@ @Category(ParallelizableTests.class) public class PreparedStatementInvalidationIT { - @Rule public CcmRule ccmRule = CcmRule.getInstance(); + private CcmRule ccmRule = CcmRule.getInstance(); - @Rule - public SessionRule sessionRule = + private SessionRule sessionRule = SessionRule.builder(ccmRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -63,6 +64,8 @@ public class PreparedStatementInvalidationIT { .build()) .build(); + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + @Rule public ExpectedException thrown = ExpectedException.none(); @Before diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index c37c94722d9..d859947ecc9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -29,14 +29,17 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class QueryTraceIT { - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + private static CcmRule ccmRule = CcmRule.getInstance(); - @ClassRule - public static SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + private static SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index f8f4aa94104..18bc12ba6b9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -47,18 +47,18 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class SimpleStatementIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule - public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccm) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -66,10 +66,15 @@ public class SimpleStatementIT { .build()) .build(); - @ClassRule - public static SessionRule simulacronSessionRule = + private static SessionRule simulacronSessionRule = SessionRule.builder(simulacron).build(); + @ClassRule public static TestRule ccmChain = RuleChain.outerRule(ccm).around(sessionRule); + + @ClassRule + public static TestRule simulacronChain = + RuleChain.outerRule(simulacron).around(simulacronSessionRule); + @Rule public TestName name = new TestName(); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index a457f2e28cc..954c042d58d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -74,15 +74,19 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; @Category(ParallelizableTests.class) @RunWith(DataProviderRunner.class) public class DataTypeIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); + private static SessionRule sessionRule = SessionRule.builder(ccm).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public TestName name = new TestName(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 67eee5a734f..27f0cfb1e1a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -49,15 +49,16 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class DefaultLoadBalancingPolicyIT { private static final String LOCAL_DC = "dc1"; - @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(4, 1).build(); + private static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(4, 1).build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -66,6 +67,8 @@ public class DefaultLoadBalancingPolicyIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + @BeforeClass public static void setup() { CqlSession session = sessionRule.session(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java index c5ffba47c46..a0f53cb370a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java @@ -41,13 +41,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class NodeTargetingIT { - @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(5)); + private SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(5)); - @Rule public SessionRule sessionRule = SessionRule.builder(simulacron).build(); + private SessionRule sessionRule = SessionRule.builder(simulacron).build(); + + @Rule public TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); @Before public void clear() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java index 164fb2e18f4..8ee13c5c2d5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java @@ -38,16 +38,18 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class PerProfileLoadBalancingPolicyIT { // 3 2-node DCs - public static @ClassRule SimulacronRule simulacron = + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2, 2, 2)); // default lb policy should consider dc1 local, profile1 dc3, profile2 empty. - public static @ClassRule SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(simulacron) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -65,6 +67,8 @@ public class PerProfileLoadBalancingPolicyIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + private static String QUERY_STRING = "select * from foo"; private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index ea80e45baf5..13d36f5a773 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -24,15 +24,15 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class ByteOrderedTokenIT extends TokenITBase { - @ClassRule - public static CustomCcmRule ccmRule = + private static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).withCreateOption("-p ByteOrderedPartitioner").build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -41,6 +41,8 @@ public class ByteOrderedTokenIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public ByteOrderedTokenIT() { super(ByteOrderedToken.class, false); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index 1bd2bd933fb..c3f8dea344a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -24,19 +24,19 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class ByteOrderedTokenVnodesIT extends TokenITBase { - @ClassRule - public static CustomCcmRule ccmRule = + private static CustomCcmRule ccmRule = CustomCcmRule.builder() .withNodes(3) .withCreateOption("-p ByteOrderedPartitioner") .withCreateOption("--vnodes") .build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -45,6 +45,8 @@ public class ByteOrderedTokenVnodesIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public ByteOrderedTokenVnodesIT() { super(ByteOrderedToken.class, true); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index 49042d24344..cf36e343035 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -38,6 +38,8 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,11 +48,10 @@ public class DescribeIT { private static final Logger logger = LoggerFactory.getLogger(DescribeIT.class); - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + private static CcmRule ccmRule = CcmRule.getInstance(); // disable debouncer to speed up test. - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -60,6 +61,8 @@ public class DescribeIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + /** * Creates a keyspace using a variety of features and ensures {@link * com.datastax.oss.driver.api.core.metadata.schema.Describable#describe(boolean)} contains the diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index c8fbe1aed2d..8079bee7b81 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -24,13 +24,14 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class Murmur3TokenIT extends TokenITBase { - @ClassRule public static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).build(); + private static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -39,6 +40,8 @@ public class Murmur3TokenIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public Murmur3TokenIT() { super(Murmur3Token.class, false); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index 05bd93ff50f..b9314faa258 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -24,15 +24,15 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class Murmur3TokenVnodesIT extends TokenITBase { - @ClassRule - public static CustomCcmRule ccmRule = + private static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).withCreateOption("--vnodes").build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -41,6 +41,8 @@ public class Murmur3TokenVnodesIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public Murmur3TokenVnodesIT() { super(Murmur3Token.class, true); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index 8ca21fb7c05..f8cca91db49 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -29,16 +29,19 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; import java.util.Collection; import org.junit.ClassRule; -import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class NodeMetadataIT { - @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); + private static CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + private static SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); @Test public void should_expose_node_metadata() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 404fc6b3c8f..a1d82de0b49 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -64,6 +64,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -75,12 +77,12 @@ @RunWith(MockitoJUnitRunner.class) public class NodeStateIT { - public @Rule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + private SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); private NodeStateListener nodeStateListener = Mockito.mock(NodeStateListener.class); private InOrder inOrder; - public @Rule SessionRule sessionRule = + private SessionRule sessionRule = SessionRule.builder(simulacron) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -93,6 +95,8 @@ public class NodeStateIT { .withNodeStateListener(nodeStateListener) .build(); + @Rule public TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + private @Captor ArgumentCaptor nodeCaptor; private InternalDriverContext driverContext; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index 0fb67fbf7f7..0b19ec4dc5d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -24,15 +24,15 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class RandomTokenIT extends TokenITBase { - @ClassRule - public static CustomCcmRule ccmRule = + private static CustomCcmRule ccmRule = CustomCcmRule.builder().withNodes(3).withCreateOption("-p RandomPartitioner").build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -41,6 +41,8 @@ public class RandomTokenIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public RandomTokenIT() { super(RandomToken.class, false); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index a67bae9fd41..9f46441c677 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -24,19 +24,19 @@ import java.time.Duration; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class RandomTokenVnodesIT extends TokenITBase { - @ClassRule - public static CustomCcmRule ccmRule = + private static CustomCcmRule ccmRule = CustomCcmRule.builder() .withNodes(3) .withCreateOption("-p RandomPartitioner") .withCreateOption("--vnodes") .build(); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(ccmRule) .withKeyspace(false) .withConfigLoader( @@ -45,6 +45,8 @@ public class RandomTokenVnodesIT extends TokenITBase { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + public RandomTokenVnodesIT() { super(RandomToken.class, true); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 922b643b326..0acfaf213a3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -43,16 +43,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; import org.mockito.Mockito; @Category(ParallelizableTests.class) public class SchemaChangesIT { - @Rule public CcmRule ccmRule = CcmRule.getInstance(); + private CcmRule ccmRule = CcmRule.getInstance(); // A client that we only use to set up the tests - @Rule - public SessionRule adminSessionRule = + private SessionRule adminSessionRule = SessionRule.builder(ccmRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -61,6 +62,8 @@ public class SchemaChangesIT { .build()) .build(); + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(adminSessionRule); + @Before public void setup() { // Always drop and re-create the keyspace to start from a clean state diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index a2728c14810..269d963258b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -38,13 +38,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class SchemaIT { - @Rule public CcmRule ccmRule = CcmRule.getInstance(); + private CcmRule ccmRule = CcmRule.getInstance(); - @Rule public SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + private SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); @Test public void should_expose_system_and_test_keyspace() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java index 0b7adadbbc6..fded5d3c6ba 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java @@ -49,15 +49,16 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class PerProfileRetryPolicyIT { // Shared across all tests methods. - public static @ClassRule SimulacronRule simulacron = - new SimulacronRule(ClusterSpec.builder().withNodes(2)); + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); - public static @ClassRule SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(simulacron) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -78,6 +79,8 @@ public class PerProfileRetryPolicyIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + private static String QUERY_STRING = "select * from foo"; private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java index b29cbd2d79e..ecccb72c8b0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java @@ -41,15 +41,15 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class ExceptionIT { - @ClassRule - public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); + private static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); - @ClassRule - public static SessionRule sessionRule = + private static SessionRule sessionRule = SessionRule.builder(simulacron) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -60,6 +60,8 @@ public class ExceptionIT { .build()) .build(); + @ClassRule public static TestRule chain = RuleChain.outerRule(simulacron).around(sessionRule); + private static String QUERY_STRING = "select * from foo"; @Before diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 38216c7b71a..deb16b6df06 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -41,6 +41,8 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; /** * A suite of tests for exercising registration of custom {@link @@ -63,9 +65,11 @@ @Category(ParallelizableTests.class) public class RequestProcessorIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); + private static SessionRule sessionRule = SessionRule.builder(ccm).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 09b160ddbc6..3be0e2279c9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -50,6 +50,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -65,8 +67,7 @@ public class RequestLoggerIT { private static final String QUERY = "SELECT release_version FROM system.local"; - @Rule - public SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + private SimulacronRule simulacronRule = new SimulacronRule(ClusterSpec.builder().withNodes(3)); private final DefaultDriverConfigLoaderBuilder.Profile lowThresholdProfile = DefaultDriverConfigLoaderBuilder.profileBuilder() @@ -101,8 +102,7 @@ public class RequestLoggerIT { .withProfile("no-traces", noTracesProfile) .build(); - @Rule - public SessionRule sessionRuleRequest = + private SessionRule sessionRuleRequest = SessionRule.builder(simulacronRule).withConfigLoader(requestLoader).build(); private final DriverConfigLoader nodeLoader = @@ -121,12 +121,10 @@ public class RequestLoggerIT { .withProfile("no-traces", noTracesProfile) .build(); - @Rule - public SessionRule sessionRuleNode = + private SessionRule sessionRuleNode = SessionRule.builder(simulacronRule).withConfigLoader(nodeLoader).build(); - @Rule - public SessionRule sessionRuleDefaults = + private SessionRule sessionRuleDefaults = SessionRule.builder(simulacronRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -139,6 +137,13 @@ public class RequestLoggerIT { .build()) .build(); + @Rule + public TestRule chain = + RuleChain.outerRule(simulacronRule) + .around(sessionRuleRequest) + .around(sessionRuleNode) + .around(sessionRuleDefaults); + @Captor private ArgumentCaptor loggingEventCaptor; @Mock private Appender appender; private Logger logger; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 8f2cbe585b0..4fbf5f20a17 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -51,14 +51,18 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; import org.junit.rules.TestName; +import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class CodecRegistryIT { - @ClassRule public static CcmRule ccm = CcmRule.getInstance(); + private static CcmRule ccm = CcmRule.getInstance(); - @ClassRule public static SessionRule sessionRule = SessionRule.builder(ccm).build(); + private static SessionRule sessionRule = SessionRule.builder(ccm).build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public TestName name = new TestName(); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java index 936683f84c2..710622532e2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java @@ -16,18 +16,26 @@ package com.datastax.oss.driver.api.testinfra; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; import java.net.InetSocketAddress; import java.util.Collections; import java.util.Set; import org.junit.rules.ExternalResource; +import org.junit.rules.RuleChain; /** - * An {@link ExternalResource} which provides a {@link #setUp()} method for initializing the - * resource externally (instead of making users use rule chains) and a {@link #getContactPoints()} - * for accessing the contact points of the cassandra cluster. + * An {@link ExternalResource} which provides a {@link #getContactPoints()} for accessing the + * contact points of the cassandra cluster. */ public abstract class CassandraResourceRule extends ExternalResource { + /** + * @deprecated this method is preserved for backward compatibility only. The correct way to ensure + * that a {@code CassandraResourceRule} gets initialized before a {@link SessionRule} is to + * wrap them into a {@link RuleChain}. Therefore there is no need to force the initialization + * of a {@code CassandraResourceRule} explicitly anymore. + */ + @Deprecated public synchronized void setUp() { try { this.before(); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java index da214af9b51..585aaff2aa7 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionRule.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.testinfra.session; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.SimpleStatement; @@ -95,9 +94,6 @@ public SessionRule( @Override protected void before() { - // ensure resource is initialized before initializing the session. - cassandraResource.setUp(); - session = SessionUtils.newSession( cassandraResource, null, nodeStateListener, schemaChangeListener, null, configLoader); @@ -113,13 +109,7 @@ protected void before() { @Override protected void after() { if (keyspace != null) { - try { - SessionUtils.dropKeyspace(session, keyspace, slowProfile); - } catch (NoNodeAvailableException e) { - // Rule ordering is not deterministic, so the cassandraResource might have shut down - // already. Dropping the keyspace is not critical since we're throwing the cluster away, so - // just ignore. - } + SessionUtils.dropKeyspace(session, keyspace, slowProfile); } session.close(); } From abd77668df8459fd0b3fea84b3cfe45373c5d31f Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 8 Nov 2018 11:55:56 -0800 Subject: [PATCH 624/742] Fix array increment in Conversions.encode --- .../com/datastax/oss/driver/internal/core/cql/Conversions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index e5ece85c8a0..e8c36eb7868 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -248,7 +248,7 @@ public static List encode( ByteBuffer[] encodedValues = new ByteBuffer[values.size()]; int i = 0; for (Object value : values) { - encodedValues[i] = (value == null) ? null : encode(value, codecRegistry, protocolVersion); + encodedValues[i++] = (value == null) ? null : encode(value, codecRegistry, protocolVersion); } return NullAllowingImmutableList.of(encodedValues); } From fcd57ad0e1e97b4b601da1fc2e44cfe218ab51d3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 7 Nov 2018 11:56:24 -0800 Subject: [PATCH 625/742] JAVA-1918: Document temporal types --- changelog/README.md | 1 + manual/core/README.md | 60 ++++++------ manual/core/temporal_types/README.md | 141 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 manual/core/temporal_types/README.md diff --git a/changelog/README.md b/changelog/README.md index 9bf7d652701..61cebb4c0c0 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-1918: Document temporal types - [improvement] JAVA-1914: Optimize use of System.nanoTime in CqlRequestHandlerBase - [improvement] JAVA-1945: Document corner cases around UDT and tuple attachment - [improvement] JAVA-2026: Make CqlDuration implement TemporalAmount diff --git a/manual/core/README.md b/manual/core/README.md index dcdf7b52eff..8a8b950a2db 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -179,35 +179,34 @@ See [AccessibleByName] for an explanation of the conversion rules. ##### CQL to Java type mapping -

        • Create statementCase-sensitive?CQL idInternal id
          CREATE TABLE t(foo int PRIMARY KEY)Nofoofoo
          CREATE TABLE t(Foo int PRIMARY KEY)Nofoofoo
          - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          CQL3 data type Getter name Java type
          ascii getString java.lang.String
          bigint getLong long
          blob getBytes java.nio.ByteBuffer
          boolean getBoolean boolean
          counter getLong long
          date getLocalDate java.time.LocalDate
          decimal getBigDecimal java.math.BigDecimal
          double getDouble double
          duration getCqlDuration CqlDuration
          float getFloat float
          inet getInetAddress java.net.InetAddress
          int getInt int
          list getList java.util.List
          map getMap java.util.Map
          set getSet java.util.Set
          smallint getShort short
          text getString java.lang.String
          time getLocalTime java.time.LocalTime
          timestamp getInstant java.time.Instant
          timeuuid getUuid java.util.UUID
          tinyint getByte byte
          tuple getTupleValue TupleValue
          user-defined types getUDTValue UDTValue
          uuid getUuid java.util.UUID
          varchar getString java.lang.String
          varint getVarint java.math.BigInteger
          +| CQL3 data type | Getter name | Java type | See also | +|---------------------|----------------|----------------------|-------------------------------------| +| ascii | getString | java.lang.String | | +| bigint | getLong | long | | +| blob | getBytes | java.nio.ByteBuffer | | +| boolean | getBoolean | boolean | | +| counter | getLong | long | | +| date | getLocalDate | java.time.LocalDate | [Temporal types](temporal_types/) | +| decimal | getBigDecimal | java.math.BigDecimal | | +| double | getDouble | double | | +| duration | getCqlDuration | [CqlDuration] | [Temporal types](temporal_types/) | +| float | getFloat | float | | +| inet | getInetAddress | java.net.InetAddress | | +| int | getInt | int | | +| list | getList | java.util.List | | +| map | getMap | java.util.Map | | +| set | getSet | java.util.Set | | +| smallint | getShort | short | | +| text | getString | java.lang.String | | +| time | getLocalTime | java.time.LocalTime | [Temporal types](temporal_types/) | +| timestamp | getInstant | java.time.Instant | [Temporal types](temporal_types/) | +| timeuuid | getUuid | java.util.UUID | | +| tinyint | getByte | byte | | +| tuple | getTupleValue | [TupleValue] | [Tuples](tuples/) | +| user-defined types | getUDTValue | [UDTValue] | [User-defined types](udts/) | +| uuid | getUuid | java.util.UUID | | +| varchar | getString | java.lang.String | | +| varint | getVarint | java.math.BigInteger | | Sometimes the driver has to infer a CQL type from a Java type (for example when handling the values of [simple statements](statements/simple/)); for those that have multiple CQL equivalents, it makes @@ -270,5 +269,8 @@ for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) { [CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html [AccessibleByName]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html [GenericType]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/reflect/GenericType.html +[CqlDuration]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/CqlDuration.html +[TupleValue]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/TupleValue.html +[UdtValue]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/UdtValue.html [CASSANDRA-10145]: https://issues.apache.org/jira/browse/CASSANDRA-10145 \ No newline at end of file diff --git a/manual/core/temporal_types/README.md b/manual/core/temporal_types/README.md new file mode 100644 index 00000000000..dd5dc75208c --- /dev/null +++ b/manual/core/temporal_types/README.md @@ -0,0 +1,141 @@ +## Temporal types + +This page provides more details about the various CQL time types, and the Java types they are mapped +to in the driver. + +### Date and time + +CQL types `date` and `time` map directly to `java.time.LocalDate` and `java.time.LocalTime`. + +These are simple, time-zone-free representations of date-only (`yyyy-mm-dd`) and time-only +(`HH:MM:SS\[.fff]`) types. + +### Timestamp + +CQL type `timestamp` is the date-and-time representation, stored as a number of milliseconds since +the epoch (01/01/1970 UTC). + + +#### No time zone + +`timestamp` does **not** store a time zone. This is not always obvious because clients generally do +use one for display. For instance, the following CQLSH snippet is from a machine in Pacific time: + +``` +cqlsh> CREATE TABLE test(t timestamp PRIMARY KEY); +cqlsh> INSERT INTO test (t) VALUES (dateof(now())); +cqlsh> SELECT * FROM test; + + t +--------------------------------- + 2018-11-07 08:50:52.433000-0800 +``` + +It looks like the timestamp has a zone (`-0800`), but it is actually the client's. If you force +CQLSH to a different zone and observe the same data, it will be displayed differently: + +``` +$ TZ=UTC cqlsh +cqlsh> SELECT * FROM test; + + t +--------------------------------- + 2018-11-07 16:50:52.433000+0000 +``` + +Internally, Cassandra only stores the raw number of milliseconds. You can observe that with a cast: + +``` +cqlsh> SELECT cast(t as bigint) FROM test; + + cast(t as bigint) +------------------- + 1541609452433 +``` + +#### Java equivalent + +By default, the driver maps `timestamp` to `java.time.Instant`. This Java type is the closest to the +internal representation; in particular, it does not have a time zone. On the downside, this means +you can't directly extract calendar fields (year, month, etc.). You need to call `atZone` to perform +the conversion: + +```java +Row row = session.execute("SELECT t FROM test").one(); +Instant instant = row.getInstant("t"); +ZonedDateTime dateTime = instant.atZone(ZoneId.of("America/Los_Angeles")); +System.out.println(dateTime.getYear()); +``` + +Conversely, you can convert a `ZonedDateTime` back to an `Instant` with `toInstant`. + +If you want to automate those `atZone`/`toInstant` conversions, the driver comes with an optional +`ZonedDateTime` codec, that must be registered explicitly with the session: + +```java +CqlSession session = CqlSession.builder() + .addTypeCodecs(TypeCodecs.ZONED_TIMESTAMP_UTC) + .build(); + +Row row = session.execute("SELECT t FROM test").one(); +ZonedDateTime dateTime = row.get("t", GenericType.ZONED_DATE_TIME); +``` + +There are various constants and methods to obtain a codec instance for a particular zone: + +* [TypeCodecs.ZONED_TIMESTAMP_SYSTEM]\: system default; +* [TypeCodecs.ZONED_TIMESTAMP_UTC]\: UTC; +* [TypeCodecs.zonedTimestampAt()]\: user-provided. + +Which zone you choose is application-dependent. The driver doesn't map to `ZonedDateTime` by default +because it would have to make an arbitrary choice; we want you to think about time zones explicitly +before you decide to use that type. + +#### Millisecond-only precision + +As already stated, `timestamp` is stored as a number of milliseconds. If you try to write an +`Instant` or `ZonedDateTime` with higher precision through the driver, the sub-millisecond part will +be truncated: + +```java +CqlSession session = + CqlSession.builder() + .addTypeCodecs(TypeCodecs.ZONED_TIMESTAMP_UTC) + .build(); + +ZonedDateTime valueOnClient = ZonedDateTime.parse("2018-11-07T16:50:52.433395762Z"); + // sub-millisecond digits ^^^^^^ +session.execute( + SimpleStatement.newInstance("INSERT INTO test (t) VALUES (?)", valueOnClient)); + +ZonedDateTime valueInDb = + session.execute("SELECT * FROM test").one().get(0, GenericType.ZONED_DATE_TIME); +System.out.println(valueInDb); +// Prints "2018-11-07T16:50:52.433Z" +``` + +### Duration + +CQL type `duration` represents a period in months, days and nanoseconds. The driver maps it to a +custom type: [CqlDuration]. + +We deliberately avoided `java.time.Period`, because it does not contain a nanoseconds part as +`CqlDuration` does; and we also avoided `java.time.Duration`, because it represents an absolute +time-based amount, regardless of the calendar, whereas `CqlDuration` manipulates conceptual days and +months instead. Thus a `CqlDuration` of "2 months" represents a different amount of time depending +on the date to which it is applied (because months have a different number of days, and because +daylight savings rules might also apply, etc). + +`CqlDuration` implements `java.time.temporal.TemporalAmount`, so it interoperates nicely with the +JDK's built-in temporal types: + +```java +ZonedDateTime dateTime = ZonedDateTime.parse("2018-10-04T00:00-07:00[America/Los_Angeles]"); +System.out.println(dateTime.minus(CqlDuration.from("1h15s15ns"))); +// prints "2018-10-03T22:59:44.999999985-07:00[America/Los_Angeles]" +``` + +[CqlDuration]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/CqlDuration.html +[TypeCodecs.ZONED_TIMESTAMP_SYSTEM]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.html#ZONED_TIMESTAMP_SYSTEM +[TypeCodecs.ZONED_TIMESTAMP_UTC]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.html#ZONED_TIMESTAMP_UTC +[TypeCodecs.zonedTimestampAt()]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/TypeCodecs.html#zonedTimestampAt-java.time.ZoneId- \ No newline at end of file From 23df2d723e8739e97ca5caae59244bd6525946db Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 9 Nov 2018 11:39:59 -0800 Subject: [PATCH 626/742] JAVA-2028: Use CQL form when parsing UDT types in system tables --- changelog/README.md | 1 + .../schema/parsing/DataTypeCqlNameParser.java | 2 +- .../api/core/metadata/CaseSensitiveUdtIT.java | 132 ++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java diff --git a/changelog/README.md b/changelog/README.md index 61cebb4c0c0..7a46764401e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2028: Use CQL form when parsing UDT types in system tables - [improvement] JAVA-1918: Document temporal types - [improvement] JAVA-1914: Optimize use of System.nanoTime in CqlRequestHandlerBase - [improvement] JAVA-1945: Document corner cases around UDT and tuple attachment diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java index f61ebd21e52..0ab6999f91b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParser.java @@ -119,7 +119,7 @@ private DataType parse( } // Otherwise it's a UDT - CqlIdentifier name = CqlIdentifier.fromInternal(type); + CqlIdentifier name = CqlIdentifier.fromCql(type); if (userTypes != null) { UserDefinedType userType = userTypes.get(name); if (userType == null) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java new file mode 100644 index 00000000000..f88fed1a4b7 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java @@ -0,0 +1,132 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.Version; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import java.time.Duration; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +/** + * Checks that case-sensitive UDT names are properly handled in schema metadata. + * + *

          In Cassandra >= 2.2, whenever a UDT is referenced in a system table (e.g. {@code + * system_schema.columns.type}, it uses the CQL form. This is in contrast to the UDT definition + * itself ({@code system_schema.types.type_name}), which uses the internal form. + * + * @see JAVA-2028 + */ +@Category(ParallelizableTests.class) +public class CaseSensitiveUdtIT { + + private static CcmRule ccmRule = CcmRule.getInstance(); + + private static SessionRule sessionRule = + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + + @Test + public void should_expose_metadata_with_correct_case() { + boolean supportsFunctions = CcmBridge.VERSION.compareTo(Version.V2_2_0) >= 0; + + CqlSession session = sessionRule.session(); + + session.execute("CREATE TYPE \"Address\"(street text)"); + + session.execute("CREATE TABLE user(id uuid PRIMARY KEY, address frozen<\"Address\">)"); + session.execute("CREATE TYPE t(a frozen<\"Address\">)"); + if (supportsFunctions) { + session.execute( + "CREATE FUNCTION eq(a \"Address\") " + + "CALLED ON NULL INPUT " + + "RETURNS \"Address\" " + + "LANGUAGE java " + + "AS $$return a;$$"); + session.execute( + "CREATE FUNCTION left(l \"Address\", r \"Address\") " + + "CALLED ON NULL INPUT " + + "RETURNS \"Address\" " + + "LANGUAGE java " + + "AS $$return l;$$"); + session.execute( + "CREATE AGGREGATE ag(\"Address\") " + + "SFUNC left " + + "STYPE \"Address\" " + + "INITCOND {street: 'foo'};"); + } + + KeyspaceMetadata keyspace = + session + .getMetadata() + .getKeyspace(sessionRule.keyspace()) + .orElseThrow(() -> new AssertionError("Couldn't find rule's keyspace")); + + UserDefinedType addressType = + keyspace + .getUserDefinedType(CqlIdentifier.fromInternal("Address")) + .orElseThrow(() -> new AssertionError("Couldn't find UDT definition")); + + assertThat(keyspace.getTable("user")) + .hasValueSatisfying( + table -> + assertThat(table.getColumn("address")) + .hasValueSatisfying( + column -> assertThat(column.getType()).isEqualTo(addressType))); + + assertThat(keyspace.getUserDefinedType("t")) + .hasValueSatisfying(type -> assertThat(type.getFieldTypes()).containsExactly(addressType)); + + if (supportsFunctions) { + assertThat(keyspace.getFunction("eq", addressType)) + .hasValueSatisfying( + function -> { + assertThat(function.getSignature().getParameterTypes()) + .containsExactly(addressType); + assertThat(function.getReturnType()).isEqualTo(addressType); + }); + + assertThat(keyspace.getAggregate("ag", addressType)) + .hasValueSatisfying( + aggregate -> { + assertThat(aggregate.getSignature().getParameterTypes()) + .containsExactly(addressType); + assertThat(aggregate.getStateType()).isEqualTo(addressType); + assertThat(aggregate.getReturnType()).isEqualTo(addressType); + }); + } + } +} From 8daf99771b0f71c26ffc31dc8b562a92efbc824c Mon Sep 17 00:00:00 2001 From: olim7t Date: Sun, 11 Nov 2018 19:02:59 -0800 Subject: [PATCH 627/742] JAVA-1947: Make schema parsing more lenient and allow missing system_virtual_schema --- changelog/README.md | 1 + .../queries/CassandraSchemaQueries.java | 74 ++++++++++++------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 7a46764401e..e3b222b0dfe 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-1947: Make schema parsing more lenient and allow missing system_virtual_schema - [bug] JAVA-2028: Use CQL form when parsing UDT types in system tables - [improvement] JAVA-1918: Document temporal types - [improvement] JAVA-1914: Optimize use of System.nanoTime in CqlRequestHandlerBase diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java index 47c8beb8e41..8aa4ebe8f83 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.NanoTime; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; @@ -131,35 +132,40 @@ private void executeOnAdminExecutor() { schemaRowsBuilder = new CassandraSchemaRows.Builder(isCassandraV3, refreshFuture, logPrefix); - query(selectKeyspacesQuery() + whereClause, schemaRowsBuilder::withKeyspaces); - query(selectTypesQuery() + whereClause, schemaRowsBuilder::withTypes); - query(selectTablesQuery() + whereClause, schemaRowsBuilder::withTables); - query(selectColumnsQuery() + whereClause, schemaRowsBuilder::withColumns); + query(selectKeyspacesQuery() + whereClause, schemaRowsBuilder::withKeyspaces, true); + query(selectTypesQuery() + whereClause, schemaRowsBuilder::withTypes, true); + query(selectTablesQuery() + whereClause, schemaRowsBuilder::withTables, true); + query(selectColumnsQuery() + whereClause, schemaRowsBuilder::withColumns, true); selectIndexesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withIndexes)); + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withIndexes, true)); selectViewsQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withViews)); + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withViews, true)); selectFunctionsQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions)); + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withFunctions, true)); selectAggregatesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates)); + .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withAggregates, true)); selectVirtualKeyspacesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualKeyspaces)); + .ifPresent( + select -> query(select + whereClause, schemaRowsBuilder::withVirtualKeyspaces, false)); selectVirtualTablesQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualTables)); + .ifPresent( + select -> query(select + whereClause, schemaRowsBuilder::withVirtualTables, false)); selectVirtualColumnsQuery() - .ifPresent(select -> query(select + whereClause, schemaRowsBuilder::withVirtualColumns)); + .ifPresent( + select -> query(select + whereClause, schemaRowsBuilder::withVirtualColumns, false)); } private void query( String queryString, - Function, CassandraSchemaRows.Builder> builderUpdater) { + Function, CassandraSchemaRows.Builder> builderUpdater, + boolean warnIfMissing) { assert adminExecutor.inEventLoop(); pendingQueries += 1; query(queryString) .whenCompleteAsync( - (result, error) -> handleResult(result, error, builderUpdater), adminExecutor); + (result, error) -> handleResult(result, error, builderUpdater, warnIfMissing), + adminExecutor); } @VisibleForTesting @@ -167,34 +173,48 @@ protected CompletionStage query(String query) { return AdminRequestHandler.query(channel, query, timeout, pageSize, logPrefix).start(); } + /** + * @param warnIfMissing whether to log a warning if the queried table does not exist: some DDAC + * versions report release_version > 4, but don't have a system_virtual_schema keyspace, so we + * want to ignore those errors silently. + */ private void handleResult( AdminResult result, Throwable error, - Function, CassandraSchemaRows.Builder> builderUpdater) { - if (schemaRowsFuture.isDone()) { // Another query failed already, ignore - return; - } + Function, CassandraSchemaRows.Builder> builderUpdater, + boolean warnIfMissing) { if (error != null) { - // Any error fails the whole refresh - schemaRowsFuture.completeExceptionally(error); + if (warnIfMissing || !error.getMessage().contains("does not exist")) { + Loggers.warnWithException( + LOG, + "[{}] Error during schema refresh, new metadata might be incomplete", + logPrefix, + error); + } + // Proceed without the results of this query, the rest of the schema refresh will run on a + // "best effort" basis + markQueryComplete(); } else { // Store the rows of the current page in the builder schemaRowsBuilder = builderUpdater.apply(result); - // Move to the next page, or complete if we're the last query if (result.hasNextPage()) { result .nextPage() .whenCompleteAsync( - (nextResult, nextError) -> handleResult(nextResult, nextError, builderUpdater), + (nextResult, nextError) -> + handleResult(nextResult, nextError, builderUpdater, warnIfMissing), adminExecutor); } else { - pendingQueries -= 1; - if (pendingQueries == 0) { - LOG.debug( - "[{}] Schema queries took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); - schemaRowsFuture.complete(schemaRowsBuilder.build()); - } + markQueryComplete(); } } } + + private void markQueryComplete() { + pendingQueries -= 1; + if (pendingQueries == 0) { + LOG.debug("[{}] Schema queries took {}", logPrefix, NanoTime.formatTimeSince(startTimeNs)); + schemaRowsFuture.complete(schemaRowsBuilder.build()); + } + } } From 00480b4d1f4c3b9116ff2bd601095d290175c207 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 12 Nov 2018 10:48:48 -0800 Subject: [PATCH 628/742] JAVA-2029: Handle schema refresh failure after a DDL query --- changelog/README.md | 1 + .../internal/core/cql/CqlRequestHandlerBase.java | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index e3b222b0dfe..cb36d2f53af 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2029: Handle schema refresh failure after a DDL query - [bug] JAVA-1947: Make schema parsing more lenient and allow missing system_virtual_schema - [bug] JAVA-2028: Use CQL form when parsing UDT types in system tables - [improvement] JAVA-1918: Document temporal types diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 252c2abb7ce..26ce9adf29b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -532,7 +532,19 @@ public void onResponse(Frame responseFrame) { .getTopologyMonitor() .checkSchemaAgreement() .thenCombine( - context.getMetadataManager().refreshSchema(schemaChange.keyspace, false, false), + context + .getMetadataManager() + .refreshSchema(schemaChange.keyspace, false, false) + .exceptionally( + error -> { + Loggers.warnWithException( + LOG, + "[{}] Error while refreshing schema after DDL query, " + + "new metadata might be incomplete", + logPrefix, + error); + return null; + }), (schemaInAgreement, metadata) -> schemaInAgreement) .whenComplete( ((schemaInAgreement, error) -> From 9e8816131d6003423f17b3519712bd175469be8e Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 13 Nov 2018 10:37:53 -0800 Subject: [PATCH 629/742] JAVA-1941: Override toString for all schema metadata types --- .../core/metadata/schema/FunctionSignature.java | 15 +++++++++++++++ .../schema/DefaultAggregateMetadata.java | 11 +++++++++++ .../metadata/schema/DefaultColumnMetadata.java | 15 +++++++++++++++ .../schema/DefaultFunctionMetadata.java | 11 +++++++++++ .../metadata/schema/DefaultIndexMetadata.java | 13 +++++++++++++ .../schema/DefaultKeyspaceMetadata.java | 9 +++++++++ .../metadata/schema/DefaultTableMetadata.java | 17 ++++++++++++++++- .../metadata/schema/DefaultViewMetadata.java | 11 +++++++++++ 8 files changed, 101 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java index b0fe9a24d8b..14bb7947a60 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/FunctionSignature.java @@ -95,4 +95,19 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(name, parameterTypes); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(name.asInternal()).append('('); + boolean first = true; + for (DataType type : parameterTypes) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(type.asCql(true, true)); + } + return builder.append(')').toString(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java index 1f05c33f278..c1d62ca0d1d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultAggregateMetadata.java @@ -149,4 +149,15 @@ public int hashCode() { stateFuncSignature, stateType); } + + @Override + public String toString() { + return "DefaultAggregateMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + signature + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java index 692356c837a..aecb40e7329 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultColumnMetadata.java @@ -92,4 +92,19 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(keyspace, parent, name, dataType, isStatic); } + + @Override + public String toString() { + return "DefaultColumnMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + parent.asInternal() + + "." + + name.asInternal() + + " " + + dataType.asCql(true, false) + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java index faea4cedfc8..bfed800046d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultFunctionMetadata.java @@ -122,4 +122,15 @@ public int hashCode() { return Objects.hash( keyspace, signature, parameterNames, body, calledOnNullInput, language, returnType); } + + @Override + public String toString() { + return "DefaultFunctionMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + signature + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java index c88aa7e9f40..3fbaeff34b6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultIndexMetadata.java @@ -105,4 +105,17 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(keyspace, table, name, kind, target, options); } + + @Override + public String toString() { + return "DefaultIndexMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + table.asInternal() + + "." + + name.asInternal() + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java index 86a91f68481..3344f46a746 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java @@ -138,4 +138,13 @@ public int hashCode() { return Objects.hash( name, durableWrites, replication, types, tables, views, functions, aggregates); } + + @Override + public String toString() { + return "DefaultKeyspaceMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + name.asInternal() + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java index 92e8e052370..7e6ed07f1f1 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java @@ -22,7 +22,11 @@ import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import net.jcip.annotations.Immutable; @Immutable @@ -145,4 +149,15 @@ public int hashCode() { return Objects.hash( keyspace, name, id, compactStorage, partitionKey, clusteringColumns, columns, indexes); } + + @Override + public String toString() { + return "DefaultTableMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + name.asInternal() + + ")"; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java index 35df9dae8e6..53d50931546 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultViewMetadata.java @@ -161,4 +161,15 @@ public int hashCode() { columns, options); } + + @Override + public String toString() { + return "DefaultViewMetadata@" + + Integer.toHexString(hashCode()) + + "(" + + keyspace.asInternal() + + "." + + name.asInternal() + + ")"; + } } From 835ae1b18fc4e77d908bee18e17a481b3dba7436 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 14 Nov 2018 13:11:11 -0800 Subject: [PATCH 630/742] Properly close NettyOptions when session init fails --- .../internal/core/session/DefaultSession.java | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index b03c12c41c3..f83d3238fa4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -94,14 +94,30 @@ public static CompletionStage init( private DefaultSession(InternalDriverContext context, Set contactPoints) { LOG.debug("Creating new session {}", context.getSessionName()); - this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); - this.context = context; - this.singleThreaded = new SingleThreaded(context, contactPoints); - this.metadataManager = context.getMetadataManager(); - this.processorRegistry = context.getRequestProcessorRegistry(); - this.poolManager = context.getPoolManager(); this.logPrefix = context.getSessionName(); - this.metricUpdater = context.getMetricsFactory().getSessionUpdater(); + this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); + try { + this.context = context; + this.singleThreaded = new SingleThreaded(context, contactPoints); + this.metadataManager = context.getMetadataManager(); + this.processorRegistry = context.getRequestProcessorRegistry(); + this.poolManager = context.getPoolManager(); + this.metricUpdater = context.getMetricsFactory().getSessionUpdater(); + } catch (Throwable t) { + // Rethrow but make sure we release any resources allocated by Netty. At this stage there are + // no scheduled tasks on the event loops so getNow() won't block. + try { + context.getNettyOptions().onClose().getNow(); + } catch (Throwable suppressed) { + Loggers.warnWithException( + LOG, + "[{}] Error while closing NettyOptions " + + "(suppressed because we're already handling an init failure)", + logPrefix, + suppressed); + } + throw t; + } } private CompletionStage init(CqlIdentifier keyspace) { @@ -288,8 +304,22 @@ private void init(CqlIdentifier keyspace) { context.getSslHandlerFactory(); context.getTimestampGenerator(); } catch (Throwable error) { - initFuture.completeExceptionally(error); RunOrSchedule.on(adminExecutor, this::closePolicies); + context + .getNettyOptions() + .onClose() + .addListener( + f -> { + if (!f.isSuccess()) { + Loggers.warnWithException( + LOG, + "[{}] Error while closing NettyOptions " + + "(suppressed because we're already handling an init failure)", + logPrefix, + f.cause()); + } + initFuture.completeExceptionally(error); + }); return; } From e024586eebe8967946b39dbea6671774d99002ad Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Tue, 13 Nov 2018 10:24:23 -0600 Subject: [PATCH 631/742] JAVA-2014: Schedule timeouts on a separate Timer Motivation: Scheduling request timeouts and speculative executions on the main I/O event loop seems to lower overall I/O throughput under load. Moving those events off of the main I/O event loop will allow the driver to achieve higher overall throughput. Modifications: Add a Netty HashedWheelTimer to the NettyOptions and use that Timer for scheduling request timeouts and speculative executions. Also, add configuration parameters for controlling the Timer tick duration (resolution) and wheel size. Result: In Cassandra stress tests, offloading the timeouts and speculative executions onto a separate timer (with the default settings) yields about a 10% increase in I/O throughput. --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 3 + .../core/context/DefaultNettyOptions.java | 22 +++ .../internal/core/context/NettyOptions.java | 9 ++ .../core/cql/CqlPrepareHandlerBase.java | 28 ++-- .../core/cql/CqlRequestHandlerBase.java | 41 +++--- core/src/main/resources/reference.conf | 24 +++ ...equestHandlerSpeculativeExecutionTest.java | 99 ++++++------- .../core/cql/CqlRequestHandlerTest.java | 14 +- .../core/cql/RequestHandlerTestHarness.java | 19 ++- .../core/util/concurrent/CapturingTimer.java | 137 ++++++++++++++++++ 11 files changed, 298 insertions(+), 99 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CapturingTimer.java diff --git a/changelog/README.md b/changelog/README.md index cb36d2f53af..6edde26a0b6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2014: Schedule timeouts on a separate Timer - [bug] JAVA-2029: Handle schema refresh failure after a DDL query - [bug] JAVA-1947: Make schema parsing more lenient and allow missing system_virtual_schema - [bug] JAVA-2028: Use CQL form when parsing UDT types in system tables diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 86ce1d79afc..793131d5627 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -168,6 +168,9 @@ public enum DefaultDriverOption implements DriverOption { COALESCER_INTERVAL("advanced.coalescer.reschedule-interval"), RESOLVE_CONTACT_POINTS("advanced.resolve-contact-points"), + + NETTY_TIMER_TICK_DURATION("advanced.netty.timer.tick-duration"), + NETTY_TIMER_TICKS_PER_WHEEL("advanced.netty.timer.ticks-per-wheel"), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 228dfbf8bc6..72a411a7f9d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -26,6 +26,8 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; @@ -45,6 +47,7 @@ public class DefaultNettyOptions implements NettyOptions { private final int adminShutdownQuietPeriod; private final int adminShutdownTimeout; private final TimeUnit adminShutdownUnit; + private final Timer timer; public DefaultNettyOptions(InternalDriverContext context) { DriverExecutionProfile config = context.getConfig().getDefaultProfile(); @@ -74,6 +77,18 @@ public DefaultNettyOptions(InternalDriverContext context) { .setNameFormat(context.getSessionName() + "-admin-%d") .build(); this.adminEventLoopGroup = new DefaultEventLoopGroup(adminGroupSize, adminThreadFactory); + // setup the Timer + ThreadFactory timerThreadFactory = + new ThreadFactoryBuilder() + .setThreadFactory(safeFactory) + .setNameFormat(context.getSessionName() + "-timer-%d") + .build(); + timer = + new HashedWheelTimer( + timerThreadFactory, + config.getDuration(DefaultDriverOption.NETTY_TIMER_TICK_DURATION).toNanos(), + TimeUnit.NANOSECONDS, + config.getInt(DefaultDriverOption.NETTY_TIMER_TICKS_PER_WHEEL)); } @Override @@ -117,6 +132,13 @@ public Future onClose() { ioShutdownQuietPeriod, ioShutdownTimeout, ioShutdownUnit)); DefaultPromise closeFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); combiner.finish(closeFuture); + // stop the timer as well + timer.stop(); return closeFuture; } + + @Override + public synchronized Timer getTimer() { + return timer; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java index 1caf7ffee9c..12f8506883a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/NettyOptions.java @@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; +import io.netty.util.Timer; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; @@ -80,4 +81,12 @@ public interface NettyOptions { * groups. */ Future onClose(); + + /** + * The Timer on which non-I/O events should be scheduled. This must always return the same + * instance. This timer should be used for things like request timeout events and scheduling + * speculative executions. Under high load, scheduling these non-I/O events on a separate, lower + * resolution timer will allow for higher overall I/O throughput. + */ + Timer getTimer(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java index fcd83937de3..6fcee8672e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java @@ -51,10 +51,10 @@ import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.result.Prepared; import edu.umd.cs.findbugs.annotations.NonNull; -import io.netty.util.concurrent.EventExecutor; +import io.netty.util.Timeout; +import io.netty.util.Timer; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.ScheduledFuture; import java.nio.ByteBuffer; import java.time.Duration; import java.util.AbstractMap; @@ -88,9 +88,9 @@ public abstract class CqlPrepareHandlerBase implements Throttled { private final Queue queryPlan; protected final CompletableFuture result; private final Message message; - private final EventExecutor scheduler; + private final Timer timer; private final Duration timeout; - private final ScheduledFuture timeoutFuture; + private final Timeout scheduledTimeout; private final RetryPolicy retryPolicy; private final RequestThrottler throttler; private final Boolean prepareOnAllNodes; @@ -144,13 +144,13 @@ protected CqlPrepareHandlerBase( } this.message = new Prepare(request.getQuery(), (keyspace == null) ? null : keyspace.asInternal()); - this.scheduler = context.getNettyOptions().ioEventLoopGroup().next(); + this.timer = context.getNettyOptions().getTimer(); this.timeout = request.getTimeout() != null ? request.getTimeout() : executionProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); - this.timeoutFuture = scheduleTimeout(timeout); + this.scheduledTimeout = scheduleTimeout(timeout); this.prepareOnAllNodes = executionProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES); this.throttler = context.getRequestThrottler(); @@ -171,16 +171,16 @@ public void onThrottleReady(boolean wasDelayed) { sendRequest(null, 0); } - private ScheduledFuture scheduleTimeout(Duration timeout) { - if (timeout.toNanos() > 0) { - return scheduler.schedule( - () -> { - setFinalError(new DriverTimeoutException("Query timed out after " + timeout)); + private Timeout scheduleTimeout(Duration timeoutDuration) { + if (timeoutDuration.toNanos() > 0) { + return this.timer.newTimeout( + (Timeout timeout1) -> { + setFinalError(new DriverTimeoutException("Query timed out after " + timeoutDuration)); if (initialCallback != null) { initialCallback.cancel(); } }, - timeout.toNanos(), + timeoutDuration.toNanos(), TimeUnit.NANOSECONDS); } else { return null; @@ -188,8 +188,8 @@ private ScheduledFuture scheduleTimeout(Duration timeout) { } private void cancelTimeout() { - if (this.timeoutFuture != null) { - this.timeoutFuture.cancel(false); + if (this.scheduledTimeout != null) { + this.scheduledTimeout.cancel(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 26ce9adf29b..8821256658a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -70,10 +70,10 @@ import com.datastax.oss.protocol.internal.util.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import io.netty.handler.codec.EncoderException; -import io.netty.util.concurrent.EventExecutor; +import io.netty.util.Timeout; +import io.netty.util.Timer; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.ScheduledFuture; import java.nio.ByteBuffer; import java.time.Duration; import java.util.AbstractMap; @@ -106,7 +106,7 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final boolean isIdempotent; protected final CompletableFuture result; private final Message message; - private final EventExecutor scheduler; + private final Timer timer; /** * How many speculative executions are currently running (including the initial execution). We * track this in order to know when to fail the request if all executions have reached the end of @@ -121,8 +121,8 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final AtomicInteger startedSpeculativeExecutionsCount; private final Duration timeout; - private final ScheduledFuture timeoutFuture; - private final List> scheduledExecutions; + final Timeout scheduledTimeout; + final List scheduledExecutions; private final List inFlightCallbacks; private final RetryPolicy retryPolicy; private final SpeculativeExecutionPolicy speculativeExecutionPolicy; @@ -179,13 +179,13 @@ protected CqlRequestHandlerBase( return null; }); this.message = Conversions.toMessage(statement, executionProfile, context); - this.scheduler = context.getNettyOptions().ioEventLoopGroup().next(); + this.timer = context.getNettyOptions().getTimer(); this.timeout = statement.getTimeout() != null ? statement.getTimeout() : executionProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT); - this.timeoutFuture = scheduleTimeout(timeout); + this.scheduledTimeout = scheduleTimeout(timeout); this.activeExecutionsCount = new AtomicInteger(1); this.startedSpeculativeExecutionsCount = new AtomicInteger(0); @@ -214,13 +214,14 @@ public void onThrottleReady(boolean wasDelayed) { sendRequest(null, 0, 0, true); } - private ScheduledFuture scheduleTimeout(Duration timeout) { - if (timeout.toNanos() > 0) { - return scheduler.schedule( - () -> - setFinalError( - new DriverTimeoutException("Query timed out after " + timeout), null, -1), - timeout.toNanos(), + private Timeout scheduleTimeout(Duration timeoutDuration) { + if (timeoutDuration.toNanos() > 0) { + return this.timer.newTimeout( + (Timeout timeout1) -> { + setFinalError( + new DriverTimeoutException("Query timed out after " + timeoutDuration), null, -1); + }, + timeoutDuration.toNanos(), TimeUnit.NANOSECONDS); } else { return null; @@ -282,12 +283,12 @@ private void recordError(Node node, Throwable error) { } private void cancelScheduledTasks() { - if (this.timeoutFuture != null) { - this.timeoutFuture.cancel(false); + if (this.scheduledTimeout != null) { + this.scheduledTimeout.cancel(); } if (scheduledExecutions != null) { - for (ScheduledFuture future : scheduledExecutions) { - future.cancel(false); + for (Timeout scheduledExecution : scheduledExecutions) { + scheduledExecution.cancel(); } } for (NodeResponseCallback callback : inFlightCallbacks) { @@ -477,8 +478,8 @@ public void operationComplete(Future future) throws Exception { nextExecution, nextDelay); scheduledExecutions.add( - scheduler.schedule( - () -> { + timer.newTimeout( + (Timeout timeout1) -> { if (!result.isDone()) { LOG.trace( "[{}] Starting speculative execution {}", diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 0c29fddec3f..bfa6e644b3f 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -1369,6 +1369,30 @@ datastax-java-driver { shutdown {quiet-period = 2, timeout = 15, unit = SECONDS} } + # The timer used for scheduling request timeouts and speculative executions + timer { + # The timer tick duration + # This is the how frequent the timer should wake-up to check for timed out tasks. + # Lower resolution (i.e. longer durations) will leave more CPU cycles for running I/O + # operations at the cost of precision of exactly when a request timeout will expire or a + # speculative execution will run. Higher resolution (i.e. shorter durations) will result in + # more precise request timeouts and speculative execution scheduling, but at the cost of CPU + # cycles taken from I/O operations, which could lead to lower overall I/O throughput. + # + # Required: yes + # Modifiable at runtime: no + # Overridable in a profile: no + tick-duration = 100 milliseconds + + # Number of ticks in a Timer wheel. The underlying implementation uses Netty's + # HashedWheelTimer, which uses hashes to arrange the timeouts. This effectively controls the + # size of the timer wheel. + # + # Required: yes + # Modifiable at runtime: no + # Overridable in a profile: no + ticks-per-wheel = 512 + } } # The component that coalesces writes on the connections. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index 3f4d3a98e1d..f07fad685a3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -30,7 +30,7 @@ import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.servererrors.BootstrappingException; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; -import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.driver.internal.core.util.concurrent.CapturingTimer.CapturedTimeout; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.Error; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -59,8 +59,8 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( node1Behavior.verifyWrite(); - harness.nextScheduledTask(); // Discard the timeout task - assertThat(harness.nextScheduledTask()).isNull(); + assertThat(harness.nextScheduledTimeout()).isNotNull(); // Discard the timeout task + assertThat(harness.nextScheduledTimeout()).isNull(); Mockito.verifyNoMoreInteractions(speculativeExecutionPolicy); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); @@ -70,7 +70,7 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( @Test @UseDataProvider("idempotentConfig") public void should_schedule_speculative_executions( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -101,26 +101,24 @@ public void should_schedule_speculative_executions( node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); Mockito.verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); - ScheduledTaskCapturingEventLoop.CapturedTask secondExecutionTask = - harness.nextScheduledTask(); - assertThat(secondExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + CapturedTimeout speculativeExecution2 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution2.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(secondExecutionDelay); Mockito.verifyNoMoreInteractions(nodeMetricUpdater2); - secondExecutionTask.run(); + speculativeExecution2.task().run(speculativeExecution2); Mockito.verify(nodeMetricUpdater2) .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); @@ -128,7 +126,7 @@ public void should_schedule_speculative_executions( node3Behavior.setWriteSuccess(); // No more scheduled tasks since the policy returns 0 on the third call. - assertThat(harness.nextScheduledTask()).isNull(); + assertThat(harness.nextScheduledTimeout()).isNull(); // Note that we don't need to complete any response, the test is just about checking that // executions are started. @@ -138,7 +136,7 @@ public void should_schedule_speculative_executions( @Test @UseDataProvider("idempotentConfig") public void should_not_start_execution_if_result_complete( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -153,18 +151,17 @@ public void should_not_start_execution_if_result_complete( any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); - CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") - .handle(); + CqlRequestAsyncHandler requestHandler = + new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test"); + CompletionStage resultSetFuture = requestHandler.handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); - ScheduledTaskCapturingEventLoop.CapturedTask timeoutFuture = harness.nextScheduledTask(); + harness.nextScheduledTimeout(); // Discard the timeout task // Check that the first execution was scheduled but don't run it yet - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); // Complete the request from the initial execution @@ -177,11 +174,11 @@ public void should_not_start_execution_if_result_complete( // Travis CI build). When that happens, the speculative execution is not recorded yet when // cancelScheduledTasks runs. // So check the timeout future instead, since it's cancelled in the same method. - assertThat(timeoutFuture.isCancelled()).isTrue(); + assertThat(requestHandler.scheduledTimeout.isCancelled()).isTrue(); // The fact that we missed the speculative execution is not a problem; even if it starts, it // will eventually find out that the result is already complete and cancel itself: - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); node2Behavior.verifyNoWrite(); Mockito.verify(nodeMetricUpdater1) @@ -216,7 +213,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task assertThatStage(resultSetFuture) .isFailed(error -> assertThat(error).isInstanceOf(NoNodeAvailableException.class)); @@ -226,7 +223,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement @Test @UseDataProvider("idempotentConfig") public void should_fail_if_no_more_nodes_and_initial_execution_is_last( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -250,16 +247,16 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( node1Behavior.setWriteSuccess(); // do not simulate a response from node1 yet - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task // Run the next scheduled task to start the speculative execution. node2 will reply with a // BOOTSTRAPPING error, causing a RETRY_NEXT; but the query plan is now empty so the // speculative execution stops. - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + // next scheduled timeout should be the first speculative execution. Get it and run it. + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); // node1 now replies with the same response, that triggers a RETRY_NEXT node1Behavior.setResponseSuccess( @@ -281,7 +278,7 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( @Test @UseDataProvider("idempotentConfig") public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -303,14 +300,13 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( node1Behavior.setWriteSuccess(); // do not simulate a response from node1 yet - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task - // Run the next scheduled task to start the speculative execution. - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + // next scheduled timeout should be the first speculative execution. Get it and run it. + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); // node1 now replies with a BOOTSTRAPPING error that triggers a RETRY_NEXT // but the query plan is empty so the initial execution stops @@ -337,7 +333,7 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( @Test @UseDataProvider("idempotentConfig") public void should_retry_in_speculative_executions( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -361,13 +357,14 @@ public void should_retry_in_speculative_executions( // do not simulate a response from node1. The request will stay hanging for the rest of this // test - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + // next scheduled timeout should be the first speculative execution. Get it and run it. + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); + node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); @@ -386,7 +383,7 @@ public void should_retry_in_speculative_executions( @Test @UseDataProvider("idempotentConfig") public void should_stop_retrying_other_executions_if_result_complete( - boolean defaultIdempotence, SimpleStatement statement) { + boolean defaultIdempotence, SimpleStatement statement) throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder().withDefaultIdempotence(defaultIdempotence); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); @@ -401,20 +398,20 @@ public void should_stop_retrying_other_executions_if_result_complete( speculativeExecutionPolicy.nextExecution( any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); - CompletionStage resultSetFuture = new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); - harness.nextScheduledTask(); // Discard the timeout task + harness.nextScheduledTimeout(); // Discard the timeout task - ScheduledTaskCapturingEventLoop.CapturedTask firstExecutionTask = - harness.nextScheduledTask(); - assertThat(firstExecutionTask.getInitialDelay(TimeUnit.MILLISECONDS)) + // next scheduled timeout should be the first speculative execution. Get it and run it. + CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); + assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); - firstExecutionTask.run(); + speculativeExecution1.task().run(speculativeExecution1); + node2Behavior.verifyWrite(); node2Behavior.setWriteSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 60e31e8fbe4..14525f54dfb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -29,7 +29,7 @@ import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.internal.core.session.RepreparePayload; -import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.driver.internal.core.util.concurrent.CapturingTimer.CapturedTimeout; import com.datastax.oss.protocol.internal.request.Prepare; import com.datastax.oss.protocol.internal.response.error.Unprepared; import com.datastax.oss.protocol.internal.response.result.Prepared; @@ -103,7 +103,7 @@ public void should_fail_if_no_node_available() { } @Test - public void should_time_out_if_first_node_takes_too_long_to_respond() { + public void should_time_out_if_first_node_takes_too_long_to_respond() throws Exception { RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); node1Behavior.setWriteSuccess(); @@ -119,16 +119,16 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() { .handle(); // First scheduled task is the timeout, run it before node1 has responded - ScheduledTaskCapturingEventLoop.CapturedTask scheduledTask = harness.nextScheduledTask(); - Duration configuredTimeout = + CapturedTimeout requestTimeout = harness.nextScheduledTimeout(); + Duration configuredTimeoutDuration = harness .getContext() .getConfig() .getDefaultProfile() .getDuration(DefaultDriverOption.REQUEST_TIMEOUT); - assertThat(scheduledTask.getInitialDelay(TimeUnit.NANOSECONDS)) - .isEqualTo(configuredTimeout.toNanos()); - scheduledTask.run(); + assertThat(requestTimeout.getDelay(TimeUnit.NANOSECONDS)) + .isEqualTo(configuredTimeoutDuration.toNanos()); + requestTimeout.task().run(requestTimeout); assertThatStage(resultSetFuture) .isFailed(t -> assertThat(t).isInstanceOf(DriverTimeoutException.class)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index f2423682ae3..ecd0dd2ead5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -47,9 +47,11 @@ import com.datastax.oss.driver.internal.core.session.throttling.PassThroughRequestThrottler; import com.datastax.oss.driver.internal.core.tracker.NoopRequestTracker; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; -import com.datastax.oss.driver.internal.core.util.concurrent.ScheduledTaskCapturingEventLoop; +import com.datastax.oss.driver.internal.core.util.concurrent.CapturingTimer; +import com.datastax.oss.driver.internal.core.util.concurrent.CapturingTimer.CapturedTimeout; import com.datastax.oss.protocol.internal.Frame; import io.netty.channel.EventLoopGroup; +import io.netty.util.TimerTask; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; @@ -74,7 +76,7 @@ public static Builder builder() { return new Builder(); } - private final ScheduledTaskCapturingEventLoop schedulingEventLoop; + private final CapturingTimer timer = new CapturingTimer(); private final Map pools; @Mock protected InternalDriverContext context; @@ -93,8 +95,7 @@ public static Builder builder() { protected RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); - this.schedulingEventLoop = new ScheduledTaskCapturingEventLoop(eventLoopGroup); - Mockito.when(eventLoopGroup.next()).thenReturn(schedulingEventLoop); + Mockito.when(nettyOptions.getTimer()).thenReturn(timer); Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); @@ -192,13 +193,17 @@ public DriverChannel getChannel(Node node) { * Returns the next task that was scheduled on the request handler's admin executor. The test must * run it manually. */ - public ScheduledTaskCapturingEventLoop.CapturedTask nextScheduledTask() { - return schedulingEventLoop.nextTask(); + public CapturedTimeout nextScheduledTimeout() { + return timer.getNextTimeout(); + } + + public void runNextTask() { + TimerTask task = timer.getNextTimeout().task(); } @Override public void close() { - schedulingEventLoop.shutdownGracefully().getNow(); + timer.stop(); } public static class Builder { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CapturingTimer.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CapturingTimer.java new file mode 100644 index 00000000000..3b2cda52b35 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/CapturingTimer.java @@ -0,0 +1,137 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.datastax.oss.driver.internal.core.util.concurrent; + +import static org.assertj.core.api.Assertions.fail; + +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Implementation of Netty's {@link io.netty.util.Timer Timer} interface to capture scheduled {@link + * io.netty.util.Timeout Timeouts} instead of running them, so they can be run manually in tests. + */ +public class CapturingTimer implements Timer { + + private final ArrayBlockingQueue timeoutQueue = new ArrayBlockingQueue<>(16); + + @Override + public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { + // delay and unit are not needed as the Timeout's TimerTask will be run manually + CapturedTimeout timeout = new CapturedTimeout(task, this, delay, unit); + // add the timeout to the queue + timeoutQueue.add(timeout); + return timeout; + } + + /** + * Retrieves the next scheduled Timeout. In tests, this will usually be a request timeout or a + * speculative execution. Tests will need be able to predict the ordering as it is not easy to + * tell from the returned Timeout itself. + */ + public CapturedTimeout getNextTimeout() { + try { + return timeoutQueue.poll(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException ie) { + fail("Unexpected interruption", ie); + throw new AssertionError(); + } + } + + @Override + public Set stop() { + if (timeoutQueue.isEmpty()) { + return Collections.emptySet(); + } + Set timeoutsRemaining = new HashSet<>(timeoutQueue.size()); + for (Timeout t : timeoutQueue) { + if (t != null) { + t.cancel(); + timeoutsRemaining.add(t); + } + } + return timeoutsRemaining; + } + + /** + * Implementation of Netty's {@link io.netty.util.Timeout Timeout} interface. It is just a simple + * class that keeps track of the {@link io.netty.util.TimerTask TimerTask} and the {@link + * io.netty.util.Timer Timer} implementation that should only be used in tests. The intended use + * is to call the {@link io.netty.util.TimerTask#run(io.netty.util.Timeout) run()} method on the + * TimerTask when you want to execute the task (so you don't have to depend on a real timer). + * + *

          Example: + * + *

          {@code
          +   * // get the next timeout from the timer
          +   * Timeout t = timer.getNextTimeout();
          +   * // run the TimerTask associated with the timeout
          +   * t.task.run(t);
          +   * }
          + */ + public static class CapturedTimeout implements Timeout { + + private final TimerTask task; + private final CapturingTimer timer; + private final long delay; + private final TimeUnit unit; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + private CapturedTimeout(TimerTask task, CapturingTimer timer, long delay, TimeUnit unit) { + this.task = task; + this.timer = timer; + this.delay = delay; + this.unit = unit; + } + + @Override + public Timer timer() { + return timer; + } + + @Override + public TimerTask task() { + return task; + } + + public long getDelay(TimeUnit targetUnit) { + return targetUnit.convert(delay, unit); + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public boolean isCancelled() { + return cancelled.get(); + } + + @Override + public boolean cancel() { + return cancelled.compareAndSet(false, true); + } + } +} From f345c5d456516817d4c021572dc80e759072d602 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 21 Nov 2018 09:26:37 +0100 Subject: [PATCH 632/742] Make constructors public for built-in implementations of RequestHandler --- .../oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java | 2 +- .../oss/driver/internal/core/cql/CqlPrepareSyncHandler.java | 2 +- .../oss/driver/internal/core/cql/CqlRequestAsyncHandler.java | 2 +- .../oss/driver/internal/core/cql/CqlRequestSyncHandler.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java index 7b08019c03b..bf7f3dbce22 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java @@ -30,7 +30,7 @@ public class CqlPrepareAsyncHandler extends CqlPrepareHandlerBase implements RequestHandler> { - CqlPrepareAsyncHandler( + public CqlPrepareAsyncHandler( PrepareRequest request, ConcurrentMap preparedStatementsCache, DefaultSession session, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java index a08c5d9d31f..d7190edc8e6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java @@ -30,7 +30,7 @@ public class CqlPrepareSyncHandler extends CqlPrepareHandlerBase implements RequestHandler { - CqlPrepareSyncHandler( + public CqlPrepareSyncHandler( PrepareRequest request, ConcurrentMap preparedStatementsCache, DefaultSession session, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java index 84e0c0ae9f5..50c226f18a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java @@ -27,7 +27,7 @@ public class CqlRequestAsyncHandler extends CqlRequestHandlerBase implements RequestHandler, CompletionStage> { - CqlRequestAsyncHandler( + public CqlRequestAsyncHandler( Statement statement, DefaultSession session, InternalDriverContext context, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java index ea5fd2d3f76..5d305abf1ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java @@ -29,7 +29,7 @@ public class CqlRequestSyncHandler extends CqlRequestHandlerBase implements RequestHandler, ResultSet> { - CqlRequestSyncHandler( + public CqlRequestSyncHandler( Statement statement, DefaultSession session, InternalDriverContext context, From 017053e29036b516898bdcd4fc30f0723d1aec0c Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 21 Nov 2018 11:16:04 +0100 Subject: [PATCH 633/742] JAVA-2037: Fix NPE when preparing statement with no bound variables --- changelog/README.md | 1 + .../driver/internal/core/cql/Conversions.java | 10 ++- ...dationIT.java => PreparedStatementIT.java} | 87 ++++++++++++++----- 3 files changed, 74 insertions(+), 24 deletions(-) rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/{PreparedStatementInvalidationIT.java => PreparedStatementIT.java} (76%) diff --git a/changelog/README.md b/changelog/README.md index 6edde26a0b6..f268c36bd09 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2037: Fix NPE when preparing statement with no bound variables - [improvement] JAVA-2014: Schedule timeouts on a separate Timer - [bug] JAVA-2029: Handle schema refresh failure after a DDL query - [bug] JAVA-1947: Make schema parsing more lenient and allow missing system_virtual_schema diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index e8c36eb7868..662b21d27f7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -362,7 +362,7 @@ public static DefaultPreparedStatement toPreparedStatement( ByteBuffer.wrap(response.preparedQueryId).asReadOnlyBuffer(), request.getQuery(), toColumnDefinitions(response.variablesMetadata, context), - Ints.asList(response.variablesMetadata.pkIndices), + asList(response.variablesMetadata.pkIndices), (response.resultMetadataId == null) ? null : ByteBuffer.wrap(response.resultMetadataId).asReadOnlyBuffer(), @@ -396,6 +396,14 @@ public static ColumnDefinitions toColumnDefinitions( return DefaultColumnDefinitions.valueOf(ImmutableList.copyOf(values)); } + public static List asList(int[] pkIndices) { + if (pkIndices == null || pkIndices.length == 0) { + return Collections.emptyList(); + } else { + return Ints.asList(pkIndices); + } + } + public static CoordinatorException toThrowable( Node node, Error errorMessage, InternalDriverContext context) { switch (errorMessage.code) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java similarity index 76% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index a51525b6307..2f8dfb3b99d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementInvalidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -51,11 +51,11 @@ * */ @Category(ParallelizableTests.class) -public class PreparedStatementInvalidationIT { +public class PreparedStatementIT { - private CcmRule ccmRule = CcmRule.getInstance(); + private final CcmRule ccmRule = CcmRule.getInstance(); - private SessionRule sessionRule = + private final SessionRule sessionRule = SessionRule.builder(ccmRule) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -72,11 +72,11 @@ public class PreparedStatementInvalidationIT { public void setupSchema() { for (String query : ImmutableList.of( - "CREATE TABLE prepared_statement_invalidation_test (a int PRIMARY KEY, b int, c int)", - "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (1, 1, 1)", - "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (2, 2, 2)", - "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (3, 3, 3)", - "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (4, 4, 4)")) { + "CREATE TABLE prepared_statement_test (a int PRIMARY KEY, b int, c int)", + "INSERT INTO prepared_statement_test (a, b, c) VALUES (1, 1, 1)", + "INSERT INTO prepared_statement_test (a, b, c) VALUES (2, 2, 2)", + "INSERT INTO prepared_statement_test (a, b, c) VALUES (3, 3, 3)", + "INSERT INTO prepared_statement_test (a, b, c) VALUES (4, 4, 4)")) { sessionRule .session() .execute( @@ -86,18 +86,61 @@ public void setupSchema() { } } + @Test + public void should_have_empty_result_definitions_for_insert_query_without_bound_variable() { + try (CqlSession session = SessionUtils.newSession(ccmRule, sessionRule.keyspace())) { + PreparedStatement prepared = + session.prepare("INSERT INTO prepared_statement_test (a, b, c) VALUES (1, 1, 1)"); + assertThat(prepared.getVariableDefinitions()).isEmpty(); + assertThat(prepared.getPartitionKeyIndices()).isEmpty(); + assertThat(prepared.getResultSetDefinitions()).isEmpty(); + } + } + + @Test + public void should_have_non_empty_result_definitions_for_insert_query_with_bound_variable() { + try (CqlSession session = SessionUtils.newSession(ccmRule, sessionRule.keyspace())) { + PreparedStatement prepared = + session.prepare("INSERT INTO prepared_statement_test (a, b, c) VALUES (?, ?, ?)"); + assertThat(prepared.getVariableDefinitions()).hasSize(3); + assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + assertThat(prepared.getResultSetDefinitions()).isEmpty(); + } + } + + @Test + public void should_have_empty_variable_definitions_for_select_query_without_bound_variable() { + try (CqlSession session = SessionUtils.newSession(ccmRule, sessionRule.keyspace())) { + PreparedStatement prepared = + session.prepare("SELECT a,b,c FROM prepared_statement_test WHERE a = 1"); + assertThat(prepared.getVariableDefinitions()).isEmpty(); + assertThat(prepared.getPartitionKeyIndices()).isEmpty(); + assertThat(prepared.getResultSetDefinitions()).hasSize(3); + } + } + + @Test + public void should_have_non_empty_variable_definitions_for_select_query_with_bound_variable() { + try (CqlSession session = SessionUtils.newSession(ccmRule, sessionRule.keyspace())) { + PreparedStatement prepared = + session.prepare("SELECT a,b,c FROM prepared_statement_test WHERE a = ?"); + assertThat(prepared.getVariableDefinitions()).hasSize(1); + assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + assertThat(prepared.getResultSetDefinitions()).hasSize(3); + } + } + @Test @CassandraRequirement(min = "4.0") public void should_update_metadata_when_schema_changed_across_executions() { // Given CqlSession session = sessionRule.session(); - PreparedStatement ps = - session.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); + PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_test WHERE a = ?"); ByteBuffer idBefore = ps.getResultMetadataId(); // When session.execute( - SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") + SimpleStatement.builder("ALTER TABLE prepared_statement_test ADD d int") .withExecutionProfile(sessionRule.slowProfile()) .build()); BoundStatement bs = ps.bind(1); @@ -121,7 +164,7 @@ public void should_update_metadata_when_schema_changed_across_executions() { public void should_update_metadata_when_schema_changed_across_pages() { // Given CqlSession session = sessionRule.session(); - PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_invalidation_test"); + PreparedStatement ps = session.prepare("SELECT * FROM prepared_statement_test"); ByteBuffer idBefore = ps.getResultMetadataId(); assertThat(ps.getResultSetDefinitions()).hasSize(3); @@ -141,7 +184,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { // When session.execute( - SimpleStatement.builder("ALTER TABLE prepared_statement_invalidation_test ADD d int") + SimpleStatement.builder("ALTER TABLE prepared_statement_test ADD d int") .withExecutionProfile(sessionRule.slowProfile()) .build()); @@ -168,10 +211,8 @@ public void should_update_metadata_when_schema_changed_across_sessions() { CqlSession session1 = sessionRule.session(); CqlSession session2 = SessionUtils.newSession(ccmRule, sessionRule.keyspace()); - PreparedStatement ps1 = - session1.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); - PreparedStatement ps2 = - session2.prepare("SELECT * FROM prepared_statement_invalidation_test WHERE a = ?"); + PreparedStatement ps1 = session1.prepare("SELECT * FROM prepared_statement_test WHERE a = ?"); + PreparedStatement ps2 = session2.prepare("SELECT * FROM prepared_statement_test WHERE a = ?"); ByteBuffer id1a = ps1.getResultMetadataId(); ByteBuffer id2a = ps2.getResultMetadataId(); @@ -185,7 +226,7 @@ public void should_update_metadata_when_schema_changed_across_sessions() { assertThat(rows2.getColumnDefinitions().contains("d")).isFalse(); // When - session1.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + session1.execute("ALTER TABLE prepared_statement_test ADD d int"); rows1 = session1.execute(ps1.bind(1)); rows2 = session2.execute(ps2.bind(1)); @@ -215,10 +256,10 @@ public void should_update_metadata_when_schema_changed_across_sessions() { public void should_fail_to_reprepare_if_query_becomes_invalid() { // Given CqlSession session = sessionRule.session(); - session.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + session.execute("ALTER TABLE prepared_statement_test ADD d int"); PreparedStatement ps = - session.prepare("SELECT a, b, c, d FROM prepared_statement_invalidation_test WHERE a = ?"); - session.execute("ALTER TABLE prepared_statement_invalidation_test DROP d"); + session.prepare("SELECT a, b, c, d FROM prepared_statement_test WHERE a = ?"); + session.execute("ALTER TABLE prepared_statement_test DROP d"); thrown.expect(InvalidQueryException.class); thrown.expectMessage("Undefined column name d"); @@ -250,7 +291,7 @@ private void should_not_store_metadata_for_conditional_updates(CqlSession sessio // Given PreparedStatement ps = session.prepare( - "INSERT INTO prepared_statement_invalidation_test (a, b, c) VALUES (?, ?, ?) IF NOT EXISTS"); + "INSERT INTO prepared_statement_test (a, b, c) VALUES (?, ?, ?) IF NOT EXISTS"); // Never store metadata in the prepared statement for conditional updates, since the result set // can change @@ -287,7 +328,7 @@ private void should_not_store_metadata_for_conditional_updates(CqlSession sessio assertThat(Bytes.toHexString(ps.getResultMetadataId())).isEqualTo(Bytes.toHexString(idBefore)); // When - session.execute("ALTER TABLE prepared_statement_invalidation_test ADD d int"); + session.execute("ALTER TABLE prepared_statement_test ADD d int"); rs = session.execute(ps.bind(5, 5, 5)); // Then From 21828c04f6fbce6ea8353626b8427de9e118e195 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 21 Nov 2018 14:16:03 +0100 Subject: [PATCH 634/742] Fix failing tests when protocol version < 4 --- .../api/core/cql/PreparedStatementIT.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index 2f8dfb3b99d..162e22787ac 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; @@ -103,7 +104,13 @@ public void should_have_non_empty_result_definitions_for_insert_query_with_bound PreparedStatement prepared = session.prepare("INSERT INTO prepared_statement_test (a, b, c) VALUES (?, ?, ?)"); assertThat(prepared.getVariableDefinitions()).hasSize(3); - assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + if (sessionRule.session().getContext().getProtocolVersion().getCode() + >= DefaultProtocolVersion.V4.getCode()) { + // partition key indices were introduced in V4 + assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + } else { + assertThat(prepared.getPartitionKeyIndices()).isEmpty(); + } assertThat(prepared.getResultSetDefinitions()).isEmpty(); } } @@ -125,7 +132,13 @@ public void should_have_non_empty_variable_definitions_for_select_query_with_bou PreparedStatement prepared = session.prepare("SELECT a,b,c FROM prepared_statement_test WHERE a = ?"); assertThat(prepared.getVariableDefinitions()).hasSize(1); - assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + if (sessionRule.session().getContext().getProtocolVersion().getCode() + >= DefaultProtocolVersion.V4.getCode()) { + // partition key indices were introduced in V4 + assertThat(prepared.getPartitionKeyIndices()).hasSize(1); + } else { + assertThat(prepared.getPartitionKeyIndices()).isEmpty(); + } assertThat(prepared.getResultSetDefinitions()).hasSize(3); } } From 9ae52e46e9451e03dfeb3935897ad2d4fa99ab0a Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 30 Nov 2018 09:25:55 -0800 Subject: [PATCH 635/742] Delay query plan computation until throttling ready Motivation: If a request is throttled, there will be a delay between the handler's construction and the time it starts sending requests. So the handler might operate on a query plan that is slightly out of date. Even though the delay should be short, and handlers are resilient to stale plans, it would be better to get the query plan as late as possible. Modifications: Build the query plan in onThrottleReady instead of the constructor. It can't be a final field anymore, so pass it around as a method parameter instead. Result: The query plan is built right before the first call to sendRequest. --- .../core/cql/CqlRequestHandlerBase.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index 8821256658a..d4a7f67553e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -101,7 +101,6 @@ public abstract class CqlRequestHandlerBase implements Throttled { private final DefaultSession session; private final CqlIdentifier keyspace; private final InternalDriverContext context; - private final Queue queryPlan; @NonNull private final DriverExecutionProfile executionProfile; private final boolean isIdempotent; protected final CompletableFuture result; @@ -149,15 +148,6 @@ protected CqlRequestHandlerBase( this.keyspace = session.getKeyspace().orElse(null); this.context = context; this.executionProfile = Conversions.resolveExecutionProfile(statement, context); - if (this.statement.getNode() != null) { - this.queryPlan = new QueryPlan(this.statement.getNode()); - - } else { - this.queryPlan = - context - .getLoadBalancingPolicyWrapper() - .newQueryPlan(statement, executionProfile.getName(), session); - } this.retryPolicy = context.getRetryPolicy(executionProfile.getName()); this.speculativeExecutionPolicy = context.getSpeculativeExecutionPolicy(executionProfile.getName()); @@ -211,7 +201,13 @@ public void onThrottleReady(boolean wasDelayed) { System.nanoTime() - startTimeNanos, TimeUnit.NANOSECONDS); } - sendRequest(null, 0, 0, true); + Queue queryPlan = + this.statement.getNode() != null + ? new QueryPlan(this.statement.getNode()) + : context + .getLoadBalancingPolicyWrapper() + .newQueryPlan(statement, executionProfile.getName(), session); + sendRequest(null, queryPlan, 0, 0, true); } private Timeout scheduleTimeout(Duration timeoutDuration) { @@ -231,7 +227,8 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { /** * Sends the request to the next available node. * - * @param node if not null, it will be attempted first before the rest of the query plan. + * @param retriedNode if not null, it will be attempted first before the rest of the query plan. + * @param queryPlan the list of nodes to try (shared with all other executions) * @param currentExecutionIndex 0 for the initial execution, 1 for the first speculative one, etc. * @param retryCount the number of times that the retry policy was invoked for this execution * already (note that some internal retries don't go through the policy, and therefore don't @@ -239,10 +236,15 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { * @param scheduleNextExecution whether to schedule the next speculative execution */ private void sendRequest( - Node node, int currentExecutionIndex, int retryCount, boolean scheduleNextExecution) { + Node retriedNode, + Queue queryPlan, + int currentExecutionIndex, + int retryCount, + boolean scheduleNextExecution) { if (result.isDone()) { return; } + Node node = retriedNode; DriverChannel channel = null; if (node == null || (channel = session.getChannel(node, logPrefix)) == null) { while (!result.isDone() && (node = queryPlan.poll()) != null) { @@ -261,7 +263,13 @@ private void sendRequest( } else { NodeResponseCallback nodeResponseCallback = new NodeResponseCallback( - node, channel, currentExecutionIndex, retryCount, scheduleNextExecution, logPrefix); + node, + queryPlan, + channel, + currentExecutionIndex, + retryCount, + scheduleNextExecution, + logPrefix); channel .write(message, statement.isTracing(), statement.getCustomPayload(), nodeResponseCallback) .addListener(nodeResponseCallback); @@ -410,6 +418,7 @@ private class NodeResponseCallback private final long nodeStartTimeNanos = System.nanoTime(); private final Node node; + private final Queue queryPlan; private final DriverChannel channel; // The identifier of the current execution (0 for the initial execution, 1 for the first // speculative execution, etc.) @@ -422,12 +431,14 @@ private class NodeResponseCallback private NodeResponseCallback( Node node, + Queue queryPlan, DriverChannel channel, int execution, int retryCount, boolean scheduleNextExecution, String logPrefix) { this.node = node; + this.queryPlan = queryPlan; this.channel = channel; this.execution = execution; this.retryCount = retryCount; @@ -455,7 +466,8 @@ public void operationComplete(Future future) throws Exception { ((DefaultNode) node) .getMetricUpdater() .incrementCounter(DefaultNodeMetric.UNSENT_REQUESTS, executionProfile.getName()); - sendRequest(null, execution, retryCount, scheduleNextExecution); // try next node + sendRequest( + null, queryPlan, execution, retryCount, scheduleNextExecution); // try next node } } else { LOG.trace("[{}] Request sent on {}", logPrefix, channel); @@ -492,7 +504,7 @@ public void operationComplete(Future future) throws Exception { .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, executionProfile.getName()); - sendRequest(null, nextExecution, 0, true); + sendRequest(null, queryPlan, nextExecution, 0, true); } }, nextDelay, @@ -625,10 +637,10 @@ private void processErrorResponse(Error errorMessage) { recordError(node, exception); trackNodeError(node, exception, NANOTIME_NOT_MEASURED_YET); LOG.trace("[{}] Reprepare failed, trying next node", logPrefix); - sendRequest(null, execution, retryCount, false); + sendRequest(null, queryPlan, execution, retryCount, false); } else { LOG.trace("[{}] Reprepare sucessful, retrying", logPrefix); - sendRequest(node, execution, retryCount, false); + sendRequest(node, queryPlan, execution, retryCount, false); } return null; }); @@ -640,7 +652,7 @@ private void processErrorResponse(Error errorMessage) { LOG.trace("[{}] {} is bootstrapping, trying next node", logPrefix, node); recordError(node, error); trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); - sendRequest(null, execution, retryCount, false); + sendRequest(null, queryPlan, execution, retryCount, false); } else if (error instanceof QueryValidationException || error instanceof FunctionFailureException || error instanceof ProtocolError) { @@ -721,12 +733,12 @@ private void processRetryDecision(RetryDecision decision, Throwable error) { case RETRY_SAME: recordError(node, error); trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); - sendRequest(node, execution, retryCount + 1, false); + sendRequest(node, queryPlan, execution, retryCount + 1, false); break; case RETRY_NEXT: recordError(node, error); trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); - sendRequest(null, execution, retryCount + 1, false); + sendRequest(null, queryPlan, execution, retryCount + 1, false); break; case RETHROW: trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET); From 505f4e6a69e20e3f1db2c77baf2ae943b049ea5a Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Dec 2018 15:40:50 -0800 Subject: [PATCH 636/742] Revisit DefaultLBP tests to use the public constructor --- .../DefaultLoadBalancingPolicy.java | 45 +++++++------------ .../DefaultLoadBalancingPolicyEventsTest.java | 11 ++++- .../DefaultLoadBalancingPolicyInitTest.java | 22 +++++---- ...faultLoadBalancingPolicyQueryPlanTest.java | 10 ++--- .../DefaultLoadBalancingPolicyTestBase.java | 20 +++++++-- 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 218c5737116..ecefa46ff1d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -86,38 +86,30 @@ public class DefaultLoadBalancingPolicy implements LoadBalancingPolicy { @VisibleForTesting volatile String localDc; public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull String profileName) { - this( - context.getSessionName() + "|" + profileName, - getLocalDcFromConfig(context, profileName), - getFilterFromConfig(context, profileName), - context, - profileName.equals(DriverExecutionProfile.DEFAULT_NAME)); - } + InternalDriverContext internalContext = (InternalDriverContext) context; - @VisibleForTesting - DefaultLoadBalancingPolicy( - @NonNull String logPrefix, - @Nullable String localDcFromConfig, - @NonNull Predicate filterFromConfig, - @NonNull DriverContext context, - boolean isDefaultPolicy) { - this.logPrefix = logPrefix; - this.metadataManager = ((InternalDriverContext) context).getMetadataManager(); + this.logPrefix = context.getSessionName() + "|" + profileName; + DriverExecutionProfile config = context.getConfig().getProfile(profileName); + String localDcFromConfig = + config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); if (localDcFromConfig != null) { LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcFromConfig); this.localDc = localDcFromConfig; } - this.isDefaultPolicy = isDefaultPolicy; + this.isDefaultPolicy = profileName.equals(DriverExecutionProfile.DEFAULT_NAME); + + this.metadataManager = internalContext.getMetadataManager(); + Predicate filterFromConfig = getFilterFromConfig(internalContext, profileName); this.filter = node -> { - String localDc = this.localDc; - if (localDc != null && !localDc.equals(node.getDatacenter())) { + String localDc1 = this.localDc; + if (localDc1 != null && !localDc1.equals(node.getDatacenter())) { LOG.debug( "[{}] Ignoring {} because it doesn't belong to the local DC {}", logPrefix, node, - localDc); + localDc1); return false; } else if (!filterFromConfig.test(node)) { LOG.debug( @@ -304,20 +296,15 @@ public void close() { // nothing to do } - private static String getLocalDcFromConfig(DriverContext context, String profileName) { - DriverExecutionProfile config = context.getConfig().getProfile(profileName); - return config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); - } - @SuppressWarnings("unchecked") - private static Predicate getFilterFromConfig(DriverContext context, String profileName) { - Predicate filterFromBuilder = - ((InternalDriverContext) context).getNodeFilter(profileName); + private static Predicate getFilterFromConfig( + InternalDriverContext context, String profileName) { + Predicate filterFromBuilder = context.getNodeFilter(profileName); return (filterFromBuilder != null) ? filterFromBuilder : (Predicate) Reflection.buildFromConfig( - (InternalDriverContext) context, + context, profileName, DefaultDriverOption.LOAD_BALANCING_FILTER_CLASS, Predicate.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index 8ca7357abdc..cdf174638f8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -20,18 +20,24 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import java.util.function.Predicate; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPolicyTestBase { + @Mock private Predicate filter; + private DefaultLoadBalancingPolicy policy; @Before @@ -39,7 +45,10 @@ public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPo public void setup() { super.setup(); - policy = new DefaultLoadBalancingPolicy("test", "dc1", filter, context, true); + Mockito.when(filter.test(Mockito.any(Node.class))).thenReturn(true); + Mockito.when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(filter); + + policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); policy.init( ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), distanceReporter, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index bf23938e05f..d9445dc5d10 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -21,6 +21,8 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -35,7 +37,7 @@ public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPoli public void should_infer_local_dc_if_no_explicit_contact_points() { // Given DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", null, filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( @@ -50,8 +52,12 @@ public void should_infer_local_dc_if_no_explicit_contact_points() { @Test public void should_require_local_dc_if_explicit_contact_points() { // Given + Mockito.when( + defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + .thenReturn(null); DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", null, filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); + thrown.expect(IllegalStateException.class); thrown.expectMessage("You provided explicit contact points, the local DC must be specified"); @@ -65,7 +71,7 @@ public void should_warn_if_contact_points_not_in_local_dc() { Mockito.when(node2.getDatacenter()).thenReturn("dc2"); Mockito.when(node3.getDatacenter()).thenReturn("dc3"); DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", "dc1", filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( @@ -89,7 +95,7 @@ public void should_warn_if_contact_points_not_in_local_dc() { public void should_include_nodes_from_local_dc() { // Given DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", "dc1", filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( @@ -110,7 +116,7 @@ public void should_ignore_nodes_from_remote_dcs() { Mockito.when(node2.getDatacenter()).thenReturn("dc2"); Mockito.when(node3.getDatacenter()).thenReturn("dc3"); DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", "dc1", filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( @@ -128,11 +134,11 @@ public void should_ignore_nodes_from_remote_dcs() { @Test public void should_ignore_nodes_excluded_by_filter() { // Given - Mockito.when(filter.test(node2)).thenReturn(false); - Mockito.when(filter.test(node3)).thenReturn(false); + Mockito.when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)) + .thenReturn(node -> node.equals(node1)); DefaultLoadBalancingPolicy policy = - new DefaultLoadBalancingPolicy("test", "dc1", filter, context, true); + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 7d2d1f7b345..937c4839d38 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -24,9 +24,9 @@ import static org.mockito.Mockito.times; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Metadata; -import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; @@ -37,7 +37,6 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.Optional; -import java.util.function.Predicate; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -67,7 +66,7 @@ public void setup() { // Use a subclass to disable shuffling, we just spy to make sure that the shuffling method was // called (makes tests easier) - policy = Mockito.spy(new NonShufflingPolicy("dc1", filter, context)); + policy = Mockito.spy(new NonShufflingPolicy(context, DriverExecutionProfile.DEFAULT_NAME)); policy.init( ImmutableMap.of( ADDRESS1, node1, @@ -185,9 +184,8 @@ public void should_prioritize_and_shuffle_replicas() { } static class NonShufflingPolicy extends DefaultLoadBalancingPolicy { - NonShufflingPolicy( - String localDcFromConfig, Predicate filterFromConfig, DriverContext context) { - super("test", localDcFromConfig, filterFromConfig, context, true); + NonShufflingPolicy(DriverContext context, String profileName) { + super(context, profileName); } @Override diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java index 0f314c0f1d9..e539dae5b69 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -15,17 +15,20 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import java.net.InetSocketAddress; -import java.util.function.Predicate; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -55,7 +58,8 @@ public abstract class DefaultLoadBalancingPolicyTestBase { @Mock protected Node node4; @Mock protected Node node5; @Mock protected InternalDriverContext context; - @Mock protected Predicate filter; + @Mock protected DriverConfig config; + @Mock protected DriverExecutionProfile defaultProfile; @Mock protected LoadBalancingPolicy.DistanceReporter distanceReporter; @Mock protected Appender appender; @@ -65,7 +69,15 @@ public abstract class DefaultLoadBalancingPolicyTestBase { @Before public void setup() { - Mockito.when(filter.test(any(Node.class))).thenReturn(true); + Mockito.when(context.getSessionName()).thenReturn("test"); + Mockito.when(context.getConfig()).thenReturn(config); + Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); + + Mockito.when( + defaultProfile.getString( + eq(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER), nullable(String.class))) + .thenReturn("dc1"); + logger = (Logger) LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); logger.addAppender(appender); From 72fee2275e77b17e24afadaabe1ba85412de22bf Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 4 Dec 2018 11:39:36 -0800 Subject: [PATCH 637/742] JAVA-2049: Add shorthand method to SessionBuilder to specify local DC --- changelog/README.md | 1 + core/revapi.json | 13 +++++++ .../api/core/session/SessionBuilder.java | 25 ++++++++++++++ .../core/context/DefaultDriverContext.java | 9 +++++ .../core/context/InternalDriverContext.java | 8 +++++ .../DefaultLoadBalancingPolicy.java | 26 +++++++++----- core/src/main/resources/reference.conf | 3 ++ .../context/StartupOptionsBuilderTest.java | 2 ++ .../DefaultLoadBalancingPolicyInitTest.java | 34 +++++++++++++++++++ .../DefaultLoadBalancingPolicyTestBase.java | 8 ++--- .../guava/api/GuavaSessionBuilder.java | 2 ++ .../guava/internal/GuavaDriverContext.java | 2 ++ manual/core/load_balancing/README.md | 10 ++++++ 13 files changed, 130 insertions(+), 13 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index f268c36bd09..9b627c2e879 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2049: Add shorthand method to SessionBuilder to specify local DC - [bug] JAVA-2037: Fix NPE when preparing statement with no bound variables - [improvement] JAVA-2014: Schedule timeouts on a separate Timer - [bug] JAVA-2029: Handle schema refresh failure after a DDL query diff --git a/core/revapi.json b/core/revapi.json index c2cf05235fa..441c785f478 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -652,6 +652,19 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", "elementKind": "method", "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" + }, + { + "code": "java.method.numberOfParametersChanged", + "old": "method com.datastax.oss.driver.api.core.context.DriverContext com.datastax.oss.driver.api.core.session.SessionBuilder::buildContext(com.datastax.oss.driver.api.core.config.DriverConfigLoader, java.util.List>, com.datastax.oss.driver.api.core.metadata.NodeStateListener, com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener, com.datastax.oss.driver.api.core.tracker.RequestTracker, java.util.Map>, java.lang.ClassLoader)", + "new": "method com.datastax.oss.driver.api.core.context.DriverContext com.datastax.oss.driver.api.core.session.SessionBuilder::buildContext(com.datastax.oss.driver.api.core.config.DriverConfigLoader, java.util.List>, com.datastax.oss.driver.api.core.metadata.NodeStateListener, com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener, com.datastax.oss.driver.api.core.tracker.RequestTracker, java.util.Map, java.util.Map>, java.lang.ClassLoader)", + "package": "com.datastax.oss.driver.api.core.session", + "classQualifiedName": "com.datastax.oss.driver.api.core.session.SessionBuilder", + "classSimpleName": "SessionBuilder", + "methodName": "buildContext", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2049: Add shorthand method to SessionBuilder to specify local DC" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index babaa845b3b..b3d88bcc986 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -68,6 +68,7 @@ public abstract class SessionBuilder { private NodeStateListener nodeStateListener; private SchemaChangeListener schemaChangeListener; protected RequestTracker requestTracker; + private ImmutableMap.Builder localDatacenters = ImmutableMap.builder(); private ImmutableMap.Builder> nodeFilters = ImmutableMap.builder(); protected CqlIdentifier keyspace; private ClassLoader classLoader = null; @@ -194,6 +195,27 @@ public SelfT withRequestTracker(@Nullable RequestTracker requestTracker) { return self; } + /** + * Specifies the datacenter that is considered "local" by the load balancing policy. + * + *

          This is a programmatic alternative to the configuration option {@code + * basic.load-balancing-policy.local-datacenter}. If this method is used, it takes precedence and + * overrides the configuration. + * + *

          Note that this setting may or may not be relevant depending on the load balancing policy + * implementation in use. The driver's built-in {@code DefaultLoadBalancingPolicy} relies on it; + * if you use a third-party implementation, refer to their documentation. + */ + public SelfT withLocalDatacenter(@NonNull String profileName, @NonNull String localDatacenter) { + this.localDatacenters.put(profileName, localDatacenter); + return self; + } + + /** Alias to {@link #withLocalDatacenter(String, String)} for the default profile. */ + public SelfT withLocalDatacenter(@NonNull String localDatacenter) { + return withLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME, localDatacenter); + } + /** * Adds a custom filter to include/exclude nodes for a particular execution profile. This assumes * that you're also using a dedicated load balancing policy for that profile. @@ -313,6 +335,7 @@ protected final CompletionStage buildDefaultSessionAsync() { nodeStateListener, schemaChangeListener, requestTracker, + localDatacenters.build(), nodeFilters.build(), classLoader), contactPoints, @@ -335,6 +358,7 @@ protected DriverContext buildContext( NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, RequestTracker requestTracker, + Map localDatacenters, Map> nodeFilters, ClassLoader classLoader) { return new DefaultDriverContext( @@ -343,6 +367,7 @@ protected DriverContext buildContext( nodeStateListener, schemaChangeListener, requestTracker, + localDatacenters, nodeFilters, classLoader); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 86f4fecd199..87cd2fce7bf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -192,6 +192,7 @@ public class DefaultDriverContext implements InternalDriverContext { private final NodeStateListener nodeStateListenerFromBuilder; private final SchemaChangeListener schemaChangeListenerFromBuilder; private final RequestTracker requestTrackerFromBuilder; + private final Map localDatacentersFromBuilder; private final Map> nodeFiltersFromBuilder; private final ClassLoader classLoader; @@ -201,6 +202,7 @@ public DefaultDriverContext( NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, RequestTracker requestTracker, + Map localDatacenters, Map> nodeFilters, ClassLoader classLoader) { this.config = configLoader.getInitialConfig(); @@ -211,6 +213,7 @@ public DefaultDriverContext( } else { this.sessionName = "s" + SESSION_NAME_COUNTER.getAndIncrement(); } + this.localDatacentersFromBuilder = localDatacenters; this.codecRegistry = buildCodecRegistry(this.sessionName, typeCodecs); this.nodeStateListenerFromBuilder = nodeStateListener; this.nodeStateListenerRef = @@ -719,6 +722,12 @@ public RequestTracker getRequestTracker() { return requestTrackerRef.get(); } + @Nullable + @Override + public String getLocalDatacenter(@NonNull String profileName) { + return localDatacentersFromBuilder.get(profileName); + } + @Nullable @Override public Predicate getNodeFilter(String profileName) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index ee926a253fa..6c130a9bf15 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -114,6 +114,14 @@ public interface InternalDriverContext extends DriverContext { @NonNull MetricsFactory getMetricsFactory(); + /** + * The value that was passed to {@link SessionBuilder#withLocalDatacenter(String,String)} for this + * particular profile. If it was specified through the configuration instead, this method will + * return {@code null}. + */ + @Nullable + String getLocalDatacenter(@NonNull String profileName); + /** * This is the filter from {@link SessionBuilder#withNodeFilter(String, Predicate)}. If the filter * for this profile was specified through the configuration instead, this method will return diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index ecefa46ff1d..4c528423228 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -90,12 +90,7 @@ public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull Strin this.logPrefix = context.getSessionName() + "|" + profileName; DriverExecutionProfile config = context.getConfig().getProfile(profileName); - String localDcFromConfig = - config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); - if (localDcFromConfig != null) { - LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcFromConfig); - this.localDc = localDcFromConfig; - } + this.localDc = getLocalDcFromConfig(internalContext, profileName, config); this.isDefaultPolicy = profileName.equals(DriverExecutionProfile.DEFAULT_NAME); this.metadataManager = internalContext.getMetadataManager(); @@ -296,9 +291,24 @@ public void close() { // nothing to do } + private String getLocalDcFromConfig( + InternalDriverContext internalContext, + @NonNull String profileName, + DriverExecutionProfile config) { + String localDc = internalContext.getLocalDatacenter(profileName); + if (localDc != null) { + LOG.debug("[{}] Local DC set from builder: {}", logPrefix, localDc); + } else { + localDc = config.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); + if (localDc != null) { + LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDc); + } + } + return localDc; + } + @SuppressWarnings("unchecked") - private static Predicate getFilterFromConfig( - InternalDriverContext context, String profileName) { + private Predicate getFilterFromConfig(InternalDriverContext context, String profileName) { Predicate filterFromBuilder = context.getNodeFilter(profileName); return (filterFromBuilder != null) ? filterFromBuilder diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index bfa6e644b3f..b8b9e449205 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -164,6 +164,9 @@ datastax-java-driver { # builder), you must define the local datacenter explicitly, and initialization will fail if # this property is absent. In addition, all contact points should be from this datacenter; # warnings will be logged for nodes that are from a different one. + # + # This can also be specified programmatically with SessionBuilder.withLocalDatacenter. If both + # are specified, the programmatic value takes precedence. // local-datacenter = datacenter1 # A custom filter to include/exclude nodes. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java index 3868212179a..ad1f12f2fb7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java @@ -50,6 +50,7 @@ public class StartupOptionsBuilderTest { @Mock private NodeStateListener nodeStateListener; @Mock private SchemaChangeListener schemaChangeListener; @Mock private RequestTracker requestTracker; + private Map localDatacenters = Maps.newHashMap(); private Map> nodeFilters = Maps.newHashMap(); @Mock private ClassLoader classLoader; @Mock private DriverConfig driverConfig; @@ -70,6 +71,7 @@ private void buildDriverContext() { nodeStateListener, schemaChangeListener, requestTracker, + localDatacenters, nodeFilters, classLoader); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index d9445dc5d10..d0d5da09fcf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.filter; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -33,9 +34,42 @@ public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPolicyTestBase { + @Test + public void should_use_local_dc_if_provided_via_config() { + // Given + Mockito.when( + defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + .thenReturn("dc1"); + + // When + DefaultLoadBalancingPolicy policy = + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); + + // Then + assertThat(policy.localDc).isEqualTo("dc1"); + } + + @Test + public void should_use_local_dc_if_provided_via_context() { + // Given + Mockito.when(context.getLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn("dc1"); + + // When + DefaultLoadBalancingPolicy policy = + new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); + + // Then + assertThat(policy.localDc).isEqualTo("dc1"); + Mockito.verify(defaultProfile, never()) + .getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); + } + @Test public void should_infer_local_dc_if_no_explicit_contact_points() { // Given + Mockito.when( + defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + .thenReturn(null); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java index e539dae5b69..edd0da9c6ca 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -15,9 +15,6 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; - import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; @@ -74,8 +71,7 @@ public void setup() { Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); Mockito.when( - defaultProfile.getString( - eq(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER), nullable(String.class))) + defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn("dc1"); logger = (Logger) LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); @@ -84,6 +80,8 @@ public void setup() { for (Node node : ImmutableList.of(node1, node2, node3, node4, node5)) { Mockito.when(node.getDatacenter()).thenReturn("dc1"); } + + Mockito.when(context.getLocalDatacenter(Mockito.anyString())).thenReturn(null); } @After diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java index 41f722d3795..1fe041fcfe7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/api/GuavaSessionBuilder.java @@ -40,6 +40,7 @@ protected DriverContext buildContext( NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, RequestTracker requestTracker, + Map localDatacenters, Map> nodeFilters, ClassLoader classLoader) { return new GuavaDriverContext( @@ -48,6 +49,7 @@ protected DriverContext buildContext( nodeStateListener, schemaChangeListener, requestTracker, + localDatacenters, nodeFilters, classLoader); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index 40c2366f82a..fe06c11012e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -50,6 +50,7 @@ public GuavaDriverContext( NodeStateListener nodeStateListener, SchemaChangeListener schemaChangeListener, RequestTracker requestTracker, + Map localDatacenters, Map> nodeFilters, ClassLoader classLoader) { super( @@ -58,6 +59,7 @@ public GuavaDriverContext( nodeStateListener, schemaChangeListener, requestTracker, + localDatacenters, nodeFilters, classLoader); } diff --git a/manual/core/load_balancing/README.md b/manual/core/load_balancing/README.md index 9a25c33e454..2e8c9aef10a 100644 --- a/manual/core/load_balancing/README.md +++ b/manual/core/load_balancing/README.md @@ -105,6 +105,16 @@ datastax-java-driver.basic.load-balancing-policy { This option is required, except when you didn't specify any contact points and let the driver default to 127.0.0.1:9042 (this is mostly for convenience during the development phase). +Note that the local datacenter can also be provided programmatically when building the session: + +```java +CqlSession session = CqlSession.builder() + .withLocalDatacenter("datacenter1") + .build(); +``` + +If both are provided, the programmatic value takes precedence. + #### Token-aware The default policy is **token-aware** by default: requests will be routed in priority to the From 0037cd355b98782dce2169d593d35bbf7d9680b0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Dec 2018 15:26:10 +0100 Subject: [PATCH 638/742] Add missing NonNull annotation --- .../oss/driver/internal/core/context/DefaultDriverContext.java | 2 +- .../oss/driver/internal/core/context/InternalDriverContext.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 87cd2fce7bf..b930ec2cb58 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -730,7 +730,7 @@ public String getLocalDatacenter(@NonNull String profileName) { @Nullable @Override - public Predicate getNodeFilter(String profileName) { + public Predicate getNodeFilter(@NonNull String profileName) { return nodeFiltersFromBuilder.get(profileName); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 6c130a9bf15..d7ff7588ef9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -128,7 +128,7 @@ public interface InternalDriverContext extends DriverContext { * {@code null}. */ @Nullable - Predicate getNodeFilter(String profileName); + Predicate getNodeFilter(@NonNull String profileName); /** * The {@link ClassLoader} to use to reflectively load class names defined in configuration. If From 6794c38674aa3b4209a8d622f016406c71d69462 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 13 Dec 2018 13:47:25 -0600 Subject: [PATCH 639/742] JAVA-2057: Do not create pool when SUGGEST_UP topology event received (#1149) Motivation: There is a small window where a NEW_NODE event may be sent over the control connection while a pool is initializing. This is more likely to happen in testing, where a cluster is brought up and the driver connects to it immediately. In this case, the driver would erroneously create another pool, which would create additional connections. The first pool created between the currently initializing one and the new one being created would be untracked with its connections remanining open even after the Session is closed. Modifications: Change onTopologyEvent to no longer call createOrReconnectPool, instead only call reconnectNow() if there is an already established pool. initializing pool future present in the pending map. Result: SUGGEST_UP events now only trigger reconnect on existing pool, and will no longer create a new pool. --- changelog/README.md | 1 + .../oss/driver/internal/core/session/PoolManager.java | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 9b627c2e879..37850dedaf6 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2057: Do not create pool when SUGGEST\_UP topology event received - [improvement] JAVA-2049: Add shorthand method to SessionBuilder to specify local DC - [bug] JAVA-2037: Fix NPE when preparing statement with no bound variables - [improvement] JAVA-2014: Schedule timeouts on a separate Timer diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java index 2d8fde37133..2595d3b1c0f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -340,7 +340,10 @@ private void onTopologyEvent(TopologyEvent event) { if (node.getDistance() != NodeDistance.IGNORED) { LOG.debug( "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); - createOrReconnectPool(node); + ChannelPool pool = pools.get(node); + if (pool != null) { + pool.reconnectNow(); + } } } } From f09258dd98a278498b9009a39631dc742da44311 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Thu, 13 Dec 2018 14:09:55 -0600 Subject: [PATCH 640/742] JAVA-2055: Fix intermittent test failures (#1147) * NodeTargetingIT: Resolve Node to query based on address instead of position * NodeTargetingIT: Wait for node to be marked down before proceeding * MetricsIT: Check up to 5 seconds for CQL_REQUESTS metric to be fully incremented * SpeculativeExecutionIT: Increase base specex delay * Multiple: Stabilize tests by introducing QueryCounter * SpeculativeExecutionIT: Set lbp when building session with profile * NodeTargetingIT: Handle race case where node is down but connection is not removed yet * NodeStateIT: Skip initial up events if fired * NodeStateIT: Wait up to 500ms for event --- .../core/loadbalancing/NodeTargetingIT.java | 42 +++-- .../api/core/metadata/NodeMetadataIT.java | 76 ++++----- .../driver/api/core/metadata/NodeStateIT.java | 21 ++- .../driver/api/core/metrics/MetricsIT.java | 30 ++-- .../core/retry/PerProfileRetryPolicyIT.java | 23 +-- .../core/specex/SpeculativeExecutionIT.java | 82 +++------ .../core/retry/DefaultRetryPolicyIT.java | 95 ++++------- .../testinfra/simulacron/QueryCounter.java | 157 ++++++++++++++++++ .../testinfra/simulacron/SimulacronRule.java | 10 +- .../api/testinfra/utils/ConditionChecker.java | 14 +- 10 files changed, 343 insertions(+), 207 deletions(-) create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/QueryCounter.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java index a0f53cb370a..24e4ac62ce8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java @@ -28,15 +28,17 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.codec.ConsistencyLevel; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import com.datastax.oss.simulacron.server.BoundNode; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -58,15 +60,15 @@ public void clear() { simulacron.cluster().clearLogs(); simulacron.cluster().clearPrimes(true); simulacron.cluster().node(4).stop(); + ConditionChecker.checkThat(() -> getNode(4).getState() == NodeState.DOWN) + .before(5, TimeUnit.SECONDS); } @Test public void should_use_node_on_statement() { - Collection nodeCol = sessionRule.session().getMetadata().getNodes().values(); - List nodes = new ArrayList<>(nodeCol); for (int i = 0; i < 10; i++) { int nodeIndex = i % 3 + 1; - Node node = nodes.get(nodeIndex); + Node node = getNode(nodeIndex); // given a statement with node explicitly set. Statement statement = SimpleStatement.newInstance("select * system.local").setNode(node); @@ -82,30 +84,25 @@ public void should_use_node_on_statement() { @Test public void should_fail_if_node_fails_query() { String query = "mock"; - Collection nodeCol = sessionRule.session().getMetadata().getNodes().values(); - List nodes = new ArrayList<>(nodeCol); simulacron.cluster().node(3).prime(when(query).then(unavailable(ConsistencyLevel.ALL, 1, 0))); // given a statement with a node configured to fail the given query. - Node node1 = nodes.get(3); - Statement statement = SimpleStatement.newInstance(query).setNode(node1); + Node node3 = getNode(3); + Statement statement = SimpleStatement.newInstance(query).setNode(node3); // when statement is executed an error should be raised. try { sessionRule.session().execute(statement); fail("Should have thrown AllNodesFailedException"); } catch (AllNodesFailedException e) { assertThat(e.getErrors().size()).isEqualTo(1); - assertThat(e.getErrors().get(node1)).isInstanceOf(UnavailableException.class); + assertThat(e.getErrors().get(node3)).isInstanceOf(UnavailableException.class); } } @Test public void should_fail_if_node_is_not_connected() { // given a statement with node explicitly set that for which we have no active pool. - - Collection nodeCol = sessionRule.session().getMetadata().getNodes().values(); - List nodes = new ArrayList<>(nodeCol); - Node node4 = nodes.get(4); + Node node4 = getNode(4); Statement statement = SimpleStatement.newInstance("select * system.local").setNode(node4); try { @@ -114,6 +111,21 @@ public void should_fail_if_node_is_not_connected() { fail("Query should have failed"); } catch (NoNodeAvailableException e) { assertThat(e.getErrors()).isEmpty(); + } catch (AllNodesFailedException e) { + // its also possible that the query is tried. This can happen if the node was marked + // down, but not all connections have been closed yet. In this case, just verify that + // the expected host failed. + assertThat(e.getErrors().size()).isEqualTo(1); + assertThat(e.getErrors()).containsOnlyKeys(node4); } } + + private Node getNode(int id) { + BoundNode boundNode = simulacron.cluster().node(id); + assertThat(boundNode).isNotNull(); + InetSocketAddress address = (InetSocketAddress) boundNode.getAddress(); + Node node = sessionRule.session().getMetadata().getNodes().get(address); + assertThat(node).isNotNull(); + return node; + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index f8cca91db49..fefa4edc8a5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; -import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -31,55 +31,49 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; -import org.junit.rules.RuleChain; -import org.junit.rules.TestRule; @Category(ParallelizableTests.class) public class NodeMetadataIT { - private static CcmRule ccmRule = CcmRule.getInstance(); - - private static SessionRule sessionRule = SessionRule.builder(ccmRule).build(); - - @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + @ClassRule public static CcmRule ccmRule = CcmRule.getInstance(); @Test public void should_expose_node_metadata() { - CqlSession session = sessionRule.session(); - Node node = getUniqueNode(session); + try (CqlSession session = SessionUtils.newSession(ccmRule)) { + Node node = getUniqueNode(session); + // Run a few basic checks given what we know about our test environment: + assertThat(node.getConnectAddress()).isNotNull(); + node.getBroadcastAddress() + .ifPresent( + broadcastAddress -> + assertThat(broadcastAddress.getAddress()) + .isEqualTo(node.getConnectAddress().getAddress())); + assertThat(node.getListenAddress().get().getAddress()) + .isEqualTo(node.getConnectAddress().getAddress()); + assertThat(node.getDatacenter()).isEqualTo("dc1"); + assertThat(node.getRack()).isEqualTo("r1"); + if (!CcmBridge.DSE_ENABLEMENT) { + // CcmBridge does not report accurate C* versions for DSE, only approximated values + assertThat(node.getCassandraVersion()).isEqualTo(ccmRule.getCassandraVersion()); + } + assertThat(node.getState()).isSameAs(NodeState.UP); + assertThat(node.getDistance()).isSameAs(NodeDistance.LOCAL); + assertThat(node.getHostId()).isNotNull(); + assertThat(node.getSchemaVersion()).isNotNull(); + long upTime1 = node.getUpSinceMillis(); + assertThat(upTime1).isGreaterThan(-1); - // Run a few basic checks given what we know about our test environment: - assertThat(node.getConnectAddress()).isNotNull(); - node.getBroadcastAddress() - .ifPresent( - broadcastAddress -> - assertThat(broadcastAddress.getAddress()) - .isEqualTo(node.getConnectAddress().getAddress())); - assertThat(node.getListenAddress().get().getAddress()) - .isEqualTo(node.getConnectAddress().getAddress()); - assertThat(node.getDatacenter()).isEqualTo("dc1"); - assertThat(node.getRack()).isEqualTo("r1"); - if (!CcmBridge.DSE_ENABLEMENT) { - // CcmBridge does not report accurate C* versions for DSE, only approximated values - assertThat(node.getCassandraVersion()).isEqualTo(ccmRule.getCassandraVersion()); - } - assertThat(node.getState()).isSameAs(NodeState.UP); - assertThat(node.getDistance()).isSameAs(NodeDistance.LOCAL); - assertThat(node.getHostId()).isNotNull(); - assertThat(node.getSchemaVersion()).isNotNull(); - long upTime1 = node.getUpSinceMillis(); - assertThat(upTime1).isGreaterThan(-1); + // Note: open connections and reconnection status are covered in NodeStateIT - // Note: open connections and reconnection status are covered in NodeStateIT - - // Force the node down and back up to check that upSinceMillis gets updated - EventBus eventBus = ((InternalDriverContext) session.getContext()).getEventBus(); - eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); - ConditionChecker.checkThat(() -> node.getState() == NodeState.FORCED_DOWN).becomesTrue(); - assertThat(node.getUpSinceMillis()).isEqualTo(-1); - eventBus.fire(TopologyEvent.forceUp(node.getConnectAddress())); - ConditionChecker.checkThat(() -> node.getState() == NodeState.UP).becomesTrue(); - assertThat(node.getUpSinceMillis()).isGreaterThan(upTime1); + // Force the node down and back up to check that upSinceMillis gets updated + EventBus eventBus = ((InternalDriverContext) session.getContext()).getEventBus(); + eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); + ConditionChecker.checkThat(() -> node.getState() == NodeState.FORCED_DOWN).becomesTrue(); + assertThat(node.getUpSinceMillis()).isEqualTo(-1); + eventBus.fire(TopologyEvent.forceUp(node.getConnectAddress())); + ConditionChecker.checkThat(() -> node.getState() == NodeState.UP).becomesTrue(); + assertThat(node.getUpSinceMillis()).isGreaterThan(upTime1); + } } private static Node getUniqueNode(CqlSession session) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index a1d82de0b49..aefaeb57f26 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -58,6 +58,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; @@ -112,8 +113,24 @@ public class NodeStateIT { public void setup() { inOrder = Mockito.inOrder(nodeStateListener); + AtomicBoolean nonInitialEvent = new AtomicBoolean(false); driverContext = (InternalDriverContext) sessionRule.session().getContext(); - driverContext.getEventBus().register(NodeStateEvent.class, stateEvents::add); + driverContext + .getEventBus() + .register( + NodeStateEvent.class, + (e) -> { + // Skip transition from unknown to up if we haven't received any other events, + // these may just be the initial events that have typically fired by now, but + // may not have depending on timing. + if (!nonInitialEvent.get() + && e.oldState == NodeState.UNKNOWN + && e.newState == NodeState.UP) { + return; + } + nonInitialEvent.set(true); + stateEvents.add(e); + }); defaultLoadBalancingPolicy = (ConfigurableIgnoresPolicy) @@ -330,7 +347,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() (DefaultNode) session.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); // UP fired a first time as part of the init process - Mockito.verify(localNodeStateListener).onUp(localMetadataNode); + Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode); localSimulacronNode.stop(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java index 927cda77b94..5ac70b54b7b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metrics/MetricsIT.java @@ -25,9 +25,11 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.google.common.collect.Lists; import java.util.Collections; +import java.util.concurrent.TimeUnit; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -50,16 +52,24 @@ public void should_expose_metrics() { session.execute("SELECT release_version FROM system.local"); } - assertThat(session.getMetrics()) - .hasValueSatisfying( - metrics -> - assertThat(metrics.getSessionMetric(DefaultSessionMetric.CQL_REQUESTS)) - .hasValueSatisfying( - cqlRequests -> { - // No need to be very sophisticated, metrics are already covered - // individually in unit tests. - assertThat(cqlRequests.getCount()).isEqualTo(10); - })); + // Should have 10 requests, check within 5 seconds as metric increments after + // caller is notified. + ConditionChecker.checkThat( + () -> { + assertThat(session.getMetrics()) + .hasValueSatisfying( + metrics -> + assertThat( + metrics.getSessionMetric( + DefaultSessionMetric.CQL_REQUESTS)) + .hasValueSatisfying( + cqlRequests -> { + // No need to be very sophisticated, metrics are already + // covered individually in unit tests. + assertThat(cqlRequests.getCount()).isEqualTo(10); + })); + }) + .before(5, TimeUnit.SECONDS); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java index fded5d3c6ba..bcceb36c632 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/retry/PerProfileRetryPolicyIT.java @@ -36,6 +36,7 @@ import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.QueryCounter; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; @@ -84,6 +85,12 @@ public class PerProfileRetryPolicyIT { private static String QUERY_STRING = "select * from foo"; private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); + @SuppressWarnings("deprecation") + private final QueryCounter counter = + QueryCounter.builder(simulacron.cluster()) + .withFilter((l) -> l.getQuery().equals(QUERY_STRING)) + .build(); + @Before public void clear() { simulacron.cluster().clearLogs(); @@ -139,21 +146,7 @@ public void should_use_policy_from_config_when_not_configured_in_request_profile assertThat(errors).hasSize(1); assertThat(errors.get(0).getValue()).isInstanceOf(UnavailableException.class); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - } - - private void assertQueryCount(int node, int expected) { - assertThat( - simulacron - .cluster() - .node(node) - .getLogs() - .getQueryLogs() - .stream() - .filter(l -> l.getQuery().equals(QUERY_STRING))) - .as("Expected query count to be %d for node %d", expected, node) - .hasSize(expected); + counter.assertNodeCounts(1, 1); } // A policy that simply rethrows always. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java index 022895b9c7d..4f91633915e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/specex/SpeculativeExecutionIT.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.QueryCounter; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; @@ -49,7 +50,7 @@ public class SpeculativeExecutionIT { // Note: it looks like shorter delays cause precision issues with Netty timers - private static final long SPECULATIVE_DELAY = 500; + private static final long SPECULATIVE_DELAY = 1000; private static String QUERY_STRING = "select * from foo"; private static final SimpleStatement QUERY = SimpleStatement.newInstance(QUERY_STRING); @@ -58,9 +59,14 @@ public class SpeculativeExecutionIT { public static @ClassRule SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(3)); + @SuppressWarnings("deprecation") + private final QueryCounter counter = + QueryCounter.builder(simulacron.cluster()) + .withFilter((l) -> l.getQuery().equals(QUERY_STRING)) + .build(); + @Before public void clear() { - simulacron.cluster().clearLogs(); simulacron.cluster().clearPrimes(true); } @@ -75,9 +81,7 @@ public void should_not_start_speculative_executions_if_not_idempotent() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); - assertQueryCount(0, 1); - assertQueryCount(1, 0); - assertQueryCount(2, 0); + counter.assertNodeCounts(1, 0, 0); } } @@ -93,9 +97,7 @@ public void should_complete_from_first_speculative_execution_if_faster() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 0); + counter.assertNodeCounts(1, 1, 0); } } @@ -112,9 +114,7 @@ public void should_complete_from_initial_execution_if_speculative_is_started_but assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 0); + counter.assertNodeCounts(1, 1, 0); } } @@ -132,9 +132,7 @@ public void should_complete_from_second_speculative_execution_if_faster() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -150,9 +148,7 @@ public void should_retry_within_initial_execution() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 0); + counter.assertNodeCounts(1, 1, 0); } } @@ -170,9 +166,7 @@ public void should_retry_within_speculative_execution() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(1); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -191,9 +185,7 @@ public void should_wait_for_last_execution_to_complete() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -211,9 +203,7 @@ public void should_fail_if_all_executions_reach_end_of_query_plan() { try (CqlSession session = buildSession(3, SPECULATIVE_DELAY)) { session.execute(QUERY); } finally { - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -232,9 +222,7 @@ public void should_allow_zero_delay() { assertThat(resultSet.getExecutionInfo().getSuccessfulExecutionIndex()).isEqualTo(2); assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -254,9 +242,7 @@ public void should_use_policy_from_request_profile() { assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(1); // Expect node 0 and 1 to be queried, but not 2. - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 0); + counter.assertNodeCounts(1, 1, 0); } } @@ -276,9 +262,7 @@ public void should_use_policy_from_request_profile_when_not_configured_in_config assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); // Expect all nodes to be queried. - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -297,10 +281,7 @@ public void should_use_policy_from_config_when_not_configured_in_request_profile // Expect speculative executions on each node since default configuration is used. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(2); - // Expect all nodes to be queried. - assertQueryCount(0, 1); - assertQueryCount(1, 1); - assertQueryCount(2, 1); + counter.assertNodeCounts(1, 1, 1); } } @@ -319,10 +300,8 @@ public void should_not_speculatively_execute_when_defined_in_profile() { // Expect no speculative executions. assertThat(resultSet.getExecutionInfo().getSpeculativeExecutionCount()).isEqualTo(0); - // Expect only node 0 to be queries since speculative execution is disabled for this profile. - assertQueryCount(0, 1); - assertQueryCount(1, 0); - assertQueryCount(2, 0); + // Expect only node 0 to be queried since speculative execution is disabled for this profile. + counter.assertNodeCounts(1, 0, 0); } } @@ -356,7 +335,9 @@ private CqlSession buildSessionWithProfile( SessionUtils.configLoaderBuilder() .withDuration( DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(SPECULATIVE_DELAY * 10)) - .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true); + .withBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE, true) + .withClass( + DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, SortingLoadBalancingPolicy.class); if (defaultMaxSpeculativeExecutions != -1 || defaultSpeculativeDelayMs != -1) { builder = @@ -456,17 +437,4 @@ private CqlSession buildSessionWithProfile( private void primeNode(int id, PrimeDsl.PrimeBuilder primeBuilder) { simulacron.cluster().node(id).prime(primeBuilder); } - - private void assertQueryCount(int node, int expected) { - assertThat( - simulacron - .cluster() - .node(node) - .getLogs() - .getQueryLogs() - .stream() - .filter(l -> l.getQuery().equals(QUERY_STRING))) - .as("Expected query count to be %d for node %d", expected, node) - .hasSize(expected); - } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index 4c33157df85..518e55a78ed 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -49,6 +49,7 @@ import com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.QueryCounter; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.common.stubbing.CloseType; @@ -99,6 +100,12 @@ public class DefaultRetryPolicyIT { private Level oldLevel; private String logPrefix; + @SuppressWarnings("deprecation") + private final QueryCounter counter = + QueryCounter.builder(simulacron.cluster()) + .withFilter((l) -> l.getQuery().equals(queryStr)) + .build(); + @Before public void setup() { logger = (Logger) LoggerFactory.getLogger(DefaultRetryPolicy.class); @@ -118,31 +125,6 @@ public void teardown() { logger.setLevel(oldLevel); } - private void assertQueryCount(int expected) { - assertThat( - simulacron - .cluster() - .getLogs() - .getQueryLogs() - .stream() - .filter(l -> l.getQuery().equals(queryStr))) - .as("Expected query count to be %d", expected) - .hasSize(expected); - } - - private void assertQueryCount(int node, int expected) { - assertThat( - simulacron - .cluster() - .node(node) - .getLogs() - .getQueryLogs() - .stream() - .filter(l -> l.getQuery().equals(queryStr))) - .as("Expected query count to be %d for node %d", expected, node) - .hasSize(expected); - } - @Test public void should_not_retry_on_read_timeout_when_data_present() { // given a node that will respond to query with a read timeout where data is present. @@ -161,7 +143,7 @@ public void should_not_retry_on_read_timeout_when_data_present() { } // should not have been retried. - assertQueryCount(1); + counter.assertTotalCount(1); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); @@ -187,7 +169,7 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() } // should not have been retried. - assertQueryCount(1); + counter.assertTotalCount(1); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); @@ -213,8 +195,8 @@ public void should_retry_on_read_timeout_when_enough_responses_and_data_not_pres } // there should have been a retry, and it should have been executed on the same host. - assertQueryCount(2); - assertQueryCount(0, 2); + counter.assertTotalCount(2); + counter.assertNodeCounts(2, 0, 0); // verify log event was emitted as expected Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -255,11 +237,9 @@ public void should_retry_on_next_host_on_connection_error_if_idempotent() { .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); // should have been retried. - assertQueryCount(2); - // expected query on node 0. - assertQueryCount(0, 1); - // expected retry on node 1. - assertQueryCount(1, 1); + counter.assertTotalCount(2); + // expected query on node 0, and retry on node 2. + counter.assertNodeCounts(1, 1, 0); // verify log event was emitted as expected Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -287,13 +267,10 @@ public void should_keep_retrying_on_next_host_on_connection_error() { } // should have been tried on all nodes. - assertQueryCount(3); - // expected query on node 0. - assertQueryCount(0, 1); - // expected retry on node 1. - assertQueryCount(1, 1); - // expected query on node 2. - assertQueryCount(2, 1); + // should have been retried. + counter.assertTotalCount(3); + // expected query on node 0, and retry on node 2 and 3. + counter.assertNodeCounts(1, 1, 1); // verify log event was emitted for each host as expected Mockito.verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); @@ -328,7 +305,7 @@ public void should_not_retry_on_connection_error_if_non_idempotent() { } // should not have been retried. - assertQueryCount(1); + counter.assertTotalCount(1); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); @@ -355,8 +332,8 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { } // there should have been a retry, and it should have been executed on the same host. - assertQueryCount(2); - assertQueryCount(0, 2); + counter.assertTotalCount(2); + counter.assertNodeCounts(2, 0, 0); // verify log event was emitted as expected Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -406,7 +383,7 @@ public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( } // should not have been retried. - assertQueryCount(1); + counter.assertTotalCount(1); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); @@ -435,7 +412,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id } // should not have been retried. - assertQueryCount(1); + counter.assertTotalCount(1); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); @@ -461,9 +438,8 @@ public void should_retry_on_next_host_on_unavailable() { .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); // should have been retried on another host. - assertQueryCount(2); - assertQueryCount(0, 1); - assertQueryCount(1, 1); + counter.assertTotalCount(2); + counter.assertNodeCounts(1, 1, 0); // verify log event was emitted as expected Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -494,9 +470,8 @@ public void should_only_retry_once_on_unavailable() { } // should have been retried on another host. - assertQueryCount(2); - assertQueryCount(0, 1); - assertQueryCount(1, 1); + counter.assertTotalCount(2); + counter.assertNodeCounts(1, 1, 0); } @Test @@ -517,13 +492,8 @@ public void should_keep_retrying_on_next_host_on_error_response() { } // should have been tried on all nodes. - assertQueryCount(3); - // expected query on node 0. - assertQueryCount(0, 1); - // expected retry on node 1. - assertQueryCount(1, 1); - // expected query on node 2. - assertQueryCount(2, 1); + counter.assertTotalCount(3); + counter.assertNodeCounts(1, 1, 1); // verify log event was emitted for each host as expected Mockito.verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); @@ -548,10 +518,9 @@ public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() assertThat(e.getMessage()).isEqualTo("this is a server error"); } - // should have been tried on all nodes. - assertQueryCount(1); - // expected query on node 0. - assertQueryCount(0, 1); + // should only have been tried on first node. + counter.assertTotalCount(1); + counter.assertNodeCounts(1, 0, 0); // expect no logging messages since there was no retry Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/QueryCounter.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/QueryCounter.java new file mode 100644 index 00000000000..ffca180c13f --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/QueryCounter.java @@ -0,0 +1,157 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.testinfra.simulacron; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; +import com.datastax.oss.simulacron.common.cluster.QueryLog; +import com.datastax.oss.simulacron.server.BoundNode; +import com.datastax.oss.simulacron.server.BoundTopic; +import com.datastax.oss.simulacron.server.listener.QueryListener; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +/** + * A convenience utility that keeps track of the number of queries matching a filter received by + * {@link BoundNode}s. + * + *

          One tricky thing about validating query counts in context of testing is in cases where you + * don't require waiting for a node to respond. In this case it's possible that a user would check + * count criteria before the node has even processed the message. This class offers the capability + * to wait a specified amount of time when asserting query arrival counts on nodes. + */ +public class QueryCounter { + + private final long beforeTimeout; + private final TimeUnit beforeUnit; + private final AtomicInteger totalCount = new AtomicInteger(0); + private final ConcurrentHashMap countMap = new ConcurrentHashMap<>(); + + public enum NotificationMode { + BEFORE_PROCESSING, + AFTER_PROCESSING + } + + private QueryCounter( + BoundTopic topic, + NotificationMode notificationMode, + Predicate queryLogFilter, + long beforeTimeout, + TimeUnit beforeUnit) { + this.beforeTimeout = beforeTimeout; + this.beforeUnit = beforeUnit; + QueryListener listener = + (boundNode, queryLog) -> { + totalCount.incrementAndGet(); + countMap.merge(boundNode.getId().intValue(), 1, Integer::sum); + }; + topic.registerQueryListener( + listener, notificationMode == NotificationMode.AFTER_PROCESSING, queryLogFilter); + } + + /** Creates a builder that tracks counts for the given {@link BoundTopic} (cluster, dc, node). */ + public static QueryCounterBuilder builder(BoundTopic topic) { + return new QueryCounterBuilder(topic); + } + + /** Clears all counters. */ + public void clearCounts() { + totalCount.set(0); + countMap.clear(); + } + + /** + * Asserts that the total number of requests received matching filter criteria matches the + * expected count within the configured time period. + */ + public void assertTotalCount(int expected) { + ConditionChecker.checkThat(() -> assertThat(totalCount.get()).isEqualTo(expected)) + .every(10, TimeUnit.MILLISECONDS) + .before(beforeTimeout, beforeUnit) + .becomesTrue(); + } + + /** + * Asserts that the total number of requests received matcher filter criteria matches the expected + * count for each node within the configured time period. + * + * @param counts The expected node counts, with the value at each index matching the expected + * count for that node id (i.e. index 0 = node id 0 expected count). + */ + public void assertNodeCounts(int... counts) { + Map expectedCounts = new HashMap<>(); + for (int id = 0; id < counts.length; id++) { + int count = counts[id]; + if (count > 0) { + expectedCounts.put(id, counts[id]); + } + } + ConditionChecker.checkThat(() -> assertThat(countMap).containsAllEntriesOf(expectedCounts)) + .every(10, TimeUnit.MILLISECONDS) + .before(beforeTimeout, beforeUnit) + .becomesTrue(); + } + + public static class QueryCounterBuilder { + + @SuppressWarnings("deprecation") + private static Predicate DEFAULT_FILTER = (q) -> !q.getQuery().isEmpty(); + + private Predicate queryLogFilter = DEFAULT_FILTER; + private BoundTopic topic; + private NotificationMode notificationMode = NotificationMode.BEFORE_PROCESSING; + private long beforeTimeout = 1; + private TimeUnit beforeUnit = TimeUnit.SECONDS; + + private QueryCounterBuilder(BoundTopic topic) { + this.topic = topic; + } + + /** + * The filter to apply to consider a message received by the node, if not provided we consider + * all messages that are queries. + */ + public QueryCounterBuilder withFilter(Predicate queryLogFilter) { + this.queryLogFilter = queryLogFilter; + return this; + } + + /** Whether or not simulacron should notify before or after the message is processed. */ + public QueryCounterBuilder withNotification(NotificationMode notificationMode) { + this.notificationMode = notificationMode; + return this; + } + + /** + * Up to how long we check counts to match. If counts don't match after this time, an exception + * is thrown. + */ + public QueryCounterBuilder before(long timeout, TimeUnit unit) { + this.beforeTimeout = timeout; + this.beforeUnit = unit; + return this; + } + + public QueryCounter build() { + return new QueryCounter(topic, notificationMode, queryLogFilter, beforeTimeout, beforeUnit); + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index 86042052dac..c531183c8b0 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -24,14 +24,22 @@ import com.datastax.oss.simulacron.server.Inet4Resolver; import com.datastax.oss.simulacron.server.Server; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; public class SimulacronRule extends CassandraResourceRule { // TODO perhaps share server some other way + // TODO: Temporarily do not release addresses to ensure IPs are always ordered public static final Server server = - Server.builder().withAddressResolver(new Inet4Resolver(9043)).build(); + Server.builder() + .withAddressResolver( + new Inet4Resolver(9043) { + @Override + public void release(SocketAddress address) {} + }) + .build(); private final ClusterSpec clusterSpec; private BoundCluster boundCluster; diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java index 2b590845ec2..e57cf1fedbd 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ConditionChecker.java @@ -107,6 +107,7 @@ public static ConditionCheckerBuilder checkThat(Runnable predicate) { private final Lock lock; private final Condition condition; private final Timer timer; + private Throwable lastFailure; public ConditionChecker( Object predicate, @@ -138,11 +139,17 @@ public void await(long timeout, TimeUnit unit) { lock.lock(); try { while (!evalCondition()) { - if (nanos <= 0L) - fail( + if (nanos <= 0L) { + String msg = String.format( "Timeout after %s %s while waiting for '%s'", - timeout, unit.toString().toLowerCase(), description)); + timeout, unit.toString().toLowerCase(), description); + if (lastFailure != null) { + fail(msg, lastFailure); + } else { + fail(msg); + } + } try { nanos = condition.awaitNanos(nanos); } catch (InterruptedException e) { @@ -175,6 +182,7 @@ private boolean evalCondition() { ((Runnable) predicate).run(); } catch (Throwable t) { succeeded = false; + lastFailure = t; } return succeeded == expectedOutcome; } else { From eb8abf63300d4221302ffd7f69e266c2ab508c39 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Thu, 13 Dec 2018 11:07:09 -0600 Subject: [PATCH 641/742] JAVA-2056: Reduce HashedWheelTimer tick duration Motivation: Timeouts and speculative executions are scheduled on the same HashedWheelTimer, which has a default tick duration of 100ms. This can be too imprecise for speculative executions. Lowering the tick duration to 1ms does not affect performance, but does allow for more precision for scheduling speculative executions. Modifications: Reduce the default timer tick duration to 1ms (from 100ms) and increase the default wheel size to 2048 (from 512). --- changelog/README.md | 1 + core/src/main/resources/reference.conf | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 37850dedaf6..b8f9d2c767a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2056: Reduce HashedWheelTimer tick duration - [bug] JAVA-2057: Do not create pool when SUGGEST\_UP topology event received - [improvement] JAVA-2049: Add shorthand method to SessionBuilder to specify local DC - [bug] JAVA-2037: Fix NPE when preparing statement with no bound variables diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index b8b9e449205..c22b74c03a6 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -1385,7 +1385,7 @@ datastax-java-driver { # Required: yes # Modifiable at runtime: no # Overridable in a profile: no - tick-duration = 100 milliseconds + tick-duration = 1 millisecond # Number of ticks in a Timer wheel. The underlying implementation uses Netty's # HashedWheelTimer, which uses hashes to arrange the timeouts. This effectively controls the @@ -1394,7 +1394,7 @@ datastax-java-driver { # Required: yes # Modifiable at runtime: no # Overridable in a profile: no - ticks-per-wheel = 512 + ticks-per-wheel = 2048 } } From 9a25061478168c0f2b93ae36301dcd7161def826 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 14 Dec 2018 15:34:20 +0100 Subject: [PATCH 642/742] Finish incomplete sentence in CqlIdentifier class javadocs --- .../java/com/datastax/oss/driver/api/core/CqlIdentifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 6fb4cd409c3..89211d75382 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -54,7 +54,8 @@ * identifier is in. Driver clients will generally want to create instances from the CQL form with * {@link #fromCql(String)}. * - *

          There is no internal caching; if you reuse the same identifiers often, + *

          There is no internal caching; if you reuse the same identifiers often, consider caching them + * in your application. */ @Immutable public class CqlIdentifier implements Serializable { From 59bae7025d67f39ed4c4f710b650b1a347ae1ba9 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 29 Nov 2018 09:24:14 -0800 Subject: [PATCH 643/742] JAVA-1943: Fail fast in execute() when the session is closed --- changelog/README.md | 1 + .../driver/api/core/AsyncAutoCloseable.java | 9 ++ .../core/context/DefaultNettyOptions.java | 3 +- .../core/cql/CqlPrepareAsyncProcessor.java | 6 + .../core/cql/CqlPrepareSyncProcessor.java | 5 + .../core/cql/CqlRequestAsyncProcessor.java | 6 + .../core/cql/CqlRequestHandlerBase.java | 90 ++++++++------ .../core/cql/CqlRequestSyncProcessor.java | 5 + .../internal/core/session/DefaultSession.java | 9 +- .../core/session/RequestProcessor.java | 3 + .../driver/api/core/session/ShutdownIT.java | 117 ++++++++++++++++++ .../internal/GuavaRequestAsyncProcessor.java | 6 + .../guava/internal/KeyRequestProcessor.java | 5 + 13 files changed, 223 insertions(+), 42 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java diff --git a/changelog/README.md b/changelog/README.md index b8f9d2c767a..e16be550b4a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-1943: Fail fast in execute() when the session is closed - [improvement] JAVA-2056: Reduce HashedWheelTimer tick duration - [bug] JAVA-2057: Do not create pool when SUGGEST\_UP topology event received - [improvement] JAVA-2049: Add shorthand method to SessionBuilder to specify local DC diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java index 6da0e9676da..f84cdf26c86 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncAutoCloseable.java @@ -35,6 +35,15 @@ public interface AsyncAutoCloseable extends AutoCloseable { @NonNull CompletionStage closeFuture(); + /** + * Whether shutdown has completed. + * + *

          This is a shortcut for {@code closeFuture().toCompletableFuture().isDone()}. + */ + default boolean isClosed() { + return closeFuture().toCompletableFuture().isDone(); + } + /** * Initiates an orderly shutdown: no new requests are accepted, but all pending requests are * allowed to complete normally. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index 72a411a7f9d..76b707234dc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -132,8 +132,7 @@ public Future onClose() { ioShutdownQuietPeriod, ioShutdownTimeout, ioShutdownUnit)); DefaultPromise closeFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); combiner.finish(closeFuture); - // stop the timer as well - timer.stop(); + closeFuture.addListener(f -> timer.stop()); return closeFuture; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index c1d7636561a..3d8aa4db825 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.nio.ByteBuffer; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentMap; @@ -53,4 +54,9 @@ public RequestHandler> newHan return new CqlPrepareAsyncHandler( request, preparedStatementsCache, session, context, sessionLogPrefix); } + + @Override + public CompletionStage newFailure(RuntimeException error) { + return CompletableFutures.failedFuture(error); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java index 3d243ff915e..a4a9f6d82b7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java @@ -52,4 +52,9 @@ public RequestHandler newHandler( return new CqlPrepareSyncHandler( request, preparedStatementsCache, session, context, sessionLogPrefix); } + + @Override + public PreparedStatement newFailure(RuntimeException error) { + throw error; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java index 064694850ac..4b563d875ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.concurrent.CompletionStage; import net.jcip.annotations.ThreadSafe; @@ -43,4 +44,9 @@ public RequestHandler, CompletionStage> newHandler( String sessionLogPrefix) { return new CqlRequestAsyncHandler(request, session, context, sessionLogPrefix); } + + @Override + public CompletionStage newFailure(RuntimeException error) { + return CompletableFutures.failedFuture(error); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java index d4a7f67553e..6d059341f66 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java @@ -212,16 +212,25 @@ public void onThrottleReady(boolean wasDelayed) { private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration.toNanos() > 0) { - return this.timer.newTimeout( - (Timeout timeout1) -> { - setFinalError( - new DriverTimeoutException("Query timed out after " + timeoutDuration), null, -1); - }, - timeoutDuration.toNanos(), - TimeUnit.NANOSECONDS); - } else { - return null; + try { + return this.timer.newTimeout( + (Timeout timeout1) -> + setFinalError( + new DriverTimeoutException("Query timed out after " + timeoutDuration), + null, + -1), + timeoutDuration.toNanos(), + TimeUnit.NANOSECONDS); + } catch (IllegalStateException e) { + // If we raced with session shutdown the timer might be closed already, rethrow with a more + // explicit message + result.completeExceptionally( + ("cannot be started once stopped".equals(e.getMessage())) + ? new IllegalStateException("Session is closed") + : e); + } } + return null; } /** @@ -479,36 +488,10 @@ public void operationComplete(Future future) throws Exception { inFlightCallbacks.add(this); if (scheduleNextExecution && isIdempotent) { int nextExecution = execution + 1; - // Note that `node` is the first node of the execution, it might not be the "slow" one - // if there were retries, but in practice retries are rare. long nextDelay = speculativeExecutionPolicy.nextExecution(node, keyspace, statement, nextExecution); if (nextDelay >= 0) { - LOG.trace( - "[{}] Scheduling speculative execution {} in {} ms", - logPrefix, - nextExecution, - nextDelay); - scheduledExecutions.add( - timer.newTimeout( - (Timeout timeout1) -> { - if (!result.isDone()) { - LOG.trace( - "[{}] Starting speculative execution {}", - CqlRequestHandlerBase.this.logPrefix, - nextExecution); - activeExecutionsCount.incrementAndGet(); - startedSpeculativeExecutionsCount.incrementAndGet(); - ((DefaultNode) node) - .getMetricUpdater() - .incrementCounter( - DefaultNodeMetric.SPECULATIVE_EXECUTIONS, - executionProfile.getName()); - sendRequest(null, queryPlan, nextExecution, 0, true); - } - }, - nextDelay, - TimeUnit.MILLISECONDS)); + scheduleSpeculativeExecution(nextExecution, nextDelay); } else { LOG.trace( "[{}] Speculative execution policy returned {}, no next execution", @@ -520,6 +503,41 @@ public void operationComplete(Future future) throws Exception { } } + private void scheduleSpeculativeExecution(int index, long delay) { + LOG.trace("[{}] Scheduling speculative execution {} in {} ms", logPrefix, index, delay); + try { + scheduledExecutions.add( + timer.newTimeout( + (Timeout timeout1) -> { + if (!result.isDone()) { + LOG.trace( + "[{}] Starting speculative execution {}", + CqlRequestHandlerBase.this.logPrefix, + index); + activeExecutionsCount.incrementAndGet(); + startedSpeculativeExecutionsCount.incrementAndGet(); + // Note that `node` is the first node of the execution, it might not be the + // "slow" + // one if there were retries, but in practice retries are rare. + ((DefaultNode) node) + .getMetricUpdater() + .incrementCounter( + DefaultNodeMetric.SPECULATIVE_EXECUTIONS, executionProfile.getName()); + sendRequest(null, queryPlan, index, 0, true); + } + }, + delay, + TimeUnit.MILLISECONDS)); + } catch (IllegalStateException e) { + // If we're racing with session shutdown, the timer might be stopped already. We don't want + // to schedule more executions anyway, so swallow the error. + if (!"cannot be started once stopped".equals(e.getMessage())) { + Loggers.warnWithException( + LOG, "[{}] Error while scheduling speculative execution", logPrefix, e); + } + } + } + @Override public void onResponse(Frame responseFrame) { long nodeResponseTimeNanos = NANOTIME_NOT_MEASURED_YET; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java index 35c25c22c99..9a05a4de5bb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java @@ -41,4 +41,9 @@ public RequestHandler, ResultSet> newHandler( String sessionLogPrefix) { return new CqlRequestSyncHandler(request, session, context, sessionLogPrefix); } + + @Override + public ResultSet newFailure(RuntimeException error) { + throw error; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f83d3238fa4..b7e5f905832 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -199,10 +199,11 @@ public Map getPools() { @Override public ResultT execute( @NonNull RequestT request, @NonNull GenericType resultType) { - return processorRegistry - .processorFor(request, resultType) - .newHandler(request, this, context, logPrefix) - .handle(); + RequestProcessor processor = + processorRegistry.processorFor(request, resultType); + return isClosed() + ? processor.newFailure(new IllegalStateException("Session is closed")) + : processor.newHandler(request, this, context, logPrefix).handle(); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index 918060ff239..287d9165f62 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -46,4 +46,7 @@ RequestHandler newHandler( DefaultSession session, InternalDriverContext context, String sessionLogPrefix); + + /** Builds a failed result to directly report the given error. */ + ResultT newFailure(RuntimeException error); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java new file mode 100644 index 00000000000..de42cac44ca --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java @@ -0,0 +1,117 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.session; + +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.noRows; +import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.NoNodeAvailableException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class ShutdownIT { + + @ClassRule + public static SimulacronRule simulacronRule = + new SimulacronRule(ClusterSpec.builder().withNodes(1)); + + private static final String QUERY_STRING = "select * from foo"; + + @Test + public void should_fail_requests_when_session_is_closed() throws Exception { + // Given + // Prime with a bit of delay to increase the chance that a query will be aborted in flight when + // we force-close the session + simulacronRule + .cluster() + .prime(when(QUERY_STRING).then(noRows()).delay(20, TimeUnit.MILLISECONDS)); + CqlSession session = SessionUtils.newSession(simulacronRule); + + // When + // Max out the in-flight requests on the connection (from a separate thread pool to get a bit of + // contention), then force-close the session abruptly. + Set unexpectedErrors = new ConcurrentSkipListSet<>(); + ExecutorService requestExecutor = Executors.newFixedThreadPool(4); + int maxConcurrentRequests = + session + .getContext() + .getConfig() + .getDefaultProfile() + .getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); + Semaphore semaphore = new Semaphore(maxConcurrentRequests); + CountDownLatch gotSessionClosedError = new CountDownLatch(1); + for (int i = 0; i < 4; i++) { + requestExecutor.execute( + () -> { + try { + while (!Thread.currentThread().isInterrupted()) { + semaphore.acquire(); + session + .executeAsync(QUERY_STRING) + .whenComplete( + (ignoredResult, error) -> { + semaphore.release(); + // Three things can happen: + // - DefaultSession.execute() detects that it's closed and fails the + // request immediately + // - the request was in flight and gets aborted when its channel is + // force-closed => ClosedConnectionException + // - the request races with the shutdown: it gets past execute() but by + // the time it tries to acquire a channel the pool was closed + // => NoNodeAvailableException + if (error instanceof IllegalStateException + && "Session is closed".equals(error.getMessage())) { + gotSessionClosedError.countDown(); + } else if (error != null + && !(error instanceof ClosedConnectionException + || error instanceof NoNodeAvailableException)) { + unexpectedErrors.add(error.toString()); + } + }); + } + } catch (InterruptedException e) { + // return + } + }); + } + TimeUnit.MILLISECONDS.sleep(100); + session.forceCloseAsync(); + assertThat(gotSessionClosedError.await(1, TimeUnit.SECONDS)) + .as("Expected to get the 'Session is closed' error shortly after shutting down") + .isTrue(); + requestExecutor.shutdownNow(); + + // Then + assertThat(unexpectedErrors).isEmpty(); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java index 1fb993b6fb7..74815a894ec 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.CompletionStage; @@ -88,4 +89,9 @@ public ListenableFuture handle() { return future; } } + + @Override + public ListenableFuture newFailure(RuntimeException error) { + return Futures.immediateFailedFuture(error); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java index 6e88fa83b88..9c98d4b4c59 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java @@ -84,4 +84,9 @@ public Integer handle() { } } } + + @Override + public Integer newFailure(RuntimeException error) { + throw error; + } } From e1d4d1c8483c8cf5ef8027998f685ff09695f30a Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 12 Dec 2018 23:21:10 +0100 Subject: [PATCH 644/742] JAVA-2058: Make programmatic config reloading part of the public API Motivation: Until now this was only possible via the internal API (by firing an event on the bus). However this not such an exotic use case, it sounds reasonable to expect that it will be commonly needed. Modifications: - add DriverConfigLoader.reload() and supportsReloading(). - remove ForceReloadConfigEvent, now obsolete since that was the only reason for its existence. Result: DriverConfigLoader is part of the public API and accessible through DriverContext, so this does not require any internal imports anymore. --- changelog/README.md | 1 + core/revapi.json | 22 +++++ .../api/core/config/DriverConfigLoader.java | 27 +++++- .../core/config/ConfigChangeEvent.java | 2 + .../core/config/ForceReloadConfigEvent.java | 21 ----- .../typesafe/DefaultDriverConfigLoader.java | 86 +++++++++++++------ .../DefaultDriverConfigLoaderTest.java | 29 +++++-- .../DriverExecutionProfileReloadIT.java | 5 +- manual/core/configuration/README.md | 55 ++++++++---- 9 files changed, 173 insertions(+), 75 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java diff --git a/changelog/README.md b/changelog/README.md index e16be550b4a..f7b20858a01 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2058: Make programmatic config reloading part of the public API - [improvement] JAVA-1943: Fail fast in execute() when the session is closed - [improvement] JAVA-2056: Reduce HashedWheelTimer tick duration - [bug] JAVA-2057: Do not create pool when SUGGEST\_UP topology event received diff --git a/core/revapi.json b/core/revapi.json index 441c785f478..0cabe57ffe9 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -665,6 +665,28 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", "elementKind": "method", "justification": "JAVA-2049: Add shorthand method to SessionBuilder to specify local DC" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.config.DriverConfigLoader::reload()", + "package": "com.datastax.oss.driver.api.core.config", + "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", + "classSimpleName": "DriverConfigLoader", + "methodName": "reload", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2058: Make programmatic config reloading part of the public API" + }, + { + "code": "java.method.addedToInterface", + "new": "method boolean com.datastax.oss.driver.api.core.config.DriverConfigLoader::supportsReloading()", + "package": "com.datastax.oss.driver.api.core.config", + "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", + "classSimpleName": "DriverConfigLoader", + "methodName": "supportsReloading", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2058: Make programmatic config reloading part of the public API" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java index 3b807a101d6..3ff4ae5ccaf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DriverConfigLoader.java @@ -17,13 +17,19 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletionStage; /** * Manages the initialization, and optionally the periodic reloading, of the driver configuration. */ public interface DriverConfigLoader extends AutoCloseable { - /** Loads the first configuration that will be used to initialize the driver. */ + /** + * Loads the first configuration that will be used to initialize the driver. + * + *

          If this loader {@linkplain #supportsReloading() supports reloading}, this object should be + * mutable and reflect later changes when the configuration gets reloaded. + */ @NonNull DriverConfig getInitialConfig(); @@ -33,6 +39,25 @@ public interface DriverConfigLoader extends AutoCloseable { */ void onDriverInit(@NonNull DriverContext context); + /** + * Triggers an immediate reload attempt. + * + * @return a stage that completes once the attempt is finished, with a boolean indicating whether + * the configuration changed as a result of this reload. If so, it's also guaranteed that + * internal driver components have been notified by that time; note however that some react to + * the notification asynchronously, so they may not have completely applied all resulting + * changes yet. If this loader does not support programmatic reloading — which you can + * check by calling {@link #supportsReloading()} before this method — the returned + * object will fail immediately with an {@link UnsupportedOperationException}. + */ + @NonNull + CompletionStage reload(); + + /** + * Whether this implementation supports programmatic reloading with the {@link #reload()} method. + */ + boolean supportsReloading(); + /** * Called when the cluster closes. This is a good time to release any external resource, for * example cancel a scheduled reloading task. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java index b52b065bf48..a4dd8fd67c0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ConfigChangeEvent.java @@ -17,5 +17,7 @@ /** An event triggered when the configuration was changed. */ public enum ConfigChangeEvent { + // Implementation note: to find where this event is consumed, look for references to the class + // itself, not INSTANCE (EventBus.register takes a class not an object). INSTANCE } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java deleted file mode 100644 index d84ec757e79..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/ForceReloadConfigEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.config; - -/** An event that forces an immediate reload of the configuration. */ -public enum ForceReloadConfigEvent { - INSTANCE -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java index a907666c0ae..712b3db5388 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoader.java @@ -22,9 +22,9 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; -import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -32,6 +32,8 @@ import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.ScheduledFuture; import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import net.jcip.annotations.ThreadSafe; @@ -83,6 +85,19 @@ public void onDriverInit(@NonNull DriverContext driverContext) { this.singleThreaded = new SingleThreaded((InternalDriverContext) driverContext); } + @NonNull + @Override + public CompletionStage reload() { + CompletableFuture result = new CompletableFuture<>(); + RunOrSchedule.on(singleThreaded.adminExecutor, () -> singleThreaded.reload(result)); + return result; + } + + @Override + public boolean supportsReloading() { + return true; + } + /** For internal use only, this leaks a Typesafe config type. */ @NonNull public Supplier getConfigSupplier() { @@ -123,10 +138,9 @@ private class SingleThreaded { private final EventExecutor adminExecutor; private final EventBus eventBus; private final DriverExecutionProfile config; - private final Object forceLoadListenerKey; private Duration reloadInterval; - private ScheduledFuture reloadFuture; + private ScheduledFuture periodicTaskHandle; private boolean closeWasCalled; private SingleThreaded(InternalDriverContext context) { @@ -140,59 +154,79 @@ private SingleThreaded(InternalDriverContext context) { .getDefaultProfile() .getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); - forceLoadListenerKey = - this.eventBus.register( - ForceReloadConfigEvent.class, e -> RunOrSchedule.on(adminExecutor, this::reload)); - - RunOrSchedule.on(adminExecutor, this::scheduleReloadTask); + RunOrSchedule.on(adminExecutor, this::schedulePeriodicReload); } - private void scheduleReloadTask() { + private void schedulePeriodicReload() { assert adminExecutor.inEventLoop(); // Cancel any previously running task - if (reloadFuture != null) { - reloadFuture.cancel(false); + if (periodicTaskHandle != null) { + periodicTaskHandle.cancel(false); } if (reloadInterval.isZero()) { LOG.debug("[{}] Reload interval is 0, disabling periodic reloading", logPrefix); } else { LOG.debug("[{}] Scheduling periodic reloading with interval {}", logPrefix, reloadInterval); - reloadFuture = + periodicTaskHandle = adminExecutor.scheduleAtFixedRate( - this::reload, + this::reloadInBackground, reloadInterval.toNanos(), reloadInterval.toNanos(), TimeUnit.NANOSECONDS); } } - private void reload() { + /** + * @param reloadedFuture a future to complete when the reload is complete (might be null if the + * caller is not interested in being notified) + */ + private void reload(CompletableFuture reloadedFuture) { assert adminExecutor.inEventLoop(); if (closeWasCalled) { + if (reloadedFuture != null) { + reloadedFuture.completeExceptionally(new IllegalStateException("session is closing")); + } return; } - if (driverConfig.reload(configSupplier.get())) { - LOG.info("[{}] Detected a configuration change", logPrefix); - eventBus.fire(ConfigChangeEvent.INSTANCE); - Duration newReloadInterval = config.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); - if (!newReloadInterval.equals(reloadInterval)) { - reloadInterval = newReloadInterval; - scheduleReloadTask(); + try { + boolean changed = driverConfig.reload(configSupplier.get()); + if (changed) { + LOG.info("[{}] Detected a configuration change", logPrefix); + eventBus.fire(ConfigChangeEvent.INSTANCE); + Duration newReloadInterval = + config.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL); + if (!newReloadInterval.equals(reloadInterval)) { + reloadInterval = newReloadInterval; + schedulePeriodicReload(); + } + } else { + LOG.debug("[{}] Reloaded configuration but it hasn't changed", logPrefix); + } + if (reloadedFuture != null) { + reloadedFuture.complete(changed); + } + } catch (Error | RuntimeException e) { + if (reloadedFuture != null) { + reloadedFuture.completeExceptionally(e); + } else { + Loggers.warnWithException( + LOG, "[{}] Unexpected exception during scheduled reload", logPrefix, e); } - } else { - LOG.debug("[{}] Reloaded configuration but it hasn't changed", logPrefix); } } + private void reloadInBackground() { + reload(null); + } + private void close() { assert adminExecutor.inEventLoop(); if (closeWasCalled) { return; } closeWasCalled = true; - eventBus.unregister(forceLoadListenerKey, ForceReloadConfigEvent.class); - if (reloadFuture != null) { - reloadFuture.cancel(false); + if (periodicTaskHandle != null) { + periodicTaskHandle.cancel(false); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index d5b31898cf4..a036383fa7e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -16,13 +16,13 @@ package com.datastax.oss.driver.internal.core.config.typesafe; import static com.datastax.oss.driver.Assertions.assertThat; +import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.Mockito.never; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; -import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; @@ -31,6 +31,7 @@ import com.typesafe.config.ConfigFactory; import io.netty.channel.EventLoopGroup; import java.time.Duration; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; @@ -97,7 +98,7 @@ public void should_schedule_reloading_task() { } @Test - public void should_reload_if_config_has_changed() { + public void should_detect_config_change_from_periodic_reload() { DefaultDriverConfigLoader loader = new DefaultDriverConfigLoader(() -> ConfigFactory.parseString(configSource.get())); DriverConfig initialConfig = loader.getInitialConfig(); @@ -117,7 +118,7 @@ public void should_reload_if_config_has_changed() { } @Test - public void should_reload_if_forced() { + public void should_detect_config_change_from_manual_reload() { DefaultDriverConfigLoader loader = new DefaultDriverConfigLoader(() -> ConfigFactory.parseString(configSource.get())); DriverConfig initialConfig = loader.getInitialConfig(); @@ -128,15 +129,16 @@ public void should_reload_if_forced() { configSource.set("int1 = 43"); - eventBus.fire(ForceReloadConfigEvent.INSTANCE); + CompletionStage reloaded = loader.reload(); adminExecutor.waitForNonScheduledTasks(); assertThat(initialConfig).hasIntOption(MockOptions.INT1, 43); Mockito.verify(eventBus).fire(ConfigChangeEvent.INSTANCE); + assertThatStage(reloaded).isSuccess(changed -> assertThat(changed).isTrue()); } @Test - public void should_not_notify_if_config_has_not_changed() { + public void should_not_notify_from_periodic_reload_if_config_has_not_changed() { DefaultDriverConfigLoader loader = new DefaultDriverConfigLoader(() -> ConfigFactory.parseString(configSource.get())); DriverConfig initialConfig = loader.getInitialConfig(); @@ -153,4 +155,21 @@ public void should_not_notify_if_config_has_not_changed() { Mockito.verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); } + + @Test + public void should_not_notify_from_manual_reload_if_config_has_not_changed() { + DefaultDriverConfigLoader loader = + new DefaultDriverConfigLoader(() -> ConfigFactory.parseString(configSource.get())); + DriverConfig initialConfig = loader.getInitialConfig(); + assertThat(initialConfig).hasIntOption(MockOptions.INT1, 42); + + loader.onDriverInit(context); + adminExecutor.waitForNonScheduledTasks(); + + CompletionStage reloaded = loader.reload(); + adminExecutor.waitForNonScheduledTasks(); + + Mockito.verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); + assertThatStage(reloaded).isSuccess(changed -> assertThat(changed).isFalse()); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index 34fac5b1613..3ff5883fe62 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -27,7 +27,6 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; -import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; @@ -109,9 +108,7 @@ public void should_reload_configuration_when_event_fired() throws Exception { // Bump up request timeout to 10 seconds and trigger a manual reload. configSource.set("basic.request.timeout = 10s"); - ((InternalDriverContext) session.getContext()) - .getEventBus() - .fire(ForceReloadConfigEvent.INSTANCE); + session.getContext().getConfigLoader().reload(); waitForConfigChange(session, 500, TimeUnit.MILLISECONDS); // Execute again, should not timeout. diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index 5e40202247b..d6ace2ef6c7 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -146,6 +146,29 @@ Duration requestTimeout = defaultProfile.getDuration(DefaultDriverOption.REQUEST int maxRequestsPerConnection = defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS); ``` +#### Manual reloading + +In addition to periodic reloading, you can trigger a reload programmatically. This returns a +`CompletionStage` that you can use for example to register a callback when the reload is complete: + +```java +DriverConfigLoader loader = session.getContext().getConfigLoader(); +if (loader.supportsReloading()) { + CompletionStage reloaded = loader.reload(); + reloaded.whenComplete( + (configChanged, error) -> { + if (error != null) { + // handle error + } else if (configChanged) { + // do something after the config change + } + }); +} +``` + +Manual reloading is optional, this can be checked with `supportsReloading()`; the driver's built-in +loader supports it. + #### Derived profiles Execution profiles are hard-coded in the configuration, and can't be changed at runtime (except @@ -325,37 +348,33 @@ Note that the option getters (`DriverExecutionProfile.getInt` and similar) are i frequently on the hot code path; if your implementation is slow, consider caching the results between reloads. -#### Configuration change events +#### Configuration change event -You can force an immediate reload instead of waiting for the next interval: +If you're writing your own policies, you might want them to be reactive to configuration changes. +You can register a callback to `ConfigChangeEvent`, which gets emitted any time a manual or periodic +reload detects changes since the last reload: ```java -import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; -// DANGER ZONE: this gives you access to the driver internals, which allow very nasty things. -// Use responsibly. InternalDriverContext context = (InternalDriverContext) session.getContext(); -EventBus eventBus = context.getEventBus(); -eventBus.fire(ForceReloadConfigEvent.INSTANCE); -``` - -An event is also fired when we detect that the configuration changed after a reload. You can -register a callback to listen to it: - -```java -import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; - Object key = eventBus.register( - ConfigChangeEvent.class, (e) -> System.out.println("The configuration changed")); + ConfigChangeEvent.class, (e) -> { + System.out.println("The configuration changed"); + // re-read the config option(s) you're interested in, and apply changes if needed + }); // If your component has a shorter lifecycle than the driver, make sure to unregister when it closes eventBus.unregister(key, ConfigChangeEvent.class); ``` -Both events are managed by the config loader. If you write a custom loader, study the source of +For example, the driver uses this mechanism internally to resize connection pools if you change the +options in `advanced.connection.pool`. + +The event is emitted by the config loader. If you write a custom loader, study the source of `DefaultDriverConfigLoader` to reproduce the behavior. #### Policies @@ -468,7 +487,7 @@ config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR); ``` [DriverConfig]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfig.html -[DriverConfigProfile]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverConfigProfile.html +[DriverExecutionProfile]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverExecutionProfile.html [DriverContext]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/context/DriverContext.html [DriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DriverOption.html [DefaultDriverOption]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/config/DefaultDriverOption.html From 24e4826d554d7a5ce308b30a67c88ccb1063c479 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 17 Dec 2018 16:57:39 +0100 Subject: [PATCH 645/742] Include virtual boolean flag in equals/hashCode implementations --- .../core/metadata/schema/DefaultKeyspaceMetadata.java | 3 ++- .../core/metadata/schema/DefaultTableMetadata.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java index 3344f46a746..cb354b583ed 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultKeyspaceMetadata.java @@ -122,6 +122,7 @@ public boolean equals(Object other) { KeyspaceMetadata that = (KeyspaceMetadata) other; return Objects.equals(this.name, that.getName()) && this.durableWrites == that.isDurableWrites() + && this.virtual == that.isVirtual() && Objects.equals(this.replication, that.getReplication()) && Objects.equals(this.types, that.getUserDefinedTypes()) && Objects.equals(this.tables, that.getTables()) @@ -136,7 +137,7 @@ public boolean equals(Object other) { @Override public int hashCode() { return Objects.hash( - name, durableWrites, replication, types, tables, views, functions, aggregates); + name, durableWrites, virtual, replication, types, tables, views, functions, aggregates); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java index 7e6ed07f1f1..479067ce0ba 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/DefaultTableMetadata.java @@ -135,6 +135,7 @@ public boolean equals(Object other) { && Objects.equals(this.name, that.getName()) && Objects.equals(Optional.ofNullable(this.id), that.getId()) && this.compactStorage == that.isCompactStorage() + && this.virtual == that.isVirtual() && Objects.equals(this.partitionKey, that.getPartitionKey()) && Objects.equals(this.clusteringColumns, that.getClusteringColumns()) && Objects.equals(this.columns, that.getColumns()) @@ -147,7 +148,15 @@ public boolean equals(Object other) { @Override public int hashCode() { return Objects.hash( - keyspace, name, id, compactStorage, partitionKey, clusteringColumns, columns, indexes); + keyspace, + name, + id, + compactStorage, + virtual, + partitionKey, + clusteringColumns, + columns, + indexes); } @Override From 1313a33406e3924fc72b28930fe2ece1448a3f2e Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Dec 2018 09:56:16 -0800 Subject: [PATCH 646/742] Remove RequestHandler interface Motivation: The current API forces the concept of a "handler" on every RequestProcessor implementation. This is not really necessary because the driver does not manipulate this object other than directly calling handle() on it. Modifications: Replace RequestProcessor.newHandler() by process(), which returns the result directly -- in other words, the equivalent of newHandler().handle(). Remove the RequestHandler interface. Result: Existing processors still use a separate handler (and this will probably remain a common pattern) but they are not forced to do so. In particular, it is now easier to implement a processor that caches its results. --- .../core/cql/CqlPrepareAsyncHandler.java | 6 +-- .../core/cql/CqlPrepareAsyncProcessor.java | 6 +-- .../core/cql/CqlPrepareSyncHandler.java | 5 +-- .../core/cql/CqlPrepareSyncProcessor.java | 6 +-- .../core/cql/CqlRequestAsyncHandler.java | 5 +-- .../core/cql/CqlRequestAsyncProcessor.java | 5 +-- .../core/cql/CqlRequestSyncHandler.java | 5 +-- .../core/cql/CqlRequestSyncProcessor.java | 5 +-- .../internal/core/session/DefaultSession.java | 2 +- .../internal/core/session/RequestHandler.java | 23 ---------- .../core/session/RequestProcessor.java | 4 +- .../internal/GuavaRequestAsyncProcessor.java | 44 ++++++------------- .../guava/internal/KeyRequestProcessor.java | 37 +++++----------- 13 files changed, 40 insertions(+), 113 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java index bf7f3dbce22..21782daaa9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java @@ -19,16 +19,13 @@ import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentMap; import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class CqlPrepareAsyncHandler extends CqlPrepareHandlerBase - implements RequestHandler> { +public class CqlPrepareAsyncHandler extends CqlPrepareHandlerBase { public CqlPrepareAsyncHandler( PrepareRequest request, @@ -39,7 +36,6 @@ public CqlPrepareAsyncHandler( super(request, preparedStatementsCache, session, context, sessionLogPrefix); } - @Override public CompletableFuture handle() { return result; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index 3d8aa4db825..cd8aaf85ab5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.nio.ByteBuffer; @@ -46,13 +45,14 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler> newHandler( + public CompletionStage process( PrepareRequest request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { return new CqlPrepareAsyncHandler( - request, preparedStatementsCache, session, context, sessionLogPrefix); + request, preparedStatementsCache, session, context, sessionLogPrefix) + .handle(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java index d7190edc8e6..86499c5d00b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.nio.ByteBuffer; @@ -27,8 +26,7 @@ import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class CqlPrepareSyncHandler extends CqlPrepareHandlerBase - implements RequestHandler { +public class CqlPrepareSyncHandler extends CqlPrepareHandlerBase { public CqlPrepareSyncHandler( PrepareRequest request, @@ -39,7 +37,6 @@ public CqlPrepareSyncHandler( super(request, preparedStatementsCache, session, context, sessionLogPrefix); } - @Override public PreparedStatement handle() { BlockingOperation.checkNotDriverThread(); return CompletableFutures.getUninterruptibly(result); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java index a4a9f6d82b7..7ad3113ffc7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentMap; @@ -44,13 +43,14 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler newHandler( + public PreparedStatement process( PrepareRequest request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { return new CqlPrepareSyncHandler( - request, preparedStatementsCache, session, context, sessionLogPrefix); + request, preparedStatementsCache, session, context, sessionLogPrefix) + .handle(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java index 50c226f18a3..9c93571060f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java @@ -19,13 +19,11 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import java.util.concurrent.CompletionStage; import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class CqlRequestAsyncHandler extends CqlRequestHandlerBase - implements RequestHandler, CompletionStage> { +public class CqlRequestAsyncHandler extends CqlRequestHandlerBase { public CqlRequestAsyncHandler( Statement statement, @@ -35,7 +33,6 @@ public CqlRequestAsyncHandler( super(statement, session, context, sessionLogPrefix); } - @Override public CompletionStage handle() { return result; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java index 4b563d875ed..b1a9be328c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.concurrent.CompletionStage; @@ -37,12 +36,12 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler, CompletionStage> newHandler( + public CompletionStage process( Statement request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestAsyncHandler(request, session, context, sessionLogPrefix); + return new CqlRequestAsyncHandler(request, session, context, sessionLogPrefix).handle(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java index 5d305abf1ec..ffe16735131 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java @@ -20,14 +20,12 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import net.jcip.annotations.ThreadSafe; @ThreadSafe -public class CqlRequestSyncHandler extends CqlRequestHandlerBase - implements RequestHandler, ResultSet> { +public class CqlRequestSyncHandler extends CqlRequestHandlerBase { public CqlRequestSyncHandler( Statement statement, @@ -37,7 +35,6 @@ public CqlRequestSyncHandler( super(statement, session, context, sessionLogPrefix); } - @Override public ResultSet handle() { BlockingOperation.checkNotDriverThread(); AsyncResultSet firstPage = CompletableFutures.getUninterruptibly(result); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java index 9a05a4de5bb..fc52ec483f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import net.jcip.annotations.ThreadSafe; @@ -34,12 +33,12 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler, ResultSet> newHandler( + public ResultSet process( Statement request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestSyncHandler(request, session, context, sessionLogPrefix); + return new CqlRequestSyncHandler(request, session, context, sessionLogPrefix).handle(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index b7e5f905832..ee032e90c94 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -203,7 +203,7 @@ public ResultT execute( processorRegistry.processorFor(request, resultType); return isClosed() ? processor.newFailure(new IllegalStateException("Session is closed")) - : processor.newHandler(request, this, context, logPrefix).handle(); + : processor.process(request, this, context, logPrefix); } @Nullable diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java deleted file mode 100644 index 21c317b8623..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.session; - -import com.datastax.oss.driver.api.core.session.Request; - -/** Manages the execution of a given request. */ -public interface RequestHandler { - ResultT handle(); -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java index 287d9165f62..b61ccebb090 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessor.java @@ -40,8 +40,8 @@ public interface RequestProcessor { */ boolean canProcess(Request request, GenericType resultType); - /** Builds a new handler to process a given request. */ - RequestHandler newHandler( + /** Processes the given request, producing a result. */ + ResultT process( RequestT request, DefaultSession session, InternalDriverContext context, diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java index 74815a894ec..4f9a6484731 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaRequestAsyncProcessor.java @@ -19,7 +19,6 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -57,37 +56,20 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler> newHandler( + public ListenableFuture process( T request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new GuavaRequestHandler( - subProcessor.newHandler(request, session, context, sessionLogPrefix)); - } - - class GuavaRequestHandler implements RequestHandler> { - - private final RequestHandler> subHandler; - - GuavaRequestHandler(RequestHandler> subHandler) { - this.subHandler = subHandler; - } - - @Override - public ListenableFuture handle() { - // convert CompletionStage to ListenableFuture by adding a whenComplete listener that sets the - // listenable future's result. - SettableFuture future = SettableFuture.create(); - subHandler - .handle() - .whenComplete( - (r, ex) -> { - if (ex != null) { - future.setException(ex); - } else { - future.set(r); - } - }); - return future; - } + SettableFuture future = SettableFuture.create(); + subProcessor + .process(request, session, context, sessionLogPrefix) + .whenComplete( + (r, ex) -> { + if (ex != null) { + future.setException(ex); + } else { + future.set(r); + } + }); + return future; } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java index 9c98d4b4c59..949db88e389 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/KeyRequestProcessor.java @@ -17,17 +17,14 @@ import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.RequestProcessorIT; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.session.RequestHandler; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import java.util.concurrent.CompletionStage; /** * A request processor that takes a given {@link KeyRequest#getKey} and generates a query, delegates @@ -50,38 +47,24 @@ public boolean canProcess(Request request, GenericType resultType) { } @Override - public RequestHandler newHandler( + public Integer process( KeyRequest request, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { + // Create statement from key and delegate it to CqlRequestSyncProcessor SimpleStatement statement = SimpleStatement.newInstance( "select v1 from test where k = ? and v0 = ?", RequestProcessorIT.KEY, request.getKey()); - RequestHandler, CompletionStage> subHandler = - subProcessor.newHandler(statement, session, context, sessionLogPrefix); - return new KeyRequestHandler(subHandler); - } - - static class KeyRequestHandler implements RequestHandler { - - private final RequestHandler, CompletionStage> subHandler; - - KeyRequestHandler(RequestHandler, CompletionStage> subHandler) { - this.subHandler = subHandler; - } - - @Override - public Integer handle() { - CompletionStage future = subHandler.handle(); - AsyncResultSet result = CompletableFutures.getUninterruptibly(future); - // If not exactly 1 rows were found, return Integer.MIN_VALUE, otherwise return the value. - if (result.remaining() != 1) { - return Integer.MIN_VALUE; - } else { - return result.currentPage().iterator().next().getInt("v1"); - } + AsyncResultSet result = + CompletableFutures.getUninterruptibly( + subProcessor.process(statement, session, context, sessionLogPrefix)); + // If not exactly 1 rows were found, return Integer.MIN_VALUE, otherwise return the value. + if (result.remaining() != 1) { + return Integer.MIN_VALUE; + } else { + return result.currentPage().iterator().next().getInt("v1"); } } From 4065ffadb801325ce71fcb5b8f39d56d43d40bcc Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Dec 2018 08:54:16 -0800 Subject: [PATCH 647/742] JAVA-2053: Cache results of session.prepare() --- changelog/README.md | 1 + .../oss/driver/api/core/CqlSession.java | 30 ++++++ .../core/metrics/DefaultSessionMetric.java | 1 + .../core/cql/CqlPrepareAsyncHandler.java | 42 -------- .../core/cql/CqlPrepareAsyncProcessor.java | 49 +++++++-- ...andlerBase.java => CqlPrepareHandler.java} | 85 +++++---------- .../core/cql/CqlPrepareSyncHandler.java | 44 -------- .../core/cql/CqlPrepareSyncProcessor.java | 29 +++-- .../core/cql/DefaultPrepareRequest.java | 17 +++ .../core/cql/DefaultSimpleStatement.java | 55 ++++++++++ .../DropwizardSessionMetricUpdater.java | 36 +++++++ .../session/RequestProcessorRegistry.java | 18 ++-- core/src/main/resources/reference.conf | 7 ++ .../core/cql/CqlPrepareHandlerTest.java | 101 ++---------------- .../api/core/cql/PreparedStatementIT.java | 76 +++++++++++++ .../guava/internal/GuavaDriverContext.java | 5 +- manual/core/statements/prepared/README.md | 20 +++- upgrade_guide/README.md | 11 ++ 18 files changed, 357 insertions(+), 270 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{CqlPrepareHandlerBase.java => CqlPrepareHandler.java} (86%) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java diff --git a/changelog/README.md b/changelog/README.md index f7b20858a01..f635babaef9 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2053: Cache results of session.prepare() - [improvement] JAVA-2058: Make programmatic config reloading part of the public API - [improvement] JAVA-1943: Fail fast in execute() when the session is closed - [improvement] JAVA-2056: Reduce HashedWheelTimer tick duration diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index b9784e3ff02..b0a0d01abda 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -123,6 +123,21 @@ default CompletionStage executeAsync(@NonNull String q * * If you want to customize this behavior, you can write your own implementation of {@link * PrepareRequest} and pass it to {@link #prepare(PrepareRequest)}. + * + *

          The result of this method is cached: if you call it twice with the same {@link + * SimpleStatement}, you will get the same {@link PreparedStatement} instance. We still recommend + * keeping a reference to it (for example by caching it as a field in a DAO); if that's not + * possible (e.g. if query strings are generated dynamically), it's OK to call this method every + * time: there will just be a small performance overhead to check the internal cache. Note that + * caching is based on: + * + *

            + *
          • the query string exactly as you provided it: the driver does not perform any kind of + * trimming or sanitizing. + *
          • all other execution parameters: for example, preparing two statements with identical + * query strings but different {@linkplain SimpleStatement#getConsistencyLevel() consistency + * levels} will yield distinct prepared statements. + *
          */ @NonNull default PreparedStatement prepare(@NonNull SimpleStatement statement) { @@ -134,6 +149,9 @@ default PreparedStatement prepare(@NonNull SimpleStatement statement) { /** * Prepares a CQL statement synchronously (the calling thread blocks until the statement is * prepared). + * + *

          The result of this method is cached (see {@link #prepare(SimpleStatement)} for more + * explanations). */ @NonNull default PreparedStatement prepare(@NonNull String query) { @@ -150,6 +168,9 @@ default PreparedStatement prepare(@NonNull String query) { * customize how attributes are propagated when you prepare a {@link SimpleStatement} (see {@link * #prepare(SimpleStatement)} for more explanations). Otherwise, you should rarely have to deal * with {@link PrepareRequest} directly. + * + *

          The result of this method is cached (see {@link #prepare(SimpleStatement)} for more + * explanations). */ @NonNull default PreparedStatement prepare(@NonNull PrepareRequest request) { @@ -165,6 +186,9 @@ default PreparedStatement prepare(@NonNull PrepareRequest request) { *

          Note that the bound statements created from the resulting prepared statement will inherit * some of the attributes of {@code query}; see {@link #prepare(SimpleStatement)} for more * details. + * + *

          The result of this method is cached (see {@link #prepare(SimpleStatement)} for more + * explanations). */ @NonNull default CompletionStage prepareAsync( @@ -177,6 +201,9 @@ default CompletionStage prepareAsync( /** * Prepares a CQL statement asynchronously (the call returns as soon as the prepare query was * sent, generally before the statement is prepared). + * + *

          The result of this method is cached (see {@link #prepare(SimpleStatement)} for more + * explanations). */ @NonNull default CompletionStage prepareAsync(@NonNull String query) { @@ -193,6 +220,9 @@ default CompletionStage prepareAsync(@NonNull Strin * customize how attributes are propagated when you prepare a {@link SimpleStatement} (see {@link * #prepare(SimpleStatement)} for more explanations). Otherwise, you should rarely have to deal * with {@link PrepareRequest} directly. + * + *

          The result of this method is cached (see {@link #prepare(SimpleStatement)} for more + * explanations). */ @NonNull default CompletionStage prepareAsync(PrepareRequest request) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java index 74954e153cf..b9b01cf3364 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/DefaultSessionMetric.java @@ -29,6 +29,7 @@ public enum DefaultSessionMetric implements SessionMetric { THROTTLING_DELAY("throttling.delay"), THROTTLING_QUEUE_SIZE("throttling.queue-size"), THROTTLING_ERRORS("throttling.errors"), + CQL_PREPARED_CACHE_SIZE("cql-prepared-cache-size"), ; private static final Map BY_PATH = sortByPath(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java deleted file mode 100644 index 21782daaa9a..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.cql; - -import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentMap; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -public class CqlPrepareAsyncHandler extends CqlPrepareHandlerBase { - - public CqlPrepareAsyncHandler( - PrepareRequest request, - ConcurrentMap preparedStatementsCache, - DefaultSession session, - InternalDriverContext context, - String sessionLogPrefix) { - super(request, preparedStatementsCache, session, context, sessionLogPrefix); - } - - public CompletableFuture handle() { - return result; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index cd8aaf85ab5..4e0b51fe482 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -23,20 +23,26 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import java.nio.ByteBuffer; +import com.datastax.oss.driver.shaded.guava.common.cache.Cache; +import com.datastax.oss.driver.shaded.guava.common.cache.CacheBuilder; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; import net.jcip.annotations.ThreadSafe; @ThreadSafe public class CqlPrepareAsyncProcessor implements RequestProcessor> { - private final ConcurrentMap preparedStatementsCache; + protected final Cache> cache; - public CqlPrepareAsyncProcessor( - ConcurrentMap preparedStatementsCache) { - this.preparedStatementsCache = preparedStatementsCache; + public CqlPrepareAsyncProcessor() { + this(CacheBuilder.newBuilder().weakValues().build()); + } + + protected CqlPrepareAsyncProcessor( + Cache> cache) { + this.cache = cache; } @Override @@ -50,13 +56,38 @@ public CompletionStage process( DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlPrepareAsyncHandler( - request, preparedStatementsCache, session, context, sessionLogPrefix) - .handle(); + + try { + CompletableFuture result = cache.getIfPresent(request); + if (result == null) { + CompletableFuture mine = new CompletableFuture<>(); + result = cache.get(request, () -> mine); + if (result == mine) { + new CqlPrepareHandler(request, session, context, sessionLogPrefix) + .handle() + .whenComplete( + (preparedStatement, error) -> { + if (error != null) { + mine.completeExceptionally(error); + cache.invalidate(request); // Make sure failure isn't cached indefinitely + } else { + mine.complete(preparedStatement); + } + }); + } + } + return result; + } catch (ExecutionException e) { + return CompletableFutures.failedFuture(e.getCause()); + } } @Override public CompletionStage newFailure(RuntimeException error) { return CompletableFutures.failedFuture(error); } + + public Cache> getCache() { + return cache; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java similarity index 86% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 6fcee8672e6..cc9c9ea0cfb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -55,7 +55,6 @@ import io.netty.util.Timer; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import java.nio.ByteBuffer; import java.time.Duration; import java.util.AbstractMap; import java.util.ArrayList; @@ -65,7 +64,6 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import net.jcip.annotations.ThreadSafe; @@ -74,14 +72,13 @@ /** Handles the lifecycle of the preparation of a CQL statement. */ @ThreadSafe -public abstract class CqlPrepareHandlerBase implements Throttled { +public class CqlPrepareHandler implements Throttled { - private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandlerBase.class); + private static final Logger LOG = LoggerFactory.getLogger(CqlPrepareHandler.class); private final long startTimeNanos; private final String logPrefix; private final PrepareRequest request; - private final ConcurrentMap preparedStatementsCache; private final DefaultSession session; private final InternalDriverContext context; private final DriverExecutionProfile executionProfile; @@ -100,9 +97,8 @@ public abstract class CqlPrepareHandlerBase implements Throttled { // We don't use a map because nodes can appear multiple times. private volatile List> errors; - protected CqlPrepareHandlerBase( + protected CqlPrepareHandler( PrepareRequest request, - ConcurrentMap preparedStatementsCache, DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { @@ -112,7 +108,6 @@ protected CqlPrepareHandlerBase( LOG.trace("[{}] Creating new handler for prepare request {}", logPrefix, request); this.request = request; - this.preparedStatementsCache = preparedStatementsCache; this.session = session; this.context = context; this.executionProfile = Conversions.resolveExecutionProfile(request, context); @@ -171,6 +166,10 @@ public void onThrottleReady(boolean wasDelayed) { sendRequest(null, 0); } + public CompletableFuture handle() { + return result; + } + private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration.toNanos() > 0) { return this.timer.newTimeout( @@ -221,7 +220,7 @@ private void recordError(Node node, Throwable error) { // Use a local variable to do only a single single volatile read in the nominal case List> errorsSnapshot = this.errors; if (errorsSnapshot == null) { - synchronized (CqlPrepareHandlerBase.this) { + synchronized (CqlPrepareHandler.this) { errorsSnapshot = this.errors; if (errorsSnapshot == null) { this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); @@ -236,58 +235,28 @@ private void setFinalResult(Prepared prepared) { // Whatever happens below, we're done with this stream id throttler.signalSuccess(this); - DefaultPreparedStatement newStatement = + DefaultPreparedStatement preparedStatement = Conversions.toPreparedStatement(prepared, request, context); - DefaultPreparedStatement cachedStatement = cache(newStatement); - - if (cachedStatement != newStatement) { - // The statement already existed in the cache, assume it's because the client called - // prepare() twice, and therefore it's already been prepared on other nodes. - result.complete(cachedStatement); - } else { - session - .getRepreparePayloads() - .put(cachedStatement.getId(), cachedStatement.getRepreparePayload()); - if (prepareOnAllNodes) { - prepareOnOtherNodes() - .thenRun( - () -> { - LOG.trace( - "[{}] Done repreparing on other nodes, completing the request", logPrefix); - result.complete(cachedStatement); - }) - .exceptionally( - error -> { - result.completeExceptionally(error); - return null; - }); - } else { - LOG.trace("[{}] Prepare on all nodes is disabled, completing the request", logPrefix); - result.complete(cachedStatement); - } - } - } - - private DefaultPreparedStatement cache(DefaultPreparedStatement preparedStatement) { - DefaultPreparedStatement previous = - preparedStatementsCache.putIfAbsent(preparedStatement.getId(), preparedStatement); - if (previous != null) { - LOG.warn( - "Re-preparing already prepared query. " - + "This is generally an anti-pattern and will likely affect performance. " - + "The cached version of the PreparedStatement will be returned, which may use " - + "different bound statement execution parameters (CL, timeout, etc.) from the " - + "current session.prepare call. Consider preparing the statement only once. " - + "Query='{}'", - preparedStatement.getQuery()); - - // The one object in the cache will get GCed once it's not referenced by the client anymore - // since we use a weak reference. So we need to make sure that the instance we do return to - // the user is the one that is in the cache. - return previous; + session + .getRepreparePayloads() + .put(preparedStatement.getId(), preparedStatement.getRepreparePayload()); + if (prepareOnAllNodes) { + prepareOnOtherNodes() + .thenRun( + () -> { + LOG.trace( + "[{}] Done repreparing on other nodes, completing the request", logPrefix); + result.complete(preparedStatement); + }) + .exceptionally( + error -> { + result.completeExceptionally(error); + return null; + }); } else { - return preparedStatement; + LOG.trace("[{}] Prepare on all nodes is disabled, completing the request", logPrefix); + result.complete(preparedStatement); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java deleted file mode 100644 index 86499c5d00b..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.cql; - -import com.datastax.oss.driver.api.core.cql.PrepareRequest; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import java.nio.ByteBuffer; -import java.util.concurrent.ConcurrentMap; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -public class CqlPrepareSyncHandler extends CqlPrepareHandlerBase { - - public CqlPrepareSyncHandler( - PrepareRequest request, - ConcurrentMap preparedStatementsCache, - DefaultSession session, - InternalDriverContext context, - String sessionLogPrefix) { - super(request, preparedStatementsCache, session, context, sessionLogPrefix); - } - - public PreparedStatement handle() { - BlockingOperation.checkNotDriverThread(); - return CompletableFutures.getUninterruptibly(result); - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java index 7ad3113ffc7..90e20f72394 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareSyncProcessor.java @@ -22,19 +22,25 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestProcessor; -import java.nio.ByteBuffer; -import java.util.concurrent.ConcurrentMap; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.shaded.guava.common.cache.Cache; +import java.util.concurrent.CompletableFuture; import net.jcip.annotations.ThreadSafe; @ThreadSafe public class CqlPrepareSyncProcessor implements RequestProcessor { - private final ConcurrentMap preparedStatementsCache; + private final CqlPrepareAsyncProcessor asyncProcessor; - public CqlPrepareSyncProcessor( - ConcurrentMap preparedStatementsCache) { - this.preparedStatementsCache = preparedStatementsCache; + /** + * Note: if you also register a {@link CqlPrepareAsyncProcessor} with your session, make sure that + * you pass that same instance to this constructor. This is necessary for proper behavior of the + * prepared statement cache. + */ + public CqlPrepareSyncProcessor(CqlPrepareAsyncProcessor asyncProcessor) { + this.asyncProcessor = asyncProcessor; } @Override @@ -48,9 +54,14 @@ public PreparedStatement process( DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlPrepareSyncHandler( - request, preparedStatementsCache, session, context, sessionLogPrefix) - .handle(); + + BlockingOperation.checkNotDriverThread(); + return CompletableFutures.getUninterruptibly( + asyncProcessor.process(request, session, context, sessionLogPrefix)); + } + + public Cache> getCache() { + return asyncProcessor.getCache(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java index 1f4ef7ef51e..149a4cb3017 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPrepareRequest.java @@ -199,4 +199,21 @@ public Node getNode() { public boolean areBoundStatementsTracing() { return statement.isTracing(); } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof DefaultPrepareRequest) { + DefaultPrepareRequest that = (DefaultPrepareRequest) other; + return this.statement.equals(that.statement); + } else { + return false; + } + } + + @Override + public int hashCode() { + return statement.hashCode(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 1379faeb781..2d13a30d9dd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -29,6 +29,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Objects; import net.jcip.annotations.Immutable; @Immutable @@ -694,4 +695,58 @@ public static Map wrapKeys(Map namedValue } return builder.build(); } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof DefaultSimpleStatement) { + DefaultSimpleStatement that = (DefaultSimpleStatement) other; + return this.query.equals(that.query) + && this.positionalValues.equals(that.positionalValues) + && this.namedValues.equals(that.namedValues) + && Objects.equals(this.executionProfileName, that.executionProfileName) + && Objects.equals(this.executionProfile, that.executionProfile) + && Objects.equals(this.keyspace, that.keyspace) + && Objects.equals(this.routingKeyspace, that.routingKeyspace) + && Objects.equals(this.routingKey, that.routingKey) + && Objects.equals(this.routingToken, that.routingToken) + && Objects.equals(this.customPayload, that.customPayload) + && Objects.equals(this.idempotent, that.idempotent) + && this.tracing == that.tracing + && this.timestamp == that.timestamp + && Objects.equals(this.pagingState, that.pagingState) + && this.pageSize == that.pageSize + && Objects.equals(this.consistencyLevel, that.consistencyLevel) + && Objects.equals(this.serialConsistencyLevel, that.serialConsistencyLevel) + && Objects.equals(this.timeout, that.timeout) + && Objects.equals(this.node, that.node); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + query, + positionalValues, + namedValues, + executionProfileName, + executionProfile, + keyspace, + routingKeyspace, + routingKey, + routingToken, + customPayload, + idempotent, + tracing, + timestamp, + pagingState, + pageSize, + consistencyLevel, + serialConsistencyLevel, + timeout, + node); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java index 1241d2fcd60..ab487546cac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java @@ -23,8 +23,13 @@ import com.datastax.oss.driver.api.core.metrics.SessionMetric; import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor; +import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; +import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler; import com.datastax.oss.driver.internal.core.session.throttling.RateLimitingRequestThrottler; +import com.datastax.oss.driver.shaded.guava.common.cache.Cache; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Set; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; @@ -62,6 +67,23 @@ public DropwizardSessionMetricUpdater( buildFullName(DefaultSessionMetric.THROTTLING_QUEUE_SIZE, null), buildQueueGauge(context.getRequestThrottler(), context.getSessionName())); } + if (enabledMetrics.contains(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE)) { + Cache cache = getPreparedStatementCache(context); + Gauge gauge; + if (cache == null) { + LOG.warn( + "[{}] Metric {} is enabled in the config, " + + "but it looks like no CQL prepare processor is registered. " + + "The gauge will always return 0", + context.getSessionName(), + DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE.getPath()); + gauge = () -> 0L; + } else { + gauge = cache::size; + } + this.registry.register( + buildFullName(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE, null), gauge); + } initializeHdrTimer( DefaultSessionMetric.CQL_REQUESTS, context.getConfig().getDefaultProfile(), @@ -97,4 +119,18 @@ private Gauge buildQueueGauge(RequestThrottler requestThrottler, String return () -> 0; } } + + @Nullable + private static Cache getPreparedStatementCache(InternalDriverContext context) { + // By default, both the sync processor and the async one are registered and they share the same + // cache. But with a custom processor registry, there could be only one of the two present. + for (RequestProcessor processor : context.getRequestProcessorRegistry().getProcessors()) { + if (processor instanceof CqlPrepareAsyncProcessor) { + return ((CqlPrepareAsyncProcessor) processor).getCache(); + } else if (processor instanceof CqlPrepareSyncProcessor) { + return ((CqlPrepareSyncProcessor) processor).getCache(); + } + } + return null; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java index 9b6aac2647b..90e4a79bc1f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -21,10 +21,7 @@ import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; -import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; -import com.datastax.oss.driver.shaded.guava.common.collect.MapMaker; -import java.nio.ByteBuffer; -import java.util.concurrent.ConcurrentMap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,15 +32,13 @@ public class RequestProcessorRegistry { private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); public static RequestProcessorRegistry defaultCqlProcessors(String logPrefix) { - ConcurrentMap preparedStatementsCache = - new MapMaker().weakValues().makeMap(); - + CqlPrepareAsyncProcessor prepareAsyncProcessor = new CqlPrepareAsyncProcessor(); return new RequestProcessorRegistry( logPrefix, new CqlRequestSyncProcessor(), new CqlRequestAsyncProcessor(), - new CqlPrepareSyncProcessor(preparedStatementsCache), - new CqlPrepareAsyncProcessor(preparedStatementsCache)); + new CqlPrepareSyncProcessor(prepareAsyncProcessor), + prepareAsyncProcessor); } private final String logPrefix; @@ -72,4 +67,9 @@ public RequestProcessor p } throw new IllegalArgumentException("No request processor found for " + request); } + + /** This creates a defensive copy on every call, do not overuse. */ + public Iterable> getProcessors() { + return ImmutableList.copyOf(processors); + } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index c22b74c03a6..8dea078bfb9 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -789,6 +789,13 @@ datastax-java-driver { # with a DriverTimeoutException (exposed as a Counter). // cql-client-timeouts, + # The size of the driver-side cache of CQL prepared statements. + # + # The cache uses weak values eviction, so this represents the number of PreparedStatement + # instances that your application has created, and is still holding a reference to. Note + # that the returned value is approximate. + // cql-prepared-cache-size, + # How long requests are being throttled (exposed as a Timer). # # This is the time between the start of the session.execute() call, and the moment when diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index b31e7c0426d..3be37fce43f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -49,8 +49,6 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -66,17 +64,12 @@ public class CqlPrepareHandlerTest { @Mock private Node node2; @Mock private Node node3; - private ConcurrentMap preparedStatementsCache = - new ConcurrentHashMap<>(); private final Map payload = ImmutableMap.of("key1", ByteBuffer.wrap(new byte[] {1, 2, 3, 4})); @Before public void setup() { MockitoAnnotations.initMocks(this); - - // By default, simulate that the prepared statement is not already in the driver's cache - preparedStatementsCache.clear(); } @Test @@ -89,12 +82,7 @@ public void should_prepare_on_first_node_and_reprepare_on_others() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); @@ -130,12 +118,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); @@ -151,44 +134,6 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { } } - @Test - public void should_not_reprepare_on_other_nodes_if_already_cached() { - // Simulate an existing entry in the driver's cache: - DefaultPreparedStatement mockExistingStatement = Mockito.mock(DefaultPreparedStatement.class); - preparedStatementsCache.put(Bytes.fromHexString("0xffff"), mockExistingStatement); - - RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); - PoolBehavior node1Behavior = harnessBuilder.customBehavior(node1); - PoolBehavior node2Behavior = harnessBuilder.customBehavior(node2); - PoolBehavior node3Behavior = harnessBuilder.customBehavior(node3); - - try (RequestHandlerTestHarness harness = harnessBuilder.build()) { - - CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") - .handle(); - - node1Behavior.verifyWrite(); - node1Behavior.setWriteSuccess(); - node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); - - // When the statement already existed, we don't prepare on other nodes, so the future should - // complete immediately. - assertThatStage(prepareFuture) - .isSuccess( - preparedStatement -> assertThat(preparedStatement).isSameAs(mockExistingStatement)); - - // And the other nodes should not be contacted: - node2Behavior.verifyNoWrite(); - node3Behavior.verifyNoWrite(); - } - } - @Test public void should_ignore_errors_while_repreparing_on_other_nodes() { RequestHandlerTestHarness.Builder harnessBuilder = @@ -199,12 +144,7 @@ public void should_ignore_errors_while_repreparing_on_other_nodes() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(prepareFuture).isNotDone(); @@ -243,12 +183,7 @@ public void should_retry_initial_prepare_if_recoverable_error() { .thenReturn(RetryDecision.RETRY_NEXT); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); // Success on node2, reprepare on node3 @@ -282,12 +217,7 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { .thenReturn(RetryDecision.RETHROW); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); // Success on node2, reprepare on node3 @@ -322,12 +252,7 @@ public void should_fail_if_retry_policy_ignores_error() { .thenReturn(RetryDecision.IGNORE); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - PREPARE_REQUEST, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") .handle(); // Success on node2, reprepare on node3 @@ -359,12 +284,7 @@ public void should_propagate_custom_payload_on_single_node() { DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - prepareRequest, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(prepareRequest, harness.getSession(), harness.getContext(), "test") .handle(); Mockito.verify(node1Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); @@ -390,12 +310,7 @@ public void should_propagate_custom_payload_on_all_nodes() { DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); CompletionStage prepareFuture = - new CqlPrepareAsyncHandler( - prepareRequest, - preparedStatementsCache, - harness.getSession(), - harness.getContext(), - "test") + new CqlPrepareHandler(prepareRequest, harness.getSession(), harness.getContext(), "test") .handle(); Mockito.verify(node1Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index 162e22787ac..6ac1c487173 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -18,10 +18,12 @@ import static junit.framework.TestCase.fail; import static org.assertj.core.api.Assertions.assertThat; +import com.codahale.metrics.Gauge; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.testinfra.CassandraRequirement; @@ -62,6 +64,9 @@ public class PreparedStatementIT { SessionUtils.configLoaderBuilder() .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 2) .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withStringList( + DefaultDriverOption.METRICS_SESSION_ENABLED, + ImmutableList.of(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE.getPath())) .build()) .build(); @@ -357,4 +362,75 @@ private void should_not_store_metadata_for_conditional_updates(CqlSession sessio assertThat(ps.getResultSetDefinitions()).hasSize(0); assertThat(Bytes.toHexString(ps.getResultMetadataId())).isEqualTo(Bytes.toHexString(idBefore)); } + + @Test + public void should_return_same_instance_when_repreparing_query() { + // Given + CqlSession session = sessionRule.session(); + assertThat(getPreparedCacheSize(session)).isEqualTo(0); + String query = "SELECT * FROM prepared_statement_test WHERE a = ?"; + + // When + PreparedStatement preparedStatement1 = session.prepare(query); + PreparedStatement preparedStatement2 = session.prepare(query); + + // Then + assertThat(preparedStatement1).isSameAs(preparedStatement2); + assertThat(getPreparedCacheSize(session)).isEqualTo(1); + } + + /** Just to illustrate that the driver does not sanitize query strings. */ + @Test + public void should_create_separate_instances_for_differently_formatted_queries() { + // Given + CqlSession session = sessionRule.session(); + assertThat(getPreparedCacheSize(session)).isEqualTo(0); + + // When + PreparedStatement preparedStatement1 = + session.prepare("SELECT * FROM prepared_statement_test WHERE a = ?"); + PreparedStatement preparedStatement2 = + session.prepare("select * from prepared_statement_test where a = ?"); + + // Then + assertThat(preparedStatement1).isNotSameAs(preparedStatement2); + assertThat(getPreparedCacheSize(session)).isEqualTo(2); + } + + @Test + public void should_create_separate_instances_for_different_statement_parameters() { + // Given + CqlSession session = sessionRule.session(); + assertThat(getPreparedCacheSize(session)).isEqualTo(0); + SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM prepared_statement_test"); + + // When + PreparedStatement preparedStatement1 = session.prepare(statement.setPageSize(1)); + PreparedStatement preparedStatement2 = session.prepare(statement.setPageSize(4)); + + // Then + assertThat(preparedStatement1).isNotSameAs(preparedStatement2); + assertThat(getPreparedCacheSize(session)).isEqualTo(2); + // Each bound statement uses the page size it was prepared with + assertThat(firstPageOf(session.executeAsync(preparedStatement1.bind()))).hasSize(1); + assertThat(firstPageOf(session.executeAsync(preparedStatement2.bind()))).hasSize(4); + } + + private static Iterable firstPageOf(CompletionStage stage) { + return CompletableFutures.getUninterruptibly(stage).currentPage(); + } + + @SuppressWarnings("unchecked") + private static long getPreparedCacheSize(CqlSession session) { + return session + .getMetrics() + .flatMap(metrics -> metrics.getSessionMetric(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE)) + .map(metric -> ((Gauge) metric).getValue()) + .orElseThrow( + () -> + new AssertionError( + "Could not access metric " + + DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE.getPath())); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index fe06c11012e..e941f8ee227 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -72,14 +72,13 @@ public RequestProcessorRegistry buildRequestProcessorRegistry() { new MapMaker().weakValues().makeMap(); CqlRequestAsyncProcessor cqlRequestAsyncProcessor = new CqlRequestAsyncProcessor(); - CqlPrepareAsyncProcessor cqlPrepareAsyncProcessor = - new CqlPrepareAsyncProcessor(preparedStatementsCache); + CqlPrepareAsyncProcessor cqlPrepareAsyncProcessor = new CqlPrepareAsyncProcessor(); CqlRequestSyncProcessor cqlRequestSyncProcessor = new CqlRequestSyncProcessor(); return new RequestProcessorRegistry( getSessionName(), cqlRequestSyncProcessor, - new CqlPrepareSyncProcessor(preparedStatementsCache), + new CqlPrepareSyncProcessor(cqlPrepareAsyncProcessor), new GuavaRequestAsyncProcessor<>( cqlRequestAsyncProcessor, Statement.class, GuavaSession.ASYNC), new GuavaRequestAsyncProcessor<>( diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md index 9e63dd0310c..6be868bce70 100644 --- a/manual/core/statements/prepared/README.md +++ b/manual/core/statements/prepared/README.md @@ -55,9 +55,22 @@ client driver Cassandra |<--------------------------------| | ``` -You should prepare only once, and cache the `PreparedStatement` in your application (it is -thread-safe). If you call `prepare` multiple times with the same query string, the driver will log a -warning. +The driver caches prepared statements: if you call `prepare()` multiple times with the same query +string (or a `SimpleStatement` with the same execution characteristics), you will get the same +`PreparedStatement` instance. We still recommend keeping a reference to it (for example by caching +it as a field in a DAO); if that's not possible (e.g. if query strings are generated dynamically), +it's OK to call `prepare()` every time: there will just be a small performance overhead to check the +internal cache. Note that caching is based on: + +* the query string exactly as you provided it: the driver does not perform any kind of trimming or + sanitizing. +* all other execution parameters: for example, preparing two statements with identical query strings + but different consistency levels will yield distinct prepared statements. + +The size of the cache is exposed as a session-level [metric](../../metrics/) +`cql-prepared-cache-size`. The cache uses [weak values]([guava eviction]) eviction, so this +represents the number of `PreparedStatement` instances that your application has created, and is +still holding a reference to. If you execute a query only once, a prepared statement is inefficient because it requires two round trips. Consider a [simple statement](../simple/) instead. @@ -268,3 +281,4 @@ observe the new columns in the result set. [BoundStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BoundStatement.html [CASSANDRA-10786]: https://issues.apache.org/jira/browse/CASSANDRA-10786 +[guava eviction]: https://github.com/google/guava/wiki/CachesExplained#reference-based-eviction \ No newline at end of file diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index a26017e8b3e..e78dff84c66 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -88,6 +88,17 @@ boundSelect = boundSelect.setInt("k", key); Note that, as indicated in the previous section, the public API exposes these types as interfaces: if for some reason you prefer a mutable implementation, it's possible to write your own. +#### Prepared statement cache + +In 3.x, calling `session.prepare()` multiple times with the same query was considered an +anti-pattern, and the driver would log a warning. Client applications were encouraged to cache +`PreparedStatement` instances on their own. + +This cache is now built in: the driver will cache the first `prepare()` call and return the same +instance on subsequent invocations. Calling the method multiple times is no longer an anti-pattern. +See [prepared statements](../manual/core/statements/prepared/) for more details. + + #### Dual result set APIs In 3.x, both synchronous and asynchronous execution models shared a common result set From 5ad43011e4b3ad7a367ace4c60ffbedf3a1bd16c Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 6 Dec 2018 13:20:30 -0800 Subject: [PATCH 648/742] Simplify CQL handler hierarchy --- .../core/cql/CqlRequestAsyncHandler.java | 39 ----------------- .../core/cql/CqlRequestAsyncProcessor.java | 2 +- ...andlerBase.java => CqlRequestHandler.java} | 18 +++++--- .../core/cql/CqlRequestSyncHandler.java | 43 ------------------- .../core/cql/CqlRequestSyncProcessor.java | 16 ++++++- .../internal/core/session/ReprepareOnUp.java | 4 +- .../session/RequestProcessorRegistry.java | 14 ++++-- .../core/cql/CqlRequestHandlerRetryTest.java | 14 +++--- ...equestHandlerSpeculativeExecutionTest.java | 20 ++++----- .../core/cql/CqlRequestHandlerTest.java | 11 +++-- .../cql/CqlRequestHandlerTrackerTest.java | 4 +- .../guava/internal/GuavaDriverContext.java | 3 +- 12 files changed, 64 insertions(+), 124 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java rename core/src/main/java/com/datastax/oss/driver/internal/core/cql/{CqlRequestHandlerBase.java => CqlRequestHandler.java} (98%) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java deleted file mode 100644 index 9c93571060f..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.cql; - -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import java.util.concurrent.CompletionStage; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -public class CqlRequestAsyncHandler extends CqlRequestHandlerBase { - - public CqlRequestAsyncHandler( - Statement statement, - DefaultSession session, - InternalDriverContext context, - String sessionLogPrefix) { - super(statement, session, context, sessionLogPrefix); - } - - public CompletionStage handle() { - return result; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java index b1a9be328c9..837c0062602 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestAsyncProcessor.java @@ -41,7 +41,7 @@ public CompletionStage process( DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestAsyncHandler(request, session, context, sessionLogPrefix).handle(); + return new CqlRequestHandler(request, session, context, sessionLogPrefix).handle(); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java similarity index 98% rename from core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java rename to core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 6d059341f66..f44ca955d2d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -82,6 +82,7 @@ import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -90,9 +91,9 @@ import org.slf4j.LoggerFactory; @ThreadSafe -public abstract class CqlRequestHandlerBase implements Throttled { +public class CqlRequestHandler implements Throttled { - private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandlerBase.class); + private static final Logger LOG = LoggerFactory.getLogger(CqlRequestHandler.class); private static final long NANOTIME_NOT_MEASURED_YET = -1; private final long startTimeNanos; @@ -133,7 +134,7 @@ public abstract class CqlRequestHandlerBase implements Throttled { // We don't use a map because nodes can appear multiple times. private volatile List> errors; - protected CqlRequestHandlerBase( + protected CqlRequestHandler( Statement statement, DefaultSession session, InternalDriverContext context, @@ -210,6 +211,10 @@ public void onThrottleReady(boolean wasDelayed) { sendRequest(null, queryPlan, 0, 0, true); } + public CompletionStage handle() { + return result; + } + private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration.toNanos() > 0) { try { @@ -289,7 +294,7 @@ private void recordError(Node node, Throwable error) { // Use a local variable to do only a single single volatile read in the nominal case List> errorsSnapshot = this.errors; if (errorsSnapshot == null) { - synchronized (CqlRequestHandlerBase.this) { + synchronized (CqlRequestHandler.this) { errorsSnapshot = this.errors; if (errorsSnapshot == null) { this.errors = errorsSnapshot = new CopyOnWriteArrayList<>(); @@ -512,13 +517,12 @@ private void scheduleSpeculativeExecution(int index, long delay) { if (!result.isDone()) { LOG.trace( "[{}] Starting speculative execution {}", - CqlRequestHandlerBase.this.logPrefix, + CqlRequestHandler.this.logPrefix, index); activeExecutionsCount.incrementAndGet(); startedSpeculativeExecutionsCount.incrementAndGet(); // Note that `node` is the first node of the execution, it might not be the - // "slow" - // one if there were retries, but in practice retries are rare. + // "slow" one if there were retries, but in practice retries are rare. ((DefaultNode) node) .getMetricUpdater() .incrementCounter( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java deleted file mode 100644 index ffe16735131..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.cql; - -import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.session.DefaultSession; -import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; -import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -public class CqlRequestSyncHandler extends CqlRequestHandlerBase { - - public CqlRequestSyncHandler( - Statement statement, - DefaultSession session, - InternalDriverContext context, - String sessionLogPrefix) { - super(statement, session, context, sessionLogPrefix); - } - - public ResultSet handle() { - BlockingOperation.checkNotDriverThread(); - AsyncResultSet firstPage = CompletableFutures.getUninterruptibly(result); - return ResultSets.newInstance(firstPage); - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java index fc52ec483f6..53cddc7772b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestSyncProcessor.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.cql; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.session.Request; @@ -22,11 +23,19 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestProcessor; +import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import net.jcip.annotations.ThreadSafe; @ThreadSafe public class CqlRequestSyncProcessor implements RequestProcessor, ResultSet> { + private final CqlRequestAsyncProcessor asyncProcessor; + + public CqlRequestSyncProcessor(CqlRequestAsyncProcessor asyncProcessor) { + this.asyncProcessor = asyncProcessor; + } + @Override public boolean canProcess(Request request, GenericType resultType) { return request instanceof Statement && resultType.equals(Statement.SYNC); @@ -38,7 +47,12 @@ public ResultSet process( DefaultSession session, InternalDriverContext context, String sessionLogPrefix) { - return new CqlRequestSyncHandler(request, session, context, sessionLogPrefix).handle(); + + BlockingOperation.checkNotDriverThread(); + AsyncResultSet firstPage = + CompletableFutures.getUninterruptibly( + asyncProcessor.process(request, session, context, sessionLogPrefix)); + return ResultSets.newInstance(firstPage); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java index 8c909c06b02..bd65c045673 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUp.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.cql.CqlRequestHandlerBase; +import com.datastax.oss.driver.internal.core.cql.CqlRequestHandler; import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; @@ -51,7 +51,7 @@ * *

          See the comments in {@code reference.conf} for more explanations about this process. If any * prepare request fail, we ignore the error because it will be retried on the fly (see {@link - * CqlRequestHandlerBase}). + * CqlRequestHandler}). * *

          Logically this code belongs to {@link DefaultSession}, but it was extracted for modularity and * testability. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java index 90e4a79bc1f..aca57fda97f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/RequestProcessorRegistry.java @@ -32,13 +32,19 @@ public class RequestProcessorRegistry { private static final Logger LOG = LoggerFactory.getLogger(RequestProcessorRegistry.class); public static RequestProcessorRegistry defaultCqlProcessors(String logPrefix) { + CqlRequestAsyncProcessor requestAsyncProcessor = new CqlRequestAsyncProcessor(); + CqlRequestSyncProcessor requestSyncProcessor = + new CqlRequestSyncProcessor(requestAsyncProcessor); CqlPrepareAsyncProcessor prepareAsyncProcessor = new CqlPrepareAsyncProcessor(); + CqlPrepareSyncProcessor prepareSyncProcessor = + new CqlPrepareSyncProcessor(prepareAsyncProcessor); + return new RequestProcessorRegistry( logPrefix, - new CqlRequestSyncProcessor(), - new CqlRequestAsyncProcessor(), - new CqlPrepareSyncProcessor(prepareAsyncProcessor), - prepareAsyncProcessor); + requestAsyncProcessor, + requestSyncProcessor, + prepareAsyncProcessor, + prepareSyncProcessor); } private final String logPrefix; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index eca062cdaac..2cb3970876c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -72,7 +72,7 @@ public void should_always_try_next_node_if_bootstrapping( .build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -112,7 +112,7 @@ public void should_always_rethrow_query_validation_error( .build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -153,7 +153,7 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETRY_NEXT); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -204,7 +204,7 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETRY_SAME); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -254,7 +254,7 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().getRetryPolicy(anyString()), RetryDecision.IGNORE); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -303,7 +303,7 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( harness.getContext().getRetryPolicy(anyString()), RetryDecision.RETHROW); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) @@ -351,7 +351,7 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re } CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); assertThatStage(resultSetFuture) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index f07fad685a3..a9d9a72a1e6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -54,8 +54,7 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") - .handle(); + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test").handle(); node1Behavior.verifyWrite(); @@ -95,8 +94,7 @@ public void should_schedule_speculative_executions( any(Node.class), eq(null), eq(statement), eq(3))) .thenReturn(-1L); - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") - .handle(); + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test").handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -151,8 +149,8 @@ public void should_not_start_execution_if_result_complete( any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); - CqlRequestAsyncHandler requestHandler = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test"); + CqlRequestHandler requestHandler = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test"); CompletionStage resultSetFuture = requestHandler.handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -210,7 +208,7 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); harness.nextScheduledTimeout(); // Discard the timeout task @@ -241,7 +239,7 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -294,7 +292,7 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -350,7 +348,7 @@ public void should_retry_in_speculative_executions( .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); @@ -399,7 +397,7 @@ public void should_stop_retrying_other_executions_if_result_complete( any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler(statement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") .handle(); node1Behavior.verifyWrite(); node1Behavior.setWriteSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 14525f54dfb..359794a726c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -56,7 +56,7 @@ public void should_complete_result_if_first_node_replies_immediately() { .build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), @@ -90,7 +90,7 @@ public void should_fail_if_no_node_available() { .build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), @@ -111,7 +111,7 @@ public void should_time_out_if_first_node_takes_too_long_to_respond() throws Exc try (RequestHandlerTestHarness harness = harnessBuilder.build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), @@ -143,7 +143,7 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { .build()) { CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), @@ -186,8 +186,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce Mockito.when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( - boundStatement, harness.getSession(), harness.getContext(), "test") + new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext(), "test") .handle(); // Before we proceed, mock the PREPARE exchange that will occur as soon as we complete the diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java index 1e73600e480..71c29d579ff 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java @@ -50,7 +50,7 @@ public void should_invoke_request_tracker() { Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), @@ -100,7 +100,7 @@ public void should_not_invoke_noop_request_tracker() { Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); CompletionStage resultSetFuture = - new CqlRequestAsyncHandler( + new CqlRequestHandler( UNDEFINED_IDEMPOTENCE_STATEMENT, harness.getSession(), harness.getContext(), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index e941f8ee227..045f376e9c8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -73,7 +73,8 @@ public RequestProcessorRegistry buildRequestProcessorRegistry() { CqlRequestAsyncProcessor cqlRequestAsyncProcessor = new CqlRequestAsyncProcessor(); CqlPrepareAsyncProcessor cqlPrepareAsyncProcessor = new CqlPrepareAsyncProcessor(); - CqlRequestSyncProcessor cqlRequestSyncProcessor = new CqlRequestSyncProcessor(); + CqlRequestSyncProcessor cqlRequestSyncProcessor = + new CqlRequestSyncProcessor(cqlRequestAsyncProcessor); return new RequestProcessorRegistry( getSessionName(), From 86a13e2e3a15aeda5967fe0361f491ded0fa5a23 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Mon, 17 Dec 2018 10:27:07 -0600 Subject: [PATCH 649/742] JAVA-2038 Add reconnection policy that adds jitter delays between attempts (#1144) --- changelog/README.md | 1 + .../ExponentialReconnectionPolicy.java | 25 +++++-- .../ExponentialReconnectionPolicyTest.java | 68 +++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java diff --git a/changelog/README.md b/changelog/README.md index f635babaef9..9aa1b16f135 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [improvement] JAVA-2038: Add jitter to delays between reconnection attempts - [improvement] JAVA-2053: Cache results of session.prepare() - [improvement] JAVA-2058: Make programmatic config reloading part of the public API - [improvement] JAVA-1943: Fail fast in execute() when the session is closed diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index c78981156ac..d8cf728e847 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; +import java.util.concurrent.ThreadLocalRandom; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,10 @@ * *

          It uses the same schedule implementation for individual nodes or the control connection: * reconnection attempt {@code i} will be tried {@code Math.min(2^(i-1) * getBaseDelayMs(), - * getMaxDelayMs())} milliseconds after the previous one. + * getMaxDelayMs())} milliseconds after the previous one. A random amount of jitter (+/- 15%) will + * be added to the pure exponential delay value to avoid situations where many clients are in the + * reconnection process at exactly the same time. The jitter will never cause the delay to be less + * than the base delay, or more than the max delay. * *

          To activate this policy, modify the {@code advanced.reconnection-policy} section in the driver * configuration, for example: @@ -137,11 +141,22 @@ private class ExponentialSchedule implements ReconnectionSchedule { @NonNull @Override public Duration nextDelay() { - long delay = - (attempts > maxAttempts) - ? maxDelayMs - : Math.min(baseDelayMs * (1L << attempts++), maxDelayMs); + long delay = (attempts > maxAttempts) ? maxDelayMs : calculateDelayWithJitter(); return Duration.ofMillis(delay); } + + private long calculateDelayWithJitter() { + // assert we haven't hit the max attempts + assert attempts <= maxAttempts; + // get the pure exponential delay based on the attempt count + long delay = Math.min(baseDelayMs * (1L << attempts++), maxDelayMs); + // calculate up to 15% jitter, plus or minus (i.e. 85 - 115% of the pure value) + int jitter = ThreadLocalRandom.current().nextInt(85, 116); + // apply jitter + delay = (jitter * delay) / 100; + // ensure the final delay is between the base and max + delay = Math.min(maxDelayMs, Math.max(baseDelayMs, delay)); + return delay; + } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java new file mode 100644 index 00000000000..c59cc273a94 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java @@ -0,0 +1,68 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.connection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.context.DriverContext; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +public class ExponentialReconnectionPolicyTest { + + @Mock private DriverContext driverContext; + @Mock private DriverConfig driverConfig; + @Mock private DriverExecutionProfile profile; + private final long baseDelay = 1000L; + private final long maxDelay = 60000L; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + Mockito.when(driverConfig.getDefaultProfile()).thenReturn(profile); + Mockito.when(driverContext.getConfig()).thenReturn(driverConfig); + Mockito.when(profile.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY)) + .thenReturn(Duration.of(baseDelay, ChronoUnit.MILLIS)); + Mockito.when(profile.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY)) + .thenReturn(Duration.of(maxDelay, ChronoUnit.MILLIS)); + } + + @Test + public void should_generate_exponential_delay_with_jitter() throws Exception { + ExponentialReconnectionPolicy policy = new ExponentialReconnectionPolicy(driverContext); + ReconnectionPolicy.ReconnectionSchedule schedule = policy.newControlConnectionSchedule(); + // generate a number of delays and make sure they are all within the base/max values range + for (int i = 0; i < 128; ++i) { + // compute the min and max delays based on attempt count (i) + long exponentialDelay = Math.min(baseDelay * (1L << i), maxDelay); + // min will be 85% of the pure exponential delay (with a floor of baseDelay) + long minJitterDelay = Math.min(baseDelay, (exponentialDelay * 85) / 100); + // max will be 115% of the pure exponential delay (with a ceiling of maxDelay) + long maxJitterDelay = Math.max(maxDelay, (exponentialDelay * 115) / 100); + long delay = schedule.nextDelay().toMillis(); + assertThat(delay).isBetween(minJitterDelay, maxJitterDelay); + } + } +} From c66b77d5416d884c27b4baa5ef5e6d3e5e22153c Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Dec 2018 14:55:22 -0600 Subject: [PATCH 650/742] JAVA-2061: Add section to upgrade guide about updated type mappings (#1152) --- changelog/README.md | 1 + upgrade_guide/README.md | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 9aa1b16f135..56babdee9be 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [documentation] JAVA-2061: Add section to upgrade guide about updated type mappings - [improvement] JAVA-2038: Add jitter to delays between reconnection attempts - [improvement] JAVA-2053: Cache results of session.prepare() - [improvement] JAVA-2058: Make programmatic config reloading part of the public API diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index e78dff84c66..30799c05c6e 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -124,6 +124,30 @@ programming][4.x async programming] and [paging][4.x paging]. [4.x async programming]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/async/ [4.x paging]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/paging/ +#### CQL to Java type mappings + +Since the driver now has access to Java 8 types, some of the [CQL to Java type +mappings] have changed when it comes to [temporal types] such as `date` and +`timestamp`. These changes are: + +* `getDate` has been replaced by `getLocalDate` and returns + [java.time.LocalDate]; +* `getTime` has been replaced by `getLocalTime` and returns + [java.time.LocalTime] instead of a `long` representing nanoseconds since + midnight; +* `getTimestamp` has been replaced by `getInstant` and returns + [java.time.Instant] instead of [java.util.Date]. + +The corresponding setter methods were also changed to expect these new types +as inputs. + +[CQL to Java type mappings]: ../manual/core#cql-to-java-type-mapping +[temporal types]: ../manual/core/temporal_types +[java.time.LocalDate]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html +[java.time.LocalTime]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html +[java.time.Instant]: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html +[java.util.Date]: https://docs.oracle.com/javase/8/docs/api/java/util/Date.html + #### Simplified request timeout The driver-side request timeout -- defined by the `request.timeout` configuration option -- now From ece9ee1e85524e25f232c0d43685b0b5e91a88dc Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Dec 2018 15:13:25 -0600 Subject: [PATCH 651/742] Test fixes for 4.0.0-beta3 (#1154) --- .../DropwizardSessionMetricUpdater.java | 41 ++++++++++--------- .../core/config/DriverConfigValidationIT.java | 2 +- .../core/config/DriverExecutionProfileIT.java | 3 +- .../DriverExecutionProfileReloadIT.java | 15 ++++--- .../PerProfileLoadBalancingPolicyIT.java | 12 ++---- .../driver/api/core/metadata/SchemaIT.java | 16 +++++++- .../driver/api/core/session/ShutdownIT.java | 22 ++++++++-- 7 files changed, 71 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java index ab487546cac..3a81bcad221 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardSessionMetricUpdater.java @@ -49,9 +49,9 @@ public DropwizardSessionMetricUpdater( this.metricNamePrefix = context.getSessionName() + "."; if (enabledMetrics.contains(DefaultSessionMetric.CONNECTED_NODES)) { - this.registry.register( + this.registry.gauge( buildFullName(DefaultSessionMetric.CONNECTED_NODES, null), - (Gauge) + () -> () -> { int count = 0; for (Node node : context.getMetadataManager().getMetadata().getNodes().values()) { @@ -63,26 +63,29 @@ public DropwizardSessionMetricUpdater( }); } if (enabledMetrics.contains(DefaultSessionMetric.THROTTLING_QUEUE_SIZE)) { - this.registry.register( + this.registry.gauge( buildFullName(DefaultSessionMetric.THROTTLING_QUEUE_SIZE, null), - buildQueueGauge(context.getRequestThrottler(), context.getSessionName())); + () -> buildQueueGauge(context.getRequestThrottler(), context.getSessionName())); } if (enabledMetrics.contains(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE)) { - Cache cache = getPreparedStatementCache(context); - Gauge gauge; - if (cache == null) { - LOG.warn( - "[{}] Metric {} is enabled in the config, " - + "but it looks like no CQL prepare processor is registered. " - + "The gauge will always return 0", - context.getSessionName(), - DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE.getPath()); - gauge = () -> 0L; - } else { - gauge = cache::size; - } - this.registry.register( - buildFullName(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE, null), gauge); + this.registry.gauge( + buildFullName(DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE, null), + () -> { + Cache cache = getPreparedStatementCache(context); + Gauge gauge; + if (cache == null) { + LOG.warn( + "[{}] Metric {} is enabled in the config, " + + "but it looks like no CQL prepare processor is registered. " + + "The gauge will always return 0", + context.getSessionName(), + DefaultSessionMetric.CQL_PREPARED_CACHE_SIZE.getPath()); + gauge = () -> 0L; + } else { + gauge = cache::size; + } + return gauge; + }); } initializeHdrTimer( DefaultSessionMetric.CQL_REQUESTS, diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java index 57cac459aee..60fb91fe6b9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverConfigValidationIT.java @@ -60,7 +60,7 @@ private void should_fail_to_init_with_invalid_policy(DefaultDriverOption option) assertThat(error).isInstanceOf(DriverExecutionException.class); assertThat(error.getCause()) .isInstanceOf(IllegalArgumentException.class) - .hasMessage( + .hasMessageContaining( "Can't find class AClassThatDoesNotExist " + "(specified by " + option.getPath() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index 98908a66e24..c26d4247535 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -78,6 +78,7 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { public void should_use_profile_request_timeout() { DriverConfigLoader loader = SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(2)) .withProfile( "olap", DefaultDriverConfigLoaderBuilder.profileBuilder() @@ -89,7 +90,7 @@ public void should_use_profile_request_timeout() { // configure query with delay of 4 seconds. simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - // Execute query without profile, should timeout with default (2s). + // Execute query without profile, should timeout with default session timeout (2s). try { session.execute(query); fail("Should have timed out"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index 3ff5883fe62..c40ed9da77e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -53,7 +53,9 @@ public void should_periodically_reload_configuration() throws Exception { new DefaultDriverConfigLoader( () -> ConfigFactory.parseString( - "basic.config-reload-interval = 2s\n" + configSource.get()) + "basic.config-reload-interval = 2s\n" + + "basic.request.timeout = 2s\n" + + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = (CqlSession) @@ -63,7 +65,7 @@ public void should_periodically_reload_configuration() throws Exception { .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - // Expect timeout since default timeout is 2s + // Expect timeout since default session timeout is 2s try { session.execute(query); fail("DriverTimeoutException expected"); @@ -88,7 +90,10 @@ public void should_reload_configuration_when_event_fired() throws Exception { DefaultDriverConfigLoader loader = new DefaultDriverConfigLoader( () -> - ConfigFactory.parseString("basic.config-reload-interval = 0\n" + configSource.get()) + ConfigFactory.parseString( + "basic.config-reload-interval = 0\n" + + "basic.request.timeout = 2s\n" + + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = (CqlSession) @@ -98,7 +103,7 @@ public void should_reload_configuration_when_event_fired() throws Exception { .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); - // Expect timeout since default timeout is 2s + // Expect timeout since default session timeout is 2s try { session.execute(query); fail("DriverTimeoutException expected"); @@ -144,7 +149,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { } // Bump up request timeout to 10 seconds on profile and wait for config to reload. - configSource.set("profiles.slow.basic.request.timeout = 2s"); + configSource.set("profiles.slow.basic.request.timeout = 10s"); waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should expect to fail again because doesn't allow to dynamically define diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java index 8ee13c5c2d5..3f8cb19d7b8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/PerProfileLoadBalancingPolicyIT.java @@ -31,7 +31,6 @@ import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; -import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import org.junit.Before; import org.junit.BeforeClass; @@ -88,13 +87,10 @@ public static void setup() { .hasSize(3) .containsKeys(DriverExecutionProfile.DEFAULT_NAME, "profile1", "profile2"); - DefaultLoadBalancingPolicy defaultPolicy = - (DefaultLoadBalancingPolicy) - context.getLoadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); - DefaultLoadBalancingPolicy policy1 = - (DefaultLoadBalancingPolicy) context.getLoadBalancingPolicy("profile1"); - DefaultLoadBalancingPolicy policy2 = - (DefaultLoadBalancingPolicy) context.getLoadBalancingPolicy("profile2"); + LoadBalancingPolicy defaultPolicy = + context.getLoadBalancingPolicy(DriverExecutionProfile.DEFAULT_NAME); + LoadBalancingPolicy policy1 = context.getLoadBalancingPolicy("profile1"); + LoadBalancingPolicy policy2 = context.getLoadBalancingPolicy("profile2"); assertThat(defaultPolicy).isSameAs(policy2).isNotSameAs(policy1); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 269d963258b..457cb576cb0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -35,6 +36,7 @@ import com.datastax.oss.driver.categories.ParallelizableTests; import java.util.Collections; import java.util.Map; +import org.junit.AssumptionViolatedException; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -179,18 +181,28 @@ public void should_refresh_schema_manually() { @Test public void should_get_virtual_metadata() { Metadata md = sessionRule.session().getMetadata(); + // Special case: DSE 6.0 reports C* 4.0 but does not support virtual tables + if (ccmRule.getDseVersion().isPresent()) { + Version dseVersion = ccmRule.getDseVersion().get(); + if (dseVersion.compareTo(Version.parse("6.7.0")) < 0) { + throw new AssumptionViolatedException("DSE 6.0 does not support virtual tables"); + } + } KeyspaceMetadata kmd = md.getKeyspace("system_views").get(); - // Keyspace name should be set, marked as virtual, and have a clients table. + // Keyspace name should be set, marked as virtual, and have at least sstable_tasks table. // All other values should be defaulted since they are not defined in the virtual schema tables. - assertThat(kmd.getTables().size()).isGreaterThanOrEqualTo(2); + assertThat(kmd.getTables().size()).isGreaterThanOrEqualTo(1); assertThat(kmd.isVirtual()).isTrue(); assertThat(kmd.isDurableWrites()).isFalse(); assertThat(kmd.getName().asCql(true)).isEqualTo("system_views"); + + // Virtual tables lack User Types, Functions, Views and Aggregates assertThat(kmd.getUserDefinedTypes().size()).isEqualTo(0); assertThat(kmd.getFunctions().size()).isEqualTo(0); assertThat(kmd.getViews().size()).isEqualTo(0); assertThat(kmd.getAggregates().size()).isEqualTo(0); + assertThat(kmd.describe(true)) .isEqualTo( "/* VIRTUAL KEYSPACE system_views WITH replication = { 'class' : 'null' } " diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java index de42cac44ca..b22cf3722d8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ShutdownIT.java @@ -19,8 +19,8 @@ import static com.datastax.oss.simulacron.common.stubbing.PrimeDsl.when; import static org.assertj.core.api.Assertions.assertThat; +import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.NoNodeAvailableException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.connection.ClosedConnectionException; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -81,7 +81,7 @@ public void should_fail_requests_when_session_is_closed() throws Exception { .whenComplete( (ignoredResult, error) -> { semaphore.release(); - // Three things can happen: + // Four things can happen: // - DefaultSession.execute() detects that it's closed and fails the // request immediately // - the request was in flight and gets aborted when its channel is @@ -89,12 +89,26 @@ public void should_fail_requests_when_session_is_closed() throws Exception { // - the request races with the shutdown: it gets past execute() but by // the time it tries to acquire a channel the pool was closed // => NoNodeAvailableException + // - the request races with the channel closing: it acquires a channel, + // but by the time it tries to write on it is closing + // => AllNodesFailedException wrapping IllegalStateException if (error instanceof IllegalStateException && "Session is closed".equals(error.getMessage())) { gotSessionClosedError.countDown(); + } else if (error instanceof AllNodesFailedException) { + AllNodesFailedException anfe = (AllNodesFailedException) error; + // if there were 0 errors, its a NoNodeAvailableException which is + // acceptable. + if (anfe.getErrors().size() > 0) { + assertThat(anfe.getErrors()).hasSize(1); + error = anfe.getErrors().values().iterator().next(); + if (!(error instanceof IllegalStateException) + && !error.getMessage().endsWith("is closing")) { + unexpectedErrors.add(error.toString()); + } + } } else if (error != null - && !(error instanceof ClosedConnectionException - || error instanceof NoNodeAvailableException)) { + && !(error instanceof ClosedConnectionException)) { unexpectedErrors.add(error.toString()); } }); From b6abed591d321645eb5dea760a139610e5acd63d Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Dec 2018 15:42:26 -0600 Subject: [PATCH 652/742] Expect IllegalArgumentException instead of AIOBE Needed after JAVA-1767 changed the exception type raised. --- .../datastax/oss/driver/api/core/cql/PreparedStatementIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index 6ac1c487173..050973004bd 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -195,7 +195,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { try { row.getInt("d"); fail("expected an error"); - } catch (ArrayIndexOutOfBoundsException e) { + } catch (IllegalArgumentException e) { /*expected*/ } } From 175864c82ef9d8006b780a0ae9b67d06ce6dd488 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Dec 2018 17:40:11 -0600 Subject: [PATCH 653/742] Use getCassandraVersion over CcmBridge.VERSION --- .../oss/driver/api/core/metadata/CaseSensitiveUdtIT.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java index f88fed1a4b7..0587f29441e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/CaseSensitiveUdtIT.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -61,7 +60,7 @@ public class CaseSensitiveUdtIT { @Test public void should_expose_metadata_with_correct_case() { - boolean supportsFunctions = CcmBridge.VERSION.compareTo(Version.V2_2_0) >= 0; + boolean supportsFunctions = ccmRule.getCassandraVersion().compareTo(Version.V2_2_0) >= 0; CqlSession session = sessionRule.session(); From 55176f83331f90914f97805180944e69142e36a1 Mon Sep 17 00:00:00 2001 From: Andrew Tolbert Date: Mon, 17 Dec 2018 18:27:33 -0600 Subject: [PATCH 654/742] Include session timeout in test config --- .../api/core/config/DriverExecutionProfileReloadIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index c40ed9da77e..a4690792dbc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -169,7 +169,9 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio new DefaultDriverConfigLoader( () -> ConfigFactory.parseString( - "profiles.slow.basic.request.consistency = ONE\nbasic.config-reload-interval = 2s\n" + "profiles.slow.basic.request.consistency = ONE\n" + + "basic.config-reload-interval = 2s\n" + + "basic.request.timeout = 2s\n" + configSource.get()) .withFallback(DEFAULT_CONFIG_SUPPLIER.get())); try (CqlSession session = From 289be8e59af39fa771d5074e1c87a34900420acc Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Tue, 18 Dec 2018 06:23:05 -0600 Subject: [PATCH 655/742] JAVA-2066: Array index range error when fetching routing keys on bound statements (#1155) --- changelog/README.md | 1 + .../core/cql/DefaultBoundStatement.java | 5 +- .../driver/api/core/cql/BoundStatementIT.java | 49 +++++++++++++++---- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 56babdee9be..7a9898b5860 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-beta3 (in progress) +- [bug] JAVA-2066: Array index range error when fetching routing keys on bound statements - [documentation] JAVA-2061: Add section to upgrade guide about updated type mappings - [improvement] JAVA-2038: Add jitter to delays between reconnection attempts - [improvement] JAVA-2053: Cache results of session.prepare() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 9e47393dd8d..11d3e049368 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -333,12 +333,13 @@ public ByteBuffer getRoutingKey() { return getBytesUnsafe(indices.get(0)); } else { ByteBuffer[] components = new ByteBuffer[indices.size()]; - for (Integer index : indices) { + for (int i = 0; i < components.length; i++) { ByteBuffer value; + int index = indices.get(i); if (!isSet(index) || (value = getBytesUnsafe(index)) == null) { return null; } else { - components[index] = value; + components[i] = value; } } return RoutingKey.compose(components); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 2497ce9d955..5027d8b637b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -40,6 +41,7 @@ import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.type.codec.CqlIntToStringCodec; +import com.datastax.oss.driver.internal.core.util.RoutingKey; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.Message; @@ -58,8 +60,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -71,14 +71,13 @@ @Category(ParallelizableTests.class) public class BoundStatementIT { - @ClassRule - public static SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); + @Rule public SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(1)); - private static CcmRule ccm = CcmRule.getInstance(); + private CcmRule ccm = CcmRule.getInstance(); - private static final boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; + private final boolean atLeastV4 = ccm.getHighestProtocolVersion().getCode() >= 4; - private static SessionRule sessionRule = + private SessionRule sessionRule = SessionRule.builder(ccm) .withConfigLoader( SessionUtils.configLoaderBuilder() @@ -86,7 +85,7 @@ public class BoundStatementIT { .build()) .build(); - @ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); + @Rule public TestRule chain = RuleChain.outerRule(ccm).around(sessionRule); @Rule public TestName name = new TestName(); @@ -96,8 +95,8 @@ public class BoundStatementIT { private static final int VALUE = 7; - @BeforeClass - public static void setupSchema() { + @Before + public void setupSchema() { // table where every column forms the primary key. sessionRule .session() @@ -122,6 +121,17 @@ public static void setupSchema() { SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v0 int)") .withExecutionProfile(sessionRule.slowProfile()) .build()); + + // table with composite partition key + sessionRule + .session() + .execute( + SimpleStatement.builder( + "CREATE TABLE IF NOT EXISTS test3 " + + "(pk1 int, pk2 int, v int, " + + "PRIMARY KEY ((pk1, pk2)))") + .withExecutionProfile(sessionRule.slowProfile()) + .build()); } @Before @@ -477,6 +487,25 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { } } + // Test for JAVA-2066 + @Test + @CassandraRequirement(min = "2.2") + public void should_compute_routing_key_when_indices_randomly_distributed() { + try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { + + PreparedStatement ps = session.prepare("INSERT INTO test3 (v, pk2, pk1) VALUES (?,?,?)"); + + List indices = ps.getPartitionKeyIndices(); + assertThat(indices).containsExactly(2, 1); + + BoundStatement bs = ps.bind(1, 2, 3); + ByteBuffer routingKey = bs.getRoutingKey(); + + assertThat(routingKey) + .isEqualTo(RoutingKey.compose(bs.getBytesUnsafe(2), bs.getBytesUnsafe(1))); + } + } + private static void verifyUnset( CqlSession session, BoundStatement boundStatement, String valueName) { session.execute(boundStatement.unset(1)); From 4a6b0def3cb9d7fc2ef87edaa746692d1fca3d30 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 18 Dec 2018 13:43:17 +0100 Subject: [PATCH 656/742] Update version in docs --- changelog/README.md | 2 +- core/revapi.json | 10 +++++----- manual/core/README.md | 2 +- manual/core/compression/README.md | 2 +- manual/core/integration/README.md | 16 ++++++++-------- manual/core/shaded_jar/README.md | 6 +++--- manual/query_builder/README.md | 2 +- query-builder/revapi.json | 8 ++++---- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 7a9898b5860..9c3e3cd9880 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-beta3 (in progress) +### 4.0.0-beta3 - [bug] JAVA-2066: Array index range error when fetching routing keys on bound statements - [documentation] JAVA-2061: Add section to upgrade guide about updated type mappings diff --git a/core/revapi.json b/core/revapi.json index 0cabe57ffe9..6ec321c55b1 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -638,7 +638,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", "classSimpleName": "ResultSet", "methodName": "getAvailableWithoutFetching", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" }, @@ -649,7 +649,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", "classSimpleName": "ResultSet", "methodName": "isFullyFetched", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" }, @@ -662,7 +662,7 @@ "classSimpleName": "SessionBuilder", "methodName": "buildContext", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2049: Add shorthand method to SessionBuilder to specify local DC" }, @@ -673,7 +673,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", "classSimpleName": "DriverConfigLoader", "methodName": "reload", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2058: Make programmatic config reloading part of the public API" }, @@ -684,7 +684,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", "classSimpleName": "DriverConfigLoader", "methodName": "supportsReloading", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2058: Make programmatic config reloading part of the public API" } diff --git a/manual/core/README.md b/manual/core/README.md index 8a8b950a2db..092a4d93c51 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 ``` diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index c6aea1c0b1a..2ab0ff75079 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -65,4 +65,4 @@ Dependency: Always double-check the exact Snappy version needed; you can find it in the driver's [parent POM]. -[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta2%7Cpom \ No newline at end of file +[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta3%7Cpom \ No newline at end of file diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 90c8a834d53..7ae32ee7677 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -39,7 +39,7 @@ dependencies, and tell Maven that we're going to use Java 8: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 ch.qos.logback @@ -144,7 +144,7 @@ You should see output similar to: [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3.1:java (default-cli) @ yourapp --- -11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta2 +11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta3 11:39:45.648 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision 11:39:45.649 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 3.11.2 @@ -176,7 +176,7 @@ repositories { } dependencies { - compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta2' + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta3' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } ``` @@ -260,7 +260,7 @@ In that case, you can exclude the dependency: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 com.typesafe @@ -288,7 +288,7 @@ are not available on your platform, you can exclude the following dependencies: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 com.github.jnr @@ -322,7 +322,7 @@ and never call [Session.getMetrics] anywhere in your application, you can remove com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 io.dropwizard.metrics @@ -343,7 +343,7 @@ If all of these metrics are disabled, you can remove the dependency: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 org.hdrhistogram @@ -369,7 +369,7 @@ exclude them: com.datastax.oss java-driver-core - 4.0.0-beta2 + 4.0.0-beta3 com.github.stephenc.jcip diff --git a/manual/core/shaded_jar/README.md b/manual/core/shaded_jar/README.md index 975e86631b5..9633b8bdd3f 100644 --- a/manual/core/shaded_jar/README.md +++ b/manual/core/shaded_jar/README.md @@ -12,7 +12,7 @@ package name: com.datastax.oss java-driver-core-shaded - 4.0.0-beta2 + 4.0.0-beta3 ``` @@ -23,12 +23,12 @@ dependency to the non-shaded JAR: com.datastax.oss java-driver-core-shaded - 4.0.0-beta2 + 4.0.0-beta3 com.datastax.oss java-driver-query-builder - 4.0.0-beta2 + 4.0.0-beta3 com.datastax.oss diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index 8ed3de9969e..d21da93e8f5 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -14,7 +14,7 @@ To use it in your application, add the following dependency: com.datastax.oss java-driver-query-builder - 4.0.0-beta2 + 4.0.0-beta3 ``` diff --git a/query-builder/revapi.json b/query-builder/revapi.json index d9fa8c894e9..5567afe6dcc 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -35,7 +35,7 @@ "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", "package": "com.datastax.oss.driver.api.querybuilder.*", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", "elementKind": "parameter", "justification": "Fix nullability annotations on query builder range selectors" }, @@ -57,7 +57,7 @@ "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", "package": "com.datastax.oss.driver.api.querybuilder.*", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", "elementKind": "parameter", "justification": "Fix nullability annotations on query builder range selectors" }, @@ -79,7 +79,7 @@ "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", "package": "com.datastax.oss.driver.api.querybuilder.select", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", "elementKind": "parameter", "justification": "Fix nullability annotations on query builder range selectors" }, @@ -101,7 +101,7 @@ "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", "package": "com.datastax.oss.driver.api.querybuilder.select", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", "elementKind": "parameter", "justification": "Fix nullability annotations on query builder range selectors" } From ea83828617227c5f35bae27892b45132abef60e2 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 18 Dec 2018 13:52:00 +0100 Subject: [PATCH 657/742] [maven-release-plugin] prepare release 4.0.0-beta3 --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 8fd37dbc1be..d9e77f3abd8 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index c9650be03f7..49b9a2341cb 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index a80259b2d33..8cf91466d72 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 91df7c247c5..c41274d838c 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index ab33ff2690f..84b898f47cc 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 pom DataStax Java driver for Apache Cassandra(R) @@ -605,7 +605,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-beta3 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index d9f815613b5..0477f76b01f 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 7dc4d5d7d32..dd5fe219719 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3-SNAPSHOT + 4.0.0-beta3 java-driver-test-infra From 6945725c6db8002cd9dd52ee880af6b2f6f632e6 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 18 Dec 2018 13:52:17 +0100 Subject: [PATCH 658/742] [maven-release-plugin] prepare for next development iteration --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index d9e77f3abd8..8b95f71f6d5 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 49b9a2341cb..b016372a1b8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index 8cf91466d72..24846fe3c9f 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c41274d838c..9235b6e5a17 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 84b898f47cc..f3b2bc914b4 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT pom DataStax Java driver for Apache Cassandra(R) @@ -605,7 +605,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-beta3 + HEAD diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 0477f76b01f..a9f8d1b244e 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index dd5fe219719..494a9249adf 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-beta3 + 4.0.0-rc1-SNAPSHOT java-driver-test-infra From e5d78c3b3912a67b060460373144367bd96292d6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 27 Dec 2018 10:59:08 +0100 Subject: [PATCH 659/742] Prepare changelog for next iteration --- changelog/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 9c3e3cd9880..836a9f435af 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,6 +2,10 @@ +### 4.0.0-rc1 (in progress) + + + ### 4.0.0-beta3 - [bug] JAVA-2066: Array index range error when fetching routing keys on bound statements From e3d15c00df6d722c27925c4bf39bcb5f0d543278 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 7 Jan 2019 14:25:54 -0200 Subject: [PATCH 660/742] Enable Travis CI builds against OpenJDK 8 and 11 (#1168) --- .travis.yml | 12 +- ci/install-jdk.sh | 318 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 ci/install-jdk.sh diff --git a/.travis.yml b/.travis.yml index 6be247e0923..7f720550319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,21 @@ language: java -jdk: -- oraclejdk8 sudo: false +# see https://sormuras.github.io/blog/2018-03-20-jdk-matrix.html +matrix: + include: + # 8 + - env: JDK='OpenJDK 8' + jdk: openjdk8 + # 11 + - env: JDK='OpenJDK 11' + install: . $TRAVIS_BUILD_DIR/ci/install-jdk.sh -F 11 -L GPL before_install: - git clone --depth 1 https://github.com/datastax/native-protocol.git - pushd native-protocol - mvn install -Dmaven.javadoc.skip=true -B -V - popd - rm -rf native-protocol +script: mvn test -B -Dmaven.main.skip=true -Dmaven.test.skip=true cache: directories: - $HOME/.m2 diff --git a/ci/install-jdk.sh b/ci/install-jdk.sh new file mode 100644 index 00000000000..674961c2daf --- /dev/null +++ b/ci/install-jdk.sh @@ -0,0 +1,318 @@ +#!/usr/bin/env bash + +# +# Install JDK for Linux and Mac OS +# +# This script determines the most recent early-access build number, +# downloads the JDK archive to the user home directory and extracts +# it there. +# +# Exported environment variables (when sourcing this script) +# +# JAVA_HOME is set to the extracted JDK directory +# PATH is prepended with ${JAVA_HOME}/bin +# +# (C) 2018 Christian Stein +# +# https://github.com/sormuras/bach/blob/master/install-jdk.sh +# + +set -o errexit +#set -o nounset # https://github.com/travis-ci/travis-ci/issues/5434 +#set -o xtrace + +function initialize() { + readonly script_name="$(basename "${BASH_SOURCE[0]}")" + readonly script_version='2018-10-17' + + dry=false + silent=false + verbose=false + emit_java_home=false + + feature='ea' + license='GPL' + os='?' + url='?' + workspace="${HOME}" + target='?' + cacerts=false +} + +function usage() { +cat << EOF +Usage: ${script_name} [OPTION]... +Download and extract the latest-and-greatest JDK from java.net or Oracle. + +Version: ${script_version} +Options: + -h|--help Displays this help + -d|--dry-run Activates dry-run mode + -s|--silent Displays no output + -e|--emit-java-home Print value of "JAVA_HOME" to stdout (ignores silent mode) + -v|--verbose Displays verbose output + + -f|--feature 9|10|...|ea JDK feature release number, defaults to "ea" + -l|--license GPL|BCL License defaults to "GPL", BCL also indicates OTN-LA for Oracle Java SE + -o|--os linux-x64|osx-x64 Operating system identifier (works best with GPL license) + -u|--url "https://..." Use custom JDK archive (provided as .tar.gz file) + -w|--workspace PATH Working directory defaults to \${HOME} [${HOME}] + -t|--target PATH Target directory, defaults to first component of the tarball + -c|--cacerts Link system CA certificates (currently only Debian/Ubuntu is supported) +EOF +} + +function script_exit() { + if [[ $# -eq 1 ]]; then + printf '%s\n' "$1" + exit 0 + fi + + if [[ $# -eq 2 && $2 =~ ^[0-9]+$ ]]; then + printf '%b\n' "$1" + exit "$2" + fi + + script_exit 'Invalid arguments passed to script_exit()!' 2 +} + +function say() { + if [[ ${silent} != true ]]; then + echo "$@" + fi +} + +function verbose() { + if [[ ${verbose} == true ]]; then + echo "$@" + fi +} + +function parse_options() { + local option + while [[ $# -gt 0 ]]; do + option="$1" + shift + case ${option} in + -h|-H|--help) + usage + exit 0 + ;; + -v|-V|--verbose) + verbose=true + ;; + -s|-S|--silent) + silent=true + verbose "Silent mode activated" + ;; + -d|-D|--dry-run) + dry=true + verbose "Dry-run mode activated" + ;; + -e|-E|--emit-java-home) + emit_java_home=true + verbose "Emitting JAVA_HOME" + ;; + -f|-F|--feature) + feature="$1" + verbose "feature=${feature}" + shift + ;; + -l|-L|--license) + license="$1" + verbose "license=${license}" + shift + ;; + -o|-O|--os) + os="$1" + verbose "os=${os}" + shift + ;; + -u|-U|--url) + url="$1" + verbose "url=${url}" + shift + ;; + -w|-W|--workspace) + workspace="$1" + verbose "workspace=${workspace}" + shift + ;; + -t|-T|--target) + target="$1" + verbose "target=${target}" + shift + ;; + -c|-C|--cacerts) + cacerts=true + verbose "Linking system CA certificates" + ;; + *) + script_exit "Invalid argument was provided: ${option}" 2 + ;; + esac + done +} + +function determine_latest_jdk() { + local number + local curl_result + local url + + verbose "Determine latest JDK feature release number" + number=9 + while [[ ${number} != 99 ]] + do + url=http://jdk.java.net/${number} + curl_result=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) + if [[ ${curl_result} -ge 400 ]]; then + break + fi + verbose " Found ${url} [${curl_result}]" + latest_jdk=${number} + number=$[$number +1] + done + + verbose "Latest JDK feature release number is: ${latest_jdk}" +} + +function perform_sanity_checks() { + if [[ ${feature} == '?' ]] || [[ ${feature} == 'ea' ]]; then + feature=${latest_jdk} + fi + if [[ ${feature} -lt 9 ]] || [[ ${feature} -gt ${latest_jdk} ]]; then + script_exit "Expected feature release number in range of 9 to ${latest_jdk}, but got: ${feature}" 3 + fi + if [[ -d "$target" ]]; then + script_exit "Target directory must not exist, but it does: $(du -hs '${target}')" 3 + fi +} + +function determine_url() { + local DOWNLOAD='https://download.java.net/java' + local ORACLE='http://download.oracle.com/otn-pub/java/jdk' + + # Archived feature or official GA build? + case "${feature}-${license}" in + 9-GPL) url="${DOWNLOAD}/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_${os}_bin.tar.gz"; return;; + 9-BCL) url="${ORACLE}/9.0.4+11/c2514751926b4512b076cc82f959763f/jdk-9.0.4_${os}_bin.tar.gz"; return;; + 10-GPL) url="${DOWNLOAD}/GA/jdk10/10.0.2/19aef61b38124481863b1413dce1855f/13/openjdk-10.0.2_${os}_bin.tar.gz"; return;; + 10-BCL) url="${ORACLE}/10.0.2+13/19aef61b38124481863b1413dce1855f/jdk-10.0.2_${os}_bin.tar.gz"; return;; + 11-GPL) url="${DOWNLOAD}/GA/jdk11/13/GPL/openjdk-11.0.1_${os}_bin.tar.gz"; return;; + 11-BCL) url="${ORACLE}/11.0.1+13/90cf5d8f270a4347a95050320eef3fb7/jdk-11.0.1_${os}_bin.tar.gz"; return;; + esac + + # EA or RC build? + local JAVA_NET="http://jdk.java.net/${feature}" + local candidates=$(wget --quiet --output-document - ${JAVA_NET} | grep -Eo 'href[[:space:]]*=[[:space:]]*"[^\"]+"' | grep -Eo '(http|https)://[^"]+') + url=$(echo "${candidates}" | grep -Eo "${DOWNLOAD}/.+/jdk${feature}/.+/${license}/.*jdk-${feature}.+${os}_bin.tar.gz$" || true) + + if [[ -z ${url} ]]; then + script_exit "Couldn't determine a download url for ${feature}-${license} on ${os}" 1 + fi +} + +function prepare_variables() { + if [[ ${os} == '?' ]]; then + if [[ "$OSTYPE" == "darwin"* ]]; then + os='osx-x64' + else + os='linux-x64' + fi + fi + if [[ ${url} == '?' ]]; then + determine_latest_jdk + perform_sanity_checks + determine_url + else + feature='' + license='' + os='' + fi + archive="${workspace}/$(basename ${url})" + status=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) +} + +function print_variables() { +cat << EOF +Variables: + feature = ${feature} + license = ${license} + os = ${os} + url = ${url} + status = ${status} + archive = ${archive} +EOF +} + +function download_and_extract_and_set_target() { + local quiet='--quiet'; if [[ ${verbose} == true ]]; then quiet=''; fi + local local="--directory-prefix ${workspace}" + local remote='--timestamping --continue' + local wget_options="${quiet} ${local} ${remote}" + local tar_options="--file ${archive}" + + say "Downloading JDK from ${url}..." + verbose "Using wget options: ${wget_options}" + if [[ ${license} == 'GPL' ]]; then + wget ${wget_options} ${url} + else + wget ${wget_options} --header "Cookie: oraclelicense=accept-securebackup-cookie" ${url} + fi + + verbose "Using tar options: ${tar_options}" + if [[ ${target} == '?' ]]; then + tar --extract ${tar_options} -C "${workspace}" + if [[ "$OSTYPE" != "darwin"* ]]; then + target="${workspace}"/$(tar --list ${tar_options} | grep 'bin/javac' | tr '/' '\n' | tail -3 | head -1) + else + target="${workspace}"/$(tar --list ${tar_options} | head -2 | tail -1 | cut -f 2 -d '/' -)/Contents/Home + fi + else + if [[ "$OSTYPE" != "darwin"* ]]; then + mkdir --parents "${target}" + tar --extract ${tar_options} -C "${target}" --strip-components=1 + else + mkdir -p "${target}" + tar --extract ${tar_options} -C "${target}" --strip-components=4 # . / / Contents / Home + fi + fi + + if [[ ${verbose} == true ]]; then + echo "Set target to: ${target}" + echo "Content of target directory:" + ls "${target}" + echo "Content of release file:" + [[ ! -f "${target}/release" ]] || cat "${target}/release" + fi + + # Link to system certificates + # http://openjdk.java.net/jeps/319 + # https://bugs.openjdk.java.net/browse/JDK-8196141 + # TODO: Provide support for other distributions than Debian/Ubuntu + if [[ ${cacerts} == true ]]; then + mv "${target}/lib/security/cacerts" "${target}/lib/security/cacerts.jdk" + ln -s /etc/ssl/certs/java/cacerts "${target}/lib/security/cacerts" + fi +} + +function main() { + initialize + say "$script_name $script_version" + + parse_options "$@" + prepare_variables + + if [[ ${silent} == false ]]; then print_variables; fi + if [[ ${dry} == true ]]; then exit 0; fi + + download_and_extract_and_set_target + + export JAVA_HOME=$(cd "${target}"; pwd) + export PATH=${JAVA_HOME}/bin:$PATH + + if [[ ${silent} == false ]]; then java -version; fi + if [[ ${emit_java_home} == true ]]; then echo "${JAVA_HOME}"; fi +} + +main "$@" \ No newline at end of file From 5ad20cacaa0b2ae3d5b8d703bb52f5fd9745cb0e Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 8 Jan 2019 17:37:13 -0800 Subject: [PATCH 661/742] JAVA-2075: Document preference for LZ4 over Snappy --- changelog/README.md | 2 +- manual/core/compression/README.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 836a9f435af..f67c043ae58 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,7 +4,7 @@ ### 4.0.0-rc1 (in progress) - +- [documentation] JAVA-2075: Document preference for LZ4 over Snappy ### 4.0.0-beta3 diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index 2ab0ff75079..2ac913a17f0 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -19,9 +19,14 @@ Compression must be set before opening a session, it cannot be changed at runtim Two algorithms are supported out of the box: [LZ4](https://github.com/jpountz/lz4-java) and -[Snappy](http://google.github.io/snappy/). Both rely on third-party libraries, declared by the -driver as *optional* dependencies; if you enable compression, you need to explicitly depend on the -corresponding library to pull it into your project. +[Snappy](http://google.github.io/snappy/). The LZ4 implementation is a good first choice; it offers +fallback implementations in case native libraries fail to load and +[benchmarks](http://java-performance.info/performance-general-compression/) suggest that it offers +better performance and compression ratios over Snappy. + +Both implementations rely on third-party libraries, declared by the driver as *optional* +dependencies; if you enable compression, you need to explicitly depend on the corresponding library +to pull it into your project. ### LZ4 From 71efaaf4585457cc188c0ce505e8e0974d00449b Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 14 Jan 2019 11:05:49 -0800 Subject: [PATCH 662/742] JAVA-2103: Expose partitioner name in TokenMap API --- changelog/README.md | 1 + core/revapi.json | 11 ++++++++ .../driver/api/core/metadata/TokenMap.java | 4 +++ .../token/ByteOrderedTokenFactory.java | 7 +++++ .../token/DefaultTokenFactoryRegistry.java | 6 ++--- .../core/metadata/token/DefaultTokenMap.java | 6 +++++ .../metadata/token/Murmur3TokenFactory.java | 7 +++++ .../metadata/token/RandomTokenFactory.java | 7 +++++ .../core/metadata/token/TokenFactory.java | 2 ++ .../api/core/metadata/ByteOrderedTokenIT.java | 2 +- .../metadata/ByteOrderedTokenVnodesIT.java | 2 +- .../api/core/metadata/Murmur3TokenIT.java | 2 +- .../core/metadata/Murmur3TokenVnodesIT.java | 2 +- .../api/core/metadata/RandomTokenIT.java | 2 +- .../core/metadata/RandomTokenVnodesIT.java | 2 +- .../driver/api/core/metadata/TokenITBase.java | 26 ++++++++++++++----- 16 files changed, 74 insertions(+), 15 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index f67c043ae58..4d29458792b 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2103: Expose partitioner name in TokenMap API - [documentation] JAVA-2075: Document preference for LZ4 over Snappy ### 4.0.0-beta3 diff --git a/core/revapi.json b/core/revapi.json index 6ec321c55b1..bc53052e86f 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -687,6 +687,17 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", "elementKind": "method", "justification": "JAVA-2058: Make programmatic config reloading part of the public API" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.lang.String com.datastax.oss.driver.api.core.metadata.TokenMap::getPartitionerName()", + "package": "com.datastax.oss.driver.api.core.metadata", + "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.TokenMap", + "classSimpleName": "TokenMap", + "methodName": "getPartitionerName", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2103: Expose partitioner name in TokenMap API" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java index 7ebe9562119..319e3e82d8f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/TokenMap.java @@ -144,4 +144,8 @@ default Set getReplicas(@NonNull CqlIdentifier keyspace, @NonNull TokenRan default Set getReplicas(@NonNull String keyspaceName, @NonNull TokenRange range) { return getReplicas(CqlIdentifier.fromCql(keyspaceName), range); } + + /** The name of the partitioner class in use, as reported by the Cassandra nodes. */ + @NonNull + String getPartitionerName(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java index 6a3c3b4b0c5..c53296f1878 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/ByteOrderedTokenFactory.java @@ -25,8 +25,15 @@ @ThreadSafe public class ByteOrderedTokenFactory implements TokenFactory { + public static final String PARTITIONER_NAME = "org.apache.cassandra.dht.ByteOrderedPartitioner"; + public static final ByteOrderedToken MIN_TOKEN = new ByteOrderedToken(ByteBuffer.allocate(0)); + @Override + public String getPartitionerName() { + return PARTITIONER_NAME; + } + @Override public Token hash(ByteBuffer partitionKey) { return new ByteOrderedToken(partitionKey); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java index f7d83c9bdc8..8717e4fb9d5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenFactoryRegistry.java @@ -33,13 +33,13 @@ public DefaultTokenFactoryRegistry(InternalDriverContext context) { @Override public TokenFactory tokenFactoryFor(String partitioner) { - if (partitioner.endsWith("Murmur3Partitioner")) { + if (Murmur3TokenFactory.PARTITIONER_NAME.equals(partitioner)) { LOG.debug("[{}] Detected Murmur3 partitioner ({})", logPrefix, partitioner); return new Murmur3TokenFactory(); - } else if (partitioner.endsWith("RandomPartitioner")) { + } else if (RandomTokenFactory.PARTITIONER_NAME.equals(partitioner)) { LOG.debug("[{}] Detected random partitioner ({})", logPrefix, partitioner); return new RandomTokenFactory(); - } else if (partitioner.endsWith("OrderedPartitioner")) { + } else if (ByteOrderedTokenFactory.PARTITIONER_NAME.equals(partitioner)) { LOG.debug("[{}] Detected byte ordered partitioner ({})", logPrefix, partitioner); return new ByteOrderedTokenFactory(); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java index 9814f3c2ae8..6b90c6f0c0a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -183,6 +183,12 @@ public Set getReplicas(@NonNull CqlIdentifier keyspace, @NonNull Token tok return (keyspaceMap == null) ? Collections.emptySet() : keyspaceMap.getReplicas(token); } + @NonNull + @Override + public String getPartitionerName() { + return tokenFactory.getPartitionerName(); + } + private KeyspaceTokenMap getKeyspaceMap(CqlIdentifier keyspace) { Map config = replicationConfigs.get(keyspace); return (config == null) ? null : keyspaceMaps.get(config); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java index e529b53b40b..252c59f671b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/Murmur3TokenFactory.java @@ -24,9 +24,16 @@ @ThreadSafe public class Murmur3TokenFactory implements TokenFactory { + public static final String PARTITIONER_NAME = "org.apache.cassandra.dht.Murmur3Partitioner"; + public static final Murmur3Token MIN_TOKEN = new Murmur3Token(Long.MIN_VALUE); public static final Murmur3Token MAX_TOKEN = new Murmur3Token(Long.MAX_VALUE); + @Override + public String getPartitionerName() { + return PARTITIONER_NAME; + } + @Override public Token hash(ByteBuffer partitionKey) { long v = murmur(partitionKey); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java index a5098fc00c4..b4e9fdaa28a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java @@ -27,6 +27,8 @@ @ThreadSafe public class RandomTokenFactory implements TokenFactory { + public static final String PARTITIONER_NAME = "org.apache.cassandra.dht.RandomPartitioner"; + private static final BigInteger MIN_VALUE = BigInteger.ONE.negate(); static final BigInteger MAX_VALUE = BigInteger.valueOf(2).pow(127); public static final RandomToken MIN_TOKEN = new RandomToken(MIN_VALUE); @@ -47,6 +49,11 @@ public RandomTokenFactory() { this.supportsClone = supportsClone; } + @Override + public String getPartitionerName() { + return PARTITIONER_NAME; + } + @Override public Token hash(ByteBuffer partitionKey) { return new RandomToken(md5(partitionKey)); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java index 02758bc5e92..e727a36cbb2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenFactory.java @@ -22,6 +22,8 @@ /** Manages token instances for a partitioner implementation. */ public interface TokenFactory { + String getPartitionerName(); + Token hash(ByteBuffer partitionKey); Token parse(String tokenString); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java index 13d36f5a773..357c1078f99 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenIT.java @@ -44,7 +44,7 @@ public class ByteOrderedTokenIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public ByteOrderedTokenIT() { - super(ByteOrderedToken.class, false); + super("org.apache.cassandra.dht.ByteOrderedPartitioner", ByteOrderedToken.class, false); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java index c3f8dea344a..239b660345a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/ByteOrderedTokenVnodesIT.java @@ -48,7 +48,7 @@ public class ByteOrderedTokenVnodesIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public ByteOrderedTokenVnodesIT() { - super(ByteOrderedToken.class, true); + super("org.apache.cassandra.dht.ByteOrderedPartitioner", ByteOrderedToken.class, true); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java index 8079bee7b81..0009eb29323 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenIT.java @@ -43,7 +43,7 @@ public class Murmur3TokenIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public Murmur3TokenIT() { - super(Murmur3Token.class, false); + super("org.apache.cassandra.dht.Murmur3Partitioner", Murmur3Token.class, false); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java index b9314faa258..ed384765f9d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/Murmur3TokenVnodesIT.java @@ -44,7 +44,7 @@ public class Murmur3TokenVnodesIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public Murmur3TokenVnodesIT() { - super(Murmur3Token.class, true); + super("org.apache.cassandra.dht.Murmur3Partitioner", Murmur3Token.class, true); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java index 0b19ec4dc5d..bdfad30aee0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenIT.java @@ -44,7 +44,7 @@ public class RandomTokenIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public RandomTokenIT() { - super(RandomToken.class, false); + super("org.apache.cassandra.dht.RandomPartitioner", RandomToken.class, false); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java index 9f46441c677..587b003b27f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/RandomTokenVnodesIT.java @@ -48,7 +48,7 @@ public class RandomTokenVnodesIT extends TokenITBase { @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); public RandomTokenVnodesIT() { - super(RandomToken.class, true); + super("org.apache.cassandra.dht.RandomPartitioner", RandomToken.class, true); } @Override diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java index a56ca55ffe9..2fa682ccc2b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TokenITBase.java @@ -68,11 +68,14 @@ protected static void createSchema(CqlSession session) { } } + private final String expectedPartitionerName; private final Class expectedTokenType; private final boolean useVnodes; private final int tokensPerNode; - protected TokenITBase(Class expectedTokenType, boolean useVnodes) { + protected TokenITBase( + String expectedPartitionerName, Class expectedTokenType, boolean useVnodes) { + this.expectedPartitionerName = expectedPartitionerName; this.expectedTokenType = expectedTokenType; this.useVnodes = useVnodes; this.tokensPerNode = useVnodes ? 256 : 1; @@ -91,8 +94,7 @@ protected TokenITBase(Class expectedTokenType, boolean useVnode */ @Test public void should_be_consistent_with_range_queries() { - Metadata metadata = session().getMetadata(); - TokenMap tokenMap = metadata.getTokenMap().get(); + TokenMap tokenMap = getTokenMap(); // Find the replica for a given partition key of ks1.foo. int key = 1; @@ -299,7 +301,7 @@ private void checkRanges(Collection ranges) { */ @Test public void should_have_only_one_wrapped_range() { - TokenMap tokenMap = session().getMetadata().getTokenMap().get(); + TokenMap tokenMap = getTokenMap(); TokenRange wrappedRange = null; for (TokenRange range : tokenMap.getTokenRanges()) { if (range.isWrappedAround()) { @@ -317,7 +319,7 @@ public void should_have_only_one_wrapped_range() { @Test public void should_create_tokens_and_ranges() { - TokenMap tokenMap = session().getMetadata().getTokenMap().get(); + TokenMap tokenMap = getTokenMap(); // Pick a random range TokenRange range = tokenMap.getTokenRanges().iterator().next(); @@ -330,7 +332,7 @@ public void should_create_tokens_and_ranges() { @Test public void should_create_token_from_partition_key() { - TokenMap tokenMap = session().getMetadata().getTokenMap().get(); + TokenMap tokenMap = getTokenMap(); Row row = session().execute("SELECT token(i) FROM foo WHERE i = 1").one(); Token expected = row.getToken(0); @@ -339,4 +341,16 @@ public void should_create_token_from_partition_key() { assertThat(tokenMap.newToken(TypeCodecs.INT.encodePrimitive(1, protocolVersion))) .isEqualTo(expected); } + + private TokenMap getTokenMap() { + return session() + .getMetadata() + .getTokenMap() + .map( + tokenMap -> { + assertThat(tokenMap.getPartitionerName()).isEqualTo(expectedPartitionerName); + return tokenMap; + }) + .orElseThrow(() -> new AssertionError("Expected token map to be present")); + } } From 8ef41a902013cf78ead2ec501f1befae379ad934 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 8 Jan 2019 11:34:50 -0800 Subject: [PATCH 663/742] JAVA-2067: Publish javadocs JAR for the shaded module --- changelog/README.md | 1 + core-shaded/pom.xml | 65 +++++++++++++++++++------ core-shaded/src/main/javadoc/README.txt | 3 -- 3 files changed, 50 insertions(+), 19 deletions(-) delete mode 100644 core-shaded/src/main/javadoc/README.txt diff --git a/changelog/README.md b/changelog/README.md index 4d29458792b..1b07c2eb26e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2067: Publish javadocs JAR for the shaded module - [improvement] JAVA-2103: Expose partitioner name in TokenMap API - [documentation] JAVA-2075: Document preference for LZ4 over Snappy diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 8b95f71f6d5..dab7a2bbd9a 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -140,22 +140,6 @@ - - maven-jar-plugin - 3.0.2 - - - empty-javadoc-jar - - jar - - - javadoc - ${basedir}/src/main/javadoc - - - - maven-dependency-plugin @@ -185,6 +169,55 @@ + + + unpack-shaded-sources + package + + unpack + + + + + com.datastax.oss + java-driver-core-shaded + ${project.version} + jar + sources + ${project.build.directory}/shaded-sources + + + + + + + + maven-javadoc-plugin + + + attach-shaded-javadocs + + jar + + + ${project.build.directory}/shaded-sources + + com.datastax.oss.driver.internal:com.datastax.oss.driver.shaded + + + + + org.jctools + jctools-core + 2.1.2 + + + + diff --git a/core-shaded/src/main/javadoc/README.txt b/core-shaded/src/main/javadoc/README.txt deleted file mode 100644 index 8ec8f952d69..00000000000 --- a/core-shaded/src/main/javadoc/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -This empty JAR is generated for compliance with Maven Central rules. - -Please refer to the javadocs of the unshaded artifact (com.datastax.oss:java-driver-core). \ No newline at end of file From 4f889cc6451b996ae5a43cedad96de36016d46f0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 8 Jan 2019 11:41:14 -0800 Subject: [PATCH 664/742] Declare version of dependency plugin in pluginManagement --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index f3b2bc914b4..18dd9e8956a 100644 --- a/pom.xml +++ b/pom.xml @@ -274,6 +274,10 @@ maven-deploy-plugin 2.7 + + maven-dependency-plugin + 3.1.1 + org.jacoco jacoco-maven-plugin From 6b0e299a1f9a82e82a5a4e56cd335991f950b07f Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 27 Dec 2018 11:50:08 +0100 Subject: [PATCH 665/742] JAVA-2077: Allow reconnection policy to distinguish first connection attempt This allows the policy to generate a different schedule for the initial connection attempt: that is, when it failed to reach any contact point at startup, and reconnect-on-init is set. --- changelog/README.md | 1 + core/revapi.json | 13 +++++ .../core/connection/ReconnectionPolicy.java | 22 +++++++-- .../ConstantReconnectionPolicy.java | 3 +- .../ExponentialReconnectionPolicy.java | 3 +- .../core/control/ControlConnection.java | 49 ++++++++++++++----- .../core/metadata/DefaultTopologyMonitor.java | 2 +- .../core/metadata/MetadataManager.java | 2 +- .../core/util/concurrent/Reconnection.java | 6 ++- .../ExponentialReconnectionPolicyTest.java | 2 +- .../control/ControlConnectionEventsTest.java | 10 ++-- .../core/control/ControlConnectionTest.java | 32 ++++++------ .../control/ControlConnectionTestBase.java | 13 ++++- .../metadata/DefaultTopologyMonitorTest.java | 2 +- .../oss/driver/api/core/ConnectIT.java | 28 ++++++++++- 15 files changed, 143 insertions(+), 45 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 1b07c2eb26e..6763419c4f7 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2077: Allow reconnection policy to detect first connection attempt - [improvement] JAVA-2067: Publish javadocs JAR for the shaded module - [improvement] JAVA-2103: Expose partitioner name in TokenMap API - [documentation] JAVA-2075: Document preference for LZ4 over Snappy diff --git a/core/revapi.json b/core/revapi.json index bc53052e86f..52006e65669 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -698,6 +698,19 @@ "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", "elementKind": "method", "justification": "JAVA-2103: Expose partitioner name in TokenMap API" + }, + { + "code": "java.method.numberOfParametersChanged", + "old": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule com.datastax.oss.driver.api.core.connection.ReconnectionPolicy::newControlConnectionSchedule()", + "new": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule com.datastax.oss.driver.api.core.connection.ReconnectionPolicy::newControlConnectionSchedule(boolean)", + "package": "com.datastax.oss.driver.api.core.connection", + "classQualifiedName": "com.datastax.oss.driver.api.core.connection.ReconnectionPolicy", + "classSimpleName": "ReconnectionPolicy", + "methodName": "newControlConnectionSchedule", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", + "elementKind": "method", + "justification": "JAVA-2077: Allow reconnection policy to detect first connection attempt" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java index fedb0ed1ebc..083e83950c6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/connection/ReconnectionPolicy.java @@ -36,8 +36,10 @@ * for a node does not have its configured number of connections (see {@code * advanced.connection.pool.*.size} in the configuration), a reconnection starts for that * pool. - *

        • {@linkplain #newControlConnectionSchedule() for the control connection}: when the control - * node goes down, a reconnection starts to find another node to replace it. + *
        • {@linkplain #newControlConnectionSchedule(boolean) for the control connection}: when the + * control node goes down, a reconnection starts to find another node to replace it. This is + * also used if the configuration option {@code advanced.reconnect-on-init} is set and the + * driver has to retry the initial connection. *
        * * This interface defines separate methods for those two cases, but implementations are free to @@ -49,9 +51,21 @@ public interface ReconnectionPolicy extends AutoCloseable { @NonNull ReconnectionSchedule newNodeSchedule(@NonNull Node node); - /** Creates a new schedule for the control connection. */ + /** + * Creates a new schedule for the control connection. + * + * @param isInitialConnection whether this schedule is generated for the driver's initial attempt + * to connect to the cluster. + *
          + *
        • {@code true} means that the configuration option {@code advanced.reconnect-on-init} + * is set, the driver failed to reach any contact point, and it is now scheduling + * reattempts. + *
        • {@code false} means that the driver was already initialized, lost connection to the + * control node, and is now scheduling attempts to connect to another node. + *
        + */ @NonNull - ReconnectionSchedule newControlConnectionSchedule(); + ReconnectionSchedule newControlConnectionSchedule(boolean isInitialConnection); /** Called when the cluster that this policy is associated with closes. */ @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java index 6d89fd14c64..ccead526906 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ConstantReconnectionPolicy.java @@ -74,7 +74,8 @@ public ReconnectionSchedule newNodeSchedule(@NonNull Node node) { @NonNull @Override - public ReconnectionSchedule newControlConnectionSchedule() { + public ReconnectionSchedule newControlConnectionSchedule( + @SuppressWarnings("ignored") boolean isInitialConnection) { LOG.debug("[{}] Creating new schedule for the control connection", logPrefix); return schedule; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index d8cf728e847..1d90f6f803f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -124,7 +124,8 @@ public ReconnectionSchedule newNodeSchedule(@NonNull Node node) { @NonNull @Override - public ReconnectionSchedule newControlConnectionSchedule() { + public ReconnectionSchedule newControlConnectionSchedule( + @SuppressWarnings("ignored") boolean isInitialConnection) { LOG.debug("[{}] Creating new schedule for the control connection", logPrefix); return new ExponentialSchedule(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index b5ad2235c64..1b717f4cb34 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -26,15 +26,17 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.channel.EventCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultTopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; -import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; +import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -57,15 +59,19 @@ import org.slf4j.LoggerFactory; /** - * Maintains a dedicated connection to a Cassandra node for administrative queries: schema - * refreshes, and cluster topology queries and events. + * Maintains a dedicated connection to a Cassandra node for administrative queries. * *

        If the control node goes down, a reconnection is triggered. The control node is chosen * randomly among the contact points at startup, or according to the load balancing policy for later * reconnections. * - *

        If a custom {@link TopologyMonitor} is used, the control connection is used only for schema - * refreshes; if schema metadata is also disabled, the control connection never initializes. + *

        The control connection is used by: + * + *

          + *
        • {@link DefaultTopologyMonitor} to determine cluster connectivity and retrieve node + * metadata; + *
        • {@link MetadataManager} to run schema metadata queries. + *
        */ @ThreadSafe public class ControlConnection implements EventCallback, AsyncAutoCloseable { @@ -88,16 +94,31 @@ public ControlConnection(InternalDriverContext context) { } /** + * Initializes the control connection. If it is already initialized, this is a no-op and all + * parameters are ignored. + * * @param listenToClusterEvents whether to register for TOPOLOGY_CHANGE and STATUS_CHANGE events. * If the control connection has already initialized with another value, this is ignored. * SCHEMA_CHANGE events are always registered. * @param reconnectOnFailure whether to schedule a reconnection if the initial attempt fails (this * does not affect the returned future, which always represent the outcome of the initial * attempt only). + * @param useInitialReconnectionSchedule if no node can be reached, the type of reconnection + * schedule to use. In other words, the value that will be passed to {@link + * ReconnectionPolicy#newControlConnectionSchedule(boolean)}. */ - public CompletionStage init(boolean listenToClusterEvents, boolean reconnectOnFailure) { + public CompletionStage init( + boolean listenToClusterEvents, + boolean reconnectOnFailure, + boolean useInitialReconnectionSchedule) { + Preconditions.checkArgument( + reconnectOnFailure || !useInitialReconnectionSchedule, + "Can't set useInitialReconnectionSchedule if reconnectOnFailure is false"); RunOrSchedule.on( - adminExecutor, () -> singleThreaded.init(listenToClusterEvents, reconnectOnFailure)); + adminExecutor, + () -> + singleThreaded.init( + listenToClusterEvents, reconnectOnFailure, useInitialReconnectionSchedule)); return singleThreaded.initFuture; } @@ -217,6 +238,7 @@ private class SingleThreaded { private boolean initWasCalled; private final CompletableFuture closeFuture = new CompletableFuture<>(); private boolean closeWasCalled; + private final ReconnectionPolicy reconnectionPolicy; private final Reconnection reconnection; private DriverChannelOptions channelOptions; // The last events received for each node @@ -225,12 +247,12 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; - ReconnectionPolicy reconnectionPolicy = context.getReconnectionPolicy(); + this.reconnectionPolicy = context.getReconnectionPolicy(); this.reconnection = new Reconnection( logPrefix, adminExecutor, - reconnectionPolicy::newControlConnectionSchedule, + () -> reconnectionPolicy.newControlConnectionSchedule(false), this::reconnect); // In "reconnect-on-init" mode, handle cancellation of the initFuture by user code CompletableFutures.whenCancelled( @@ -248,7 +270,10 @@ private SingleThreaded(InternalDriverContext context) { .register(NodeStateEvent.class, RunOrSchedule.on(adminExecutor, this::onStateEvent)); } - private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { + private void init( + boolean listenToClusterEvents, + boolean reconnectOnFailure, + boolean useInitialReconnectionSchedule) { assert adminExecutor.inEventLoop(); if (initWasCalled) { return; @@ -274,7 +299,9 @@ private void init(boolean listenToClusterEvents, boolean reconnectOnFailure) { }, error -> { if (reconnectOnFailure && !closeWasCalled) { - reconnection.start(); + reconnection.start( + reconnectionPolicy.newControlConnectionSchedule( + useInitialReconnectionSchedule)); } else { // Special case for the initial connection: reword to a more user-friendly error // message diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 4801e1e12db..84eee637fbf 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -89,7 +89,7 @@ public CompletionStage init() { if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - return controlConnection.init(true, reconnectOnInit); + return controlConnection.init(true, reconnectOnInit, true); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index ce51374e9f1..d7b711bc9e5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -411,7 +411,7 @@ private CompletionStage maybeInitControlConnection() { // Not the first schema refresh, so we know init was attempted already return firstSchemaRefreshFuture; } else { - controlConnection.init(false, true); + controlConnection.init(false, true, false); // The control connection might fail to connect and reattempt, but for the metadata refresh // that led us here we only care about the first attempt (metadata is not vital, so if we // can't get it right now it's OK to move on) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java index eaf2a0eba75..d40265bd09a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/concurrent/Reconnection.java @@ -98,6 +98,10 @@ public boolean isRunning() { /** This is a no-op if the reconnection is already running. */ public void start() { + start(null); + } + + public void start(ReconnectionSchedule customSchedule) { assert executor.inEventLoop(); switch (state) { case SCHEDULED: @@ -109,7 +113,7 @@ public void start() { state = State.ATTEMPT_IN_PROGRESS; break; case STOPPED: - reconnectionSchedule = scheduleSupplier.get(); + reconnectionSchedule = (customSchedule == null) ? scheduleSupplier.get() : customSchedule; onStart.run(); scheduleNextAttempt(); break; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java index c59cc273a94..85b3bb83ef3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java @@ -52,7 +52,7 @@ public void setup() { @Test public void should_generate_exponential_delay_with_jitter() throws Exception { ExponentialReconnectionPolicy policy = new ExponentialReconnectionPolicy(driverContext); - ReconnectionPolicy.ReconnectionSchedule schedule = policy.newControlConnectionSchedule(); + ReconnectionPolicy.ReconnectionSchedule schedule = policy.newControlConnectionSchedule(false); // generate a number of delays and make sure they are all within the base/max values range for (int i = 0; i < 128; ++i) { // compute the min and max delays based on attempt count (i) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index e77427c6e3f..5bb888c5cfe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -44,7 +44,7 @@ public void should_register_for_all_events_if_topology_requested() { .thenReturn(CompletableFuture.completedFuture(channel1)); // When - controlConnection.init(true, false); + controlConnection.init(true, false, false); waitForPendingAdminTasks(); DriverChannelOptions channelOptions = optionsCaptor.getValue(); @@ -67,7 +67,7 @@ public void should_register_for_schema_events_only_if_topology_not_requested() { .thenReturn(CompletableFuture.completedFuture(channel1)); // When - controlConnection.init(false, false); + controlConnection.init(false, false, false); waitForPendingAdminTasks(); DriverChannelOptions channelOptions = optionsCaptor.getValue(); @@ -85,7 +85,7 @@ public void should_process_status_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(true, false); + controlConnection.init(true, false, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; StatusChangeEvent event = @@ -107,7 +107,7 @@ public void should_process_topology_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(true, false); + controlConnection.init(true, false, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; TopologyChangeEvent event = @@ -129,7 +129,7 @@ public void should_process_schema_change_events() { ArgumentCaptor.forClass(DriverChannelOptions.class); Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); - controlConnection.init(false, false); + controlConnection.init(false, false, false); waitForPendingAdminTasks(); EventCallback callback = optionsCaptor.getValue().eventCallback; SchemaChangeEvent event = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index 3abf455d14d..b91a13dc482 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -56,7 +56,7 @@ public void should_init_with_first_contact_point_if_reachable() { MockChannelFactoryHelper.builder(channelFactory).success(node1, channel1).build(); // When - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -76,9 +76,9 @@ public void should_always_return_same_init_future() { MockChannelFactoryHelper.builder(channelFactory).success(node1, channel1).build(); // When - CompletionStage initFuture1 = controlConnection.init(false, false); + CompletionStage initFuture1 = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); - CompletionStage initFuture2 = controlConnection.init(false, false); + CompletionStage initFuture2 = controlConnection.init(false, false, false); // Then assertThatStage(initFuture1).isEqualTo(initFuture2); @@ -97,7 +97,7 @@ public void should_init_with_second_contact_point_if_first_one_fails() { .build(); // When - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); @@ -123,7 +123,7 @@ public void should_fail_to_init_if_all_contact_points_fail() { .build(); // When - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); @@ -151,7 +151,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -190,7 +190,7 @@ public void should_reconnect_if_node_becomes_ignored() { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -238,7 +238,7 @@ private void should_reconnect_if_event(NodeStateEvent event) { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -282,7 +282,7 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( .success(node1, channel3) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -339,7 +339,7 @@ private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEven .success(node1, channel3) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); @@ -383,7 +383,7 @@ public void should_force_reconnection_if_pending() { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); @@ -421,7 +421,7 @@ public void should_force_reconnection_even_if_connected() { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); @@ -459,7 +459,7 @@ public void should_not_force_reconnection_if_closed() { DriverChannel channel1 = newMockDriverChannel(1); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory).success(node1, channel1).build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); @@ -483,7 +483,7 @@ public void should_close_channel_when_closing() { MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory).success(node1, channel1).build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); @@ -514,7 +514,7 @@ public void should_close_channel_if_closed_during_reconnection() { .pending(node2, channel2Future) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); @@ -561,7 +561,7 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { .success(node2, channel2) .build(); - CompletionStage initFuture = controlConnection.init(false, false); + CompletionStage initFuture = controlConnection.init(false, false, false); factoryHelper.waitForCall(node1); waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index cf2496f32dc..5c224594543 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -19,6 +19,9 @@ import static org.mockito.ArgumentMatchers.any; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.addresstranslation.PassThroughAddressTranslator; @@ -57,6 +60,8 @@ abstract class ControlConnectionTestBase { protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); @Mock protected InternalDriverContext context; + @Mock protected DriverConfig config; + @Mock protected DriverExecutionProfile defaultProfile; @Mock protected ReconnectionPolicy reconnectionPolicy; @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; @Mock protected NettyOptions nettyOptions; @@ -95,8 +100,14 @@ public void setup() { return channelFuture; }); + Mockito.when(context.getConfig()).thenReturn(config); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.RECONNECT_ON_INIT)) + .thenReturn(false); + Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(reconnectionPolicy.newControlConnectionSchedule()) + // Child classes only cover "runtime" reconnections when the driver is already initialized + Mockito.when(reconnectionPolicy.newControlConnectionSchedule(false)) .thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index ffcf23f8360..32ffd70cb30 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -102,7 +102,7 @@ public void should_initialize_control_connection() { topologyMonitor.init(); // Then - Mockito.verify(controlConnection).init(true, false); + Mockito.verify(controlConnection).init(true, false, false); } @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index aa88e55b487..169516500a7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; @@ -29,6 +30,7 @@ import com.datastax.oss.driver.internal.core.connection.ConstantReconnectionPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.server.RejectScope; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -79,7 +81,7 @@ public void should_wait_for_contact_points_if_reconnection_enabled() throws Exce SessionUtils.configLoaderBuilder() .withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true) .withClass( - DefaultDriverOption.RECONNECTION_POLICY_CLASS, ConstantReconnectionPolicy.class) + DefaultDriverOption.RECONNECTION_POLICY_CLASS, InitOnlyReconnectionPolicy.class) // Use a short delay so we don't have to wait too long: .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofMillis(500)) .build(); @@ -133,4 +135,28 @@ private CompletionStage newSessionAsync( .withConfigLoader(loader) .buildAsync(); } + + /** + * Test policy that fails if a "runtime" control connection schedule is requested. + * + *

        This is just to check that {@link #newControlConnectionSchedule(boolean)} is called with the + * correct boolean parameter. + */ + public static class InitOnlyReconnectionPolicy extends ConstantReconnectionPolicy { + + public InitOnlyReconnectionPolicy(DriverContext context) { + super(context); + } + + @NonNull + @Override + public ReconnectionSchedule newControlConnectionSchedule(boolean isInitialConnection) { + if (isInitialConnection) { + return super.newControlConnectionSchedule(true); + } else { + throw new UnsupportedOperationException( + "should not be called with isInitialConnection==false"); + } + } + } } From c4b6b56438427d56ff6d454254ec698d6fa1ead0 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Dec 2018 20:38:17 +0100 Subject: [PATCH 666/742] Revisit API conventions in manual --- manual/api_conventions/README.md | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/manual/api_conventions/README.md b/manual/api_conventions/README.md index f12a7a9d742..e55a4cc80c3 100644 --- a/manual/api_conventions/README.md +++ b/manual/api_conventions/README.md @@ -8,12 +8,36 @@ for advanced users, or keeping them hidden to limit the API surface. Starting with 4.0, we adopt a package naming convention to address those issues: * Everything under `com.datastax.oss.driver.api` is part of the "official" public API of the driver, - that can be used by regular client applications to execute queries. It follows - [semantic versioning] and binary compatibility is guaranteed across minor and patch versions. -* Everything under `com.datastax.oss.driver.internal` is the "internal" API, primarily used by - driver components to communicate with each other. It also exposes hooks for expert tweaking or - framework implementors. We'll do our best to also keep that API stable, but full compatibility is - not strictly guaranteed. + intended for regular client applications to execute queries. It follows [semantic versioning]: + binary compatibility is guaranteed across minor and patch versions. + +* Everything under `com.datastax.oss.driver.internal` is the "internal" API, intended primarily for + internal communication between driver components, and secondarily for advanced customization. If + you use it from your code, the rules are: + 1. with great power comes great responsibility: this stuff is more involved, and has the + potential to break the driver. You should probably have some familiarity with the source + code. + 2. backward compatibility is "best-effort" only: we'll try to preserve it as much as possible, + but it's not formally guaranteed. +The public API never exposes internal types (this is enforced automatically by our build). You'll +generally have to go through an explicit cast: + +```java +import com.datastax.oss.driver.api.core.context.DriverContext; + +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; + +// Public API: +DriverContext context = session.getContext(); + +// Switch to the internal API to force a reload of the configuration: +InternalDriverContext internalContext = (InternalDriverContext) context; +internalContext.getEventBus().fire(ForceReloadConfigEvent.INSTANCE); +``` + +So the risk of unintentionally using the internal API is very low. To double-check, you can always +grep `import com.datastax.oss.driver.internal` in your source files. [semantic versioning]: http://semver.org/ \ No newline at end of file From 0a0efd3ba4849f9d8314e20dd9eea80de8f6b3db Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Dec 2018 20:39:23 +0100 Subject: [PATCH 667/742] Document thread names in Netty option comments --- core/src/main/resources/reference.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 8dea078bfb9..cf9b12417e7 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -1354,6 +1354,8 @@ datastax-java-driver { # Options related to the Netty event loop groups used internally by the driver. advanced.netty { # The event loop group used for I/O operations (reading and writing to Cassandra nodes). + # By default, threads in this group are named after the session name, "-io-" and an incrementing + # counter, for example "s0-io-0". io-group { # The number of threads. # If this is set to 0, the driver will use `Runtime.getRuntime().availableProcessors() * 2`. @@ -1374,12 +1376,16 @@ datastax-java-driver { } # The event loop group used for admin tasks not related to request I/O (handle cluster events, # refresh metadata, schedule reconnections, etc.) + # By default, threads in this group are named after the session name, "-admin-" and an + # incrementing counter, for example "s0-admin-0". admin-group { size = 2 shutdown {quiet-period = 2, timeout = 15, unit = SECONDS} } # The timer used for scheduling request timeouts and speculative executions + # By default, this thread is named after the session name and "-timer-0", for example + # "s0-timer-0". timer { # The timer tick duration # This is the how frequent the timer should wake-up to check for timed out tasks. From 5a9a6d65d93b4fd52dbc72e8168ae8566e607abc Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 11 Dec 2018 15:15:14 +0100 Subject: [PATCH 668/742] JAVA-2034: Add performance recommendations in the manual --- changelog/README.md | 1 + manual/core/performance/README.md | 352 ++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 manual/core/performance/README.md diff --git a/changelog/README.md b/changelog/README.md index 6763419c4f7..87a296443b4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [documentation] JAVA-2034: Add performance recommendations in the manual - [improvement] JAVA-2077: Allow reconnection policy to detect first connection attempt - [improvement] JAVA-2067: Publish javadocs JAR for the shaded module - [improvement] JAVA-2103: Expose partitioner name in TokenMap API diff --git a/manual/core/performance/README.md b/manual/core/performance/README.md new file mode 100644 index 00000000000..9a81d01df5b --- /dev/null +++ b/manual/core/performance/README.md @@ -0,0 +1,352 @@ +## Performance + +This page is intended as a checklist for everything related to driver performance. Most of the +information is already available in other sections of the manual, but it's linked here for +easy reference if you're benchmarking your application or diagnosing performance issues. + + +### Statements + +[Statements](../statements/) are some of the driver types you'll use the most. Every request needs +one -- even `session.execute(String)` creates a `SimpleStatement` under the hood. + +#### Immutability and builders + +Statements are by default implemented with immutable classes: every call to a setter method creates +an intermediary copy. If you have multiple attributes to set, use a builder instead: + +```java +SimpleStatement statement = SimpleStatement.builder("SELECT * FROM foo") + .withPageSize(20) + .withConsistencyLevel(DefaultConsistencyLevel.QUORUM) + .withIdempotence(true) + .build(); +``` + +Also, note that you don't need a driver `Session` to create simple statements: they can be +initialized statically and stored as constants. + +#### Prepared statements + +[Prepared statements](../statements/prepared) allow Cassandra to cache parsed query strings +server-side, but that's not their only benefit for performance: + +* the driver also caches the response metadata, which can then be skipped in subsequent responses. + This saves bandwidth, as well as the CPU and memory resources required to parse it. +* in some cases, prepared statements set routing information automatically, which allows the driver + to target the most appropriate replicas. + +You should use prepared statements for all recurring requests in your application. Simple statements +should only be used for one-off queries, for example some initialization that will be done only once +at startup. + +The driver caches prepared statements -- see [CqlSession.prepare(SimpleStatement)] for the fine +print. However, if the query is static, it's still a good practice to cache your `PreparedStatement` +instances to avoid calling `prepare()` every time. One common pattern is to use some sort of DAO +component: + +```java +public static class UserDao { + + private final CqlSession session; + private final PreparedStatement preparedFindById; + + public UserDao(CqlSession session) { + this.session = session; + this.preparedFindById = session.prepare("SELECT * FROM user WHERE id = ?"); + } + + public User findById(int id) { + Row row = session.execute(preparedFindById.bind(id)).one(); + return new User(row.getInt(id), row.getString("first_name"), row.getString("last_name")); + } +} +``` + + +### Request execution + +#### Connection pooling + +By default, the driver opens 1 connection per node, and allows 1024 concurrent requests on each +connection. In our experience this is enough for most scenarios. + +If your application generates a very high throughput (hundreds of thousands of requests per second), +you might want to experiment with different settings. See the [tuning](../pooling/#tuning) section +in the connection pooling page. + +#### Compression + +Consider [compression](../compression/) if your queries return large payloads; it might help to +reduce network traffic. + +#### Timestamp generation + +Each query is assigned a [timestamp](../query_timestamps/) to order them relative to each other. + +By default, this is done driver-side with +[AtomicTimestampGenerator](../query_timestamps/#atomic-timestamp-generator). This is a very simple +operation so unlikely to be a bottleneck, but note that there are other options, such as a +[thread-local](../query_timestamps/#thread-local-timestamp-generator) variant that creates slightly +less contention, writing your own implementation or letting the server assign timestamps. + +#### Tracing + +[Tracing](../tracing/) should be used for only a small percentage of your queries. It consumes +additional resources on the server, and fetching each trace requires background requests. + +Do not enable tracing for every request; it's a sure way to bring your performance down. + +#### Request trackers + +[Request trackers](../request_tracker/) are on the hot path (that is, invoked on I/O threads, each +time a request is executed), and users can plug custom implementations. + +If you experience throughput issues, check if any trackers are configured, and what they are doing. +They should avoid blocking calls, as well as any CPU-intensive computations. + +#### Metrics + +Similarly, some of the driver's [metrics](../metrics/) are updated for every request (if the metric +is enabled). + +By default, the driver ships with all metrics disabled. Enable them conservatively, and if you're +investigating a performance issue, try disabling them temporarily to check that they are not the +cause. + +#### Throttling + +[Throttling](../throttling/) can help establish more predictable server performance, by controlling +how much load each driver instance is allowed to put on the cluster. The throttling algorithm itself +incurs a bit of overhead in the driver, but that shouldn't be a problem since the goal is to stay +under reasonable rates in the first place. + +If you're debugging an unfamiliar application and experience a throughput plateau, make sure that +it's not caused by a throttler. + + +### Caching reusable objects + +Many driver objects are immutable. If you reuse the same values often, consider caching them in +private fields or constants to alleviate GC pressure. + +#### Identifiers + +The driver uses [CqlIdentifier] to deal with [case sensitivity](../../case_sensitivity). When you +call methods that take raw strings, the driver generally wraps them under the hood: + +```java +session.getMetadata().getKeyspace("inventory"); // shortcut for getKeyspace(CqlIdentifier.fromCql("inventory") + +// Caching the identifier: +public static final String INVENTORY_ID = "inventory"; + +session.getMetadata().getKeyspace(INVENTORY_ID); +``` + +This also applies to built queries, although it's less important because generally the whole query +can be cached (see below). + +Note however that row getters and bound statement setters do **not** wrap their argument: because +those methods are used very often, they handle raw strings with an optimized algorithm that does not +require creating an identifier (the rules are detailed [here][AccessibleByName]). + +```java +// No need to extract a CqlIdentifier, raw strings are handled efficiently: +Row row = session.execute("SELECT * FROM user WHERE id = 1").one(); +row.getInt("age"); + +PreparedStatement pst = session.prepare("UPDATE user SET name=:name WHERE id=:id"); +pst.bind().setInt("age", 25); +``` + +#### Type tokens + +[GenericType] is used to express complex generic types -- such as +[nested collections](../#collection-types) -- in getters and setters. These objects are immutable +and stateless, so they are good candidates for constants: + +```java +public static final GenericType>> SET_OF_LIST_OF_STRING = new GenericType>>() {}; + +Set> teams = row.get("teams", SET_OF_LIST_OF_STRING); +``` + +`GenericType` itself already exposes a few of those constants. You can create your own utility class +to store yours. + +#### Built queries + +Similarly, [built queries](../../query_builder/) are immutable and don't need a reference to a live +driver instance. If you create them statically, they can be stored as constants: + +```java +public static final BuildableQuery SELECT_SERVER_VERSION = + selectFrom("system", "local").column("release_version"); +``` + +Note that you don't necessarily need to extract `CqlIdentifier` constants since the construction +already happens at initialization time. + +#### Derived configuration profiles + +The configuration API allows you to build [derived profiles](../configuration/#derived-profiles) at +runtime. + +```java +DriverExecutionProfile dynamicProfile = + defaultProfile.withString( + DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.EACH_QUORUM.name()); +``` + +Their use is generally discouraged (you should define profiles statically in the configuration file +as much as possible), but if there's no other way and you reuse them over time, store them instead +of recreating them each time. + +### Metadata + +The driver maintains [metadata](../metadata/) about the state of the Cassandra cluster. This work is +done on dedicated "admin" threads (see the [thread pooling](#thread-pooling) section below), so it's +not in direct competition with regular requests. + + +#### Filtering + +You can disable entire parts of the metadata with those configuration options: + +``` +datastax-java-driver.advanced.metadata { + schema.enabled = true + token-map.enabled = true +} +``` + +This will save CPU and memory resources, but you lose some driver features: + +* if schema is disabled, `session.getMetadata().getKeyspaces()` will always be empty: your + application won't be able to inspect the database schema dynamically. +* if the token map is disabled, `session.getMetadata().getTokenMap()` will always be empty, and you + lose the ability to use [token-aware routing](../load_balancing/#token-aware). + +Note that disabling the schema implicitly disables the token map (because computing the token map +requires the keyspace replication settings). + +Perhaps more interestingly, metadata can be [filtered](../metadata/schema/#filtering) to a specific +subset of keyspaces. This is handy if you connect to a shared cluster that holds data for multiple +applications: + +``` +datastax-java-driver.advanced.metadata { + schema.refreshed-keyspaces = [ "users", "inventory" ] +} +``` + +To get a sense of the time spent on metadata refreshes, enable [debug logs](../logging/) and look +for entries like this: + +``` +[s0-io-0] DEBUG c.d.o.d.i.c.m.s.q.CassandraSchemaQueries - [s0] Schema queries took 88 ms +[s0-admin-0] DEBUG c.d.o.d.i.c.m.s.p.CassandraSchemaParser - [s0] Schema parsing took 71 ms +[s0-admin-0] DEBUG c.d.o.d.i.c.metadata.DefaultMetadata - [s0] Refreshing token map (only schema has changed) +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.DefaultTokenMap - [s0] Computing keyspace-level data for {system_auth={class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=1}, system_schema={class=org.apache.cassandra.locator.LocalStrategy}, system_distributed={class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=3}, system={class=org.apache.cassandra.locator.LocalStrategy}, system_traces={class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=2}} +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.DefaultTokenMap - [s0] Computing new keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=1} +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.KeyspaceTokenMap - [s0] Computing keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=1} took 12 ms +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.DefaultTokenMap - [s0] Computing new keyspace-level data for {class=org.apache.cassandra.locator.LocalStrategy} +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.KeyspaceTokenMap - [s0] Computing keyspace-level data for {class=org.apache.cassandra.locator.LocalStrategy} took 1 ms +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.DefaultTokenMap - [s0] Computing new keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=3} +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.KeyspaceTokenMap - [s0] Computing keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=3} took 54 us +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.DefaultTokenMap - [s0] Computing new keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=2} +[s0-admin-0] DEBUG c.d.o.d.i.c.m.token.KeyspaceTokenMap - [s0] Computing keyspace-level data for {class=org.apache.cassandra.locator.SimpleStrategy, replication_factor=2} took 98 us +[s0-admin-0] DEBUG c.d.o.d.i.c.metadata.DefaultMetadata - [s0] Rebuilding token map took 32 ms +[s0-admin-0] DEBUG c.d.o.d.i.c.metadata.MetadataManager - [s0] Applying schema refresh took 34 ms +``` + +#### Debouncing + +The driver receives push notifications of schema and topology changes from the Cassandra cluster. +These signals are *debounced*, meaning that rapid series of events will be amortized, for example: + +* if multiple schema objects are created or modified, only perform a single schema refresh at the + end. +* if a node's status oscillates rapidly between UP and DOWN, wait for gossip to stabilize and only + apply the last state. + +Debouncing is controlled by these configuration options (shown here with their defaults): + +``` +datastax-java-driver.advanced.metadata { + topology-event-debouncer { + # How long the driver waits to propagate an event. If another event is received within that + # time, the window is reset and a batch of accumulated events will be delivered. + window = 1 second + + # The maximum number of events that can accumulate. If this count is reached, the events are + # delivered immediately and the time window is reset. + max-events = 20 + } + schema.debouncer { + window = 1 second + max-events = 20 + } +} +``` + +You may adjust those settings depending on your application's needs: higher values mean less impact +on performance, but the driver will be slower to react to changes. + +#### Schema updates + +You should group your schema changes as much as possible. + +Every change made from a client will be pushed to all other clients, causing them to refresh their +metadata. If you have multiple client instances, it might be a good idea to +[deactivate the metadata](../metadata/schema/#enabling-disabling) on other clients while you apply +the updates, and reactivate it at the end. Reactivating will trigger an immediate refresh, so you +can even ramp this up to avoid a "thundering herd" effect. + +Schema changes have to replicate to all nodes in the cluster. To minimize the chance of schema +disagreement errors: + +* apply your changes serially. The driver handles this automatically by checking for + [schema agreement](../metadata/schema/#schema-agreement) after each DDL query. Run them from the + same application thread, and, if you use the asynchronous API, chain the futures properly. +* send all the changes to the same coordinator. This is one of the rare cases where we recommend + using [Statement.setNode()]. + +### Thread pooling + +The driver architecture is designed around two code paths: + +* the **hot path** is everything directly related to the execution of requests: encoding/decoding + driver types to/from low-level binary payloads, and network I/O. This is where the driver spends + most of its cycles in a typical application: when we have to make design tradeoffs, performance is + always the priority. Hot code runs on 3 categories of threads: + * your application's thread for the construction of statements; + * the driver's "I/O" event loop group for encoding/decoding and network I/O. You can configure + it with the options in `datastax-java-driver.advanced.netty.io-group`. + * the driver's "timer" thread for request timeouts and speculative executions. See + `datastax-java-driver.advanced.netty.timer`. +* the **cold path** is for all administrative tasks: managing the + [control connection](../control_connection), parsing [metadata](../metadata/), reacting to cluster + events (node going up/down, getting added/removed, etc), and scheduling periodic events + (reconnections, reloading the configuration). Comparatively, these tasks happen less often, and + are less critical (for example, stale schema metadata is not a blocker for request execution). + They are scheduled on a separate "admin" event loop group, controlled by the options in + `datastax-java-driver.advanced.netty.admin-group`. + +By default, the number of I/O threads is set to `Runtime.getRuntime().availableProcessors() * 2`, +and the number of admin threads to 2. It's hard to give one-size-fits-all recommendations because +every case is different, but you might want to try lowering I/O threads, especially if your +application already creates a lot of threads on its side. + +Note that you can gain more fine-grained control over thread pools via the +[internal](../../api_conventions) API (look at the `NettyOptions` interface). In particular, it is +possible to reuse the same event loop group for I/O, admin tasks, and even your application code +(the driver's internal code is fully asynchronous so it will never block any thread). The timer is +the only one that will have to stay on a separate thread. + +[AccessibleByName]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/data/AccessibleByName.html +[CqlIdentifier]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html +[CqlSession.prepare(SimpleStatement)]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlSession.html#prepare-com.datastax.oss.driver.api.core.cql.SimpleStatement- +[GenericType]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/reflect/GenericType.html +[Statement.setNode()]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/Statement.html#setNode-com.datastax.oss.driver.api.core.metadata.Node- \ No newline at end of file From a2af2edcc8f3f52983ef54122983fca8da8ea91a Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Tue, 29 Jan 2019 04:10:10 -0800 Subject: [PATCH 669/742] JAVA-2084: Fix session.executeAsync examples in the docs to reflect current API (#1179) Since eb4dcda2 it returns CompletionStage. --- manual/core/async/README.md | 10 +++++----- manual/core/paging/README.md | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/manual/core/async/README.md b/manual/core/async/README.md index 8b63e30243f..b345e02f7d3 100644 --- a/manual/core/async/README.md +++ b/manual/core/async/README.md @@ -10,7 +10,7 @@ Here is a short example that opens a session and runs a query asynchronously: CompletionStage sessionStage = CqlSession.builder().buildAsync(); // Chain one async operation after another: -CompletionStage responseStage = +CompletionStage responseStage = sessionStage.thenCompose( session -> session.executeAsync("SELECT release_version FROM system.local")); @@ -45,7 +45,7 @@ CompletionStage sessionStage = CqlSession.builder().buildAsync(); sessionStage.thenAccept(session -> System.out.println(Thread.currentThread().getName())); // prints s0-admin-n (admin pool thread) -CompletionStage resultStage = +CompletionStage resultStage = session.executeAsync("SELECT release_version FROM system.local"); resultStage.thenAccept(resultSet -> System.out.println(Thread.currentThread().getName())); // prints s0-io-n (I/O pool thread) @@ -56,11 +56,11 @@ method from inside a callback: ```java // Get the department id for a given user: -CompletionStage idStage = +CompletionStage idStage = session.executeAsync("SELECT department_id FROM user WHERE id = 1"); // Once we have the id, query the details of that department: -CompletionStage dataStage = +CompletionStage dataStage = idStage.thenCompose( resultSet -> { UUID departmentId = resultSet.one().getUuid(0); @@ -122,7 +122,7 @@ specific to the driver). When all your callback does is a side effect, it's easy swallow an exception: ```java -CompletionStage responseStage = +CompletionStage responseStage = sessionStage.thenCompose( session -> session.executeAsync("SELECT release_version FROM system.local")); responseStage.thenAccept( diff --git a/manual/core/paging/README.md b/manual/core/paging/README.md index 5fe17b6a40b..b81d196081a 100644 --- a/manual/core/paging/README.md +++ b/manual/core/paging/README.md @@ -97,7 +97,8 @@ iteration only yields the current page, and the next page must be explicitly fet that translates to our example: ```java -CompletionStage futureRs = session.executeAsync("SELECT * FROM myTable WHERE id = 1"); +CompletionStage futureRs = + session.executeAsync("SELECT * FROM myTable WHERE id = 1"); futureRs.whenComplete(this::processRows); void processRows(AsyncResultSet rs, Throwable error) { From 9a81e4470c2d45d61d8f08b962597b0a3be0677f Mon Sep 17 00:00:00 2001 From: Greg Bestland Date: Tue, 29 Jan 2019 15:55:18 -0600 Subject: [PATCH 670/742] Java2063 Normalize authentication error logging. (#1176) * JAVA-2063: Normalize authentication logging --- changelog/README.md | 1 + .../core/control/ControlConnection.java | 54 +++++++++++++++++-- .../control/ControlConnectionTestBase.java | 6 +++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 87a296443b4..acc975152d4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [bug] JAVA-2063: Normalize authentication logging - [documentation] JAVA-2034: Add performance recommendations in the manual - [improvement] JAVA-2077: Allow reconnection policy to detect first connection attempt - [improvement] JAVA-2067: Publish javadocs JAR for the shaded module diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 1b717f4cb34..011bf9c02ad 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -17,6 +17,9 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; +import com.datastax.oss.driver.api.core.auth.AuthenticationException; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; @@ -47,6 +50,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import io.netty.util.concurrent.EventExecutor; import java.net.InetSocketAddress; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Queue; @@ -233,6 +237,7 @@ private void processSchemaChange(Event event) { private class SingleThreaded { private final InternalDriverContext context; + private final DriverConfig config; private final CompletableFuture initFuture = new CompletableFuture<>(); private final CompletableFuture firstConnectionAttemptFuture = new CompletableFuture<>(); private boolean initWasCalled; @@ -247,6 +252,7 @@ private class SingleThreaded { private SingleThreaded(InternalDriverContext context) { this.context = context; + this.config = context.getConfig(); this.reconnectionPolicy = context.getReconnectionPolicy(); this.reconnection = new Reconnection( @@ -298,6 +304,11 @@ private void init( firstConnectionAttemptFuture.complete(null); }, error -> { + if (isAuthFailure(error)) { + LOG.warn( + "[{}] Authentication errors encountered on all contact points. Please check your authentication configuration.", + logPrefix); + } if (reconnectOnFailure && !closeWasCalled) { reconnection.start( reconnectionPolicy.newControlConnectionSchedule( @@ -359,11 +370,27 @@ private void connect( if (closeWasCalled || initFuture.isCancelled()) { onSuccess.run(); // abort, we don't really care about the result } else { - LOG.debug( - "[{}] Error connecting to {}, trying next node", - logPrefix, - node, - error); + if (error instanceof AuthenticationException) { + Loggers.warnWithException( + LOG, "[{}] Authentication error", logPrefix, error); + } else { + if (config + .getDefaultProfile() + .getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) { + Loggers.warnWithException( + LOG, + "[{}] Error connecting to {}, trying next node", + logPrefix, + node, + error); + } else { + LOG.debug( + "[{}] Error connecting to {}, trying next node", + logPrefix, + node, + error); + } + } Map newErrors = (errors == null) ? new LinkedHashMap<>() : errors; newErrors.put(node, error); @@ -528,6 +555,23 @@ private void forceClose() { } } + private boolean isAuthFailure(Throwable error) { + boolean authFailure = true; + if (error instanceof AllNodesFailedException) { + Collection errors = ((AllNodesFailedException) error).getErrors().values(); + if (errors.size() == 0) { + return false; + } + for (Throwable nodeError : errors) { + if (!(nodeError instanceof AuthenticationException)) { + authFailure = false; + break; + } + } + } + return authFailure; + } + private static ImmutableList buildEventTypes(boolean listenClusterEvents) { ImmutableList.Builder builder = ImmutableList.builder(); builder.add(ProtocolConstants.EventType.SCHEMA_CHANGE); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 5c224594543..9a9c6cc0ded 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -72,6 +72,8 @@ abstract class ControlConnectionTestBase { @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @Mock protected MetadataManager metadataManager; @Mock protected MetricsFactory metricsFactory; + @Mock protected DriverConfig config; + @Mock protected DriverExecutionProfile defaultProfile; protected AddressTranslator addressTranslator; protected DefaultNode node1; @@ -126,6 +128,10 @@ public void setup() { addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); + Mockito.when(context.getConfig()).thenReturn(config); + Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) + .thenReturn(false); controlConnection = new ControlConnection(context); } From d6cb615e82c991810b12095b795377e594e1ebe3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 23 Jan 2019 16:18:38 -0800 Subject: [PATCH 671/742] JAVA-2119: Add PagingIterable abstraction as a supertype of ResultSet --- changelog/README.md | 1 + .../driver/api/core/AsyncPagingIterable.java | 108 ++++++++++++ .../oss/driver/api/core/PagingIterable.java | 161 ++++++++++++++++++ .../driver/api/core/cql/AsyncResultSet.java | 66 +------ .../oss/driver/api/core/cql/ResultSet.java | 121 +------------ .../core/AsyncPagingIterableWrapper.java | 96 +++++++++++ .../internal/core/PagingIterableWrapper.java | 78 +++++++++ .../core/AsyncPagingIterableWrapperTest.java | 139 +++++++++++++++ .../core/PagingIterableWrapperTest.java | 105 ++++++++++++ .../internal/core/cql/ResultSetTestBase.java | 88 ++++++++++ .../internal/core/cql/ResultSetsTest.java | 64 +------ 11 files changed, 791 insertions(+), 236 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/PagingIterable.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/PagingIterableWrapper.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/PagingIterableWrapperTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java diff --git a/changelog/README.md b/changelog/README.md index acc975152d4..f5842f5c57e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2119: Add PagingIterable abstraction as a supertype of ResultSet - [bug] JAVA-2063: Normalize authentication logging - [documentation] JAVA-2034: Add performance recommendations in the manual - [improvement] JAVA-2077: Allow reconnection policy to detect first connection attempt diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java new file mode 100644 index 00000000000..fcd03e15f77 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java @@ -0,0 +1,108 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.internal.core.AsyncPagingIterableWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +/** + * An iterable of elements which are fetched asynchronously by the driver, possibly in multiple + * requests. + */ +public interface AsyncPagingIterable { + + /** Metadata about the columns returned by the CQL request that was used to build this result. */ + @NonNull + ColumnDefinitions getColumnDefinitions(); + + /** Returns {@linkplain ExecutionInfo information about the execution} of this page of results. */ + @NonNull + ExecutionInfo getExecutionInfo(); + + /** How many rows are left before the current page is exhausted. */ + int remaining(); + + /** + * The elements in the current page. To keep iterating beyond that, use {@link #hasMorePages()} + * and {@link #fetchNextPage()}. + * + *

        Note that this method always returns the same object, and that that object can only be + * iterated once: elements are "consumed" as they are read. + */ + @NonNull + Iterable currentPage(); + + /** + * Returns the next element, or {@code null} if the results are exhausted. + * + *

        This is convenient for queries that are known to return exactly one element, for example + * count queries. + */ + @Nullable + default ElementT one() { + Iterator iterator = currentPage().iterator(); + return iterator.hasNext() ? iterator.next() : null; + } + + /** + * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next + * one asynchronously. + */ + boolean hasMorePages(); + + /** + * Fetch the next page of results asynchronously. + * + * @throws IllegalStateException if there are no more pages. Use {@link #hasMorePages()} to check + * if you can call this method. + */ + @NonNull + CompletionStage> fetchNextPage() + throws IllegalStateException; + + /** + * If the query that produced this result was a CQL conditional update, indicate whether it was + * successfully applied. + * + *

        For consistency, this method always returns {@code true} for non-conditional queries + * (although there is no reason to call the method in that case). This is also the case for + * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF + * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. + * + *

        Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this + * method to always return {@code true} for batches containing conditional queries. + */ + boolean wasApplied(); + + /** + * Creates a new instance by transforming each element of this iterable with the provided + * function. + * + *

        Note that both instances share the same underlying data: consuming elements from the + * transformed iterable will also consume them from this object, and vice-versa. + */ + default AsyncPagingIterable map( + Function elementMapper) { + return new AsyncPagingIterableWrapper<>(this, elementMapper); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/PagingIterable.java b/core/src/main/java/com/datastax/oss/driver/api/core/PagingIterable.java new file mode 100644 index 00000000000..0a7f4768de4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/PagingIterable.java @@ -0,0 +1,161 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.internal.core.PagingIterableWrapper; +import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +/** + * An iterable of elements which are fetched synchronously by the driver, possibly in multiple + * requests. + * + *

        It uses asynchronous calls internally, but blocks on the results in order to provide a + * synchronous API to its clients. If the query is paged, only the first page will be fetched + * initially, and iteration will trigger background fetches of the next pages when necessary. + * + *

        Note that this object can only be iterated once: elements are "consumed" as they are read, + * subsequent calls to {@code iterator()} will return the same iterator instance. + * + *

        Implementations of this type are not thread-safe. They can only be iterated by the + * thread that invoked {@code session.execute}. + * + *

        This is a generalization of {@link ResultSet}, replacing rows by an arbitrary element type. + */ +public interface PagingIterable extends Iterable { + + /** Metadata about the columns returned by the CQL request that was used to build this result. */ + @NonNull + ColumnDefinitions getColumnDefinitions(); + + /** + * The execution information for the last query performed for this iterable. + * + *

        This is a shortcut for: + * + *

        +   * getExecutionInfos().get(getExecutionInfos().size() - 1)
        +   * 
        + * + * @see #getExecutionInfos() + */ + @NonNull + default ExecutionInfo getExecutionInfo() { + List infos = getExecutionInfos(); + return infos.get(infos.size() - 1); + } + + /** + * The execution information for all the queries that have been performed so far to assemble this + * iterable. + * + *

        This will have multiple elements if the query is paged, since the driver performs blocking + * background queries to fetch additional pages transparently as the result set is being iterated. + */ + @NonNull + List getExecutionInfos(); + + /** + * Returns the next element, or {@code null} if the iterable is exhausted. + * + *

        This is convenient for queries that are known to return exactly one row, for example count + * queries. + */ + @Nullable + default ElementT one() { + Iterator iterator = iterator(); + return iterator.hasNext() ? iterator.next() : null; + } + + /** + * Returns all the remaining elements as a list; not recommended for queries that return a + * large number of elements. + * + *

        Contrary to {@link #iterator()} or successive calls to {@link #one()}, this method forces + * fetching the full contents at once; in particular, this means that a large number of + * background queries might have to be run, and that all the data will be held in memory locally. + * Therefore it is crucial to only call this method for queries that are known to return a + * reasonable number of results. + */ + @NonNull + default List all() { + if (!iterator().hasNext()) { + return Collections.emptyList(); + } + // We can't know the actual size in advance since more pages could be fetched, but we can at + // least allocate for what we already have. + List result = Lists.newArrayListWithExpectedSize(getAvailableWithoutFetching()); + Iterables.addAll(result, this); + return result; + } + + /** + * Whether all pages have been fetched from the database. + * + *

        If this is {@code false}, it means that more blocking background queries will be triggered + * as iteration continues. + */ + boolean isFullyFetched(); + + /** + * The number of elements that can be returned from this result set before a blocking background + * query needs to be performed to retrieve more results. In other words, this is the number of + * elements remaining in the current page. + * + *

        This is useful if you use the paging state to pause the iteration and resume it later: after + * you've retrieved the state ({@link ExecutionInfo#getPagingState() + * getExecutionInfo().getPagingState()}), call this method and iterate the remaining elements; + * that way you're not leaving a gap between the last element and the position you'll restart from + * when you reinject the state in a new query. + */ + int getAvailableWithoutFetching(); + + /** + * If the query that produced this result was a CQL conditional update, indicate whether it was + * successfully applied. + * + *

        For consistency, this method always returns {@code true} for non-conditional queries + * (although there is no reason to call the method in that case). This is also the case for + * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF + * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. + * + *

        Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this + * method to always return {@code true} for batches containing conditional queries. + */ + boolean wasApplied(); + + /** + * Creates a new instance by transforming each element of this iterable with the provided + * function. + * + *

        Note that both instances share the same underlying data: consuming elements from the + * transformed iterable will also consume them from this object, and vice-versa. + */ + default PagingIterable map( + Function elementMapper) { + return new PagingIterableWrapper<>(this, elementMapper); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 8f3c6827ab3..87673208d99 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -15,10 +15,9 @@ */ package com.datastax.oss.driver.api.core.cql; +import com.datastax.oss.driver.api.core.AsyncPagingIterable; import com.datastax.oss.driver.api.core.CqlSession; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Iterator; import java.util.concurrent.CompletionStage; /** @@ -27,59 +26,16 @@ * @see CqlSession#executeAsync(Statement) * @see CqlSession#executeAsync(String) */ -public interface AsyncResultSet { +public interface AsyncResultSet extends AsyncPagingIterable { - /** Returns metadata about the {@linkplain ColumnDefinitions columns} contained in this row. */ - @NonNull - ColumnDefinitions getColumnDefinitions(); - - /** Returns {@linkplain ExecutionInfo information about the execution} of this page of results. */ - @NonNull - ExecutionInfo getExecutionInfo(); - - /** How many rows are left before the current page is exhausted. */ - int remaining(); - - /** - * The rows in the current page. To keep iterating beyond that, use {@link #hasMorePages()} and - * {@link #fetchNextPage()}. - * - *

        Note that this method always returns the same object, and that that object can only be - * iterated once: rows are "consumed" as they are read. - */ - @NonNull - Iterable currentPage(); - - /** - * Returns the next row, or {@code null} if the result set is exhausted. - * - *

        This is convenient for queries that are known to return exactly one row, for example count - * queries. - */ - @Nullable - default Row one() { - Iterator iterator = currentPage().iterator(); - return iterator.hasNext() ? iterator.next() : null; - } - - /** - * Whether there are more pages of results. If so, call {@link #fetchNextPage()} to fetch the next - * one asynchronously. - */ - boolean hasMorePages(); - - /** - * Fetch the next page of results asynchronously. - * - * @throws IllegalStateException if there are no more pages. Use {@link #hasMorePages()} to check - * if you can call this method. - */ + // overridden to use a covariant return type: @NonNull + @Override CompletionStage fetchNextPage() throws IllegalStateException; + // overridden to amend the javadocs: /** - * If the query that produced this result was a conditional update, indicate whether it was - * successfully applied. + * {@inheritDoc} * *

        This is equivalent to calling: * @@ -88,15 +44,7 @@ default Row one() { * * * Except that this method peeks at the next row without consuming it. - * - *

        For consistency, this method always returns {@code true} for non-conditional queries - * (although there is no reason to call the method in that case). This is also the case for - * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF - * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. - * - *

        Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this - * method to always return {@code true} for batches containing conditional queries. */ + @Override boolean wasApplied(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java index c9b13189716..d4383476ca3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/ResultSet.java @@ -16,122 +16,23 @@ package com.datastax.oss.driver.api.core.cql; import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; -import com.datastax.oss.driver.shaded.guava.common.collect.Lists; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import com.datastax.oss.driver.api.core.PagingIterable; /** * The result of a synchronous CQL query. * - *

        It uses {@link AsyncResultSet asynchronous calls} internally, but blocks on the results in - * order to provide a synchronous API to its clients. If the query is paged, only the first page - * will be fetched initially, and iteration will trigger background fetches of the next pages when - * necessary. - * - *

        Note that this object can only be iterated once: rows are "consumed" as they are read, - * subsequent calls to {@code iterator()} will the same iterator instance. - * - *

        Implementations of this type are not thread-safe. They can only be iterated by the + *

        See {@link PagingIterable} for a few generic explanations about the behavior of this object; + * in particular, implementations are not thread-safe. They can only be iterated by the * thread that invoked {@code session.execute}. * * @see CqlSession#execute(Statement) * @see CqlSession#execute(String) */ -public interface ResultSet extends Iterable { - - /** @return the column definitions contained in this result set. */ - @NonNull - ColumnDefinitions getColumnDefinitions(); +public interface ResultSet extends PagingIterable { + // overridden to amend the javadocs: /** - * The execution information for the last query performed for this result set. - * - *

        This is a shortcut for: - * - *

        -   * getExecutionInfos().get(getExecutionInfos().size() - 1)
        -   * 
        - * - * @see #getExecutionInfos() - */ - @NonNull - default ExecutionInfo getExecutionInfo() { - List infos = getExecutionInfos(); - return infos.get(infos.size() - 1); - } - - /** - * The execution information for all the queries that have been performed so far to assemble this - * result set. - * - *

        This will have multiple elements if the query is paged, since the driver performs blocking - * background queries to fetch additional pages transparently as the result set is being iterated. - */ - @NonNull - List getExecutionInfos(); - - /** - * Returns the next row, or {@code null} if the result set is exhausted. - * - *

        This is convenient for queries that are known to return exactly one row, for example count - * queries. - */ - @Nullable - default Row one() { - Iterator iterator = iterator(); - return iterator.hasNext() ? iterator.next() : null; - } - - /** - * Returns all the remaining rows as a list; not recommended for queries that return a large - * number of rows. - * - *

        Contrary to {@link #iterator()} or successive calls to {@link #one()}, this method forces - * fetching the full contents of the result set at once; in particular, this means that a - * large number of background queries might have to be run, and that all the data will be held in - * memory locally. Therefore it is crucial to only call this method for queries that are known to - * return a reasonable number of results. - */ - @NonNull - default List all() { - if (!iterator().hasNext()) { - return Collections.emptyList(); - } - // We can't know the actual size in advance since more pages could be fetched, but we can at - // least allocate for what we already have. - List result = Lists.newArrayListWithExpectedSize(getAvailableWithoutFetching()); - Iterables.addAll(result, this); - return result; - } - - /** - * Whether all pages have been fetched from the database. - * - *

        If this is {@code false}, it means that more blocking background queries will be triggered - * as iteration continues. - */ - boolean isFullyFetched(); - - /** - * The number of rows that can be returned from this result set before a blocking background query - * needs to be performed to retrieve more results. In other words, this is the number of rows - * remaining in the current page. - * - *

        This is useful if you use the paging state to pause the iteration and resume it later: after - * you've retrieved the state ({@link ExecutionInfo#getPagingState() - * getExecutionInfo().getPagingState()}), call this method and iterate the remaining rows; that - * way you're not leaving a gap between the last row and the position you'll restart from when you - * reinject the state in a new query. - */ - int getAvailableWithoutFetching(); - - /** - * If the query that produced this result was a conditional update, indicate whether it was - * successfully applied. + * {@inheritDoc} * *

        This is equivalent to calling: * @@ -140,15 +41,7 @@ default List all() { * * * Except that this method peeks at the next row without consuming it. - * - *

        For consistency, this method always returns {@code true} for non-conditional queries - * (although there is no reason to call the method in that case). This is also the case for - * conditional DDL statements ({@code CREATE KEYSPACE... IF NOT EXISTS}, {@code CREATE TABLE... IF - * NOT EXISTS}), for which Cassandra doesn't return an {@code [applied]} column. - * - *

        Note that, for versions of Cassandra strictly lower than 2.1.0-rc2, a server-side bug (CASSANDRA-7337) causes this - * method to always return {@code true} for batches containing conditional queries. */ + @Override boolean wasApplied(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java new file mode 100644 index 00000000000..63589c25eeb --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java @@ -0,0 +1,96 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.AsyncPagingIterable; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.shaded.guava.common.collect.AbstractIterator; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +public class AsyncPagingIterableWrapper implements AsyncPagingIterable { + + private final AsyncPagingIterable source; + private final Function elementMapper; + + private final Iterable currentPage; + + public AsyncPagingIterableWrapper( + AsyncPagingIterable source, + Function elementMapper) { + this.source = source; + this.elementMapper = elementMapper; + + Iterator sourceIterator = source.currentPage().iterator(); + Iterator iterator = + new AbstractIterator() { + @Override + protected TargetT computeNext() { + return (sourceIterator.hasNext()) + ? elementMapper.apply(sourceIterator.next()) + : endOfData(); + } + }; + this.currentPage = () -> iterator; + } + + @NonNull + @Override + public ColumnDefinitions getColumnDefinitions() { + return source.getColumnDefinitions(); + } + + @NonNull + @Override + public ExecutionInfo getExecutionInfo() { + return source.getExecutionInfo(); + } + + @Override + public int remaining() { + return source.remaining(); + } + + @NonNull + @Override + public Iterable currentPage() { + return currentPage; + } + + @Override + public boolean hasMorePages() { + return source.hasMorePages(); + } + + @NonNull + @Override + public CompletionStage> fetchNextPage() + throws IllegalStateException { + return source + .fetchNextPage() + .thenApply( + nextSource -> + new AsyncPagingIterableWrapper(nextSource, elementMapper)); + } + + @Override + public boolean wasApplied() { + return source.wasApplied(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/PagingIterableWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/PagingIterableWrapper.java new file mode 100644 index 00000000000..675f9f2cd4b --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/PagingIterableWrapper.java @@ -0,0 +1,78 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import com.datastax.oss.driver.api.core.PagingIterable; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.shaded.guava.common.collect.AbstractIterator; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +public class PagingIterableWrapper implements PagingIterable { + + private final PagingIterable source; + private final Iterator iterator; + + public PagingIterableWrapper( + PagingIterable source, Function elementMapper) { + this.source = source; + Iterator sourceIterator = source.iterator(); + this.iterator = + new AbstractIterator() { + @Override + protected TargetT computeNext() { + return (sourceIterator.hasNext()) + ? elementMapper.apply(sourceIterator.next()) + : endOfData(); + } + }; + } + + @NonNull + @Override + public ColumnDefinitions getColumnDefinitions() { + return source.getColumnDefinitions(); + } + + @NonNull + @Override + public List getExecutionInfos() { + return source.getExecutionInfos(); + } + + @Override + public boolean isFullyFetched() { + return source.isFullyFetched(); + } + + @Override + public int getAvailableWithoutFetching() { + return source.getAvailableWithoutFetching(); + } + + @Override + public boolean wasApplied() { + return source.wasApplied(); + } + + @Override + public Iterator iterator() { + return iterator; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java new file mode 100644 index 00000000000..cc9b869b484 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java @@ -0,0 +1,139 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.AsyncPagingIterable; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +import com.datastax.oss.driver.api.core.cql.ColumnDefinition; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.cql.DefaultAsyncResultSet; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +public class AsyncPagingIterableWrapperTest { + + @Mock private ColumnDefinitions columnDefinitions; + @Mock private Statement statement; + @Mock private CqlSession session; + @Mock private InternalDriverContext context; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + // One single column "i" of type int: + Mockito.when(columnDefinitions.contains("i")).thenReturn(true); + ColumnDefinition iDefinition = Mockito.mock(ColumnDefinition.class); + Mockito.when(iDefinition.getType()).thenReturn(DataTypes.INT); + Mockito.when(columnDefinitions.get("i")).thenReturn(iDefinition); + Mockito.when(columnDefinitions.firstIndexOf("i")).thenReturn(0); + Mockito.when(columnDefinitions.get(0)).thenReturn(iDefinition); + + Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + Mockito.when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); + } + + @Test + public void should_wrap_result_set() throws Exception { + // Given + // two pages of data: + ExecutionInfo executionInfo1 = mockExecutionInfo(); + DefaultAsyncResultSet resultSet1 = + new DefaultAsyncResultSet( + columnDefinitions, executionInfo1, mockData(0, 5), session, context); + DefaultAsyncResultSet resultSet2 = + new DefaultAsyncResultSet( + columnDefinitions, mockExecutionInfo(), mockData(5, 10), session, context); + // chain them together: + ByteBuffer mockPagingState = ByteBuffer.allocate(0); + Mockito.when(executionInfo1.getPagingState()).thenReturn(mockPagingState); + Statement mockNextStatement = Mockito.mock(Statement.class); + Mockito.when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); + Mockito.when(session.executeAsync(mockNextStatement)) + .thenAnswer(invocation -> CompletableFuture.completedFuture(resultSet2)); + + // When + AsyncPagingIterable iterable1 = resultSet1.map(row -> row.getInt("i")); + + // Then + for (int i = 0; i < 5; i++) { + assertThat(iterable1.one()).isEqualTo(i); + assertThat(iterable1.remaining()).isEqualTo(resultSet1.remaining()).isEqualTo(4 - i); + } + assertThat(iterable1.hasMorePages()).isTrue(); + + AsyncPagingIterable iterable2 = iterable1.fetchNextPage().toCompletableFuture().get(); + for (int i = 5; i < 10; i++) { + assertThat(iterable2.one()).isEqualTo(i); + assertThat(iterable2.remaining()).isEqualTo(resultSet2.remaining()).isEqualTo(9 - i); + } + assertThat(iterable2.hasMorePages()).isFalse(); + } + + /** Checks that consuming from the wrapper consumes from the source, and vice-versa. */ + @Test + public void should_share_iteration_progress_with_wrapped_result_set() { + // Given + DefaultAsyncResultSet resultSet = + new DefaultAsyncResultSet( + columnDefinitions, mockExecutionInfo(), mockData(0, 10), session, context); + + // When + AsyncPagingIterable iterable = resultSet.map(row -> row.getInt("i")); + + // Then + // Consume alternatively from the source and mapped iterable, and check that they stay in sync + for (int i = 0; i < 10; i++) { + Object element = (i % 2 == 0 ? resultSet : iterable).one(); + assertThat(element).isNotNull(); + assertThat(iterable.remaining()).isEqualTo(resultSet.remaining()).isEqualTo(9 - i); + } + assertThat(resultSet.hasMorePages()).isFalse(); + assertThat(iterable.hasMorePages()).isFalse(); + } + + private ExecutionInfo mockExecutionInfo() { + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(executionInfo.getStatement()).thenAnswer(invocation -> statement); + return executionInfo; + } + + private Queue> mockData(int start, int end) { + Queue> data = new ArrayDeque<>(); + for (int i = start; i < end; i++) { + data.add(Lists.newArrayList(TypeCodecs.INT.encode(i, DefaultProtocolVersion.DEFAULT))); + } + return data; + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/PagingIterableWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/PagingIterableWrapperTest.java new file mode 100644 index 00000000000..85718052928 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/PagingIterableWrapperTest.java @@ -0,0 +1,105 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.PagingIterable; +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.cql.ResultSetTestBase; +import com.datastax.oss.driver.internal.core.cql.ResultSets; +import java.util.Iterator; +import org.junit.Test; + +public class PagingIterableWrapperTest extends ResultSetTestBase { + + @Test + public void should_wrap_result_set() { + // Given + AsyncResultSet page1 = mockPage(true, 0, 1, 2); + AsyncResultSet page2 = mockPage(true, 3, 4, 5); + AsyncResultSet page3 = mockPage(false, 6, 7, 8); + + complete(page1.fetchNextPage(), page2); + complete(page2.fetchNextPage(), page3); + + // When + PagingIterable iterable = ResultSets.newInstance(page1).map(row -> row.getInt(0)); + + // Then + assertThat(iterable.getExecutionInfo()).isSameAs(page1.getExecutionInfo()); + assertThat(iterable.getExecutionInfos()).containsExactly(page1.getExecutionInfo()); + + Iterator iterator = iterable.iterator(); + + assertThat(iterator.next()).isEqualTo(0); + assertThat(iterator.next()).isEqualTo(1); + assertThat(iterator.next()).isEqualTo(2); + + assertThat(iterator.hasNext()).isTrue(); + // This should have triggered the fetch of page2 + assertThat(iterable.getExecutionInfo()).isEqualTo(page2.getExecutionInfo()); + assertThat(iterable.getExecutionInfos()) + .containsExactly(page1.getExecutionInfo(), page2.getExecutionInfo()); + + assertThat(iterator.next()).isEqualTo(3); + assertThat(iterator.next()).isEqualTo(4); + assertThat(iterator.next()).isEqualTo(5); + + assertThat(iterator.hasNext()).isTrue(); + // This should have triggered the fetch of page3 + assertThat(iterable.getExecutionInfo()).isEqualTo(page3.getExecutionInfo()); + assertThat(iterable.getExecutionInfos()) + .containsExactly( + page1.getExecutionInfo(), page2.getExecutionInfo(), page3.getExecutionInfo()); + + assertThat(iterator.next()).isEqualTo(6); + assertThat(iterator.next()).isEqualTo(7); + assertThat(iterator.next()).isEqualTo(8); + } + + /** Checks that consuming from the wrapper consumes from the source, and vice-versa. */ + @Test + public void should_share_iteration_progress_with_wrapped_result_set() { + // Given + AsyncResultSet page1 = mockPage(true, 0, 1, 2); + AsyncResultSet page2 = mockPage(true, 3, 4, 5); + AsyncResultSet page3 = mockPage(false, 6, 7, 8); + + complete(page1.fetchNextPage(), page2); + complete(page2.fetchNextPage(), page3); + + // When + ResultSet resultSet = ResultSets.newInstance(page1); + PagingIterable iterable = resultSet.map(row -> row.getInt(0)); + + // Then + Iterator sourceIterator = resultSet.iterator(); + Iterator mappedIterator = iterable.iterator(); + + assertThat(mappedIterator.next()).isEqualTo(0); + assertNextRow(sourceIterator, 1); + assertThat(mappedIterator.next()).isEqualTo(2); + assertNextRow(sourceIterator, 3); + assertThat(mappedIterator.next()).isEqualTo(4); + assertNextRow(sourceIterator, 5); + assertThat(mappedIterator.next()).isEqualTo(6); + assertNextRow(sourceIterator, 7); + assertThat(mappedIterator.next()).isEqualTo(8); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java new file mode 100644 index 00000000000..1a25d73d384 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java @@ -0,0 +1,88 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.util.CountingIterator; +import com.datastax.oss.driver.shaded.guava.common.collect.Lists; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.mockito.Mockito; + +public abstract class ResultSetTestBase { + + /** Mocks an async result set where column 0 has type INT, with rows with the provided data. */ + protected AsyncResultSet mockPage(boolean nextPage, Integer... data) { + AsyncResultSet page = Mockito.mock(AsyncResultSet.class); + + ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); + Mockito.when(page.getColumnDefinitions()).thenReturn(columnDefinitions); + + ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); + Mockito.when(page.getExecutionInfo()).thenReturn(executionInfo); + + if (nextPage) { + Mockito.when(page.hasMorePages()).thenReturn(true); + Mockito.when(page.fetchNextPage()).thenReturn(Mockito.spy(new CompletableFuture<>())); + } else { + Mockito.when(page.hasMorePages()).thenReturn(false); + Mockito.when(page.fetchNextPage()).thenThrow(new IllegalStateException()); + } + + // Emulate DefaultAsyncResultSet's internals (this is a bit sketchy, maybe it would be better + // to use real DefaultAsyncResultSet instances) + Queue queue = Lists.newLinkedList(Arrays.asList(data)); + CountingIterator iterator = + new CountingIterator(queue.size()) { + @Override + protected Row computeNext() { + Integer index = queue.poll(); + return (index == null) ? endOfData() : mockRow(index); + } + }; + Mockito.when(page.currentPage()).thenReturn(() -> iterator); + Mockito.when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); + + return page; + } + + private Row mockRow(int index) { + Row row = Mockito.mock(Row.class); + Mockito.when(row.getInt(0)).thenReturn(index); + return row; + } + + protected static void complete( + CompletionStage stage, AsyncResultSet result) { + @SuppressWarnings("unchecked") + CompletableFuture future = (CompletableFuture) stage; + future.complete(result); + } + + protected void assertNextRow(Iterator iterator, int expectedValue) { + assertThat(iterator.hasNext()).isTrue(); + Row row = iterator.next(); + assertThat(row.getInt(0)).isEqualTo(expectedValue); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java index e196481adab..3d52fc3d22e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetsTest.java @@ -18,21 +18,12 @@ import static com.datastax.oss.driver.Assertions.assertThat; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; -import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; -import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; -import com.datastax.oss.driver.internal.core.util.CountingIterator; -import com.datastax.oss.driver.shaded.guava.common.collect.Lists; -import java.util.Arrays; import java.util.Iterator; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.junit.Test; -import org.mockito.Mockito; -public class ResultSetsTest { +public class ResultSetsTest extends ResultSetTestBase { @Test public void should_create_result_set_from_single_page() { @@ -103,57 +94,4 @@ public void should_create_result_set_from_multiple_pages() { assertNextRow(iterator, 7); assertNextRow(iterator, 8); } - - private void assertNextRow(Iterator iterator, int expectedValue) { - assertThat(iterator.hasNext()).isTrue(); - Row row0 = iterator.next(); - assertThat(row0.getInt(0)).isEqualTo(expectedValue); - } - - private AsyncResultSet mockPage(boolean nextPage, Integer... data) { - AsyncResultSet page = Mockito.mock(AsyncResultSet.class); - - ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); - Mockito.when(page.getColumnDefinitions()).thenReturn(columnDefinitions); - - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(page.getExecutionInfo()).thenReturn(executionInfo); - - if (nextPage) { - Mockito.when(page.hasMorePages()).thenReturn(true); - Mockito.when(page.fetchNextPage()).thenReturn(Mockito.spy(new CompletableFuture<>())); - } else { - Mockito.when(page.hasMorePages()).thenReturn(false); - Mockito.when(page.fetchNextPage()).thenThrow(new IllegalStateException()); - } - - // Emulate DefaultAsyncResultSet's internals (this is a bit sketchy, maybe it would be better - // to use real DefaultAsyncResultSet instances) - Queue queue = Lists.newLinkedList(Arrays.asList(data)); - CountingIterator iterator = - new CountingIterator(queue.size()) { - @Override - protected Row computeNext() { - Integer index = queue.poll(); - return (index == null) ? endOfData() : mockRow(index); - } - }; - Mockito.when(page.currentPage()).thenReturn(() -> iterator); - Mockito.when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); - - return page; - } - - private Row mockRow(int index) { - Row row = Mockito.mock(Row.class); - Mockito.when(row.getInt(0)).thenReturn(index); - return row; - } - - private static void complete( - CompletionStage stage, AsyncResultSet result) { - @SuppressWarnings("unchecked") - CompletableFuture future = (CompletableFuture) stage; - future.complete(result); - } } From 5fc73d6556360bcf543eee93304d21a82cda43cb Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 30 Jan 2019 09:49:46 -0800 Subject: [PATCH 672/742] Fix merge issue --- .../driver/internal/core/control/ControlConnectionTestBase.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 9a9c6cc0ded..a6ecae1bd37 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -72,8 +72,6 @@ abstract class ControlConnectionTestBase { @Mock protected LoadBalancingPolicyWrapper loadBalancingPolicyWrapper; @Mock protected MetadataManager metadataManager; @Mock protected MetricsFactory metricsFactory; - @Mock protected DriverConfig config; - @Mock protected DriverExecutionProfile defaultProfile; protected AddressTranslator addressTranslator; protected DefaultNode node1; From 1262240711ffdb654d4ef8d077e2ebc0c08a4c3f Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 30 Jan 2019 10:10:25 -0800 Subject: [PATCH 673/742] Fix wrong assertion in test This was introduced by JAVA-2077 (6b0e299a1f9a82e82a5a4e56cd335991f950b07f). --- .../oss/driver/internal/core/control/ControlConnection.java | 3 ++- .../internal/core/metadata/DefaultTopologyMonitorTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 011bf9c02ad..487860018f4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -109,7 +109,8 @@ public ControlConnection(InternalDriverContext context) { * attempt only). * @param useInitialReconnectionSchedule if no node can be reached, the type of reconnection * schedule to use. In other words, the value that will be passed to {@link - * ReconnectionPolicy#newControlConnectionSchedule(boolean)}. + * ReconnectionPolicy#newControlConnectionSchedule(boolean)}. Note that this parameter is only + * relevant if {@code reconnectOnFailure} is true, otherwise it is not used. */ public CompletionStage init( boolean listenToClusterEvents, diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 32ffd70cb30..779d35b83c4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -102,7 +102,7 @@ public void should_initialize_control_connection() { topologyMonitor.init(); // Then - Mockito.verify(controlConnection).init(true, false, false); + Mockito.verify(controlConnection).init(true, false, true); } @Test From d65dbe296a6284abdbb735c0841e1088e5cb3713 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 30 Jan 2019 10:17:31 -0800 Subject: [PATCH 674/742] Fix Revapi exception for JAVA-2077 This was introduced by 6b0e299a1f9a82e82a5a4e56cd335991f950b07f. --- core/revapi.json | 1 - 1 file changed, 1 deletion(-) diff --git a/core/revapi.json b/core/revapi.json index 52006e65669..340e5a90f5a 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -707,7 +707,6 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.connection.ReconnectionPolicy", "classSimpleName": "ReconnectionPolicy", "methodName": "newControlConnectionSchedule", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", "elementKind": "method", "justification": "JAVA-2077: Allow reconnection policy to detect first connection attempt" From 46d7fb0459b32bc1c3e477a709565ef12e6aa8d8 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sat, 22 Sep 2018 11:43:40 +0200 Subject: [PATCH 675/742] Describe usage of nullability annotations --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4e23c36fcf..26d2d3f1cd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,6 +198,15 @@ Add them for all new code, with the exception of: Make sure you import the types from `net.jcip`, there are homonyms in the classpath. +### Nullability annotations + +We use the [Spotbugs annotations](https://spotbugs.github.io) to document nullability of parameters, +method return types and class members. + +Please annotate any new class or interface with the appropriate annotations: `@NonNull`, `@Nullable`. Make sure you import +the types from `edu.umd.cs.findbugs.annotations`, there are homonyms in the classpath. + + ## Coding style -- test code Static imports are permitted in a couple of places: From 125662842ba2a6e493ae7253de0873ee9db88094 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sat, 22 Sep 2018 11:44:55 +0200 Subject: [PATCH 676/742] Allow static import of all Mockito methods --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26d2d3f1cd9..2d2b49ab382 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,11 +211,9 @@ the types from `edu.umd.cs.findbugs.annotations`, there are homonyms in the clas Static imports are permitted in a couple of places: * AssertJ's `assertThat` / `fail`. -* Some Mockito methods, provided that you're already using a non-statically imported method at the - beginning of the line. For example: +* Mockito methods, e.g.: ```java - // any and eq are statically imported, it's pretty clear that they at least relate to Mockito - Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); ``` Test methods names use lower snake case, generally start with `should`, and clearly indicate the From aba625d023c6379368d263a50a024c0562a28d5b Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sat, 22 Sep 2018 11:46:21 +0200 Subject: [PATCH 677/742] Allow static import of all AssertJ methods --- CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d2b49ab382..fb602ac5bc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -210,10 +210,15 @@ the types from `edu.umd.cs.findbugs.annotations`, there are homonyms in the clas ## Coding style -- test code Static imports are permitted in a couple of places: -* AssertJ's `assertThat` / `fail`. -* Mockito methods, e.g.: +* All AssertJ methods, e.g.: ```java - verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + assertThat(node.getDatacenter()).isNotNull(); + fail("Expecting IllegalStateException to be thrown"); + ``` +* All Mockito methods, e.g.: + ```java + when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(codec); + verify(codec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); ``` Test methods names use lower snake case, generally start with `should`, and clearly indicate the From c4262fc85b47b2598d471f37499483a1183817e2 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sat, 22 Sep 2018 11:48:49 +0200 Subject: [PATCH 678/742] Replace TestNG mention with JUnit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb602ac5bc8..37a65653a8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -225,7 +225,7 @@ Test methods names use lower snake case, generally start with `should`, and clea purpose of the test, for example: `should_fail_if_key_already_exists`. If you have trouble coming up with a simple name, it might be a sign that your test does too much, and should be split. -We use AssertJ (`assertThat`) for assertions. Don't use TestNG's assertions (`assertEquals`, +We use AssertJ (`assertThat`) for assertions. Don't use JUnit assertions (`assertEquals`, `assertNull`, etc). Don't try to generify at all cost: a bit of duplication is acceptable, if that helps keep the tests From fbebe3541f6ed3573bf59658ab031c0b888a307a Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 1 Feb 2019 14:09:38 +0100 Subject: [PATCH 679/742] Fix wrong setting name in javadocs of SessionBuilder.addContactPoints --- .../datastax/oss/driver/api/core/session/SessionBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index b3d88bcc986..898cbad790a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -126,7 +126,7 @@ protected DriverConfigLoader defaultConfigLoader() { *

        Contrary to the configuration, DNS names with multiple A-records will not be handled here. * If you need that, extract them manually with {@link java.net.InetAddress#getAllByName(String)} * before calling this method. Similarly, if you need connect addresses to stay unresolved, make - * sure you pass unresolved instances here (see {@code advanced.resolve-addresses} in the + * sure you pass unresolved instances here (see {@code advanced.resolve-contact-points} in the * configuration for more explanations). */ @NonNull From 2f3fa119228220bc097af14716b0c36df0894241 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Thu, 31 Jan 2019 11:00:01 -0600 Subject: [PATCH 680/742] Allow ControlConnection to initialize if reconnectOnFailure is false --- .../oss/driver/internal/core/control/ControlConnection.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 487860018f4..9d229f89f10 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -39,7 +39,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.Reconnection; import com.datastax.oss.driver.internal.core.util.concurrent.RunOrSchedule; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; -import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -116,9 +115,6 @@ public CompletionStage init( boolean listenToClusterEvents, boolean reconnectOnFailure, boolean useInitialReconnectionSchedule) { - Preconditions.checkArgument( - reconnectOnFailure || !useInitialReconnectionSchedule, - "Can't set useInitialReconnectionSchedule if reconnectOnFailure is false"); RunOrSchedule.on( adminExecutor, () -> From 837a3399e932eb3fd50f40c478d3746f8f094187 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Thu, 31 Jan 2019 23:09:16 +0100 Subject: [PATCH 681/742] Reformat test files to comply with updated contribution guidelines --- .../oss/driver/api/core/VersionAssert.java | 17 +- .../api/core/retry/RetryPolicyTestBase.java | 6 +- ...onstantSpeculativeExecutionPolicyTest.java | 10 +- .../core/AsyncPagingIterableWrapperTest.java | 33 +-- ...tocolVersionRegistryHighestCommonTest.java | 7 +- .../ChannelFactoryAvailableIdsTest.java | 16 +- .../ChannelFactoryClusterNameTest.java | 10 +- ...ChannelFactoryProtocolNegotiationTest.java | 33 ++- .../core/channel/ChannelFactoryTestBase.java | 37 ++- .../core/channel/InFlightHandlerTest.java | 62 ++--- .../channel/MockChannelFactoryHelper.java | 10 +- .../core/channel/ProtocolInitHandlerTest.java | 39 +-- .../DefaultDriverConfigLoaderTest.java | 30 +-- .../ExponentialReconnectionPolicyTest.java | 10 +- .../context/StartupOptionsBuilderTest.java | 11 +- .../control/ControlConnectionEventsTest.java | 23 +- .../core/control/ControlConnectionTest.java | 125 +++++----- .../control/ControlConnectionTestBase.java | 66 +++-- .../core/cql/CqlPrepareHandlerTest.java | 40 ++-- .../core/cql/CqlRequestHandlerRetryTest.java | 126 +++++----- ...equestHandlerSpeculativeExecutionTest.java | 67 +++--- .../core/cql/CqlRequestHandlerTest.java | 24 +- .../core/cql/CqlRequestHandlerTestBase.java | 14 +- .../cql/CqlRequestHandlerTrackerTest.java | 21 +- .../core/cql/DefaultAsyncResultSetTest.java | 62 ++--- .../internal/core/cql/PoolBehavior.java | 33 ++- .../core/cql/QueryTraceFetcherTest.java | 117 ++++----- .../core/cql/RequestHandlerTestHarness.java | 88 ++++--- .../internal/core/cql/ResultSetTestBase.java | 30 +-- .../internal/core/cql/StatementSizeTest.java | 20 +- .../core/data/AccessibleByIdTestBase.java | 145 ++++++----- .../core/data/AccessibleByIndexTestBase.java | 114 ++++----- .../core/data/DefaultTupleValueTest.java | 6 +- .../core/data/DefaultUdtValueTest.java | 6 +- .../DefaultLoadBalancingPolicyEventsTest.java | 44 ++-- .../DefaultLoadBalancingPolicyInitTest.java | 46 ++-- ...faultLoadBalancingPolicyQueryPlanTest.java | 57 ++--- .../DefaultLoadBalancingPolicyTestBase.java | 17 +- .../core/metadata/AddNodeRefreshTest.java | 4 +- .../metadata/DefaultMetadataTokenMapTest.java | 15 +- .../metadata/DefaultTopologyMonitorTest.java | 144 +++++------ .../metadata/FullNodeListRefreshTest.java | 4 +- .../InitContactPointsRefreshTest.java | 4 +- .../LoadBalancingPolicyWrapperTest.java | 49 ++-- .../core/metadata/MetadataManagerTest.java | 60 ++--- .../core/metadata/NodeStateManagerTest.java | 92 +++---- .../core/metadata/RemoveNodeRefreshTest.java | 4 +- .../metadata/SchemaAgreementCheckerTest.java | 65 +++-- .../schema/parsing/AggregateParserTest.java | 6 +- .../parsing/DataTypeClassNameParserTest.java | 7 +- .../parsing/DataTypeCqlNameParserTest.java | 11 +- .../schema/parsing/SchemaParserTest.java | 4 +- .../schema/parsing/SchemaParserTestBase.java | 225 +++++++++--------- .../queries/Cassandra21SchemaQueriesTest.java | 7 +- .../queries/Cassandra22SchemaQueriesTest.java | 7 +- .../queries/Cassandra3SchemaQueriesTest.java | 12 +- .../schema/queries/SchemaQueriesTest.java | 26 +- .../metadata/token/DefaultTokenMapTest.java | 17 +- ...etworkTopologyReplicationStrategyTest.java | 16 +- .../core/pool/ChannelPoolInitTest.java | 42 ++-- .../core/pool/ChannelPoolKeyspaceTest.java | 25 +- .../core/pool/ChannelPoolReconnectTest.java | 37 ++- .../core/pool/ChannelPoolResizeTest.java | 88 +++---- .../core/pool/ChannelPoolShutdownTest.java | 38 +-- .../core/pool/ChannelPoolTestBase.java | 45 ++-- .../internal/core/pool/ChannelSetTest.java | 25 +- .../core/session/DefaultSessionPoolsTest.java | 185 +++++++------- .../session/MockChannelPoolFactoryHelper.java | 21 +- .../core/session/ReprepareOnUpTest.java | 38 ++- ...ncurrencyLimitingRequestThrottlerTest.java | 11 +- .../RateLimitingRequestThrottlerTest.java | 19 +- .../time/AtomicTimestampGeneratorTest.java | 4 +- .../MonotonicTimestampGeneratorTestBase.java | 31 ++- .../ThreadLocalTimestampGeneratorTest.java | 4 +- .../core/tracker/RequestLogFormatterTest.java | 6 +- .../core/type/codec/ListCodecTest.java | 34 ++- .../core/type/codec/MapCodecTest.java | 74 +++--- .../core/type/codec/SetCodecTest.java | 34 ++- .../core/type/codec/TupleCodecTest.java | 53 +++-- .../core/type/codec/UdtCodecTest.java | 53 +++-- .../registry/CachingCodecRegistryTest.java | 67 +++--- .../internal/core/util/ArrayUtilsTest.java | 8 +- .../internal/core/util/ReflectionTest.java | 7 +- .../core/util/concurrent/DebouncerTest.java | 66 ++--- .../util/concurrent/ReconnectionTest.java | 74 +++--- .../ScheduledTaskCapturingEventLoop.java | 12 +- .../driver/api/core/metadata/NodeStateIT.java | 44 ++-- .../api/core/metadata/SchemaChangesIT.java | 55 +++-- .../api/core/tracker/RequestLoggerIT.java | 20 +- .../core/retry/DefaultRetryPolicyIT.java | 28 +-- .../schema/CreateAggregateTest.java | 7 +- 91 files changed, 1796 insertions(+), 1860 deletions(-) diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/VersionAssert.java b/core/src/test/java/com/datastax/oss/driver/api/core/VersionAssert.java index 3d66b5de4b6..1525d95b1da 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/VersionAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/VersionAssert.java @@ -18,7 +18,6 @@ import static com.datastax.oss.driver.Assertions.assertThat; import org.assertj.core.api.AbstractComparableAssert; -import org.assertj.core.api.Assertions; public class VersionAssert extends AbstractComparableAssert { @@ -27,29 +26,29 @@ public VersionAssert(Version actual) { } public VersionAssert hasMajorMinorPatch(int major, int minor, int patch) { - Assertions.assertThat(actual.getMajor()).isEqualTo(major); - Assertions.assertThat(actual.getMinor()).isEqualTo(minor); - Assertions.assertThat(actual.getPatch()).isEqualTo(patch); + assertThat(actual.getMajor()).isEqualTo(major); + assertThat(actual.getMinor()).isEqualTo(minor); + assertThat(actual.getPatch()).isEqualTo(patch); return this; } public VersionAssert hasDsePatch(int dsePatch) { - Assertions.assertThat(actual.getDSEPatch()).isEqualTo(dsePatch); + assertThat(actual.getDSEPatch()).isEqualTo(dsePatch); return this; } public VersionAssert hasPreReleaseLabels(String... labels) { - Assertions.assertThat(actual.getPreReleaseLabels()).containsExactly(labels); + assertThat(actual.getPreReleaseLabels()).containsExactly(labels); return this; } public VersionAssert hasNoPreReleaseLabels() { - Assertions.assertThat(actual.getPreReleaseLabels()).isNull(); + assertThat(actual.getPreReleaseLabels()).isNull(); return this; } public VersionAssert hasBuildLabel(String label) { - Assertions.assertThat(actual.getBuildLabel()).isEqualTo(label); + assertThat(actual.getBuildLabel()).isEqualTo(label); return this; } @@ -60,7 +59,7 @@ public VersionAssert hasNextStable(String version) { @Override public VersionAssert hasToString(String string) { - Assertions.assertThat(actual.toString()).isEqualTo(string); + assertThat(actual.toString()).isEqualTo(string); return this; } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java index 142352747f3..78c227816e9 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/retry/RetryPolicyTestBase.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.retry; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; @@ -24,7 +25,6 @@ import org.assertj.core.api.Assert; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -56,11 +56,11 @@ protected Assert assertOnUnavailable( protected Assert assertOnRequestAborted( Class errorClass, int retryCount) { - return assertThat(policy.onRequestAborted(request, Mockito.mock(errorClass), retryCount)); + return assertThat(policy.onRequestAborted(request, mock(errorClass), retryCount)); } protected Assert assertOnErrorResponse( Class errorClass, int retryCount) { - return assertThat(policy.onErrorResponse(request, Mockito.mock(errorClass), retryCount)); + return assertThat(policy.onErrorResponse(request, mock(errorClass), retryCount)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java index 58658d2fa74..7a279dfd99d 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/specex/ConstantSpeculativeExecutionPolicyTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.specex; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -28,7 +29,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -40,14 +40,14 @@ public class ConstantSpeculativeExecutionPolicyTest { @Before public void setup() { - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); + when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); } private void mockOptions(int maxExecutions, long constantDelayMillis) { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX)) + when(defaultProfile.getInt(DefaultDriverOption.SPECULATIVE_EXECUTION_MAX)) .thenReturn(maxExecutions); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY)) + when(defaultProfile.getDuration(DefaultDriverOption.SPECULATIVE_EXECUTION_DELAY)) .thenReturn(Duration.ofMillis(constantDelayMillis)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java index cc9b869b484..8741d1a231a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.AsyncPagingIterable; import com.datastax.oss.driver.api.core.CqlSession; @@ -38,7 +40,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class AsyncPagingIterableWrapperTest { @@ -53,15 +54,15 @@ public void setup() { MockitoAnnotations.initMocks(this); // One single column "i" of type int: - Mockito.when(columnDefinitions.contains("i")).thenReturn(true); - ColumnDefinition iDefinition = Mockito.mock(ColumnDefinition.class); - Mockito.when(iDefinition.getType()).thenReturn(DataTypes.INT); - Mockito.when(columnDefinitions.get("i")).thenReturn(iDefinition); - Mockito.when(columnDefinitions.firstIndexOf("i")).thenReturn(0); - Mockito.when(columnDefinitions.get(0)).thenReturn(iDefinition); - - Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); + when(columnDefinitions.contains("i")).thenReturn(true); + ColumnDefinition iDefinition = mock(ColumnDefinition.class); + when(iDefinition.getType()).thenReturn(DataTypes.INT); + when(columnDefinitions.get("i")).thenReturn(iDefinition); + when(columnDefinitions.firstIndexOf("i")).thenReturn(0); + when(columnDefinitions.get(0)).thenReturn(iDefinition); + + when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); } @Test @@ -77,10 +78,10 @@ public void should_wrap_result_set() throws Exception { columnDefinitions, mockExecutionInfo(), mockData(5, 10), session, context); // chain them together: ByteBuffer mockPagingState = ByteBuffer.allocate(0); - Mockito.when(executionInfo1.getPagingState()).thenReturn(mockPagingState); - Statement mockNextStatement = Mockito.mock(Statement.class); - Mockito.when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); - Mockito.when(session.executeAsync(mockNextStatement)) + when(executionInfo1.getPagingState()).thenReturn(mockPagingState); + Statement mockNextStatement = mock(Statement.class); + when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); + when(session.executeAsync(mockNextStatement)) .thenAnswer(invocation -> CompletableFuture.completedFuture(resultSet2)); // When @@ -124,8 +125,8 @@ public void should_share_iteration_progress_with_wrapped_result_set() { } private ExecutionInfo mockExecutionInfo() { - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(executionInfo.getStatement()).thenAnswer(invocation -> statement); + ExecutionInfo executionInfo = mock(ExecutionInfo.class); + when(executionInfo.getStatement()).thenAnswer(invocation -> statement); return executionInfo; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java index 0dd3f527c34..19146e6c286 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistryHighestCommonTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -25,7 +27,6 @@ import java.util.Collection; import java.util.Collections; import org.junit.Test; -import org.mockito.Mockito; /** * Covers {@link CassandraProtocolVersionRegistry#highestCommon(Collection)} separately, because it @@ -92,9 +93,9 @@ public void should_fail_if_no_nodes() { } private Node mockNode(String cassandraVersion) { - Node node = Mockito.mock(Node.class); + Node node = mock(Node.class); if (cassandraVersion != null) { - Mockito.when(node.getCassandraVersion()).thenReturn(Version.parse(cassandraVersion)); + when(node.getCassandraVersion()).thenReturn(Version.parse(cassandraVersion)); } return node; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java index 182637538d7..1f9ad10478a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryAvailableIdsTest.java @@ -19,6 +19,8 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -31,7 +33,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @@ -41,14 +42,13 @@ public class ChannelFactoryAvailableIdsTest extends ChannelFactoryTestBase { @Override public void setup() throws InterruptedException { super.setup(); - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); - Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); + when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)) - .thenReturn(128); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)).thenReturn(128); - Mockito.when(responseCallback.isLastResponse(any(Frame.class))).thenReturn(true); + when(responseCallback.isLastResponse(any(Frame.class))).thenReturn(true); } @Test @@ -78,7 +78,7 @@ public void should_report_available_ids() { // Complete the request, should increase again writeInboundFrame(readOutboundFrame(), Void.INSTANCE); - Mockito.verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); + verify(responseCallback, timeout(500)).onResponse(any(Frame.class)); assertThat(channel.getAvailableIds()).isEqualTo(128); }); }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java index 6bdaf6f24df..f61b0501c61 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryClusterNameTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -25,15 +26,14 @@ import com.datastax.oss.protocol.internal.response.Ready; import java.util.concurrent.CompletionStage; import org.junit.Test; -import org.mockito.Mockito; public class ChannelFactoryClusterNameTest extends ChannelFactoryTestBase { @Test public void should_set_cluster_name_from_first_connection() { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); + when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -52,8 +52,8 @@ public void should_set_cluster_name_from_first_connection() { @Test public void should_check_cluster_name_for_next_connections() throws Throwable { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); + when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java index ad702571a8a..500c665cdd7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryProtocolNegotiationTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; @@ -32,16 +33,15 @@ import java.util.Optional; import java.util.concurrent.CompletionStage; import org.junit.Test; -import org.mockito.Mockito; public class ChannelFactoryProtocolNegotiationTest extends ChannelFactoryTestBase { @Test public void should_succeed_if_version_specified_and_supported_by_server() { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); - Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); + when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -61,9 +61,9 @@ public void should_succeed_if_version_specified_and_supported_by_server() { @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_version_specified_and_not_supported_by_server(int errorCode) { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); - Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); - Mockito.when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn("V4"); + when(protocolVersionRegistry.fromName("V4")).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -92,8 +92,8 @@ public void should_fail_if_version_specified_and_not_supported_by_server(int err @Test public void should_succeed_if_version_not_specified_and_server_supports_latest_supported() { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); + when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); ChannelFactory factory = newChannelFactory(); // When @@ -118,9 +118,9 @@ public void should_succeed_if_version_not_specified_and_server_supports_latest_s @UseDataProvider("unsupportedProtocolCodes") public void should_negotiate_if_version_not_specified_and_server_supports_legacy(int errorCode) { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); - Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); + when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) .thenReturn(Optional.of(DefaultProtocolVersion.V3)); ChannelFactory factory = newChannelFactory(); @@ -152,12 +152,11 @@ public void should_negotiate_if_version_not_specified_and_server_supports_legacy @UseDataProvider("unsupportedProtocolCodes") public void should_fail_if_negotiation_finds_no_matching_version(int errorCode) { // Given - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); - Mockito.when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); - Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(false); + when(protocolVersionRegistry.highestNonBeta()).thenReturn(DefaultProtocolVersion.V4); + when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V4)) .thenReturn(Optional.of(DefaultProtocolVersion.V3)); - Mockito.when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V3)) - .thenReturn(Optional.empty()); + when(protocolVersionRegistry.downgrade(DefaultProtocolVersion.V3)).thenReturn(Optional.empty()); ChannelFactory factory = newChannelFactory(); // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 798116c5e7d..800ff532d4e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -17,6 +17,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -59,7 +60,6 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @@ -111,31 +111,30 @@ public void setup() throws InterruptedException { serverGroup = new DefaultEventLoopGroup(1); clientGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.getConfig()).thenReturn(driverConfig); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)) - .thenReturn(false); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + when(context.getConfig()).thenReturn(driverConfig); + when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.isDefined(DefaultDriverOption.AUTH_PROVIDER_CLASS)).thenReturn(false); + when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) + when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_SET_KEYSPACE_TIMEOUT)) .thenReturn(Duration.ofMillis(TIMEOUT_MILLIS)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)).thenReturn(1); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS)).thenReturn(1); + when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); - Mockito.when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); - Mockito.when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); - Mockito.when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); - Mockito.when(context.getFrameCodec()) + when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.ioEventLoopGroup()).thenReturn(clientGroup); + when(nettyOptions.channelClass()).thenAnswer((Answer) i -> LocalChannel.class); + when(nettyOptions.allocator()).thenReturn(ByteBufAllocator.DEFAULT); + when(context.getFrameCodec()) .thenReturn( FrameCodec.defaultClient( new ByteBufPrimitiveCodec(ByteBufAllocator.DEFAULT), Compressor.none())); - Mockito.when(context.getSslHandlerFactory()).thenReturn(Optional.empty()); - Mockito.when(context.getEventBus()).thenReturn(eventBus); - Mockito.when(context.getWriteCoalescer()).thenReturn(new PassThroughWriteCoalescer(null)); - Mockito.when(context.getCompressor()).thenReturn(compressor); + when(context.getSslHandlerFactory()).thenReturn(Optional.empty()); + when(context.getEventBus()).thenReturn(eventBus); + when(context.getWriteCoalescer()).thenReturn(new PassThroughWriteCoalescer(null)); + when(context.getCompressor()).thenReturn(compressor); // Start local server ServerBootstrap serverBootstrap = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java index 5fcd92f715a..7b8c7f870ce 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/InFlightHandlerTest.java @@ -16,7 +16,10 @@ package com.datastax.oss.driver.internal.core.channel; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -40,7 +43,6 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class InFlightHandlerTest extends ChannelHandlerTestBase { @@ -61,7 +63,7 @@ public void setup() { public void should_fail_if_connection_busy() throws Throwable { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(-1); + when(streamIds.acquire()).thenReturn(-1); // When ChannelFuture writeFuture = @@ -78,7 +80,7 @@ public void should_fail_if_connection_busy() throws Throwable { public void should_assign_streamid_and_send_frame() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); // When @@ -88,7 +90,7 @@ public void should_assign_streamid_and_send_frame() { // Then assertThat(writeFuture).isSuccess(); - Mockito.verify(streamIds).acquire(); + verify(streamIds).acquire(); Frame frame = readOutboundFrame(); assertThat(frame.streamId).isEqualTo(42); @@ -99,7 +101,7 @@ public void should_assign_streamid_and_send_frame() { public void should_notify_callback_of_response() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); @@ -111,14 +113,14 @@ public void should_notify_callback_of_response() { // Then assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); - Mockito.verify(streamIds).release(42); + verify(streamIds).release(42); } @Test public void should_notify_response_promise_when_decoding_fails() throws Throwable { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -131,14 +133,14 @@ public void should_notify_response_promise_when_decoding_fails() throws Throwabl // Then assertThat(responseCallback.getFailure()).isSameAs(mockCause); - Mockito.verify(streamIds).release(42); + verify(streamIds).release(42); } @Test public void should_release_stream_id_when_orphaned_callback_receives_response() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel.writeAndFlush( new DriverChannel.RequestMessage(QUERY, false, Frame.NO_PAYLOAD, responseCallback)); @@ -150,7 +152,7 @@ public void should_release_stream_id_when_orphaned_callback_receives_response() writeInboundFrame(responseFrame); // Then - Mockito.verify(streamIds).release(42); + verify(streamIds).release(42); // The response is not propagated, because we assume a callback that cancelled managed its own // termination assertThat(responseCallback.getLastResponse()).isNull(); @@ -160,7 +162,7 @@ public void should_release_stream_id_when_orphaned_callback_receives_response() public void should_delay_graceful_close_and_complete_when_last_pending_completes() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -187,7 +189,7 @@ public void should_delay_graceful_close_and_complete_when_last_pending_completes public void should_delay_graceful_close_and_complete_when_last_pending_cancelled() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -225,7 +227,7 @@ public void should_graceful_close_immediately_if_no_pending() { public void should_refuse_new_writes_during_graceful_close() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -256,7 +258,7 @@ public void should_close_gracefully_if_orphan_ids_above_max_and_pending_requests addToPipeline(); // Generate n orphan ids by writing and cancelling the requests: for (int i = 0; i < MAX_ORPHAN_IDS; i++) { - Mockito.when(streamIds.acquire()).thenReturn(i); + when(streamIds.acquire()).thenReturn(i); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -265,7 +267,7 @@ public void should_close_gracefully_if_orphan_ids_above_max_and_pending_requests channel.writeAndFlush(responseCallback).awaitUninterruptibly(); } // Generate another request that is pending and not cancelled: - Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); + when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); MockResponseCallback pendingResponseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -275,7 +277,7 @@ public void should_close_gracefully_if_orphan_ids_above_max_and_pending_requests // When // Generate the n+1th orphan id that makes us go above the threshold - Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS + 1); + when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS + 1); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -312,7 +314,7 @@ public void should_close_immediately_if_orphan_ids_above_max_and_no_pending_requ addToPipeline(); // Generate n orphan ids by writing and cancelling the requests: for (int i = 0; i < MAX_ORPHAN_IDS; i++) { - Mockito.when(streamIds.acquire()).thenReturn(i); + when(streamIds.acquire()).thenReturn(i); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -323,7 +325,7 @@ public void should_close_immediately_if_orphan_ids_above_max_and_no_pending_requ // When // Generate the n+1th orphan id that makes us go above the threshold - Mockito.when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); + when(streamIds.acquire()).thenReturn(MAX_ORPHAN_IDS); MockResponseCallback responseCallback = new MockResponseCallback(); channel .writeAndFlush( @@ -340,7 +342,7 @@ public void should_close_immediately_if_orphan_ids_above_max_and_no_pending_requ public void should_fail_all_pending_when_force_closed() throws Throwable { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42, 43); + when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); channel @@ -368,7 +370,7 @@ public void should_fail_all_pending_when_force_closed() throws Throwable { public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() throws Throwable { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42, 43); + when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); channel @@ -397,7 +399,7 @@ public void should_fail_all_pending_and_close_on_unexpected_inbound_exception() public void should_fail_all_pending_if_connection_lost() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42, 43); + when(streamIds.acquire()).thenReturn(42, 43); MockResponseCallback responseCallback1 = new MockResponseCallback(); MockResponseCallback responseCallback2 = new MockResponseCallback(); channel @@ -424,7 +426,7 @@ public void should_fail_all_pending_if_connection_lost() { public void should_hold_stream_id_for_multi_response_callback() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(frame -> frame.message instanceof Error); @@ -448,7 +450,7 @@ public void should_hold_stream_id_for_multi_response_callback() { // Then assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); // Stream id not released, callback can receive more responses - Mockito.verify(streamIds, never()).release(42); + verify(streamIds, never()).release(42); } // When @@ -457,7 +459,7 @@ public void should_hold_stream_id_for_multi_response_callback() { writeInboundFrame(responseFrame); // Then - Mockito.verify(streamIds).release(42); + verify(streamIds).release(42); assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); // When @@ -475,7 +477,7 @@ public void should_hold_stream_id_for_multi_response_callback() { should_release_stream_id_when_orphaned_multi_response_callback_receives_last_response() { // Given addToPipeline(); - Mockito.when(streamIds.acquire()).thenReturn(42); + when(streamIds.acquire()).thenReturn(42); MockResponseCallback responseCallback = new MockResponseCallback(frame -> frame.message instanceof Error); @@ -489,7 +491,7 @@ public void should_hold_stream_id_for_multi_response_callback() { Frame responseFrame = buildInboundFrame(requestFrame, Void.INSTANCE); writeInboundFrame(responseFrame); assertThat(responseCallback.getLastResponse()).isSameAs(responseFrame); - Mockito.verify(streamIds, never()).release(42); + verify(streamIds, never()).release(42); } // When @@ -501,7 +503,7 @@ public void should_hold_stream_id_for_multi_response_callback() { // already), but do not release the stream id writeInboundFrame(requestFrame, Void.INSTANCE); assertThat(responseCallback.getLastResponse()).isNull(); - Mockito.verify(streamIds, never()).release(42); + verify(streamIds, never()).release(42); // When // the terminal response arrives @@ -510,7 +512,7 @@ public void should_hold_stream_id_for_multi_response_callback() { // Then // still not propagated but the id is released assertThat(responseCallback.getLastResponse()).isNull(); - Mockito.verify(streamIds).release(42); + verify(streamIds).release(42); } @Test @@ -551,7 +553,7 @@ public void should_fail_to_set_keyspace_if_query_times_out() throws InterruptedE @Test public void should_notify_callback_of_events() { // Given - EventCallback eventCallback = Mockito.mock(EventCallback.class); + EventCallback eventCallback = mock(EventCallback.class); addToPipelineWithEventCallback(eventCallback); // When @@ -570,7 +572,7 @@ public void should_notify_callback_of_events() { // Then ArgumentCaptor captor = ArgumentCaptor.forClass(StatusChangeEvent.class); - Mockito.verify(eventCallback).onEvent(captor.capture()); + verify(eventCallback).onEvent(captor.capture()); assertThat(captor.getValue()).isSameAs(event); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index 272a43b9a6a..8379585ddf4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -19,7 +19,10 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -35,7 +38,6 @@ import java.util.concurrent.CompletionStage; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import org.mockito.Mockito; import org.mockito.internal.util.MockUtil; import org.mockito.stubbing.OngoingStubbing; @@ -63,7 +65,7 @@ public static Builder builder(ChannelFactory channelFactory) { public MockChannelFactoryHelper(ChannelFactory channelFactory) { this.channelFactory = channelFactory; - this.inOrder = Mockito.inOrder(channelFactory); + this.inOrder = inOrder(channelFactory); } public void waitForCall(Node node) { @@ -118,7 +120,7 @@ public static class Builder { public Builder(ChannelFactory channelFactory) { assertThat(MockUtil.isMock(channelFactory)).as("expected a mock").isTrue(); - Mockito.verifyZeroInteractions(channelFactory); + verifyZeroInteractions(channelFactory); this.channelFactory = channelFactory; } @@ -166,7 +168,7 @@ private void stub() { if (results.size() > 0) { CompletionStage first = results.poll(); OngoingStubbing> ongoingStubbing = - Mockito.when(channelFactory.connect(eq(node), any(DriverChannelOptions.class))) + when(channelFactory.connect(eq(node), any(DriverChannelOptions.class))) .thenReturn(first); for (CompletionStage result : results) { ongoingStubbing.thenReturn(result); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index d1618dcb4be..fc2dfc9f8e8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -16,6 +16,9 @@ package com.datastax.oss.driver.internal.core.channel; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -52,7 +55,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { @@ -72,14 +74,13 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { public void setup() { super.setup(); MockitoAnnotations.initMocks(this); - Mockito.when(internalDriverContext.getConfig()).thenReturn(driverConfig); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) + when(internalDriverContext.getConfig()).thenReturn(driverConfig); + when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.getDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT)) .thenReturn(Duration.ofMillis(QUERY_TIMEOUT_MILLIS)); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) + when(defaultProfile.getDuration(DefaultDriverOption.HEARTBEAT_INTERVAL)) .thenReturn(Duration.ofSeconds(30)); - Mockito.when(internalDriverContext.getProtocolVersionRegistry()) - .thenReturn(protocolVersionRegistry); + when(internalDriverContext.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); channel .pipeline() @@ -206,11 +207,11 @@ public void should_initialize_with_authentication() { heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; - AuthProvider authProvider = Mockito.mock(AuthProvider.class); + AuthProvider authProvider = mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); - Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) + when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); + when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -222,7 +223,7 @@ public void should_initialize_with_authentication() { writeInboundFrame(requestFrame, new Authenticate(serverAuthenticator)); // The connection should have created an authenticator from the auth provider - Mockito.verify(authProvider).newAuthenticator(channel.remoteAddress(), serverAuthenticator); + verify(authProvider).newAuthenticator(channel.remoteAddress(), serverAuthenticator); // And sent an auth response requestFrame = readOutboundFrame(); @@ -269,8 +270,8 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { DriverChannelOptions.DEFAULT, heartbeatHandler)); - AuthProvider authProvider = Mockito.mock(AuthProvider.class); - Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); + AuthProvider authProvider = mock(AuthProvider.class); + when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -279,7 +280,7 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { // Simulate a READY response, the provider should be notified writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); - Mockito.verify(authProvider).onMissingChallenge(channel.remoteAddress()); + verify(authProvider).onMissingChallenge(channel.remoteAddress()); // Since our mock does nothing, init should proceed normally requestFrame = readOutboundFrame(); @@ -302,11 +303,11 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; - AuthProvider authProvider = Mockito.mock(AuthProvider.class); + AuthProvider authProvider = mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); - Mockito.when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) + when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) .thenReturn(authenticator); - Mockito.when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); + when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -416,7 +417,7 @@ public void should_initialize_with_keyspace() { @Test public void should_initialize_with_events() { List eventTypes = ImmutableList.of("foo", "bar"); - EventCallback eventCallback = Mockito.mock(EventCallback.class); + EventCallback eventCallback = mock(EventCallback.class); DriverChannelOptions driverChannelOptions = DriverChannelOptions.builder().withEvents(eventTypes, eventCallback).build(); channel @@ -446,7 +447,7 @@ public void should_initialize_with_events() { @Test public void should_initialize_with_keyspace_and_events() { List eventTypes = ImmutableList.of("foo", "bar"); - EventCallback eventCallback = Mockito.mock(EventCallback.class); + EventCallback eventCallback = mock(EventCallback.class); DriverChannelOptions driverChannelOptions = DriverChannelOptions.builder() .withKeyspace(CqlIdentifier.fromCql("ks")) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java index a036383fa7e..36d0ad4bb43 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/DefaultDriverConfigLoaderTest.java @@ -18,6 +18,9 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -37,7 +40,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class DefaultDriverConfigLoaderTest { @@ -55,22 +57,22 @@ public class DefaultDriverConfigLoaderTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(context.getSessionName()).thenReturn("test"); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); + when(context.getSessionName()).thenReturn("test"); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); adminExecutor = new ScheduledTaskCapturingEventLoop(adminEventExecutorGroup); - Mockito.when(adminEventExecutorGroup.next()).thenReturn(adminExecutor); + when(adminEventExecutorGroup.next()).thenReturn(adminExecutor); - eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); + eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); // The already loaded config in the context. // In real life, it's the object managed by the loader, but in this test it's simpler to mock // it. - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.getDuration(DefaultDriverOption.CONFIG_RELOAD_INTERVAL)) .thenReturn(Duration.ofSeconds(12)); configSource = new AtomicReference<>("int1 = 42"); @@ -114,7 +116,7 @@ public void should_detect_config_change_from_periodic_reload() { task.run(); assertThat(initialConfig).hasIntOption(MockOptions.INT1, 43); - Mockito.verify(eventBus).fire(ConfigChangeEvent.INSTANCE); + verify(eventBus).fire(ConfigChangeEvent.INSTANCE); } @Test @@ -133,7 +135,7 @@ public void should_detect_config_change_from_manual_reload() { adminExecutor.waitForNonScheduledTasks(); assertThat(initialConfig).hasIntOption(MockOptions.INT1, 43); - Mockito.verify(eventBus).fire(ConfigChangeEvent.INSTANCE); + verify(eventBus).fire(ConfigChangeEvent.INSTANCE); assertThatStage(reloaded).isSuccess(changed -> assertThat(changed).isTrue()); } @@ -153,7 +155,7 @@ public void should_not_notify_from_periodic_reload_if_config_has_not_changed() { task.run(); - Mockito.verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); + verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); } @Test @@ -169,7 +171,7 @@ public void should_not_notify_from_manual_reload_if_config_has_not_changed() { CompletionStage reloaded = loader.reload(); adminExecutor.waitForNonScheduledTasks(); - Mockito.verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); + verify(eventBus, never()).fire(ConfigChangeEvent.INSTANCE); assertThatStage(reloaded).isSuccess(changed -> assertThat(changed).isFalse()); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java index 85b3bb83ef3..25454a3d76b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicyTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.connection; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -27,7 +28,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ExponentialReconnectionPolicyTest { @@ -41,11 +41,11 @@ public class ExponentialReconnectionPolicyTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(profile); - Mockito.when(driverContext.getConfig()).thenReturn(driverConfig); - Mockito.when(profile.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY)) + when(driverConfig.getDefaultProfile()).thenReturn(profile); + when(driverContext.getConfig()).thenReturn(driverConfig); + when(profile.getDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY)) .thenReturn(Duration.of(baseDelay, ChronoUnit.MILLIS)); - Mockito.when(profile.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY)) + when(profile.getDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY)) .thenReturn(Duration.of(maxDelay, ChronoUnit.MILLIS)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java index ad1f12f2fb7..21eea2aa331 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.context; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -37,7 +38,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class StartupOptionsBuilderTest { @@ -59,8 +59,8 @@ public class StartupOptionsBuilderTest { @Before public void before() { MockitoAnnotations.initMocks(this); - Mockito.when(configLoader.getInitialConfig()).thenReturn(driverConfig); - Mockito.when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); + when(configLoader.getInitialConfig()).thenReturn(driverConfig); + when(driverConfig.getDefaultProfile()).thenReturn(defaultProfile); } private void buildDriverContext() { @@ -96,10 +96,9 @@ public void should_build_minimal_startup_options() { @Test public void should_build_startup_options_with_compression() { - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_COMPRESSION)) + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_COMPRESSION)) .thenReturn(Boolean.TRUE); - Mockito.when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION)) - .thenReturn("lz4"); + when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION)).thenReturn("lz4"); buildDriverContext(); Startup startup = new Startup(defaultDriverContext.getStartupOptions()); // assert the compression option is present diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 5bb888c5cfe..16edf993c46 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -17,6 +17,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; @@ -30,7 +32,6 @@ import java.util.concurrent.CompletableFuture; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; public class ControlConnectionEventsTest extends ControlConnectionTestBase { @@ -40,7 +41,7 @@ public void should_register_for_all_events_if_topology_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) + when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -63,7 +64,7 @@ public void should_register_for_schema_events_only_if_topology_not_requested() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) + when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); // When @@ -83,7 +84,7 @@ public void should_process_status_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) + when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true, false, false); waitForPendingAdminTasks(); @@ -95,8 +96,8 @@ public void should_process_status_change_events() { callback.onEvent(event); // Then - Mockito.verify(addressTranslator).translate(ADDRESS1); - Mockito.verify(eventBus).fire(TopologyEvent.suggestUp(ADDRESS1)); + verify(addressTranslator).translate(ADDRESS1); + verify(eventBus).fire(TopologyEvent.suggestUp(ADDRESS1)); } @Test @@ -105,7 +106,7 @@ public void should_process_topology_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) + when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(true, false, false); waitForPendingAdminTasks(); @@ -117,8 +118,8 @@ public void should_process_topology_change_events() { callback.onEvent(event); // Then - Mockito.verify(addressTranslator).translate(ADDRESS1); - Mockito.verify(eventBus).fire(TopologyEvent.suggestAdded(ADDRESS1)); + verify(addressTranslator).translate(ADDRESS1); + verify(eventBus).fire(TopologyEvent.suggestAdded(ADDRESS1)); } @Test @@ -127,7 +128,7 @@ public void should_process_schema_change_events() { DriverChannel channel1 = newMockDriverChannel(1); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(DriverChannelOptions.class); - Mockito.when(channelFactory.connect(eq(node1), optionsCaptor.capture())) + when(channelFactory.connect(eq(node1), optionsCaptor.capture())) .thenReturn(CompletableFuture.completedFuture(channel1)); controlConnection.init(false, false, false); waitForPendingAdminTasks(); @@ -144,6 +145,6 @@ public void should_process_schema_change_events() { callback.onEvent(event); // Then - Mockito.verify(metadataManager).refreshSchema("ks", false, false); + verify(metadataManager).refreshSchema("ks", false, false); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java index b91a13dc482..845c0435aa4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTest.java @@ -19,6 +19,8 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; @@ -34,7 +36,6 @@ import java.util.concurrent.CompletionStage; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; @RunWith(DataProviderRunner.class) public class ControlConnectionTest extends ControlConnectionTestBase { @@ -63,7 +64,7 @@ public void should_init_with_first_contact_point_if_reachable() { // Then assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); factoryHelper.verifyNoMoreCalls(); } @@ -105,10 +106,10 @@ public void should_init_with_second_contact_point_if_first_one_fails() { // Then assertThatStage(initFuture) .isSuccess(v -> assertThat(controlConnection.channel()).isEqualTo(channel2)); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); // each attempt tries all nodes, so there is no reconnection - Mockito.verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); + verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); factoryHelper.verifyNoMoreCalls(); } @@ -130,10 +131,10 @@ public void should_fail_to_init_if_all_contact_points_fail() { // Then assertThatStage(initFuture).isFailed(); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node2)); + verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node1)); + verify(eventBus).fire(ChannelEvent.controlConnectionFailed(node2)); // no reconnections at init - Mockito.verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); + verify(reconnectionPolicy, never()).newNodeSchedule(any(Node.class)); factoryHelper.verifyNoMoreCalls(); } @@ -141,7 +142,7 @@ public void should_fail_to_init_if_all_contact_points_fail() { @Test public void should_reconnect_if_channel_goes_down() throws Exception { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = @@ -157,7 +158,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When channel1.close(); @@ -165,15 +166,15 @@ public void should_reconnect_if_channel_goes_down() throws Exception { // Then // a reconnection was started - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCall(node1); factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); - Mockito.verify(metadataManager).refreshNodes(); - Mockito.verify(loadBalancingPolicyWrapper).init(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(metadataManager).refreshNodes(); + verify(loadBalancingPolicyWrapper).init(); factoryHelper.verifyNoMoreCalls(); } @@ -181,7 +182,7 @@ public void should_reconnect_if_channel_goes_down() throws Exception { @Test public void should_reconnect_if_node_becomes_ignored() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = @@ -196,7 +197,7 @@ public void should_reconnect_if_node_becomes_ignored() { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When mockQueryPlan(node2); @@ -205,14 +206,14 @@ public void should_reconnect_if_node_becomes_ignored() { // Then // an immediate reconnection was started - Mockito.verify(reconnectionSchedule, never()).nextDelay(); + verify(reconnectionSchedule, never()).nextDelay(); factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); - Mockito.verify(metadataManager).refreshNodes(); - Mockito.verify(loadBalancingPolicyWrapper).init(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(metadataManager).refreshNodes(); + verify(loadBalancingPolicyWrapper).init(); factoryHelper.verifyNoMoreCalls(); } @@ -229,7 +230,7 @@ public void should_reconnect_if_node_is_forced_down() { private void should_reconnect_if_event(NodeStateEvent event) { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); MockChannelFactoryHelper factoryHelper = @@ -244,7 +245,7 @@ private void should_reconnect_if_event(NodeStateEvent event) { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When mockQueryPlan(node2); @@ -253,14 +254,14 @@ private void should_reconnect_if_event(NodeStateEvent event) { // Then // an immediate reconnection was started - Mockito.verify(reconnectionSchedule, never()).nextDelay(); + verify(reconnectionSchedule, never()).nextDelay(); factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); - Mockito.verify(metadataManager).refreshNodes(); - Mockito.verify(loadBalancingPolicyWrapper).init(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(metadataManager).refreshNodes(); + verify(loadBalancingPolicyWrapper).init(); factoryHelper.verifyNoMoreCalls(); } @@ -268,7 +269,7 @@ private void should_reconnect_if_event(NodeStateEvent event) { @Test public void should_reconnect_if_node_became_ignored_during_reconnection_attempt() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); CompletableFuture channel2Future = new CompletableFuture<>(); @@ -288,14 +289,14 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); mockQueryPlan(node2, node1); // channel1 goes down, triggering a reconnection channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(reconnectionSchedule).nextDelay(); // the reconnection to node2 is in progress factoryHelper.waitForCall(node2); @@ -308,7 +309,7 @@ public void should_reconnect_if_node_became_ignored_during_reconnection_attempt( // Then // The channel should get closed and we should try the next node - Mockito.verify(channel2).forceClose(); + verify(channel2).forceClose(); factoryHelper.waitForCall(node1); } @@ -325,7 +326,7 @@ public void should_reconnect_if_node_was_forced_down_during_reconnection_attempt private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEvent event) { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); CompletableFuture channel2Future = new CompletableFuture<>(); @@ -345,14 +346,14 @@ private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEven waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); mockQueryPlan(node2, node1); // channel1 goes down, triggering a reconnection channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(reconnectionSchedule).nextDelay(); // the reconnection to node2 is in progress factoryHelper.waitForCall(node2); @@ -365,14 +366,14 @@ private void should_reconnect_if_event_during_reconnection_attempt(NodeStateEven // Then // The channel should get closed and we should try the next node - Mockito.verify(channel2).forceClose(); + verify(channel2).forceClose(); factoryHelper.waitForCall(node1); } @Test public void should_force_reconnection_if_pending() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -388,13 +389,13 @@ public void should_force_reconnection_if_pending() { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled for later channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(reconnectionSchedule).nextDelay(); // When controlConnection.reconnectNow(); @@ -404,7 +405,7 @@ public void should_force_reconnection_if_pending() { // Then assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -426,7 +427,7 @@ public void should_force_reconnection_even_if_connected() { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // When controlConnection.reconnectNow(); @@ -436,9 +437,9 @@ public void should_force_reconnection_even_if_connected() { factoryHelper.waitForCall(node2); waitForPendingAdminTasks(); assertThat(controlConnection.channel()).isEqualTo(channel2); - Mockito.verify(channel1).forceClose(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node2)); + verify(channel1).forceClose(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -450,7 +451,7 @@ public void should_not_force_reconnection_if_not_init() { waitForPendingAdminTasks(); // Then - Mockito.verify(reconnectionSchedule, never()).nextDelay(); + verify(reconnectionSchedule, never()).nextDelay(); } @Test @@ -471,7 +472,7 @@ public void should_not_force_reconnection_if_closed() { waitForPendingAdminTasks(); // Then - Mockito.verify(reconnectionSchedule, never()).nextDelay(); + verify(reconnectionSchedule, never()).nextDelay(); factoryHelper.verifyNoMoreCalls(); } @@ -494,7 +495,7 @@ public void should_close_channel_when_closing() { // Then assertThatStage(closeFuture).isSuccess(); - Mockito.verify(channel1).forceClose(); + verify(channel1).forceClose(); factoryHelper.verifyNoMoreCalls(); } @@ -502,7 +503,7 @@ public void should_close_channel_when_closing() { @Test public void should_close_channel_if_closed_during_reconnection() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -519,13 +520,13 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCall(node1); // channel2 starts initializing (but the future is not completed yet) factoryHelper.waitForCall(node2); @@ -538,10 +539,10 @@ public void should_close_channel_if_closed_during_reconnection() { waitForPendingAdminTasks(); // Then - Mockito.verify(channel2).forceClose(); + verify(channel2).forceClose(); // no event because the control connection never "owned" the channel - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node2)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node2)); + verify(eventBus, never()).fire(ChannelEvent.channelOpened(node2)); + verify(eventBus, never()).fire(ChannelEvent.channelClosed(node2)); factoryHelper.verifyNoMoreCalls(); } @@ -549,7 +550,7 @@ public void should_close_channel_if_closed_during_reconnection() { @Test public void should_handle_channel_failure_if_closed_during_reconnection() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -566,13 +567,13 @@ public void should_handle_channel_failure_if_closed_during_reconnection() { waitForPendingAdminTasks(); assertThatStage(initFuture).isSuccess(); assertThat(controlConnection.channel()).isEqualTo(channel1); - Mockito.verify(eventBus).fire(ChannelEvent.channelOpened(node1)); + verify(eventBus).fire(ChannelEvent.channelOpened(node1)); // the channel fails and a reconnection is scheduled channel1.close(); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.channelClosed(node1)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.channelClosed(node1)); + verify(reconnectionSchedule).nextDelay(); // channel1 starts initializing (but the future is not completed yet) factoryHelper.waitForCall(node1); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index a6ecae1bd37..82c1956d6d1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -17,6 +17,9 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -52,7 +55,6 @@ import org.junit.After; import org.junit.Before; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; abstract class ControlConnectionTestBase { @@ -85,14 +87,14 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); - Mockito.when(context.getChannelFactory()).thenReturn(channelFactory); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); + when(context.getChannelFactory()).thenReturn(channelFactory); channelFactoryFuture = new Exchanger<>(); - Mockito.when(channelFactory.connect(any(Node.class), any(DriverChannelOptions.class))) + when(channelFactory.connect(any(Node.class), any(DriverChannelOptions.class))) .thenAnswer( invocation -> { CompletableFuture channelFuture = new CompletableFuture<>(); @@ -100,42 +102,39 @@ public void setup() { return channelFuture; }); - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.RECONNECT_ON_INIT)) - .thenReturn(false); + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.getBoolean(DefaultDriverOption.RECONNECT_ON_INIT)).thenReturn(false); - Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); + when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); // Child classes only cover "runtime" reconnections when the driver is already initialized - Mockito.when(reconnectionPolicy.newControlConnectionSchedule(false)) - .thenReturn(reconnectionSchedule); + when(reconnectionPolicy.newControlConnectionSchedule(false)).thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); mockQueryPlan(node1, node2); - Mockito.when(metadataManager.refreshNodes()) - .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); + when(metadataManager.refreshNodes()).thenReturn(CompletableFuture.completedFuture(null)); + when(context.getMetadataManager()).thenReturn(metadataManager); - addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) + addressTranslator = spy(new PassThroughAddressTranslator(context)); + when(context.getAddressTranslator()).thenReturn(addressTranslator); + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) .thenReturn(false); controlConnection = new ControlConnection(context); } protected void mockQueryPlan(Node... nodes) { - Mockito.when(loadBalancingPolicyWrapper.newQueryPlan()) + when(loadBalancingPolicyWrapper.newQueryPlan()) .thenAnswer( i -> { ConcurrentLinkedQueue queryPlan = new ConcurrentLinkedQueue<>(); @@ -152,26 +151,25 @@ public void teardown() { } protected DriverChannel newMockDriverChannel(int id) { - DriverChannel driverChannel = Mockito.mock(DriverChannel.class); - Channel channel = Mockito.mock(Channel.class); + DriverChannel driverChannel = mock(DriverChannel.class); + Channel channel = mock(Channel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); DefaultChannelPromise closeFuture = new DefaultChannelPromise(channel, adminExecutor); - Mockito.when(driverChannel.close()) + when(driverChannel.close()) .thenAnswer( i -> { closeFuture.trySuccess(null); return closeFuture; }); - Mockito.when(driverChannel.forceClose()) + when(driverChannel.forceClose()) .thenAnswer( i -> { closeFuture.trySuccess(null); return closeFuture; }); - Mockito.when(driverChannel.closeFuture()).thenReturn(closeFuture); - Mockito.when(driverChannel.toString()).thenReturn("channel" + id); - Mockito.when(driverChannel.connectAddress()) - .thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); + when(driverChannel.closeFuture()).thenReturn(closeFuture); + when(driverChannel.toString()).thenReturn("channel" + id); + when(driverChannel.connectAddress()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); return driverChannel; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java index 3be37fce43f..ced7d095ee1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandlerTest.java @@ -21,6 +21,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -52,7 +54,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class CqlPrepareHandlerTest { @@ -115,7 +116,7 @@ public void should_not_reprepare_on_other_nodes_if_disabled_in_config() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); - Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); + when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = new CqlPrepareHandler(PREPARE_REQUEST, harness.getSession(), harness.getContext(), "test") @@ -175,11 +176,10 @@ public void should_retry_initial_prepare_if_recoverable_error() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { // Make node1's error recoverable, will switch to node2 - Mockito.when( - harness - .getContext() - .getRetryPolicy(anyString()) - .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + when(harness + .getContext() + .getRetryPolicy(anyString()) + .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) .thenReturn(RetryDecision.RETRY_NEXT); CompletionStage prepareFuture = @@ -209,11 +209,10 @@ public void should_not_retry_initial_prepare_if_unrecoverable_error() { try (RequestHandlerTestHarness harness = harnessBuilder.build()) { // Make node1's error unrecoverable, will rethrow - Mockito.when( - harness - .getContext() - .getRetryPolicy(anyString()) - .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + when(harness + .getContext() + .getRetryPolicy(anyString()) + .onErrorResponse(eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) .thenReturn(RetryDecision.RETHROW); CompletionStage prepareFuture = @@ -246,9 +245,8 @@ public void should_fail_if_retry_policy_ignores_error() { // Make node1's error unrecoverable, will rethrow RetryPolicy mockRetryPolicy = harness.getContext().getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME); - Mockito.when( - mockRetryPolicy.onErrorResponse( - eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) + when(mockRetryPolicy.onErrorResponse( + eq(PREPARE_REQUEST), any(OverloadedException.class), eq(0))) .thenReturn(RetryDecision.IGNORE); CompletionStage prepareFuture = @@ -282,11 +280,11 @@ public void should_propagate_custom_payload_on_single_node() { node1Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); - Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); + when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(false); CompletionStage prepareFuture = new CqlPrepareHandler(prepareRequest, harness.getSession(), harness.getContext(), "test") .handle(); - Mockito.verify(node1Behavior.channel) + verify(node1Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); node2Behavior.verifyNoWrite(); node3Behavior.verifyNoWrite(); @@ -308,15 +306,15 @@ public void should_propagate_custom_payload_on_all_nodes() { node3Behavior.setResponseSuccess(defaultFrameOf(simplePrepared())); try (RequestHandlerTestHarness harness = harnessBuilder.build()) { DriverExecutionProfile config = harness.getContext().getConfig().getDefaultProfile(); - Mockito.when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); + when(config.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); CompletionStage prepareFuture = new CqlPrepareHandler(prepareRequest, harness.getSession(), harness.getContext(), "test") .handle(); - Mockito.verify(node1Behavior.channel) + verify(node1Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); - Mockito.verify(node2Behavior.channel) + verify(node2Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); - Mockito.verify(node3Behavior.channel) + verify(node3Behavior.channel) .write(any(Prepare.class), anyBoolean(), eq(payload), any(ResponseCallback.class)); assertThatStage(prepareFuture).isSuccess(CqlPrepareHandlerTest::assertMatchesSimplePrepared); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index 2cb3970876c..0e503a134c8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -22,6 +22,10 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; @@ -53,7 +57,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.mockito.Mockito; public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { @@ -94,7 +97,7 @@ public void should_always_try_next_node_if_bootstrapping( assertThat(executionInfo.getSuccessfulExecutionIndex()).isEqualTo(0); assertThat(executionInfo.getWarnings()).isEmpty(); - Mockito.verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); + verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); }); } } @@ -121,20 +124,20 @@ public void should_always_rethrow_query_validation_error( assertThat(error) .isInstanceOf(InvalidQueryException.class) .hasMessage("mock message"); - Mockito.verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); + verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.OTHER_ERRORS, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -168,24 +171,24 @@ public void should_try_next_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getErrors()).hasSize(1); assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.RETRIES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -219,24 +222,24 @@ public void should_try_same_node_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getErrors()).hasSize(1); assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.RETRIES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.retryMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(2)) + verify(nodeMetricUpdater1, atMost(2)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(2)) + verify(nodeMetricUpdater1, atMost(2)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -267,24 +270,24 @@ public void should_ignore_error_if_idempotent_and_retry_policy_decides_so( assertThat(executionInfo.getCoordinator()).isEqualTo(node1); assertThat(executionInfo.getErrors()).hasSize(0); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.IGNORES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.ignoreMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -311,18 +314,18 @@ public void should_rethrow_error_if_idempotent_and_retry_policy_decides_so( error -> { assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -360,22 +363,21 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re assertThat(error).isInstanceOf(failureScenario.expectedExceptionClass); // When non idempotent, the policy is bypassed completely: if (!shouldCallRetryPolicy) { - Mockito.verifyNoMoreInteractions( - harness.getContext().getRetryPolicy(anyString())); + verifyNoMoreInteractions(harness.getContext().getRetryPolicy(anyString())); } - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( failureScenario.errorMetric, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1, atMost(1)) + verify(nodeMetricUpdater1, atMost(1)) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); }); } } @@ -425,14 +427,13 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { - Mockito.when( - policy.onReadTimeout( - any(SimpleStatement.class), - eq(DefaultConsistencyLevel.LOCAL_ONE), - eq(2), - eq(1), - eq(true), - eq(0))) + when(policy.onReadTimeout( + any(SimpleStatement.class), + eq(DefaultConsistencyLevel.LOCAL_ONE), + eq(2), + eq(1), + eq(true), + eq(0))) .thenReturn(decision); } }, @@ -456,14 +457,13 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { - Mockito.when( - policy.onWriteTimeout( - any(SimpleStatement.class), - eq(DefaultConsistencyLevel.LOCAL_ONE), - eq(DefaultWriteType.SIMPLE), - eq(2), - eq(1), - eq(0))) + when(policy.onWriteTimeout( + any(SimpleStatement.class), + eq(DefaultConsistencyLevel.LOCAL_ONE), + eq(DefaultWriteType.SIMPLE), + eq(2), + eq(1), + eq(0))) .thenReturn(decision); } }, @@ -483,13 +483,12 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { - Mockito.when( - policy.onUnavailable( - any(SimpleStatement.class), - eq(DefaultConsistencyLevel.LOCAL_ONE), - eq(2), - eq(1), - eq(0))) + when(policy.onUnavailable( + any(SimpleStatement.class), + eq(DefaultConsistencyLevel.LOCAL_ONE), + eq(2), + eq(1), + eq(0))) .thenReturn(decision); } }, @@ -508,9 +507,7 @@ public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node nod @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { - Mockito.when( - policy.onErrorResponse( - any(SimpleStatement.class), any(ServerError.class), eq(0))) + when(policy.onErrorResponse(any(SimpleStatement.class), any(ServerError.class), eq(0))) .thenReturn(decision); } }, @@ -521,14 +518,13 @@ public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) DefaultNodeMetric.IGNORES_ON_ABORTED) { @Override public void mockRequestError(RequestHandlerTestHarness.Builder builder, Node node) { - builder.withResponseFailure(node, Mockito.mock(HeartbeatException.class)); + builder.withResponseFailure(node, mock(HeartbeatException.class)); } @Override public void mockRetryPolicyDecision(RetryPolicy policy, RetryDecision decision) { - Mockito.when( - policy.onRequestAborted( - any(SimpleStatement.class), any(HeartbeatException.class), eq(0))) + when(policy.onRequestAborted( + any(SimpleStatement.class), any(HeartbeatException.class), eq(0))) .thenReturn(decision); } }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java index a9d9a72a1e6..2eca70f1dc2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerSpeculativeExecutionTest.java @@ -20,6 +20,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.NoNodeAvailableException; @@ -38,7 +41,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.mockito.Mockito; public class CqlRequestHandlerSpeculativeExecutionTest extends CqlRequestHandlerTestBase { @@ -61,8 +63,8 @@ public void should_not_schedule_speculative_executions_if_not_idempotent( assertThat(harness.nextScheduledTimeout()).isNotNull(); // Discard the timeout task assertThat(harness.nextScheduledTimeout()).isNull(); - Mockito.verifyNoMoreInteractions(speculativeExecutionPolicy); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(speculativeExecutionPolicy); + verifyNoMoreInteractions(nodeMetricUpdater1); } } @@ -81,17 +83,14 @@ public void should_schedule_speculative_executions( harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; long secondExecutionDelay = 200L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(2))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(2))) .thenReturn(secondExecutionDelay); - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(3))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(3))) .thenReturn(-1L); new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test").handle(); @@ -104,9 +103,9 @@ public void should_schedule_speculative_executions( CapturedTimeout speculativeExecution1 = harness.nextScheduledTimeout(); assertThat(speculativeExecution1.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(firstExecutionDelay); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); speculativeExecution1.task().run(speculativeExecution1); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); node2Behavior.verifyWrite(); @@ -115,9 +114,9 @@ public void should_schedule_speculative_executions( CapturedTimeout speculativeExecution2 = harness.nextScheduledTimeout(); assertThat(speculativeExecution2.getDelay(TimeUnit.MILLISECONDS)) .isEqualTo(secondExecutionDelay); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater2); + verifyNoMoreInteractions(nodeMetricUpdater2); speculativeExecution2.task().run(speculativeExecution2); - Mockito.verify(nodeMetricUpdater2) + verify(nodeMetricUpdater2) .incrementCounter( DefaultNodeMetric.SPECULATIVE_EXECUTIONS, DriverExecutionProfile.DEFAULT_NAME); node3Behavior.verifyWrite(); @@ -144,9 +143,8 @@ public void should_not_start_execution_if_result_complete( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CqlRequestHandler requestHandler = @@ -179,15 +177,15 @@ public void should_not_start_execution_if_result_complete( speculativeExecution1.task().run(speculativeExecution1); node2Behavior.verifyNoWrite(); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .isEnabled(DefaultNodeMetric.CQL_MESSAGES, DriverExecutionProfile.DEFAULT_NAME); - Mockito.verify(nodeMetricUpdater1) + verify(nodeMetricUpdater1) .updateTimer( eq(DefaultNodeMetric.CQL_MESSAGES), eq(DriverExecutionProfile.DEFAULT_NAME), anyLong(), eq(TimeUnit.NANOSECONDS)); - Mockito.verifyNoMoreInteractions(nodeMetricUpdater1); + verifyNoMoreInteractions(nodeMetricUpdater1); } } @@ -202,9 +200,8 @@ public void should_fail_if_no_nodes(boolean defaultIdempotence, SimpleStatement SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -233,9 +230,8 @@ public void should_fail_if_no_more_nodes_and_initial_execution_is_last( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -286,9 +282,8 @@ public void should_fail_if_no_more_nodes_and_speculative_execution_is_last( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -342,9 +337,8 @@ public void should_retry_in_speculative_executions( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = @@ -392,9 +386,8 @@ public void should_stop_retrying_other_executions_if_result_complete( SpeculativeExecutionPolicy speculativeExecutionPolicy = harness.getContext().getSpeculativeExecutionPolicy(DriverExecutionProfile.DEFAULT_NAME); long firstExecutionDelay = 100L; - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), eq(null), eq(statement), eq(1))) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), eq(null), eq(statement), eq(1))) .thenReturn(firstExecutionDelay); CompletionStage resultSetFuture = new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java index 359794a726c..78542f4adb5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTest.java @@ -17,6 +17,9 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; @@ -44,7 +47,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.mockito.Mockito; public class CqlRequestHandlerTest extends CqlRequestHandlerTestBase { @@ -153,7 +155,7 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { assertThatStage(resultSetFuture) .isSuccess( resultSet -> - Mockito.verify(harness.getSession()) + verify(harness.getSession()) .setKeyspace(CqlIdentifier.fromInternal("newKeyspace"))); } } @@ -162,14 +164,14 @@ public void should_switch_keyspace_on_session_after_successful_use_statement() { public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedException { ByteBuffer mockId = Bytes.fromHexString("0xffff"); - PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class); - Mockito.when(preparedStatement.getId()).thenReturn(mockId); - ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); - Mockito.when(columnDefinitions.size()).thenReturn(0); - Mockito.when(preparedStatement.getResultSetDefinitions()).thenReturn(columnDefinitions); - BoundStatement boundStatement = Mockito.mock(BoundStatement.class); - Mockito.when(boundStatement.getPreparedStatement()).thenReturn(preparedStatement); - Mockito.when(boundStatement.getValues()).thenReturn(Collections.emptyList()); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(preparedStatement.getId()).thenReturn(mockId); + ColumnDefinitions columnDefinitions = mock(ColumnDefinitions.class); + when(columnDefinitions.size()).thenReturn(0); + when(preparedStatement.getResultSetDefinitions()).thenReturn(columnDefinitions); + BoundStatement boundStatement = mock(BoundStatement.class); + when(boundStatement.getPreparedStatement()).thenReturn(preparedStatement); + when(boundStatement.getValues()).thenReturn(Collections.emptyList()); RequestHandlerTestHarness.Builder harnessBuilder = RequestHandlerTestHarness.builder(); // For the first attempt that gets the UNPREPARED response @@ -183,7 +185,7 @@ public void should_reprepare_on_the_fly_if_not_prepared() throws InterruptedExce ConcurrentMap repreparePayloads = new ConcurrentHashMap<>(); repreparePayloads.put( mockId, new RepreparePayload(mockId, "mock query", null, Collections.emptyMap())); - Mockito.when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); + when(harness.getSession().getRepreparePayloads()).thenReturn(repreparePayloads); CompletionStage resultSetFuture = new CqlRequestHandler(boundStatement, harness.getSession(), harness.getContext(), "test") diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 88c29c79d47..09b43e09b23 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -17,6 +17,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -44,7 +45,6 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @RunWith(DataProviderRunner.class) @@ -71,12 +71,12 @@ public abstract class CqlRequestHandlerTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(node1.getMetricUpdater()).thenReturn(nodeMetricUpdater1); - Mockito.when(nodeMetricUpdater1.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); - Mockito.when(node2.getMetricUpdater()).thenReturn(nodeMetricUpdater2); - Mockito.when(nodeMetricUpdater2.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); - Mockito.when(node3.getMetricUpdater()).thenReturn(nodeMetricUpdater3); - Mockito.when(nodeMetricUpdater3.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); + when(node1.getMetricUpdater()).thenReturn(nodeMetricUpdater1); + when(nodeMetricUpdater1.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); + when(node2.getMetricUpdater()).thenReturn(nodeMetricUpdater2); + when(nodeMetricUpdater2.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); + when(node3.getMetricUpdater()).thenReturn(nodeMetricUpdater3); + when(nodeMetricUpdater3.isEnabled(any(NodeMetric.class), anyString())).thenReturn(true); } protected static Frame defaultFrameOf(Message responseMessage) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java index 71c29d579ff..330c6bff50d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTrackerTest.java @@ -19,7 +19,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; @@ -30,7 +34,6 @@ import com.datastax.oss.protocol.internal.response.Error; import java.util.concurrent.CompletionStage; import org.junit.Test; -import org.mockito.Mockito; public class CqlRequestHandlerTrackerTest extends CqlRequestHandlerTestBase { @@ -46,8 +49,8 @@ public void should_invoke_request_tracker() { .withResponse(node2, defaultFrameOf(singleRow())) .build()) { - RequestTracker requestTracker = Mockito.mock(RequestTracker.class); - Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); + RequestTracker requestTracker = mock(RequestTracker.class); + when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); CompletionStage resultSetFuture = new CqlRequestHandler( @@ -60,26 +63,26 @@ public void should_invoke_request_tracker() { assertThatStage(resultSetFuture) .isSuccess( resultSet -> { - Mockito.verify(requestTracker) + verify(requestTracker) .onNodeError( eq(UNDEFINED_IDEMPOTENCE_STATEMENT), any(BootstrappingException.class), anyLong(), any(DriverExecutionProfile.class), eq(node1)); - Mockito.verify(requestTracker) + verify(requestTracker) .onNodeSuccess( eq(UNDEFINED_IDEMPOTENCE_STATEMENT), anyLong(), any(DriverExecutionProfile.class), eq(node2)); - Mockito.verify(requestTracker) + verify(requestTracker) .onSuccess( eq(UNDEFINED_IDEMPOTENCE_STATEMENT), anyLong(), any(DriverExecutionProfile.class), eq(node2)); - Mockito.verifyNoMoreInteractions(requestTracker); + verifyNoMoreInteractions(requestTracker); }); } } @@ -97,7 +100,7 @@ public void should_not_invoke_noop_request_tracker() { .build()) { RequestTracker requestTracker = spy(new NoopRequestTracker(harness.getContext())); - Mockito.when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); + when(harness.getContext().getRequestTracker()).thenReturn(requestTracker); CompletionStage resultSetFuture = new CqlRequestHandler( @@ -108,7 +111,7 @@ public void should_not_invoke_noop_request_tracker() { .handle(); assertThatStage(resultSetFuture) - .isSuccess(resultSet -> Mockito.verifyNoMoreInteractions(requestTracker)); + .isSuccess(resultSet -> verifyNoMoreInteractions(requestTracker)); } } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index a2b01aa708f..4d6f86080ea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -17,6 +17,10 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -40,7 +44,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class DefaultAsyncResultSetTest { @@ -55,15 +58,15 @@ public class DefaultAsyncResultSetTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(executionInfo.getStatement()).thenAnswer(invocation -> statement); - Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); + when(executionInfo.getStatement()).thenAnswer(invocation -> statement); + when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + when(context.getProtocolVersion()).thenReturn(DefaultProtocolVersion.DEFAULT); } @Test(expected = IllegalStateException.class) public void should_fail_to_fetch_next_page_if_last() { // Given - Mockito.when(executionInfo.getPagingState()).thenReturn(null); + when(executionInfo.getPagingState()).thenReturn(null); // When DefaultAsyncResultSet resultSet = @@ -79,14 +82,13 @@ public void should_fail_to_fetch_next_page_if_last() { public void should_invoke_session_to_fetch_next_page() { // Given ByteBuffer mockPagingState = ByteBuffer.allocate(0); - Mockito.when(executionInfo.getPagingState()).thenReturn(mockPagingState); + when(executionInfo.getPagingState()).thenReturn(mockPagingState); - Statement mockNextStatement = Mockito.mock(Statement.class); - Mockito.when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); + Statement mockNextStatement = mock(Statement.class); + when(((Statement) statement).copy(mockPagingState)).thenReturn(mockNextStatement); CompletableFuture mockResultFuture = new CompletableFuture<>(); - Mockito.when(session.executeAsync(Mockito.any(Statement.class))) - .thenAnswer(invocation -> mockResultFuture); + when(session.executeAsync(any(Statement.class))).thenAnswer(invocation -> mockResultFuture); // When DefaultAsyncResultSet resultSet = @@ -96,15 +98,15 @@ public void should_invoke_session_to_fetch_next_page() { CompletionStage nextPageFuture = resultSet.fetchNextPage(); // Then - Mockito.verify(statement).copy(mockPagingState); - Mockito.verify(session).executeAsync(mockNextStatement); + verify(statement).copy(mockPagingState); + verify(session).executeAsync(mockNextStatement); assertThatStage(nextPageFuture).isEqualTo(mockResultFuture); } @Test public void should_report_applied_if_column_not_present_and_empty() { // Given - Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(false); + when(columnDefinitions.contains("[applied]")).thenReturn(false); // When DefaultAsyncResultSet resultSet = @@ -118,7 +120,7 @@ public void should_report_applied_if_column_not_present_and_empty() { @Test public void should_report_applied_if_column_not_present_and_not_empty() { // Given - Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(false); + when(columnDefinitions.contains("[applied]")).thenReturn(false); Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(Bytes.fromHexString("0xffff"))); @@ -133,12 +135,12 @@ public void should_report_applied_if_column_not_present_and_not_empty() { @Test public void should_report_not_applied_if_column_present_and_false() { // Given - Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); - ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); - Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); - Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); - Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); - Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); + when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = mock(ColumnDefinition.class); + when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); + when(columnDefinitions.get(0)).thenReturn(columnDefinition); Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(false, DefaultProtocolVersion.DEFAULT))); @@ -154,12 +156,12 @@ public void should_report_not_applied_if_column_present_and_false() { @Test public void should_report_not_applied_if_column_present_and_true() { // Given - Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); - ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); - Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); - Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); - Mockito.when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); - Mockito.when(columnDefinitions.get(0)).thenReturn(columnDefinition); + when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = mock(ColumnDefinition.class); + when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + when(columnDefinitions.firstIndexOf("[applied]")).thenReturn(0); + when(columnDefinitions.get(0)).thenReturn(columnDefinition); Queue> data = new ArrayDeque<>(); data.add(Lists.newArrayList(TypeCodecs.BOOLEAN.encode(true, DefaultProtocolVersion.DEFAULT))); @@ -175,10 +177,10 @@ public void should_report_not_applied_if_column_present_and_true() { @Test(expected = IllegalStateException.class) public void should_fail_to_report_if_applied_if_column_present_but_empty() { // Given - Mockito.when(columnDefinitions.contains("[applied]")).thenReturn(true); - ColumnDefinition columnDefinition = Mockito.mock(ColumnDefinition.class); - Mockito.when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); - Mockito.when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); + when(columnDefinitions.contains("[applied]")).thenReturn(true); + ColumnDefinition columnDefinition = mock(ColumnDefinition.class); + when(columnDefinition.getType()).thenReturn(DataTypes.BOOLEAN); + when(columnDefinitions.get("[applied]")).thenReturn(columnDefinition); // When DefaultAsyncResultSet resultSet = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java index 351ab29b93c..55594f46aed 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/PoolBehavior.java @@ -18,7 +18,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.channel.DriverChannel; @@ -32,7 +35,6 @@ import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.Promise; import java.util.concurrent.CompletableFuture; -import org.mockito.Mockito; /** * The simulated behavior of the connection pool for a given node in a {@link @@ -54,13 +56,11 @@ public PoolBehavior(Node node, boolean createChannel) { this.channel = null; this.writePromise = null; } else { - this.channel = Mockito.mock(DriverChannel.class); - EventLoop eventLoop = Mockito.mock(EventLoop.class); - ChannelConfig config = Mockito.mock(DefaultSocketChannelConfig.class); + this.channel = mock(DriverChannel.class); + EventLoop eventLoop = mock(EventLoop.class); + ChannelConfig config = mock(DefaultSocketChannelConfig.class); this.writePromise = GlobalEventExecutor.INSTANCE.newPromise(); - Mockito.when( - channel.write( - any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class))) + when(channel.write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class))) .thenAnswer( invocation -> { ResponseCallback callback = invocation.getArgument(3); @@ -68,20 +68,19 @@ public PoolBehavior(Node node, boolean createChannel) { callbackFuture.complete(callback); return writePromise; }); - ChannelFuture closeFuture = Mockito.mock(ChannelFuture.class); - Mockito.when(channel.closeFuture()).thenReturn(closeFuture); - Mockito.when(channel.eventLoop()).thenReturn(eventLoop); - Mockito.when(channel.config()).thenReturn(config); + ChannelFuture closeFuture = mock(ChannelFuture.class); + when(channel.closeFuture()).thenReturn(closeFuture); + when(channel.eventLoop()).thenReturn(eventLoop); + when(channel.config()).thenReturn(config); } } public void verifyWrite() { - Mockito.verify(channel) - .write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class)); + verify(channel).write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class)); } public void verifyNoWrite() { - Mockito.verify(channel, never()) + verify(channel, never()) .write(any(Message.class), anyBoolean(), anyMap(), any(ResponseCallback.class)); } @@ -113,9 +112,7 @@ public DriverChannel getChannel() { public void mockFollowupRequest(Class expectedMessage, Frame responseFrame) { Promise writePromise2 = GlobalEventExecutor.INSTANCE.newPromise(); CompletableFuture callbackFuture2 = new CompletableFuture<>(); - Mockito.when( - channel.write( - any(expectedMessage), anyBoolean(), anyMap(), any(ResponseCallback.class))) + when(channel.write(any(expectedMessage), anyBoolean(), anyMap(), any(ResponseCallback.class))) .thenAnswer( invocation -> { callbackFuture2.complete(invocation.getArgument(3)); @@ -126,6 +123,6 @@ public void mockFollowupRequest(Class expectedMessage, Frame } public void verifyCancellation() { - Mockito.verify(channel).cancel(any(ResponseCallback.class)); + verify(channel).cancel(any(ResponseCallback.class)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index df7b2afa16c..f84abfe39f7 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -19,7 +19,11 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; @@ -56,7 +60,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -78,11 +81,11 @@ public class QueryTraceFetcherTest { @Before public void setup() { - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); - Mockito.when(adminEventExecutorGroup.next()).thenReturn(eventExecutor); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventExecutorGroup); + when(adminEventExecutorGroup.next()).thenReturn(eventExecutor); // Always execute scheduled tasks immediately: - Mockito.when(eventExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + when(eventExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer( invocation -> { Runnable runnable = invocation.getArgument(0); @@ -91,18 +94,16 @@ public void setup() { return null; }); - Mockito.when(config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS)).thenReturn(3); + when(config.getInt(DefaultDriverOption.REQUEST_TRACE_ATTEMPTS)).thenReturn(3); // Doesn't really matter since we mock the scheduler - Mockito.when(config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL)) - .thenReturn(Duration.ZERO); - Mockito.when(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + when(config.getDuration(DefaultDriverOption.REQUEST_TRACE_INTERVAL)).thenReturn(Duration.ZERO); + when(config.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); - Mockito.when(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)) + when(config.getString(DefaultDriverOption.REQUEST_TRACE_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.ONE.name()); - Mockito.when( - config.withString( - DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) + when(config.withString( + DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.ONE.name())) .thenReturn(traceConfig); } @@ -111,7 +112,7 @@ public void should_succeed_when_both_queries_succeed_immediately() { // Given CompletionStage sessionRow = completeSessionRow(); CompletionStage eventRows = singlePageEventRows(); - Mockito.when(session.executeAsync(any(SimpleStatement.class))) + when(session.executeAsync(any(SimpleStatement.class))) .thenAnswer(invocation -> sessionRow) .thenAnswer(invocation -> eventRows); @@ -120,12 +121,12 @@ public void should_succeed_when_both_queries_succeed_immediately() { CompletionStage traceFuture = fetcher.fetch(); // Then - Mockito.verify(session, times(2)).executeAsync(statementCaptor.capture()); + verify(session, times(2)).executeAsync(statementCaptor.capture()); List statements = statementCaptor.getAllValues(); assertSessionQuery(statements.get(0)); SimpleStatement statement = statements.get(1); assertEventsQuery(statement); - Mockito.verifyNoMoreInteractions(session); + verifyNoMoreInteractions(session); assertThatStage(traceFuture) .isSuccess( @@ -163,7 +164,7 @@ public void should_succeed_when_events_query_is_paged() { CompletionStage sessionRow = completeSessionRow(); CompletionStage eventRows1 = multiPageEventRows1(); CompletionStage eventRows2 = multiPageEventRows2(); - Mockito.when(session.executeAsync(any(SimpleStatement.class))) + when(session.executeAsync(any(SimpleStatement.class))) .thenAnswer(invocation -> sessionRow) .thenAnswer(invocation -> eventRows1) .thenAnswer(invocation -> eventRows2); @@ -173,13 +174,13 @@ public void should_succeed_when_events_query_is_paged() { CompletionStage traceFuture = fetcher.fetch(); // Then - Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + verify(session, times(3)).executeAsync(statementCaptor.capture()); List statements = statementCaptor.getAllValues(); assertSessionQuery(statements.get(0)); assertEventsQuery(statements.get(1)); assertEventsQuery(statements.get(2)); assertThat(statements.get(2).getPagingState()).isEqualTo(PAGING_STATE); - Mockito.verifyNoMoreInteractions(session); + verifyNoMoreInteractions(session); assertThatStage(traceFuture).isSuccess(trace -> assertThat(trace.getEvents()).hasSize(2)); } @@ -190,7 +191,7 @@ public void should_retry_when_session_row_is_incomplete() { CompletionStage sessionRow1 = incompleteSessionRow(); CompletionStage sessionRow2 = completeSessionRow(); CompletionStage eventRows = singlePageEventRows(); - Mockito.when(session.executeAsync(any(SimpleStatement.class))) + when(session.executeAsync(any(SimpleStatement.class))) .thenAnswer(invocation -> sessionRow1) .thenAnswer(invocation -> sessionRow2) .thenAnswer(invocation -> eventRows); @@ -200,12 +201,12 @@ public void should_retry_when_session_row_is_incomplete() { CompletionStage traceFuture = fetcher.fetch(); // Then - Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + verify(session, times(3)).executeAsync(statementCaptor.capture()); List statements = statementCaptor.getAllValues(); assertSessionQuery(statements.get(0)); assertSessionQuery(statements.get(1)); assertEventsQuery(statements.get(2)); - Mockito.verifyNoMoreInteractions(session); + verifyNoMoreInteractions(session); assertThatStage(traceFuture) .isSuccess( @@ -237,7 +238,7 @@ public void should_retry_when_session_row_is_incomplete() { public void should_fail_when_session_query_fails() { // Given RuntimeException mockError = new RuntimeException("mock error"); - Mockito.when(session.executeAsync(any(SimpleStatement.class))) + when(session.executeAsync(any(SimpleStatement.class))) .thenReturn(CompletableFutures.failedFuture(mockError)); // When @@ -245,10 +246,10 @@ public void should_fail_when_session_query_fails() { CompletionStage traceFuture = fetcher.fetch(); // Then - Mockito.verify(session).executeAsync(statementCaptor.capture()); + verify(session).executeAsync(statementCaptor.capture()); SimpleStatement statement = statementCaptor.getValue(); assertSessionQuery(statement); - Mockito.verifyNoMoreInteractions(session); + verifyNoMoreInteractions(session); assertThatStage(traceFuture).isFailed(error -> assertThat(error).isSameAs(mockError)); } @@ -259,7 +260,7 @@ public void should_fail_when_session_query_still_incomplete_after_max_tries() { CompletionStage sessionRow1 = incompleteSessionRow(); CompletionStage sessionRow2 = incompleteSessionRow(); CompletionStage sessionRow3 = incompleteSessionRow(); - Mockito.when(session.executeAsync(any(SimpleStatement.class))) + when(session.executeAsync(any(SimpleStatement.class))) .thenAnswer(invocation -> sessionRow1) .thenAnswer(invocation -> sessionRow2) .thenAnswer(invocation -> sessionRow3); @@ -269,7 +270,7 @@ public void should_fail_when_session_query_still_incomplete_after_max_tries() { CompletionStage traceFuture = fetcher.fetch(); // Then - Mockito.verify(session, times(3)).executeAsync(statementCaptor.capture()); + verify(session, times(3)).executeAsync(statementCaptor.capture()); List statements = statementCaptor.getAllValues(); for (int i = 0; i < 3; i++) { assertSessionQuery(statements.get(i)); @@ -292,21 +293,21 @@ private CompletionStage incompleteSessionRow() { } private CompletionStage sessionRow(Integer duration) { - Row row = Mockito.mock(Row.class); - Mockito.when(row.getString("request")).thenReturn("mock request"); + Row row = mock(Row.class); + when(row.getString("request")).thenReturn("mock request"); if (duration == null) { - Mockito.when(row.isNull("duration")).thenReturn(true); + when(row.isNull("duration")).thenReturn(true); } else { - Mockito.when(row.getInt("duration")).thenReturn(duration); + when(row.getInt("duration")).thenReturn(duration); } - Mockito.when(row.getInetAddress("coordinator")).thenReturn(address); - Mockito.when(row.getMap("parameters", String.class, String.class)) + when(row.getInetAddress("coordinator")).thenReturn(address); + when(row.getMap("parameters", String.class, String.class)) .thenReturn(ImmutableMap.of("key1", "value1", "key2", "value2")); - Mockito.when(row.isNull("started_at")).thenReturn(false); - Mockito.when(row.getInstant("started_at")).thenReturn(Instant.EPOCH); + when(row.isNull("started_at")).thenReturn(false); + when(row.getInstant("started_at")).thenReturn(Instant.EPOCH); - AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); - Mockito.when(rs.one()).thenReturn(row); + AsyncResultSet rs = mock(AsyncResultSet.class); + when(rs.one()).thenReturn(row); return CompletableFuture.completedFuture(rs); } @@ -316,49 +317,49 @@ private CompletionStage singlePageEventRows() { rows.add(eventRow(i)); } - AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); - Mockito.when(rs.currentPage()).thenReturn(rows); + AsyncResultSet rs = mock(AsyncResultSet.class); + when(rs.currentPage()).thenReturn(rows); - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(executionInfo.getPagingState()).thenReturn(null); - Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + ExecutionInfo executionInfo = mock(ExecutionInfo.class); + when(executionInfo.getPagingState()).thenReturn(null); + when(rs.getExecutionInfo()).thenReturn(executionInfo); return CompletableFuture.completedFuture(rs); } private CompletionStage multiPageEventRows1() { - AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + AsyncResultSet rs = mock(AsyncResultSet.class); ImmutableList rows = ImmutableList.of(eventRow(0)); - Mockito.when(rs.currentPage()).thenReturn(rows); + when(rs.currentPage()).thenReturn(rows); - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(executionInfo.getPagingState()).thenReturn(PAGING_STATE); - Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + ExecutionInfo executionInfo = mock(ExecutionInfo.class); + when(executionInfo.getPagingState()).thenReturn(PAGING_STATE); + when(rs.getExecutionInfo()).thenReturn(executionInfo); return CompletableFuture.completedFuture(rs); } private CompletionStage multiPageEventRows2() { - AsyncResultSet rs = Mockito.mock(AsyncResultSet.class); + AsyncResultSet rs = mock(AsyncResultSet.class); ImmutableList rows = ImmutableList.of(eventRow(1)); - Mockito.when(rs.currentPage()).thenReturn(rows); + when(rs.currentPage()).thenReturn(rows); - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(executionInfo.getPagingState()).thenReturn(null); - Mockito.when(rs.getExecutionInfo()).thenReturn(executionInfo); + ExecutionInfo executionInfo = mock(ExecutionInfo.class); + when(executionInfo.getPagingState()).thenReturn(null); + when(rs.getExecutionInfo()).thenReturn(executionInfo); return CompletableFuture.completedFuture(rs); } private Row eventRow(int i) { - Row row = Mockito.mock(Row.class); - Mockito.when(row.getString("activity")).thenReturn("mock activity " + i); - Mockito.when(row.getUuid("event_id")).thenReturn(Uuids.startOf(i)); - Mockito.when(row.getInetAddress("source")).thenReturn(address); - Mockito.when(row.getInt("source_elapsed")).thenReturn(i); - Mockito.when(row.getString("thread")).thenReturn("mock thread " + i); + Row row = mock(Row.class); + when(row.getString("activity")).thenReturn("mock activity " + i); + when(row.getUuid("event_id")).thenReturn(Uuids.startOf(i)); + when(row.getInetAddress("source")).thenReturn(address); + when(row.getInt("source_elapsed")).thenReturn(i); + when(row.getString("thread")).thenReturn("mock thread " + i); return row; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index ecd0dd2ead5..11ba0cd5f55 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -18,6 +18,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; @@ -62,7 +64,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; @@ -95,85 +96,76 @@ public static Builder builder() { protected RequestHandlerTestHarness(Builder builder) { MockitoAnnotations.initMocks(this); - Mockito.when(nettyOptions.getTimer()).thenReturn(timer); - Mockito.when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.getTimer()).thenReturn(timer); + when(nettyOptions.ioEventLoopGroup()).thenReturn(eventLoopGroup); + when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(defaultProfile.getName()).thenReturn(DriverExecutionProfile.DEFAULT_NAME); + when(defaultProfile.getName()).thenReturn(DriverExecutionProfile.DEFAULT_NAME); // TODO make configurable in the test, also handle profiles - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT)) + when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + when(defaultProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.LOCAL_ONE.name()); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); - Mockito.when(defaultProfile.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) + when(defaultProfile.getInt(DefaultDriverOption.REQUEST_PAGE_SIZE)).thenReturn(5000); + when(defaultProfile.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) .thenReturn(DefaultConsistencyLevel.SERIAL.name()); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) + when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_DEFAULT_IDEMPOTENCE)) .thenReturn(builder.defaultIdempotence); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)) - .thenReturn(true); + when(defaultProfile.getBoolean(DefaultDriverOption.PREPARE_ON_ALL_NODES)).thenReturn(true); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); - Mockito.when( - loadBalancingPolicyWrapper.newQueryPlan( - any(Request.class), anyString(), any(Session.class))) + when(loadBalancingPolicyWrapper.newQueryPlan( + any(Request.class), anyString(), any(Session.class))) .thenReturn(builder.buildQueryPlan()); - Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.getRetryPolicy(anyString())).thenReturn(retryPolicy); + when(context.getRetryPolicy(anyString())).thenReturn(retryPolicy); // Disable speculative executions by default - Mockito.when( - speculativeExecutionPolicy.nextExecution( - any(Node.class), any(CqlIdentifier.class), any(Request.class), anyInt())) + when(speculativeExecutionPolicy.nextExecution( + any(Node.class), any(CqlIdentifier.class), any(Request.class), anyInt())) .thenReturn(-1L); - Mockito.when(context.getSpeculativeExecutionPolicy(anyString())) - .thenReturn(speculativeExecutionPolicy); + when(context.getSpeculativeExecutionPolicy(anyString())).thenReturn(speculativeExecutionPolicy); - Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); - Mockito.when(timestampGenerator.next()).thenReturn(Long.MIN_VALUE); - Mockito.when(context.getTimestampGenerator()).thenReturn(timestampGenerator); + when(timestampGenerator.next()).thenReturn(Long.MIN_VALUE); + when(context.getTimestampGenerator()).thenReturn(timestampGenerator); pools = builder.buildMockPools(); - Mockito.when(session.getChannel(any(Node.class), anyString())) + when(session.getChannel(any(Node.class), anyString())) .thenAnswer( invocation -> { Node node = invocation.getArgument(0); return pools.get(node).next(); }); - Mockito.when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); + when(session.getRepreparePayloads()).thenReturn(new ConcurrentHashMap<>()); - Mockito.when(session.setKeyspace(any(CqlIdentifier.class))) + when(session.setKeyspace(any(CqlIdentifier.class))) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(session.getMetricUpdater()).thenReturn(sessionMetricUpdater); - Mockito.when(sessionMetricUpdater.isEnabled(any(SessionMetric.class), anyString())) - .thenReturn(true); + when(session.getMetricUpdater()).thenReturn(sessionMetricUpdater); + when(sessionMetricUpdater.isEnabled(any(SessionMetric.class), anyString())).thenReturn(true); - Mockito.when(session.getMetadata()).thenReturn(DefaultMetadata.EMPTY); + when(session.getMetadata()).thenReturn(DefaultMetadata.EMPTY); - Mockito.when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); - Mockito.when( - protocolVersionRegistry.supports( - any(ProtocolVersion.class), any(ProtocolFeature.class))) + when(context.getProtocolVersionRegistry()).thenReturn(protocolVersionRegistry); + when(protocolVersionRegistry.supports(any(ProtocolVersion.class), any(ProtocolFeature.class))) .thenReturn(true); if (builder.protocolVersion != null) { - Mockito.when(context.getProtocolVersion()).thenReturn(builder.protocolVersion); + when(context.getProtocolVersion()).thenReturn(builder.protocolVersion); } - Mockito.when(context.getConsistencyLevelRegistry()) - .thenReturn(new DefaultConsistencyLevelRegistry()); + when(context.getConsistencyLevelRegistry()).thenReturn(new DefaultConsistencyLevelRegistry()); - Mockito.when(context.getWriteTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); + when(context.getWriteTypeRegistry()).thenReturn(new DefaultWriteTypeRegistry()); - Mockito.when(context.getRequestThrottler()) - .thenReturn(new PassThroughRequestThrottler(context)); + when(context.getRequestThrottler()).thenReturn(new PassThroughRequestThrottler(context)); - Mockito.when(context.getRequestTracker()).thenReturn(new NoopRequestTracker(context)); + when(context.getRequestTracker()).thenReturn(new NoopRequestTracker(context)); } public DefaultSession getSession() { @@ -297,11 +289,11 @@ private Map buildMockPools() { Map> stubbings = new HashMap<>(); for (PoolBehavior behavior : poolBehaviors) { Node node = behavior.node; - ChannelPool pool = pools.computeIfAbsent(node, n -> Mockito.mock(ChannelPool.class)); + ChannelPool pool = pools.computeIfAbsent(node, n -> mock(ChannelPool.class)); // The goal of the code below is to generate the equivalent of: // - // Mockito.when(pool.next()) + // when(pool.next()) // .thenReturn(behavior1.channel) // .thenReturn(behavior2.channel) // ... @@ -309,7 +301,7 @@ private Map buildMockPools() { node, (sameNode, previous) -> { if (previous == null) { - previous = Mockito.when(pool.next()); + previous = when(pool.next()); } return previous.thenReturn(behavior.channel); }); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java index 1a25d73d384..ded7dffb6a6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java @@ -16,6 +16,9 @@ package com.datastax.oss.driver.internal.core.cql; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.cql.AsyncResultSet; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; @@ -28,26 +31,25 @@ import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import org.mockito.Mockito; public abstract class ResultSetTestBase { /** Mocks an async result set where column 0 has type INT, with rows with the provided data. */ protected AsyncResultSet mockPage(boolean nextPage, Integer... data) { - AsyncResultSet page = Mockito.mock(AsyncResultSet.class); + AsyncResultSet page = mock(AsyncResultSet.class); - ColumnDefinitions columnDefinitions = Mockito.mock(ColumnDefinitions.class); - Mockito.when(page.getColumnDefinitions()).thenReturn(columnDefinitions); + ColumnDefinitions columnDefinitions = mock(ColumnDefinitions.class); + when(page.getColumnDefinitions()).thenReturn(columnDefinitions); - ExecutionInfo executionInfo = Mockito.mock(ExecutionInfo.class); - Mockito.when(page.getExecutionInfo()).thenReturn(executionInfo); + ExecutionInfo executionInfo = mock(ExecutionInfo.class); + when(page.getExecutionInfo()).thenReturn(executionInfo); if (nextPage) { - Mockito.when(page.hasMorePages()).thenReturn(true); - Mockito.when(page.fetchNextPage()).thenReturn(Mockito.spy(new CompletableFuture<>())); + when(page.hasMorePages()).thenReturn(true); + when(page.fetchNextPage()).thenReturn(spy(new CompletableFuture<>())); } else { - Mockito.when(page.hasMorePages()).thenReturn(false); - Mockito.when(page.fetchNextPage()).thenThrow(new IllegalStateException()); + when(page.hasMorePages()).thenReturn(false); + when(page.fetchNextPage()).thenThrow(new IllegalStateException()); } // Emulate DefaultAsyncResultSet's internals (this is a bit sketchy, maybe it would be better @@ -61,15 +63,15 @@ protected Row computeNext() { return (index == null) ? endOfData() : mockRow(index); } }; - Mockito.when(page.currentPage()).thenReturn(() -> iterator); - Mockito.when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); + when(page.currentPage()).thenReturn(() -> iterator); + when(page.remaining()).thenAnswer(invocation -> iterator.remaining()); return page; } private Row mockRow(int index) { - Row row = Mockito.mock(Row.class); - Mockito.when(row.getInt(0)).thenReturn(index); + Row row = mock(Row.class); + when(row.getInt(0)).thenReturn(index); return row; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java index 58001862a8a..59ca780136f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/StatementSizeTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.cql; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -45,7 +46,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class StatementSizeTest { @@ -69,9 +69,9 @@ public void setup() { MockitoAnnotations.initMocks(this); ByteBuffer preparedId = ByteBuffer.wrap(PREPARED_ID); - Mockito.when(preparedStatement.getId()).thenReturn(preparedId); + when(preparedStatement.getId()).thenReturn(preparedId); ByteBuffer resultMetadataId = ByteBuffer.wrap(RESULT_METADATA_ID); - Mockito.when(preparedStatement.getResultMetadataId()).thenReturn(resultMetadataId); + when(preparedStatement.getResultMetadataId()).thenReturn(resultMetadataId); ColumnDefinitions columnDefinitions = DefaultColumnDefinitions.valueOf( @@ -79,15 +79,15 @@ public void setup() { phonyColumnDef("ks", "table", "c1", -1, ProtocolConstants.DataType.INT), phonyColumnDef("ks", "table", "c2", -1, ProtocolConstants.DataType.VARCHAR))); - Mockito.when(preparedStatement.getVariableDefinitions()).thenReturn(columnDefinitions); + when(preparedStatement.getVariableDefinitions()).thenReturn(columnDefinitions); - Mockito.when(driverContext.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V5); - Mockito.when(driverContext.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(driverContext.getProtocolVersionRegistry()) + when(driverContext.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V5); + when(driverContext.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + when(driverContext.getProtocolVersionRegistry()) .thenReturn(new CassandraProtocolVersionRegistry(null)); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(driverContext.getConfig()).thenReturn(config); - Mockito.when(driverContext.getTimestampGenerator()).thenReturn(timestampGenerator); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(driverContext.getConfig()).thenReturn(config); + when(driverContext.getTimestampGenerator()).thenReturn(timestampGenerator); } private ColumnDefinition phonyColumnDef( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index 4587f4bb773..a8eb7c7c72f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -18,6 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -32,7 +36,6 @@ import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; import org.junit.Test; -import org.mockito.Mockito; public abstract class AccessibleByIdTestBase< T extends GettableById & SettableById & GettableByName & SettableByName> @@ -50,8 +53,8 @@ public void should_set_primitive_value_by_id() { t.setInt(FIELD0_ID, 1); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -64,8 +67,8 @@ public void should_set_object_value_by_id() { t.setString(FIELD0_ID, "a"); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).encode("a", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x61")); } @@ -78,7 +81,7 @@ public void should_set_bytes_by_id() { t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -92,32 +95,31 @@ public void should_set_to_null_by_id() { t.setToNull(FIELD0_ID); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_ID)).isNull(); } @Test public void should_set_with_explicit_class_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(FIELD0_ID, "1", String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_type_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); @@ -125,23 +127,23 @@ public void should_set_with_explicit_type_by_id() { t.set(FIELD0_ID, "1", GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_codec_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(FIELD0_ID, "1", intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -155,8 +157,8 @@ public void should_get_primitive_value_by_id() { int i = t.getInt(FIELD0_ID); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(i).isEqualTo(1); } @@ -170,8 +172,8 @@ public void should_get_object_value_by_id() { String s = t.getString(FIELD0_ID); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("a"); } @@ -185,7 +187,7 @@ public void should_get_bytes_by_id() { ByteBuffer bytes = t.getBytesUnsafe(FIELD0_ID); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -199,16 +201,15 @@ public void should_test_if_null_by_id() { boolean isNull = t.isNull(FIELD0_ID); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(isNull).isTrue(); } @Test public void should_get_with_explicit_class_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); @@ -216,16 +217,16 @@ public void should_get_with_explicit_class_by_id() { String s = t.get(FIELD0_ID, String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_type_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); @@ -234,15 +235,15 @@ public void should_get_with_explicit_type_by_id() { String s = t.get(FIELD0_ID, GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_codec_by_id() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); @@ -250,8 +251,8 @@ public void should_get_with_explicit_codec_by_id() { String s = t.get(FIELD0_ID, intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @@ -264,8 +265,8 @@ public void should_set_primitive_value_by_name() { t.setInt(FIELD0_NAME, 1); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -278,8 +279,8 @@ public void should_set_object_value_by_name() { t.setString(FIELD0_NAME, "a"); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).encode("a", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x61")); } @@ -292,7 +293,7 @@ public void should_set_bytes_by_name() { t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -306,32 +307,31 @@ public void should_set_to_null_by_name() { t.setToNull(FIELD0_NAME); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isNull(); } @Test public void should_set_with_explicit_class_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(FIELD0_NAME, "1", String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_type_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); @@ -339,23 +339,23 @@ public void should_set_with_explicit_type_by_name() { t.set(FIELD0_NAME, "1", GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_codec_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(FIELD0_NAME, "1", intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -369,8 +369,8 @@ public void should_get_primitive_value_by_name() { int i = t.getInt(FIELD0_NAME); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(i).isEqualTo(1); } @@ -384,8 +384,8 @@ public void should_get_object_value_by_name() { String s = t.getString(FIELD0_NAME); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("a"); } @@ -399,7 +399,7 @@ public void should_get_bytes_by_name() { ByteBuffer bytes = t.getBytesUnsafe(FIELD0_NAME); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -413,16 +413,15 @@ public void should_test_if_null_by_name() { boolean isNull = t.isNull(FIELD0_NAME); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(isNull).isTrue(); } @Test public void should_get_with_explicit_class_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); @@ -430,16 +429,16 @@ public void should_get_with_explicit_class_by_name() { String s = t.get(FIELD0_NAME, String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_type_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); @@ -448,15 +447,15 @@ public void should_get_with_explicit_type_by_name() { String s = t.get(FIELD0_NAME, GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_codec_by_name() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); @@ -464,8 +463,8 @@ public void should_get_with_explicit_codec_by_name() { String s = t.get(FIELD0_NAME, intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index ca2d62134b0..e574360ef45 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -18,6 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -39,7 +43,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public abstract class AccessibleByIndexTestBase> { @@ -60,24 +63,23 @@ protected abstract T newInstance( public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); - Mockito.when(v3AttachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); - Mockito.when(v3AttachmentPoint.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V3); + when(v3AttachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + when(v3AttachmentPoint.getProtocolVersion()).thenReturn(DefaultProtocolVersion.V3); - intCodec = Mockito.spy(TypeCodecs.INT); - doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); - textCodec = Mockito.spy(TypeCodecs.TEXT); + intCodec = spy(TypeCodecs.INT); + doubleCodec = spy(TypeCodecs.DOUBLE); + textCodec = spy(TypeCodecs.TEXT); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) - .thenAnswer(i -> doubleCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)).thenAnswer(i -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(t -> textCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(d -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(t -> textCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(d -> doubleCodec); } @Test @@ -89,8 +91,8 @@ public void should_set_primitive_value_by_index() { t.setInt(0, 1); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -103,8 +105,8 @@ public void should_set_object_value_by_index() { t.setString(0, "a"); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).encode("a", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x61")); } @@ -117,7 +119,7 @@ public void should_set_bytes_by_index() { t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -131,32 +133,31 @@ public void should_set_to_null_by_index() { t.setToNull(0); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(t.getBytesUnsafe(0)).isNull(); } @Test public void should_set_with_explicit_class_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(0, "1", String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_type_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); @@ -164,31 +165,31 @@ public void should_set_with_explicit_type_by_index() { t.set(0, "1", GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_with_explicit_codec_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When t.set(0, "1", intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @Test public void should_set_values_in_bulk() { // Given - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, 1)).thenReturn(TypeCodecs.INT); + when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + when(codecRegistry.codecFor(DataTypes.INT, 1)).thenReturn(TypeCodecs.INT); // When T t = @@ -200,14 +201,14 @@ public void should_set_values_in_bulk() { // Then assertThat(t.getString(0)).isEqualTo("foo"); assertThat(t.getInt(1)).isEqualTo(1); - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, "foo"); - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, 1); + verify(codecRegistry).codecFor(DataTypes.TEXT, "foo"); + verify(codecRegistry).codecFor(DataTypes.INT, 1); } @Test public void should_set_values_in_bulk_when_not_enough_values() { // Given - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); // When T t = @@ -219,7 +220,7 @@ public void should_set_values_in_bulk_when_not_enough_values() { // Then assertThat(t.getString(0)).isEqualTo("foo"); assertThat(t.isNull(1)).isTrue(); - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, "foo"); + verify(codecRegistry).codecFor(DataTypes.TEXT, "foo"); } @Test(expected = IllegalArgumentException.class) @@ -240,8 +241,8 @@ public void should_get_primitive_value_by_index() { int i = t.getInt(0); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); - Mockito.verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); + verify(intCodec).decodePrimitive(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(i).isEqualTo(1); } @@ -255,8 +256,8 @@ public void should_get_object_value_by_index() { String s = t.getString(0); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); - Mockito.verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); + verify(textCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("a"); } @@ -270,7 +271,7 @@ public void should_get_bytes_by_index() { ByteBuffer bytes = t.getBytesUnsafe(0); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -284,16 +285,15 @@ public void should_test_if_null_by_index() { boolean isNull = t.isNull(0); // Then - Mockito.verifyZeroInteractions(codecRegistry); + verifyZeroInteractions(codecRegistry); assertThat(isNull).isTrue(); } @Test public void should_get_with_explicit_class_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, String.class)) - .thenAnswer(i -> intToStringCodec); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); @@ -301,16 +301,16 @@ public void should_get_with_explicit_class_by_index() { String s = t.get(0, String.class); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, String.class); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, String.class); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_type_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); - Mockito.when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); + when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); @@ -319,15 +319,15 @@ public void should_get_with_explicit_type_by_index() { String s = t.get(0, GenericType.STRING); // Then - Mockito.verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @Test public void should_get_with_explicit_codec_by_index() { // Given - CqlIntToStringCodec intToStringCodec = Mockito.spy(new CqlIntToStringCodec()); + CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); @@ -335,8 +335,8 @@ public void should_get_with_explicit_codec_by_index() { String s = t.get(0, intToStringCodec); // Then - Mockito.verifyZeroInteractions(codecRegistry); - Mockito.verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); + verifyZeroInteractions(codecRegistry); + verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index 57994165592..6c2b86dd1ef 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.data; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.detach.AttachmentPoint; @@ -30,7 +31,6 @@ import java.io.UnsupportedEncodingException; import java.util.List; import org.junit.Test; -import org.mockito.Mockito; public class DefaultTupleValueTest extends AccessibleByIndexTestBase { @@ -67,8 +67,8 @@ public void should_serialize_and_deserialize() { public void should_support_null_items_when_setting_in_bulk() throws UnsupportedEncodingException { DefaultTupleType type = new DefaultTupleType(ImmutableList.of(DataTypes.INT, DataTypes.TEXT), attachmentPoint); - Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); + when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); TupleValue value = type.newValue(null, "foo"); assertThat(value.isNull(0)).isTrue(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index ce3cadf4227..4a2752dc80a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.data; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; @@ -30,7 +31,6 @@ import java.io.UnsupportedEncodingException; import java.util.List; import org.junit.Test; -import org.mockito.Mockito; public class DefaultUdtValueTest extends AccessibleByIdTestBase { @@ -89,8 +89,8 @@ public void should_support_null_items_when_setting_in_bulk() throws UnsupportedE .withField(CqlIdentifier.fromInternal("field1"), DataTypes.INT) .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) .build(); - Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); + when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); + when(codecRegistry.codecFor(DataTypes.TEXT, "foo")).thenReturn(TypeCodecs.TEXT); UdtValue value = type.newValue(null, "foo"); assertThat(value.isNull(0)).isTrue(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index cdf174638f8..39883e1f4e6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -19,6 +19,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -30,7 +33,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -45,8 +47,8 @@ public class DefaultLoadBalancingPolicyEventsTest extends DefaultLoadBalancingPo public void setup() { super.setup(); - Mockito.when(filter.test(Mockito.any(Node.class))).thenReturn(true); - Mockito.when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(filter); + when(filter.test(any(Node.class))).thenReturn(true); + when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(filter); policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); policy.init( @@ -55,7 +57,7 @@ public void setup() { ImmutableSet.of(ADDRESS1)); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); - Mockito.reset(distanceReporter); + reset(distanceReporter); } @Test @@ -65,9 +67,9 @@ public void should_remove_down_node_from_live_set() { // Then assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); - Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); + verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); // should have been called only once, during initialization, but not during onDown - Mockito.verify(filter).test(node2); + verify(filter).test(node2); } @Test @@ -77,9 +79,9 @@ public void should_remove_removed_node_from_live_set() { // Then assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); - Mockito.verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); + verify(distanceReporter, never()).setDistance(eq(node2), any(NodeDistance.class)); // should have been called only once, during initialization, but not during onRemove - Mockito.verify(filter).test(node2); + verify(filter).test(node2); } @Test @@ -88,8 +90,8 @@ public void should_set_added_node_to_local() { policy.onAdd(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - Mockito.verify(filter).test(node3); + verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + verify(filter).test(node3); // Not added to the live set yet, we're waiting for the pool to open assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } @@ -97,26 +99,26 @@ public void should_set_added_node_to_local() { @Test public void should_ignore_added_node_when_filtered() { // Given - Mockito.when(filter.test(node3)).thenReturn(false); + when(filter.test(node3)).thenReturn(false); // When policy.onAdd(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } @Test public void should_ignore_added_node_when_remote_dc() { // Given - Mockito.when(node3.getDatacenter()).thenReturn("dc2"); + when(node3.getDatacenter()).thenReturn("dc2"); // When policy.onAdd(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } @@ -126,35 +128,35 @@ public void should_add_up_node_to_live_set() { policy.onUp(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - Mockito.verify(filter).test(node3); + verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + verify(filter).test(node3); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); } @Test public void should_ignore_up_node_when_filtered() { // Given - Mockito.when(filter.test(node3)).thenReturn(false); + when(filter.test(node3)).thenReturn(false); // When policy.onUp(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); - Mockito.verify(filter).test(node3); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(filter).test(node3); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } @Test public void should_ignore_up_node_when_remote_dc() { // Given - Mockito.when(node3.getDatacenter()).thenReturn("dc2"); + when(node3.getDatacenter()).thenReturn("dc2"); // When policy.onUp(node3); // Then - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index d0d5da09fcf..63f69632701 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.filter; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -30,15 +32,13 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import java.util.Collections; import org.junit.Test; -import org.mockito.Mockito; public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPolicyTestBase { @Test public void should_use_local_dc_if_provided_via_config() { // Given - Mockito.when( - defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn("dc1"); // When @@ -52,7 +52,7 @@ public void should_use_local_dc_if_provided_via_config() { @Test public void should_use_local_dc_if_provided_via_context() { // Given - Mockito.when(context.getLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn("dc1"); + when(context.getLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn("dc1"); // When DefaultLoadBalancingPolicy policy = @@ -60,15 +60,14 @@ public void should_use_local_dc_if_provided_via_context() { // Then assertThat(policy.localDc).isEqualTo("dc1"); - Mockito.verify(defaultProfile, never()) + verify(defaultProfile, never()) .getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null); } @Test public void should_infer_local_dc_if_no_explicit_contact_points() { // Given - Mockito.when( - defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn(null); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -86,8 +85,7 @@ public void should_infer_local_dc_if_no_explicit_contact_points() { @Test public void should_require_local_dc_if_explicit_contact_points() { // Given - Mockito.when( - defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn(null); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -102,8 +100,8 @@ public void should_require_local_dc_if_explicit_contact_points() { @Test public void should_warn_if_contact_points_not_in_local_dc() { // Given - Mockito.when(node2.getDatacenter()).thenReturn("dc2"); - Mockito.when(node3.getDatacenter()).thenReturn("dc3"); + when(node2.getDatacenter()).thenReturn("dc2"); + when(node3.getDatacenter()).thenReturn("dc3"); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -114,7 +112,7 @@ public void should_warn_if_contact_points_not_in_local_dc() { ImmutableSet.of(ADDRESS1, ADDRESS2, ADDRESS3)); // Then - Mockito.verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); + verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); Iterable warnLogs = filter(loggingEventCaptor.getAllValues()).with("level", Level.WARN).get(); assertThat(warnLogs).hasSize(1); @@ -138,17 +136,17 @@ public void should_include_nodes_from_local_dc() { ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases // Then - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); + verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); + verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); } @Test public void should_ignore_nodes_from_remote_dcs() { // Given - Mockito.when(node2.getDatacenter()).thenReturn("dc2"); - Mockito.when(node3.getDatacenter()).thenReturn("dc3"); + when(node2.getDatacenter()).thenReturn("dc2"); + when(node3.getDatacenter()).thenReturn("dc3"); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -159,16 +157,16 @@ public void should_ignore_nodes_from_remote_dcs() { ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases // Then - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); } @Test public void should_ignore_nodes_excluded_by_filter() { // Given - Mockito.when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)) + when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)) .thenReturn(node -> node.equals(node1)); DefaultLoadBalancingPolicy policy = @@ -181,9 +179,9 @@ public void should_ignore_nodes_excluded_by_filter() { ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases // Then - Mockito.verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); - Mockito.verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); - Mockito.verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); + verify(distanceReporter).setDistance(node2, NodeDistance.IGNORED); + verify(distanceReporter).setDistance(node3, NodeDistance.IGNORED); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 937c4839d38..337f8ab4897 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -21,7 +21,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -40,7 +43,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancingPolicyTestBase { @@ -60,13 +62,13 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin public void setup() { super.setup(); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); - Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(metadata.getTokenMap()).thenAnswer(invocation -> Optional.of(this.tokenMap)); + when(context.getMetadataManager()).thenReturn(metadataManager); + when(metadataManager.getMetadata()).thenReturn(metadata); + when(metadata.getTokenMap()).thenAnswer(invocation -> Optional.of(this.tokenMap)); // Use a subclass to disable shuffling, we just spy to make sure that the shuffling method was // called (makes tests easier) - policy = Mockito.spy(new NonShufflingPolicy(context, DriverExecutionProfile.DEFAULT_NAME)); + policy = spy(new NonShufflingPolicy(context, DriverExecutionProfile.DEFAULT_NAME)); policy.init( ImmutableMap.of( ADDRESS1, node1, @@ -90,43 +92,43 @@ public void should_use_round_robin_when_request_has_no_routing_keyspace() { assertRoundRobinQueryPlans(); - Mockito.verify(request, never()).getRoutingKey(); - Mockito.verify(request, never()).getRoutingToken(); - Mockito.verify(metadataManager, never()).getMetadata(); + verify(request, never()).getRoutingKey(); + verify(request, never()).getRoutingToken(); + verify(metadataManager, never()).getMetadata(); } @Test public void should_use_round_robin_when_request_has_no_routing_key_or_token() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); assertThat(request.getRoutingKey()).isNull(); assertThat(request.getRoutingToken()).isNull(); assertRoundRobinQueryPlans(); - Mockito.verify(metadataManager, never()).getMetadata(); + verify(metadataManager, never()).getMetadata(); } @Test public void should_use_round_robin_when_token_map_absent() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); - Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + when(request.getRoutingKey()).thenReturn(ROUTING_KEY); - Mockito.when(metadata.getTokenMap()).thenReturn(Optional.empty()); + when(metadata.getTokenMap()).thenReturn(Optional.empty()); assertRoundRobinQueryPlans(); - Mockito.verify(metadata, atLeast(1)).getTokenMap(); + verify(metadata, atLeast(1)).getTokenMap(); } @Test public void should_use_round_robin_when_token_map_returns_no_replicas() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); - Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); - Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(Collections.emptySet()); + when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(Collections.emptySet()); assertRoundRobinQueryPlans(); - Mockito.verify(tokenMap, atLeast(1)).getReplicas(KEYSPACE, ROUTING_KEY); + verify(tokenMap, atLeast(1)).getReplicas(KEYSPACE, ROUTING_KEY); } private void assertRoundRobinQueryPlans() { @@ -146,9 +148,9 @@ private void assertRoundRobinQueryPlans() { @Test public void should_prioritize_single_replica() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); - Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); - Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(ImmutableSet.of(node3)); + when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(ImmutableSet.of(node3)); // node3 always first, round-robin on the rest assertThat(policy.newQueryPlan(request, session)) @@ -161,15 +163,14 @@ public void should_prioritize_single_replica() { .containsExactly(node3, node5, node1, node2, node4); // Should not shuffle replicas since there is only one - Mockito.verify(policy, never()).shuffleHead(any(), anyInt()); + verify(policy, never()).shuffleHead(any(), anyInt()); } @Test public void should_prioritize_and_shuffle_replicas() { - Mockito.when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); - Mockito.when(request.getRoutingKey()).thenReturn(ROUTING_KEY); - Mockito.when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)) - .thenReturn(ImmutableSet.of(node3, node5)); + when(request.getRoutingKeyspace()).thenReturn(KEYSPACE); + when(request.getRoutingKey()).thenReturn(ROUTING_KEY); + when(tokenMap.getReplicas(KEYSPACE, ROUTING_KEY)).thenReturn(ImmutableSet.of(node3, node5)); assertThat(policy.newQueryPlan(request, session)) .containsExactly(node3, node5, node1, node2, node4); @@ -178,9 +179,9 @@ public void should_prioritize_and_shuffle_replicas() { assertThat(policy.newQueryPlan(request, session)) .containsExactly(node3, node5, node4, node1, node2); - Mockito.verify(policy, times(3)).shuffleHead(any(), eq(2)); + verify(policy, times(3)).shuffleHead(any(), eq(2)); // No power of two choices with only two replicas - Mockito.verify(session, never()).getPools(); + verify(session, never()).getPools(); } static class NonShufflingPolicy extends DefaultLoadBalancingPolicy { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java index edd0da9c6ca..b5dad76f154 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -15,6 +15,9 @@ */ package com.datastax.oss.driver.internal.core.loadbalancing; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.when; + import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; @@ -34,7 +37,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.LoggerFactory; @@ -66,22 +68,21 @@ public abstract class DefaultLoadBalancingPolicyTestBase { @Before public void setup() { - Mockito.when(context.getSessionName()).thenReturn("test"); - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); + when(context.getSessionName()).thenReturn("test"); + when(context.getConfig()).thenReturn(config); + when(config.getProfile(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(defaultProfile); - Mockito.when( - defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) + when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn("dc1"); logger = (Logger) LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); logger.addAppender(appender); for (Node node : ImmutableList.of(node1, node2, node3, node4, node5)) { - Mockito.when(node.getDatacenter()).thenReturn("dc1"); + when(node.getDatacenter()).thenReturn("dc1"); } - Mockito.when(context.getLocalDatacenter(Mockito.anyString())).thenReturn(null); + when(context.getLocalDatacenter(anyString())).thenReturn(null); } @After diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index ccc7cdeea90..9ac8eba62fe 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.uuid.Uuids; @@ -30,7 +31,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -45,7 +45,7 @@ public class AddNodeRefreshTest { @Before public void setup() { - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index 300af568de9..cbbbde46c29 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Node; @@ -32,7 +34,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -59,7 +60,7 @@ public class DefaultMetadataTokenMapTest { public void setup() { DefaultReplicationStrategyFactory replicationStrategyFactory = new DefaultReplicationStrategyFactory(context); - Mockito.when(context.getReplicationStrategyFactory()).thenReturn(replicationStrategyFactory); + when(context.getReplicationStrategyFactory()).thenReturn(replicationStrategyFactory); } @Test @@ -126,16 +127,16 @@ public void should_update_token_map_when_schema_changes() { } private static DefaultNode mockNode(String token) { - DefaultNode node = Mockito.mock(DefaultNode.class); - Mockito.when(node.getRawTokens()).thenReturn(ImmutableSet.of(token)); + DefaultNode node = mock(DefaultNode.class); + when(node.getRawTokens()).thenReturn(ImmutableSet.of(token)); return node; } private static KeyspaceMetadata mockKeyspace( CqlIdentifier name, Map replicationConfig) { - KeyspaceMetadata keyspace = Mockito.mock(KeyspaceMetadata.class); - Mockito.when(keyspace.getName()).thenReturn(name); - Mockito.when(keyspace.getReplication()).thenReturn(replicationConfig); + KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class); + when(keyspace.getName()).thenReturn(name); + when(keyspace.getReplication()).thenReturn(replicationConfig); return keyspace; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 779d35b83c4..28191e3f241 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -19,7 +19,11 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -51,7 +55,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class DefaultTopologyMonitorTest { @@ -76,19 +79,19 @@ public class DefaultTopologyMonitorTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); - Mockito.when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultConfig); + when(context.getConfig()).thenReturn(config); - addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); + addressTranslator = spy(new PassThroughAddressTranslator(context)); + when(context.getAddressTranslator()).thenReturn(addressTranslator); - Mockito.when(channel.connectAddress()).thenReturn(ADDRESS1); - Mockito.when(controlConnection.channel()).thenReturn(channel); - Mockito.when(context.getControlConnection()).thenReturn(controlConnection); + when(channel.connectAddress()).thenReturn(ADDRESS1); + when(controlConnection.channel()).thenReturn(channel); + when(context.getControlConnection()).thenReturn(controlConnection); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); @@ -102,7 +105,7 @@ public void should_initialize_control_connection() { topologyMonitor.init(); // Then - Mockito.verify(controlConnection).init(true, false, true); + verify(controlConnection).init(true, false, true); } @Test @@ -186,13 +189,13 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() }); // The rpc_address in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getString(anyString()); + verify(peer3).getInetAddress("rpc_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2).getString("data_center"); + verify(peer2).getInetAddress("rpc_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2).getString("data_center"); } @Test @@ -218,13 +221,13 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present_V }); // The rpc_address in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInetAddress("native_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getString(anyString()); + verify(peer3).getInetAddress("native_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInetAddress("native_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2).getString("data_center"); + verify(peer2).getInetAddress("native_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2).getString("data_center"); } @Test @@ -250,17 +253,17 @@ public void should_get_new_node_from_peers() { }); // The rpc_address in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getString(anyString()); + verify(peer3).getInetAddress("rpc_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2, never()).getString(anyString()); + verify(peer2).getInetAddress("rpc_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2, never()).getString(anyString()); - Mockito.verify(peer1).getInetAddress("rpc_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); - Mockito.verify(peer1).getString("data_center"); + verify(peer1).getInetAddress("rpc_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); + verify(peer1).getString("data_center"); } @Test @@ -286,17 +289,17 @@ public void should_get_new_node_from_peers_v2() { }); // The natove in each row should have been tried, only the last row should have been // converted - Mockito.verify(peer3).getInetAddress("native_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); - Mockito.verify(peer3, never()).getString(anyString()); + verify(peer3).getInetAddress("native_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3, never()).getString(anyString()); - Mockito.verify(peer2).getInetAddress("native_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); - Mockito.verify(peer2, never()).getString(anyString()); + verify(peer2).getInetAddress("native_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2, never()).getString(anyString()); - Mockito.verify(peer1).getInetAddress("native_address"); - Mockito.verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); - Mockito.verify(peer1).getString("data_center"); + verify(peer1).getInetAddress("native_address"); + verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); + verify(peer1).getString("data_center"); } @Test @@ -405,20 +408,19 @@ private CompletionStage throwException() throws Exception { private AdminRow mockLocalRow(int i) { try { - AdminRow row = Mockito.mock(AdminRow.class); - Mockito.when(row.getInetAddress("broadcast_address")) + AdminRow row = mock(AdminRow.class); + when(row.getInetAddress("broadcast_address")) .thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getString("data_center")).thenReturn("dc" + i); - Mockito.when(row.getInetAddress("listen_address")) - .thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getString("rack")).thenReturn("rack" + i); - Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); + when(row.getString("data_center")).thenReturn("dc" + i); + when(row.getInetAddress("listen_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + when(row.getString("rack")).thenReturn("rack" + i); + when(row.getString("release_version")).thenReturn("release_version" + i); // The driver should not use this column for the local row, because it can contain the // non-broadcast RPC address. Simulate the bug to ensure it's handled correctly. - Mockito.when(row.getInetAddress("rpc_address")).thenReturn(InetAddress.getByName("0.0.0.0")); + when(row.getInetAddress("rpc_address")).thenReturn(InetAddress.getByName("0.0.0.0")); - Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); + when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); return row; } catch (UnknownHostException e) { fail("unexpected", e); @@ -428,14 +430,13 @@ private AdminRow mockLocalRow(int i) { private AdminRow mockPeersRow(int i) { try { - AdminRow row = Mockito.mock(AdminRow.class); - Mockito.when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getString("data_center")).thenReturn("dc" + i); - Mockito.when(row.getString("rack")).thenReturn("rack" + i); - Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); - Mockito.when(row.getInetAddress("rpc_address")) - .thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); + AdminRow row = mock(AdminRow.class); + when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); + when(row.getString("data_center")).thenReturn("dc" + i); + when(row.getString("rack")).thenReturn("rack" + i); + when(row.getString("release_version")).thenReturn("release_version" + i); + when(row.getInetAddress("rpc_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); return row; } catch (UnknownHostException e) { fail("unexpected", e); @@ -445,18 +446,17 @@ private AdminRow mockPeersRow(int i) { private AdminRow mockPeersV2Row(int i) { try { - AdminRow row = Mockito.mock(AdminRow.class); - Mockito.when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getInteger("peer_port")).thenReturn(7000 + i); - Mockito.when(row.getString("data_center")).thenReturn("dc" + i); - Mockito.when(row.getString("rack")).thenReturn("rack" + i); - Mockito.when(row.getString("release_version")).thenReturn("release_version" + i); - Mockito.when(row.getInetAddress("native_address")) - .thenReturn(InetAddress.getByName("127.0.0." + i)); - Mockito.when(row.getInteger("native_port")).thenReturn(9042); - Mockito.when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); - Mockito.when(row.contains("peer_port")).thenReturn(true); - Mockito.when(row.contains("native_port")).thenReturn(true); + AdminRow row = mock(AdminRow.class); + when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); + when(row.getInteger("peer_port")).thenReturn(7000 + i); + when(row.getString("data_center")).thenReturn("dc" + i); + when(row.getString("rack")).thenReturn("rack" + i); + when(row.getString("release_version")).thenReturn("release_version" + i); + when(row.getInetAddress("native_address")).thenReturn(InetAddress.getByName("127.0.0." + i)); + when(row.getInteger("native_port")).thenReturn(9042); + when(row.getSetOfString("tokens")).thenReturn(ImmutableSet.of("token" + i)); + when(row.contains("peer_port")).thenReturn(true); + when(row.contains("native_port")).thenReturn(true); return row; } catch (UnknownHostException e) { fail("unexpected", e); @@ -465,8 +465,8 @@ private AdminRow mockPeersV2Row(int i) { } private AdminResult mockResult(AdminRow... rows) { - AdminResult result = Mockito.mock(AdminResult.class); - Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + AdminResult result = mock(AdminResult.class); + when(result.iterator()).thenReturn(Iterators.forArray(rows)); return result; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 66bec3f337e..59a7b445b35 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -29,7 +30,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -48,7 +48,7 @@ public class FullNodeListRefreshTest { @Before public void setup() { - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java index 17a67028362..ba0485b0047 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; @@ -25,7 +26,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -39,7 +39,7 @@ public class InitContactPointsRefreshTest { @Before public void setup() { - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 8a90ac1daaa..1b0b0db3df6 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -19,8 +19,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -47,7 +52,6 @@ import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; @@ -75,7 +79,7 @@ public class LoadBalancingPolicyWrapperTest { @Before public void setup() { - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); @@ -86,16 +90,16 @@ public void setup() { .put(node1.getConnectAddress(), node1) .put(node2.getConnectAddress(), node2) .build(); - Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap); - Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(metadataManager.getContactPoints()).thenReturn(contactPointsMap.keySet()); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); + when(metadata.getNodes()).thenReturn(contactPointsMap); + when(metadataManager.getMetadata()).thenReturn(metadata); + when(metadataManager.getContactPoints()).thenReturn(contactPointsMap.keySet()); + when(context.getMetadataManager()).thenReturn(metadataManager); defaultPolicysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); - Mockito.when(policy1.newQueryPlan(null, null)).thenReturn(defaultPolicysQueryPlan); + when(policy1.newQueryPlan(null, null)).thenReturn(defaultPolicysQueryPlan); - eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); + eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); wrapper = new LoadBalancingPolicyWrapper( @@ -118,7 +122,7 @@ public void should_build_query_plan_from_contact_points_before_init() { // Then for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy, never()).newQueryPlan(null, null); + verify(policy, never()).newQueryPlan(null, null); } assertThat(queryPlan).containsOnlyElementsOf(contactPointsMap.values()); } @@ -128,8 +132,7 @@ public void should_fetch_query_plan_from_policy_after_init() { // Given wrapper.init(); for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy) - .init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); + verify(policy).init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); } // When @@ -137,7 +140,7 @@ public void should_fetch_query_plan_from_policy_after_init() { // Then // no-arg newQueryPlan() uses the default profile - Mockito.verify(policy1).newQueryPlan(null, null); + verify(policy1).newQueryPlan(null, null); assertThat(queryPlan).isEqualTo(defaultPolicysQueryPlan); } @@ -153,14 +156,14 @@ public void should_init_policies_with_up_or_unknown_nodes() { .put(node2.getConnectAddress(), node2) .put(node3.getConnectAddress(), node3) .build(); - Mockito.when(metadata.getNodes()).thenReturn(contactPointsMap2); + when(metadata.getNodes()).thenReturn(contactPointsMap2); // When wrapper.init(); // Then for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy) + verify(policy) .init( initNodesCaptor.capture(), any(DistanceReporter.class), @@ -175,16 +178,16 @@ public void should_propagate_distances_from_policies() { // Given wrapper.init(); ArgumentCaptor captor1 = ArgumentCaptor.forClass(DistanceReporter.class); - Mockito.verify(policy1).init(anyMap(), captor1.capture(), eq(contactPointsMap.keySet())); + verify(policy1).init(anyMap(), captor1.capture(), eq(contactPointsMap.keySet())); DistanceReporter distanceReporter1 = captor1.getValue(); ArgumentCaptor captor2 = ArgumentCaptor.forClass(DistanceReporter.class); - Mockito.verify(policy2).init(anyMap(), captor2.capture(), eq(contactPointsMap.keySet())); + verify(policy2).init(anyMap(), captor2.capture(), eq(contactPointsMap.keySet())); DistanceReporter distanceReporter2 = captor1.getValue(); ArgumentCaptor captor3 = ArgumentCaptor.forClass(DistanceReporter.class); - Mockito.verify(policy3).init(anyMap(), captor3.capture(), eq(contactPointsMap.keySet())); + verify(policy3).init(anyMap(), captor3.capture(), eq(contactPointsMap.keySet())); DistanceReporter distanceReporter3 = captor3.getValue(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); // When distanceReporter1.setDistance(node1, NodeDistance.REMOTE); @@ -222,7 +225,7 @@ public void should_not_propagate_node_states_to_policies_until_init() { // Then for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy, never()).onUp(node1); + verify(policy, never()).onUp(node1); } } @@ -236,7 +239,7 @@ public void should_propagate_node_states_to_policies_after_init() { // Then for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy).onUp(node1); + verify(policy).onUp(node1); } } @@ -254,7 +257,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted return null; }; for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.doAnswer(mockInit) + doAnswer(mockInit) .when(policy) .init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); } @@ -278,7 +281,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted // wait for init launch to signal that runnable is complete. initLatch.await(500, TimeUnit.MILLISECONDS); for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - Mockito.verify(policy).onDown(node1); + verify(policy).onDown(node1); } if (thread.isAlive()) { // thread still completing - sleep to allow thread to complete. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index d6ca3a9ea26..0dcae978494 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -18,7 +18,10 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -50,7 +53,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class MetadataManagerTest { @@ -77,23 +79,22 @@ public void setup() { MockitoAnnotations.initMocks(this); adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(context.getTopologyMonitor()).thenReturn(topologyMonitor); + when(context.getTopologyMonitor()).thenReturn(topologyMonitor); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW)) + when(defaultProfile.getDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW)) .thenReturn(Duration.ZERO); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)) - .thenReturn(1); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.getConfig()).thenReturn(config); + when(defaultProfile.getInt(DefaultDriverOption.METADATA_SCHEMA_MAX_EVENTS)).thenReturn(1); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); - Mockito.when(context.getEventBus()).thenReturn(eventBus); - Mockito.when(context.getSchemaQueriesFactory()).thenReturn(schemaQueriesFactory); - Mockito.when(context.getSchemaParserFactory()).thenReturn(schemaParserFactory); + when(context.getEventBus()).thenReturn(eventBus); + when(context.getSchemaQueriesFactory()).thenReturn(schemaQueriesFactory); + when(context.getSchemaParserFactory()).thenReturn(schemaParserFactory); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); metadataManager = new TestMetadataManager(context); } @@ -136,11 +137,10 @@ public void should_use_default_if_no_contact_points_provided() { @Test public void should_refresh_all_nodes() { // Given - NodeInfo info1 = Mockito.mock(NodeInfo.class); - NodeInfo info2 = Mockito.mock(NodeInfo.class); + NodeInfo info1 = mock(NodeInfo.class); + NodeInfo info2 = mock(NodeInfo.class); List infos = ImmutableList.of(info1, info2); - Mockito.when(topologyMonitor.refreshNodeList()) - .thenReturn(CompletableFuture.completedFuture(infos)); + when(topologyMonitor.refreshNodeList()).thenReturn(CompletableFuture.completedFuture(infos)); // When CompletionStage refreshNodesFuture = metadataManager.refreshNodes(); @@ -157,9 +157,9 @@ public void should_refresh_all_nodes() { public void should_refresh_single_node() { // Given Node node = new DefaultNode(ADDRESS1, context); - NodeInfo info = Mockito.mock(NodeInfo.class); - Mockito.when(info.getDatacenter()).thenReturn("dc1"); - Mockito.when(topologyMonitor.refreshNode(node)) + NodeInfo info = mock(NodeInfo.class); + when(info.getDatacenter()).thenReturn("dc1"); + when(topologyMonitor.refreshNode(node)) .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); // When @@ -168,15 +168,15 @@ public void should_refresh_single_node() { // Then // the info should have been copied to the node assertThatStage(refreshNodeFuture).isSuccess(); - Mockito.verify(info, timeout(500)).getDatacenter(); + verify(info, timeout(500)).getDatacenter(); assertThat(node.getDatacenter()).isEqualTo("dc1"); } @Test public void should_ignore_node_refresh_if_topology_monitor_does_not_have_info() { // Given - Node node = Mockito.mock(Node.class); - Mockito.when(topologyMonitor.refreshNode(node)) + Node node = mock(Node.class); + when(topologyMonitor.refreshNode(node)) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); // When @@ -189,9 +189,9 @@ public void should_ignore_node_refresh_if_topology_monitor_does_not_have_info() @Test public void should_add_node() { // Given - NodeInfo info = Mockito.mock(NodeInfo.class); - Mockito.when(info.getConnectAddress()).thenReturn(ADDRESS1); - Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + NodeInfo info = mock(NodeInfo.class); + when(info.getConnectAddress()).thenReturn(ADDRESS1); + when(topologyMonitor.getNewNodeInfo(ADDRESS1)) .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); // When @@ -207,10 +207,10 @@ public void should_add_node() { @Test public void should_not_add_node_if_connect_address_does_not_match() { // Given - NodeInfo info = Mockito.mock(NodeInfo.class); - Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + NodeInfo info = mock(NodeInfo.class); + when(topologyMonitor.getNewNodeInfo(ADDRESS1)) .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); - Mockito.when(info.getConnectAddress()) + when(info.getConnectAddress()) .thenReturn( ADDRESS2 // Does not match the address we got the info with ); @@ -226,7 +226,7 @@ public void should_not_add_node_if_connect_address_does_not_match() { @Test public void should_not_add_node_if_topology_monitor_does_not_have_info() { // Given - Mockito.when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + when(topologyMonitor.getNewNodeInfo(ADDRESS1)) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 58bd29020d4..8e52ad9c588 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -19,7 +19,10 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -48,7 +51,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class NodeStateManagerTest { @@ -70,21 +72,20 @@ public void setup() { MockitoAnnotations.initMocks(this); // Disable debouncing by default, tests that need it will override - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofSeconds(0)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) - .thenReturn(1); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.getConfig()).thenReturn(config); + when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)).thenReturn(1); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); - this.eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); + this.eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); adminEventLoopGroup = new DefaultEventLoopGroup(1, new BlockingOperation.SafeThreadFactory()); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); ImmutableMap nodes = @@ -92,11 +93,11 @@ public void setup() { .put(node1.getConnectAddress(), node1) .put(node2.getConnectAddress(), node2) .build(); - Mockito.when(metadata.getNodes()).thenReturn(nodes); - Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(metadataManager.refreshNode(any(Node.class))) + when(metadata.getNodes()).thenReturn(nodes); + when(metadataManager.getMetadata()).thenReturn(metadata); + when(metadataManager.refreshNode(any(Node.class))) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); + when(context.getMetadataManager()).thenReturn(metadataManager); } @After @@ -119,7 +120,7 @@ public void should_ignore_up_event_if_node_is_already_up_or_forced_down() { // Then assertThat(node1.state).isEqualTo(oldState); } - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -138,9 +139,9 @@ public void should_apply_up_event_if_node_is_unknown_or_down() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); if (oldState != NodeState.UNKNOWN) { - Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + verify(metadataManager, times(++i)).refreshNode(node1); } - Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); + verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -154,8 +155,8 @@ public void should_add_node_if_up_event_and_not_in_metadata() { waitForPendingAdminTasks(); // Then - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); - Mockito.verify(metadataManager).addNode(NEW_ADDRESS); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(metadataManager).addNode(NEW_ADDRESS); } @Test @@ -173,7 +174,7 @@ public void should_ignore_down_event_if_node_is_down_or_forced_down() { // Then assertThat(node1.state).isEqualTo(oldState); } - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -190,7 +191,7 @@ public void should_ignore_down_event_if_node_has_active_connections() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -208,7 +209,7 @@ public void should_apply_down_event_if_node_has_no_active_connections() { // Then assertThat(node1.state).isEqualTo(NodeState.DOWN); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.DOWN, node1)); + verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.DOWN, node1)); } } @@ -222,8 +223,8 @@ public void should_ignore_down_event_if_not_in_metadata() { waitForPendingAdminTasks(); // Then - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); - Mockito.verify(metadataManager, never()).addNode(NEW_ADDRESS); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(metadataManager, never()).addNode(NEW_ADDRESS); } @Test @@ -238,7 +239,7 @@ public void should_ignore_force_down_event_if_already_forced_down() { // Then assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -255,7 +256,7 @@ public void should_apply_force_down_event_over_any_other_state() { // Then assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.FORCED_DOWN, node1)); + verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.FORCED_DOWN, node1)); } } @@ -269,8 +270,8 @@ public void should_ignore_force_down_event_if_not_in_metadata() { waitForPendingAdminTasks(); // Then - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); - Mockito.verify(metadataManager, never()).addNode(NEW_ADDRESS); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(metadataManager, never()).addNode(NEW_ADDRESS); } @Test @@ -285,7 +286,7 @@ public void should_ignore_force_up_event_if_node_is_already_up() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -304,9 +305,9 @@ public void should_apply_force_up_event_if_node_is_not_up() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); + verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); if (oldState != NodeState.UNKNOWN) { - Mockito.verify(metadataManager, times(++i)).refreshNode(node1); + verify(metadataManager, times(++i)).refreshNode(node1); } } } @@ -321,8 +322,8 @@ public void should_add_node_if_force_up_and_not_in_metadata() { waitForPendingAdminTasks(); // Then - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); - Mockito.verify(metadataManager).addNode(NEW_ADDRESS); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(metadataManager).addNode(NEW_ADDRESS); } @Test @@ -336,7 +337,7 @@ public void should_notify_metadata_of_node_addition() { waitForPendingAdminTasks(); // Then - Mockito.verify(metadataManager).addNode(newAddress); + verify(metadataManager).addNode(newAddress); } @Test @@ -349,7 +350,7 @@ public void should_ignore_addition_of_existing_node() { waitForPendingAdminTasks(); // Then - Mockito.verify(metadataManager, never()).addNode(any(InetSocketAddress.class)); + verify(metadataManager, never()).addNode(any(InetSocketAddress.class)); } @Test @@ -362,7 +363,7 @@ public void should_notify_metadata_of_node_removal() { waitForPendingAdminTasks(); // Then - Mockito.verify(metadataManager).removeNode(node1.getConnectAddress()); + verify(metadataManager).removeNode(node1.getConnectAddress()); } @Test @@ -376,16 +377,15 @@ public void should_ignore_removal_of_nonexistent_node() { waitForPendingAdminTasks(); // Then - Mockito.verify(metadataManager, never()).removeNode(any(InetSocketAddress.class)); + verify(metadataManager, never()).removeNode(any(InetSocketAddress.class)); } @Test public void should_coalesce_topology_events() { // Given - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ofDays(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) - .thenReturn(5); + when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)).thenReturn(5); new NodeStateManager(context); node1.state = NodeState.FORCED_DOWN; node2.state = NodeState.DOWN; @@ -435,7 +435,7 @@ public void should_mark_node_up_if_down_or_unknown_and_connection_opened() { // Then assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); + verify(eventBus).fire(NodeStateEvent.changed(oldState, NodeState.UP, node1)); } } @@ -451,7 +451,7 @@ public void should_not_mark_node_up_if_forced_down_and_connection_opened() { // Then assertThat(node1.state).isEqualTo(NodeState.FORCED_DOWN); - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test @@ -482,7 +482,7 @@ public void should_mark_node_down_if_reconnection_starts_with_no_connections() { waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.DOWN); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); + verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); } @Test @@ -497,7 +497,7 @@ public void should_mark_node_down_if_no_connections_and_reconnection_already_sta waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.DOWN); - Mockito.verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); + verify(eventBus).fire(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, node1)); } @Test @@ -512,7 +512,7 @@ public void should_keep_node_up_if_reconnection_starts_with_some_connections() { waitForPendingAdminTasks(); assertThat(node1.state).isEqualTo(NodeState.UP); - Mockito.verify(eventBus, never()).fire(any(NodeStateEvent.class)); + verify(eventBus, never()).fire(any(NodeStateEvent.class)); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 863f462a8d2..8d1bec7d6f9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; @@ -26,7 +27,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -43,7 +43,7 @@ public class RemoveNodeRefreshTest { @Before public void setup() { - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); node1 = new DefaultNode(ADDRESS1, context); node2 = new DefaultNode(ADDRESS2, context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index 5230b39cfe5..05bb540d31e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -19,7 +19,11 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -51,7 +55,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -75,44 +78,41 @@ public class SchemaAgreementCheckerTest { @Before public void setup() { - Mockito.when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); - Mockito.when( - defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) .thenReturn(Duration.ofMillis(200)); - Mockito.when( - defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ofSeconds(10)); - Mockito.when(defaultConfig.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) + when(defaultConfig.getBoolean(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN)) .thenReturn(true); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultConfig); - Mockito.when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultConfig); + when(context.getConfig()).thenReturn(config); - addressTranslator = Mockito.spy(new PassThroughAddressTranslator(context)); - Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); + addressTranslator = spy(new PassThroughAddressTranslator(context)); + when(context.getAddressTranslator()).thenReturn(addressTranslator); Map nodes = ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2); - Mockito.when(metadata.getNodes()).thenReturn(nodes); - Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); + when(metadata.getNodes()).thenReturn(nodes); + when(metadataManager.getMetadata()).thenReturn(metadata); + when(context.getMetadataManager()).thenReturn(metadataManager); - Mockito.when(node2.getState()).thenReturn(NodeState.UP); + when(node2.getState()).thenReturn(NodeState.UP); - Mockito.when(eventLoop.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + when(eventLoop.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer( invocation -> { // Ignore delay and run immediately: Runnable task = invocation.getArgument(0); task.run(); return null; }); - Mockito.when(channel.eventLoop()).thenReturn(eventLoop); + when(channel.eventLoop()).thenReturn(eventLoop); } @Test public void should_skip_if_timeout_is_zero() { // Given - Mockito.when( - defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ZERO); TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); @@ -158,14 +158,14 @@ public void should_succeed_if_versions_match_on_first_try() { // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - Mockito.verify(addressTranslator).translate(ADDRESS2); + verify(addressTranslator).translate(ADDRESS2); } @Test public void should_ignore_down_peers() { // Given TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); - Mockito.when(node2.getState()).thenReturn(NodeState.DOWN); + when(node2.getState()).thenReturn(NodeState.DOWN); checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", @@ -179,7 +179,7 @@ public void should_ignore_down_peers() { // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - Mockito.verify(addressTranslator).translate(ADDRESS2); + verify(addressTranslator).translate(ADDRESS2); } @Test @@ -199,14 +199,14 @@ public void should_ignore_malformed_rows() { // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - Mockito.verify(addressTranslator, never()).translate(ADDRESS2); + verify(addressTranslator, never()).translate(ADDRESS2); } @Test public void should_use_peer_if_rpc_address_is_0_0_0_0() { // Given TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); - Mockito.when(node2.getState()).thenReturn(NodeState.DOWN); + when(node2.getState()).thenReturn(NodeState.DOWN); checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", @@ -222,7 +222,7 @@ public void should_use_peer_if_rpc_address_is_0_0_0_0() { // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - Mockito.verify(addressTranslator).translate(ADDRESS2); + verify(addressTranslator).translate(ADDRESS2); } @Test @@ -256,8 +256,7 @@ public void should_reschedule_if_versions_do_not_match_on_first_try() { @Test public void should_fail_if_versions_do_not_match_after_timeout() { // Given - Mockito.when( - defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT)) .thenReturn(Duration.ofNanos(10)); TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); checker.stubQueries( @@ -308,16 +307,16 @@ private StubbedQuery(String queryString, AdminResult result) { } private AdminRow mockRow(InetAddress peer, InetAddress rpcAddress, UUID uuid) { - AdminRow row = Mockito.mock(AdminRow.class); - Mockito.when(row.getInetAddress("peer")).thenReturn(peer); - Mockito.when(row.getInetAddress("rpc_address")).thenReturn(rpcAddress); - Mockito.when(row.getUuid("schema_version")).thenReturn(uuid); + AdminRow row = mock(AdminRow.class); + when(row.getInetAddress("peer")).thenReturn(peer); + when(row.getInetAddress("rpc_address")).thenReturn(rpcAddress); + when(row.getUuid("schema_version")).thenReturn(uuid); return row; } private AdminResult mockResult(AdminRow... rows) { - AdminResult result = Mockito.mock(AdminResult.class); - Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + AdminResult result = mock(AdminResult.class); + when(result.iterator()).thenReturn(Iterators.forArray(rows)); return result; } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java index a3355adff4b..14dfe6bfb4e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/AggregateParserTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata; @@ -29,7 +30,6 @@ import java.util.Optional; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; public class AggregateParserTest extends SchemaParserTestBase { @@ -57,8 +57,8 @@ public class AggregateParserTest extends SchemaParserTestBase { @Before public void setup() { - Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); - Mockito.when(context.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + when(context.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java index fa29c71dbf4..21ff579464d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeClassNameParserTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; @@ -30,7 +32,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -143,7 +144,7 @@ public void should_parse_user_type_when_definition_not_already_available() { @Test public void should_make_a_frozen_copy_user_type_when_definition_already_available() { - UserDefinedType existing = Mockito.mock(UserDefinedType.class); + UserDefinedType existing = mock(UserDefinedType.class); parse( "org.apache.cassandra.db.marshal.UserType(foo,70686f6e65," @@ -151,7 +152,7 @@ public void should_make_a_frozen_copy_user_type_when_definition_already_availabl + "6e756d626572:org.apache.cassandra.db.marshal.UTF8Type)", ImmutableMap.of(CqlIdentifier.fromInternal("phone"), existing)); - Mockito.verify(existing).copy(true); + verify(existing).copy(true); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java index fb1b0a3a832..eff6e1290bb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/DataTypeCqlNameParserTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; @@ -30,7 +32,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; public class DataTypeCqlNameParserTest { @@ -84,10 +85,10 @@ public void should_parse_top_level_user_type_as_shallow() { @Test public void should_reuse_existing_user_type_when_not_top_level() { - UserDefinedType addressType = Mockito.mock(UserDefinedType.class); - UserDefinedType frozenAddressType = Mockito.mock(UserDefinedType.class); - Mockito.when(addressType.copy(false)).thenReturn(addressType); - Mockito.when(addressType.copy(true)).thenReturn(frozenAddressType); + UserDefinedType addressType = mock(UserDefinedType.class); + UserDefinedType frozenAddressType = mock(UserDefinedType.class); + when(addressType.copy(false)).thenReturn(addressType); + when(addressType.copy(true)).thenReturn(frozenAddressType); ImmutableMap existingTypes = ImmutableMap.of(CqlIdentifier.fromInternal("address"), addressType); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java index 8f9e1bfd488..7e030628bf4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; @@ -30,7 +31,6 @@ import java.util.Map; import java.util.function.Consumer; import org.junit.Test; -import org.mockito.Mockito; public class SchemaParserTest extends SchemaParserTestBase { @@ -59,7 +59,7 @@ public void should_parse_legacy_keyspace_row() { @Test public void should_parse_keyspace_with_all_children() { // Needed to parse the aggregate - Mockito.when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); + when(context.getCodecRegistry()).thenReturn(new DefaultCodecRegistry("test")); SchemaRefresh refresh = (SchemaRefresh) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java index b2b9ab10ed4..9adce5643d9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/SchemaParserTestBase.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata.schema.parsing; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; @@ -27,7 +29,6 @@ import java.util.List; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.Silent.class) @@ -47,25 +48,25 @@ protected static AdminRow mockFunctionRow( String language, String returnType) { - AdminRow row = Mockito.mock(AdminRow.class); - - Mockito.when(row.contains("keyspace_name")).thenReturn(true); - Mockito.when(row.contains("function_name")).thenReturn(true); - Mockito.when(row.contains("argument_names")).thenReturn(true); - Mockito.when(row.contains("argument_types")).thenReturn(true); - Mockito.when(row.contains("body")).thenReturn(true); - Mockito.when(row.contains("called_on_null_input")).thenReturn(true); - Mockito.when(row.contains("language")).thenReturn(true); - Mockito.when(row.contains("return_type")).thenReturn(true); - - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); - Mockito.when(row.getString("function_name")).thenReturn(name); - Mockito.when(row.getListOfString("argument_names")).thenReturn(argumentNames); - Mockito.when(row.getListOfString("argument_types")).thenReturn(argumentTypes); - Mockito.when(row.getString("body")).thenReturn(body); - Mockito.when(row.getBoolean("called_on_null_input")).thenReturn(calledOnNullInput); - Mockito.when(row.getString("language")).thenReturn(language); - Mockito.when(row.getString("return_type")).thenReturn(returnType); + AdminRow row = mock(AdminRow.class); + + when(row.contains("keyspace_name")).thenReturn(true); + when(row.contains("function_name")).thenReturn(true); + when(row.contains("argument_names")).thenReturn(true); + when(row.contains("argument_types")).thenReturn(true); + when(row.contains("body")).thenReturn(true); + when(row.contains("called_on_null_input")).thenReturn(true); + when(row.contains("language")).thenReturn(true); + when(row.contains("return_type")).thenReturn(true); + + when(row.getString("keyspace_name")).thenReturn(keyspace); + when(row.getString("function_name")).thenReturn(name); + when(row.getListOfString("argument_names")).thenReturn(argumentNames); + when(row.getListOfString("argument_types")).thenReturn(argumentTypes); + when(row.getString("body")).thenReturn(body); + when(row.getBoolean("called_on_null_input")).thenReturn(calledOnNullInput); + when(row.getString("language")).thenReturn(language); + when(row.getString("return_type")).thenReturn(returnType); return row; } @@ -80,31 +81,31 @@ protected static AdminRow mockAggregateRow( String returnType, Object initCond) { - AdminRow row = Mockito.mock(AdminRow.class); - - Mockito.when(row.contains("keyspace_name")).thenReturn(true); - Mockito.when(row.contains("aggregate_name")).thenReturn(true); - Mockito.when(row.contains("argument_types")).thenReturn(true); - Mockito.when(row.contains("state_func")).thenReturn(true); - Mockito.when(row.contains("state_type")).thenReturn(true); - Mockito.when(row.contains("final_func")).thenReturn(true); - Mockito.when(row.contains("return_type")).thenReturn(true); - Mockito.when(row.contains("initcond")).thenReturn(true); - - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); - Mockito.when(row.getString("aggregate_name")).thenReturn(name); - Mockito.when(row.getListOfString("argument_types")).thenReturn(argumentTypes); - Mockito.when(row.getString("state_func")).thenReturn(stateFunc); - Mockito.when(row.getString("state_type")).thenReturn(stateType); - Mockito.when(row.getString("final_func")).thenReturn(finalFunc); - Mockito.when(row.getString("return_type")).thenReturn(returnType); + AdminRow row = mock(AdminRow.class); + + when(row.contains("keyspace_name")).thenReturn(true); + when(row.contains("aggregate_name")).thenReturn(true); + when(row.contains("argument_types")).thenReturn(true); + when(row.contains("state_func")).thenReturn(true); + when(row.contains("state_type")).thenReturn(true); + when(row.contains("final_func")).thenReturn(true); + when(row.contains("return_type")).thenReturn(true); + when(row.contains("initcond")).thenReturn(true); + + when(row.getString("keyspace_name")).thenReturn(keyspace); + when(row.getString("aggregate_name")).thenReturn(name); + when(row.getListOfString("argument_types")).thenReturn(argumentTypes); + when(row.getString("state_func")).thenReturn(stateFunc); + when(row.getString("state_type")).thenReturn(stateType); + when(row.getString("final_func")).thenReturn(finalFunc); + when(row.getString("return_type")).thenReturn(returnType); if (initCond instanceof ByteBuffer) { - Mockito.when(row.isString("initcond")).thenReturn(false); - Mockito.when(row.getByteBuffer("initcond")).thenReturn(((ByteBuffer) initCond)); + when(row.isString("initcond")).thenReturn(false); + when(row.getByteBuffer("initcond")).thenReturn(((ByteBuffer) initCond)); } else if (initCond instanceof String) { - Mockito.when(row.isString("initcond")).thenReturn(true); - Mockito.when(row.getString("initcond")).thenReturn(((String) initCond)); + when(row.isString("initcond")).thenReturn(true); + when(row.getString("initcond")).thenReturn(((String) initCond)); } else { fail("Unsupported initcond type" + initCond.getClass()); } @@ -114,32 +115,31 @@ protected static AdminRow mockAggregateRow( protected static AdminRow mockTypeRow( String keyspace, String name, List fieldNames, List fieldTypes) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); - Mockito.when(row.getString("type_name")).thenReturn(name); - Mockito.when(row.getListOfString("field_names")).thenReturn(fieldNames); - Mockito.when(row.getListOfString("field_types")).thenReturn(fieldTypes); + when(row.getString("keyspace_name")).thenReturn(keyspace); + when(row.getString("type_name")).thenReturn(name); + when(row.getListOfString("field_names")).thenReturn(fieldNames); + when(row.getListOfString("field_types")).thenReturn(fieldTypes); return row; } protected static AdminRow mockLegacyTableRow(String keyspace, String name, String comparator) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.contains("table_name")).thenReturn(false); + when(row.contains("table_name")).thenReturn(false); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); - Mockito.when(row.getString("columnfamily_name")).thenReturn(name); - Mockito.when(row.getBoolean("is_dense")).thenReturn(false); - Mockito.when(row.getString("comparator")).thenReturn(comparator); - Mockito.when(row.isString("caching")).thenReturn(true); - Mockito.when(row.getString("caching")) + when(row.getString("keyspace_name")).thenReturn(keyspace); + when(row.getString("columnfamily_name")).thenReturn(name); + when(row.getBoolean("is_dense")).thenReturn(false); + when(row.getString("comparator")).thenReturn(comparator); + when(row.isString("caching")).thenReturn(true); + when(row.getString("caching")) .thenReturn("{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}"); - Mockito.when(row.getString("compaction_strategy_class")) + when(row.getString("compaction_strategy_class")) .thenReturn("org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"); - Mockito.when(row.getString("compaction_strategy_options")) - .thenReturn("{\"mock_option\":\"1\"}"); + when(row.getString("compaction_strategy_options")).thenReturn("{\"mock_option\":\"1\"}"); return row; } @@ -165,36 +165,36 @@ protected static AdminRow mockLegacyColumnRow( String indexName, String indexType, String indexOptions) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.contains("validator")).thenReturn(true); + when(row.contains("validator")).thenReturn(true); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getString("columnfamily_name")).thenReturn(tableName); - Mockito.when(row.getString("column_name")).thenReturn(name); - Mockito.when(row.getString("type")).thenReturn(kind); - Mockito.when(row.getString("validator")).thenReturn(dataType); - Mockito.when(row.getInteger("component_index")).thenReturn(position); - Mockito.when(row.getString("index_name")).thenReturn(indexName); - Mockito.when(row.getString("index_type")).thenReturn(indexType); - Mockito.when(row.getString("index_options")).thenReturn(indexOptions); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getString("columnfamily_name")).thenReturn(tableName); + when(row.getString("column_name")).thenReturn(name); + when(row.getString("type")).thenReturn(kind); + when(row.getString("validator")).thenReturn(dataType); + when(row.getInteger("component_index")).thenReturn(position); + when(row.getString("index_name")).thenReturn(indexName); + when(row.getString("index_type")).thenReturn(indexType); + when(row.getString("index_options")).thenReturn(indexOptions); return row; } protected static AdminRow mockModernTableRow(String keyspace, String name) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.contains("flags")).thenReturn(true); - Mockito.when(row.contains("table_name")).thenReturn(true); + when(row.contains("flags")).thenReturn(true); + when(row.contains("table_name")).thenReturn(true); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspace); - Mockito.when(row.getString("table_name")).thenReturn(name); - Mockito.when(row.getSetOfString("flags")).thenReturn(ImmutableSet.of("compound")); - Mockito.when(row.isString("caching")).thenReturn(false); - Mockito.when(row.get("caching", RelationParser.MAP_OF_TEXT_TO_TEXT)) + when(row.getString("keyspace_name")).thenReturn(keyspace); + when(row.getString("table_name")).thenReturn(name); + when(row.getSetOfString("flags")).thenReturn(ImmutableSet.of("compound")); + when(row.isString("caching")).thenReturn(false); + when(row.get("caching", RelationParser.MAP_OF_TEXT_TO_TEXT)) .thenReturn(ImmutableMap.of("keys", "ALL", "rows_per_partition", "NONE")); - Mockito.when(row.get("compaction", RelationParser.MAP_OF_TEXT_TO_TEXT)) + when(row.get("compaction", RelationParser.MAP_OF_TEXT_TO_TEXT)) .thenReturn( ImmutableMap.of( "class", @@ -213,19 +213,19 @@ protected static AdminRow mockModernColumnRow( String dataType, String clusteringOrder, Integer position) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.contains("kind")).thenReturn(true); - Mockito.when(row.contains("position")).thenReturn(true); - Mockito.when(row.contains("clustering_order")).thenReturn(true); + when(row.contains("kind")).thenReturn(true); + when(row.contains("position")).thenReturn(true); + when(row.contains("clustering_order")).thenReturn(true); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getString("table_name")).thenReturn(tableName); - Mockito.when(row.getString("column_name")).thenReturn(name); - Mockito.when(row.getString("kind")).thenReturn(kind); - Mockito.when(row.getString("type")).thenReturn(dataType); - Mockito.when(row.getInteger("position")).thenReturn(position); - Mockito.when(row.getString("clustering_order")).thenReturn(clusteringOrder); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getString("table_name")).thenReturn(tableName); + when(row.getString("column_name")).thenReturn(name); + when(row.getString("kind")).thenReturn(kind); + when(row.getString("type")).thenReturn(dataType); + when(row.getInteger("position")).thenReturn(position); + when(row.getString("clustering_order")).thenReturn(clusteringOrder); return row; } @@ -236,13 +236,13 @@ protected static AdminRow mockIndexRow( String name, String kind, ImmutableMap options) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getString("table_name")).thenReturn(tableName); - Mockito.when(row.getString("index_name")).thenReturn(name); - Mockito.when(row.getString("kind")).thenReturn(kind); - Mockito.when(row.getMapOfStringToString("options")).thenReturn(options); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getString("table_name")).thenReturn(tableName); + when(row.getString("index_name")).thenReturn(name); + when(row.getString("kind")).thenReturn(kind); + when(row.getMapOfStringToString("options")).thenReturn(options); return row; } @@ -253,25 +253,25 @@ protected static AdminRow mockViewRow( String baseTableName, boolean includeAllColumns, String whereClause) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getString("view_name")).thenReturn(viewName); - Mockito.when(row.getString("base_table_name")).thenReturn(baseTableName); - Mockito.when(row.getBoolean("include_all_columns")).thenReturn(includeAllColumns); - Mockito.when(row.getString("where_clause")).thenReturn(whereClause); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getString("view_name")).thenReturn(viewName); + when(row.getString("base_table_name")).thenReturn(baseTableName); + when(row.getBoolean("include_all_columns")).thenReturn(includeAllColumns); + when(row.getString("where_clause")).thenReturn(whereClause); return row; } protected static AdminRow mockModernKeyspaceRow(String keyspaceName) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getBoolean("durable_writes")).thenReturn(true); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getBoolean("durable_writes")).thenReturn(true); - Mockito.when(row.contains("strategy_class")).thenReturn(false); - Mockito.when(row.getMapOfStringToString("replication")) + when(row.contains("strategy_class")).thenReturn(false); + when(row.getMapOfStringToString("replication")) .thenReturn( ImmutableMap.of( "class", "org.apache.cassandra.locator.SimpleStrategy", "replication_factor", "1")); @@ -280,15 +280,14 @@ protected static AdminRow mockModernKeyspaceRow(String keyspaceName) { } protected static AdminRow mockLegacyKeyspaceRow(String keyspaceName) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); - Mockito.when(row.getString("keyspace_name")).thenReturn(keyspaceName); - Mockito.when(row.getBoolean("durable_writes")).thenReturn(true); + when(row.getString("keyspace_name")).thenReturn(keyspaceName); + when(row.getBoolean("durable_writes")).thenReturn(true); - Mockito.when(row.contains("strategy_class")).thenReturn(true); - Mockito.when(row.getString("strategy_class")) - .thenReturn("org.apache.cassandra.locator.SimpleStrategy"); - Mockito.when(row.getString("strategy_options")).thenReturn("{\"replication_factor\":\"1\"}"); + when(row.contains("strategy_class")).thenReturn(true); + when(row.getString("strategy_class")).thenReturn("org.apache.cassandra.locator.SimpleStrategy"); + when(row.getString("strategy_options")).thenReturn("{\"replication_factor\":\"1\"}"); return row; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java index 87758c85f41..9fbfa0e7349 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra21SchemaQueriesTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -29,7 +30,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.LinkedBlockingDeque; import org.junit.Test; -import org.mockito.Mockito; // Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is // shared, this class just validates the query strings. @@ -37,9 +37,8 @@ public class Cassandra21SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when( - config.getStringList( - DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + when(config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) .thenReturn(Collections.emptyList()); SchemaQueriesWithMockedChannel queries = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java index 5337503b4d1..7fd37d2541a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra22SchemaQueriesTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -29,7 +30,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.LinkedBlockingDeque; import org.junit.Test; -import org.mockito.Mockito; // Note: we don't repeat the other tests in Cassandra3SchemaQueriesTest because the logic is // shared, this class just validates the query strings. @@ -37,9 +37,8 @@ public class Cassandra22SchemaQueriesTest extends SchemaQueriesTest { @Test public void should_query() { - Mockito.when( - config.getStringList( - DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + when(config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) .thenReturn(Collections.emptyList()); SchemaQueriesWithMockedChannel queries = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java index 22258d75fd9..e2792935378 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/Cassandra3SchemaQueriesTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -31,7 +32,6 @@ import java.util.concurrent.LinkedBlockingDeque; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; public class Cassandra3SchemaQueriesTest extends SchemaQueriesTest { @@ -41,9 +41,8 @@ public void setup() { super.setup(); // By default, no keyspace filter - Mockito.when( - config.getStringList( - DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + when(config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) .thenReturn(Collections.emptyList()); } @@ -54,9 +53,8 @@ public void should_query_without_keyspace_filter() { @Test public void should_query_with_keyspace_filter() { - Mockito.when( - config.getStringList( - DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) + when(config.getStringList( + DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES, Collections.emptyList())) .thenReturn(ImmutableList.of("ks1", "ks2")); should_query_with_where_clause(" WHERE keyspace_name in ('ks1','ks2')"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java index df632be39d9..dd309ffac37 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/SchemaQueriesTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata.schema.queries; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -31,7 +33,6 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -50,21 +51,20 @@ public abstract class SchemaQueriesTest { @Before public void setup() { // Whatever, not actually used because the requests are mocked - Mockito.when(config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT)) + when(config.getDuration(DefaultDriverOption.METADATA_SCHEMA_REQUEST_TIMEOUT)) .thenReturn(Duration.ZERO); - Mockito.when(config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE)) - .thenReturn(5000); + when(config.getInt(DefaultDriverOption.METADATA_SCHEMA_REQUEST_PAGE_SIZE)).thenReturn(5000); channel = new EmbeddedChannel(); - driverChannel = Mockito.mock(DriverChannel.class); - Mockito.when(driverChannel.eventLoop()).thenReturn(channel.eventLoop()); + driverChannel = mock(DriverChannel.class); + when(driverChannel.eventLoop()).thenReturn(channel.eventLoop()); } protected static AdminRow mockRow(String... values) { - AdminRow row = Mockito.mock(AdminRow.class); + AdminRow row = mock(AdminRow.class); assertThat(values.length % 2).as("Expecting an even number of parameters").isZero(); for (int i = 0; i < values.length / 2; i++) { - Mockito.when(row.getString(values[i * 2])).thenReturn(values[i * 2 + 1]); + when(row.getString(values[i * 2])).thenReturn(values[i * 2 + 1]); } return row; } @@ -74,14 +74,14 @@ protected static AdminResult mockResult(AdminRow... rows) { } protected static AdminResult mockResult(AdminResult next, AdminRow... rows) { - AdminResult result = Mockito.mock(AdminResult.class); + AdminResult result = mock(AdminResult.class); if (next == null) { - Mockito.when(result.hasNextPage()).thenReturn(false); + when(result.hasNextPage()).thenReturn(false); } else { - Mockito.when(result.hasNextPage()).thenReturn(true); - Mockito.when(result.nextPage()).thenReturn(CompletableFuture.completedFuture(next)); + when(result.hasNextPage()).thenReturn(true); + when(result.nextPage()).thenReturn(CompletableFuture.completedFuture(next)); } - Mockito.when(result.iterator()).thenReturn(Iterators.forArray(rows)); + when(result.iterator()).thenReturn(Iterators.forArray(rows)); return result; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java index 4ebc60cce2b..fc4a8a3a7e5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMapTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.metadata.token; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -37,7 +39,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -357,17 +358,17 @@ public void should_refresh_when_updated_keyspace_with_different_replication() { } private DefaultNode mockNode(String dc, String rack, Set tokens) { - DefaultNode node = Mockito.mock(DefaultNode.class); - Mockito.when(node.getDatacenter()).thenReturn(dc); - Mockito.when(node.getRack()).thenReturn(rack); - Mockito.when(node.getRawTokens()).thenReturn(tokens); + DefaultNode node = mock(DefaultNode.class); + when(node.getDatacenter()).thenReturn(dc); + when(node.getRack()).thenReturn(rack); + when(node.getRawTokens()).thenReturn(tokens); return node; } private KeyspaceMetadata mockKeyspace(CqlIdentifier name, Map replicationConfig) { - KeyspaceMetadata keyspace = Mockito.mock(KeyspaceMetadata.class); - Mockito.when(keyspace.getName()).thenReturn(name); - Mockito.when(keyspace.getReplication()).thenReturn(replicationConfig); + KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class); + when(keyspace.getName()).thenReturn(name); + when(keyspace.getReplication()).thenReturn(replicationConfig); return keyspace; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java index 6fff4a6909f..01627628609 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/token/NetworkTopologyReplicationStrategyTest.java @@ -19,6 +19,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -37,7 +40,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.LoggerFactory; @@ -659,7 +661,7 @@ public void should_abort_early_and_log_when_bad_replication_factor_cannot_be_met // Then // No logs: - Mockito.verify(appender, never()).doAppend(any(ILoggingEvent.class)); + verify(appender, never()).doAppend(any(ILoggingEvent.class)); // When int traversedTokensForInvalidSettings = @@ -667,7 +669,7 @@ public void should_abort_early_and_log_when_bad_replication_factor_cannot_be_met // Did not take more steps than the valid settings assertThat(traversedTokensForInvalidSettings).isEqualTo(traversedTokensForValidSettings); // Did log: - Mockito.verify(appender).doAppend(loggingEventCaptor.capture()); + verify(appender).doAppend(loggingEventCaptor.capture()); ILoggingEvent log = loggingEventCaptor.getValue(); assertThat(log.getLevel()).isEqualTo(Level.WARN); assertThat(log.getMessage()).contains("could not achieve replication factor"); @@ -682,8 +684,8 @@ private int countTraversedTokens( Map tokenToPrimary, ImmutableMap replicationConfig) { AtomicInteger count = new AtomicInteger(); - List ringSpy = Mockito.spy(ring); - Mockito.when(ringSpy.get(anyInt())) + List ringSpy = spy(ring); + when(ringSpy.get(anyInt())) .thenAnswer( invocation -> { count.incrementAndGet(); @@ -695,7 +697,7 @@ private int countTraversedTokens( } private void locate(Node node, String dc, String rack) { - Mockito.when(node.getDatacenter()).thenReturn(dc); - Mockito.when(node.getRack()).thenReturn(rack); + when(node.getDatacenter()).thenReturn(dc); + when(node.getRack()).thenReturn(rack); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index 6b0b11db9f1..d59d9660828 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -17,8 +17,11 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -34,14 +37,12 @@ import java.util.concurrent.CompletionStage; import org.junit.Test; import org.mockito.InOrder; -import org.mockito.Mockito; public class ChannelPoolInitTest extends ChannelPoolTestBase { @Test public void should_initialize_when_all_channels_succeed() throws Exception { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -61,15 +62,14 @@ public void should_initialize_when_all_channels_succeed() throws Exception { assertThatStage(poolFuture) .isSuccess(pool -> assertThat(pool.channels).containsOnly(channel1, channel2, channel3)); - Mockito.verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); + verify(eventBus, times(3)).fire(ChannelEvent.channelOpened(node)); factoryHelper.verifyNoMoreCalls(); } @Test public void should_initialize_when_all_channels_fail() throws Exception { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -85,8 +85,8 @@ public void should_initialize_when_all_channels_fail() throws Exception { waitForPendingAdminTasks(); assertThatStage(poolFuture).isSuccess(pool -> assertThat(pool.channels).isEmpty()); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); - Mockito.verify(nodeMetricUpdater, times(3)) + verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + verify(nodeMetricUpdater, times(3)) .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); factoryHelper.verifyNoMoreCalls(); @@ -94,8 +94,7 @@ public void should_initialize_when_all_channels_fail() throws Exception { @Test public void should_indicate_when_keyspace_failed_on_all_channels() { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) @@ -113,15 +112,14 @@ public void should_indicate_when_keyspace_failed_on_all_channels() { .isSuccess( pool -> { assertThat(pool.isInvalidKeyspace()).isTrue(); - Mockito.verify(nodeMetricUpdater, times(3)) + verify(nodeMetricUpdater, times(3)) .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); }); } @Test public void should_fire_force_down_event_when_cluster_name_does_not_match() throws Exception { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); ClusterNameMismatchException error = new ClusterNameMismatchException(ADDRESS, "actual", "expected"); @@ -137,10 +135,10 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); - Mockito.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); + verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); + verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); - Mockito.verify(nodeMetricUpdater, times(3)) + verify(nodeMetricUpdater, times(3)) .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); factoryHelper.verifyNoMoreCalls(); } @@ -148,10 +146,9 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro @Test public void should_reconnect_when_init_incomplete() throws Exception { // Short delay so we don't have to wait in the test - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -164,7 +161,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { // 1st reconnection .pending(node, channel2Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -178,7 +175,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); // A reconnection should have been scheduled - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); channel2Future.complete(channel2); @@ -189,8 +186,7 @@ public void should_reconnect_when_init_incomplete() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); - Mockito.verify(nodeMetricUpdater) - .incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); + verify(nodeMetricUpdater).incrementCounter(DefaultNodeMetric.CONNECTION_INIT_ERRORS, null); factoryHelper.verifyNoMoreCalls(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java index d7a48d3aedf..a5a6e33c821 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolKeyspaceTest.java @@ -17,6 +17,8 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -28,14 +30,12 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.junit.Test; -import org.mockito.Mockito; public class ChannelPoolKeyspaceTest extends ChannelPoolTestBase { @Test public void should_switch_keyspace_on_existing_channels() throws Exception { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -59,8 +59,8 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { CompletionStage setKeyspaceFuture = pool.setKeyspace(newKeyspace); waitForPendingAdminTasks(); - Mockito.verify(channel1).setKeyspace(newKeyspace); - Mockito.verify(channel2).setKeyspace(newKeyspace); + verify(channel1).setKeyspace(newKeyspace); + verify(channel2).setKeyspace(newKeyspace); assertThatStage(setKeyspaceFuture).isSuccess(); @@ -69,10 +69,9 @@ public void should_switch_keyspace_on_existing_channels() throws Exception { @Test public void should_switch_keyspace_on_pending_channels() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); CompletableFuture channel1Future = new CompletableFuture<>(); @@ -98,8 +97,8 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { ChannelPool pool = poolFuture.toCompletableFuture().get(); // Check that reconnection has kicked in, but do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); + verify(reconnectionSchedule).nextDelay(); + verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(node, 2); // Switch keyspace, it succeeds immediately since there is no active channel @@ -113,9 +112,9 @@ public void should_switch_keyspace_on_pending_channels() throws Exception { channel2Future.complete(channel2); waitForPendingAdminTasks(); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); - Mockito.verify(channel1).setKeyspace(newKeyspace); - Mockito.verify(channel2).setKeyspace(newKeyspace); + verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); + verify(channel1).setKeyspace(newKeyspace); + verify(channel2).setKeyspace(newKeyspace); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java index d5648fc4d66..a932bfb4bea 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolReconnectTest.java @@ -18,8 +18,11 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -33,16 +36,14 @@ import java.util.concurrent.ExecutionException; import org.junit.Test; import org.mockito.InOrder; -import org.mockito.Mockito; public class ChannelPoolReconnectTest extends ChannelPoolTestBase { @Test public void should_reconnect_when_channel_closes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -56,7 +57,7 @@ public void should_reconnect_when_channel_closes() throws Exception { // reconnection .pending(node, channel3Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -75,14 +76,14 @@ public void should_reconnect_when_channel_closes() throws Exception { waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCall(node); channel3Future.complete(channel3); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); + verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel3); @@ -91,10 +92,9 @@ public void should_reconnect_when_channel_closes() throws Exception { @Test public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -108,7 +108,7 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce // reconnection .pending(node, channel3Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -126,14 +126,14 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCall(node); channel3Future.complete(channel3); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); + verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel1, channel3); @@ -143,10 +143,9 @@ public void should_reconnect_when_channel_starts_graceful_shutdown() throws Exce @Test public void should_let_current_attempt_complete_when_reconnecting_now() throws ExecutionException, InterruptedException { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(1); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(1); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -159,7 +158,7 @@ public void should_let_current_attempt_complete_when_reconnecting_now() .pending(node, channel2Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); // Initial connection CompletionStage poolFuture = @@ -176,7 +175,7 @@ public void should_let_current_attempt_complete_when_reconnecting_now() waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelClosed(node)); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(node, 1); // Force a reconnection, should not try to create a new channel since we have a pending one @@ -189,7 +188,7 @@ public void should_let_current_attempt_complete_when_reconnecting_now() channel2Future.complete(channel2); waitForPendingAdminTasks(); inOrder.verify(eventBus).fire(ChannelEvent.channelOpened(node)); - Mockito.verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); + verify(eventBus).fire(ChannelEvent.reconnectionStopped(node)); assertThat(pool.channels).containsOnly(channel2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java index 686bd947754..57e5cf145eb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolResizeTest.java @@ -17,8 +17,11 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -31,16 +34,13 @@ import java.util.concurrent.CompletionStage; import org.junit.Test; import org.mockito.InOrder; -import org.mockito.Mockito; public class ChannelPoolResizeTest extends ChannelPoolTestBase { @Test public void should_shrink_outside_of_reconnection() throws Exception { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) - .thenReturn(4); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -53,7 +53,7 @@ public void should_shrink_outside_of_reconnection() throws Exception { .success(node, channel3) .success(node, channel4) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.REMOTE, context, "test"); @@ -78,12 +78,10 @@ public void should_shrink_outside_of_reconnection() throws Exception { @Test public void should_shrink_during_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) - .thenReturn(4); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -102,7 +100,7 @@ public void should_shrink_during_reconnection() throws Exception { .pending(node, channel3Future) .pending(node, channel4Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.REMOTE, context, "test"); @@ -116,7 +114,7 @@ public void should_shrink_during_reconnection() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); // A reconnection should have been scheduled to add the missing channels, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); pool.resize(NodeDistance.LOCAL); @@ -141,12 +139,10 @@ public void should_shrink_during_reconnection() throws Exception { @Test public void should_grow_outside_of_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) - .thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -161,7 +157,7 @@ public void should_grow_outside_of_reconnection() throws Exception { .success(node, channel3) .success(node, channel4) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -178,7 +174,7 @@ public void should_grow_outside_of_reconnection() throws Exception { waitForPendingAdminTasks(); // The resizing should have triggered a reconnection - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(node, 2); @@ -193,12 +189,10 @@ public void should_grow_outside_of_reconnection() throws Exception { @Test public void should_grow_during_reconnection() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) - .thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(4); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -218,7 +212,7 @@ public void should_grow_during_reconnection() throws Exception { .pending(node, channel3Future) .pending(node, channel4Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -232,7 +226,7 @@ public void should_grow_during_reconnection() throws Exception { assertThat(pool.channels).containsOnly(channel1); // A reconnection should have been scheduled to add the missing channel, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); pool.resize(NodeDistance.REMOTE); @@ -248,7 +242,7 @@ public void should_grow_during_reconnection() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); // A second attempt should have been scheduled since we're now still under the target size - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + verify(reconnectionSchedule, times(2)).nextDelay(); // Same reconnection is still running, no additional events inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(node)); inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(node)); @@ -268,10 +262,9 @@ public void should_grow_during_reconnection() throws Exception { @Test public void should_resize_outside_of_reconnection_if_config_changes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -286,7 +279,7 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc .success(node, channel3) .success(node, channel4) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -300,13 +293,12 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc assertThat(pool.channels).containsOnly(channel1, channel2); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); // It should have triggered a reconnection - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); factoryHelper.waitForCalls(node, 2); @@ -321,10 +313,9 @@ public void should_resize_outside_of_reconnection_if_config_changes() throws Exc @Test public void should_resize_during_reconnection_if_config_changes() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -344,7 +335,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti .pending(node, channel3Future) .pending(node, channel4Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -358,12 +349,11 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti assertThat(pool.channels).containsOnly(channel1); // A reconnection should have been scheduled to add the missing channel, don't complete yet - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); inOrder.verify(eventBus).fire(ChannelEvent.reconnectionStarted(node)); // Simulate a configuration change - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(4); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(4); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); @@ -376,7 +366,7 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti assertThat(pool.channels).containsOnly(channel1, channel2); // A second attempt should have been scheduled since we're now still under the target size - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + verify(reconnectionSchedule, times(2)).nextDelay(); // Same reconnection is still running, no additional events inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStopped(node)); inOrder.verify(eventBus, never()).fire(ChannelEvent.reconnectionStarted(node)); @@ -396,10 +386,9 @@ public void should_resize_during_reconnection_if_config_changes() throws Excepti @Test public void should_ignore_config_change_if_not_relevant() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(2); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(2); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -408,7 +397,7 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { .success(node, channel1) .success(node, channel2) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -422,13 +411,12 @@ public void should_ignore_config_change_if_not_relevant() throws Exception { assertThat(pool.channels).containsOnly(channel1, channel2); // Config changes, but not for our distance - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)) - .thenReturn(1); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE)).thenReturn(1); eventBus.fire(ConfigChangeEvent.INSTANCE); waitForPendingAdminTasks(); // It should not have triggered a reconnection - Mockito.verify(reconnectionSchedule, never()).nextDelay(); + verify(reconnectionSchedule, never()).nextDelay(); factoryHelper.verifyNoMoreCalls(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java index 5f087bac3a5..3efb2147247 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolShutdownTest.java @@ -16,8 +16,11 @@ package com.datastax.oss.driver.internal.core.pool; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -30,16 +33,14 @@ import java.util.concurrent.CompletionStage; import org.junit.Test; import org.mockito.InOrder; -import org.mockito.Mockito; public class ChannelPoolShutdownTest extends ChannelPoolTestBase { @Test public void should_close_all_channels_when_closed() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -55,7 +56,7 @@ public void should_close_all_channels_when_closed() throws Exception { // reconnection .pending(node, channel4Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -73,25 +74,25 @@ public void should_close_all_channels_when_closed() throws Exception { inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(node)); // Reconnection should have kicked in and started to open channel4, do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(node, 1); CompletionStage closeFuture = pool.closeAsync(); waitForPendingAdminTasks(); // The two original channels were closed normally - Mockito.verify(channel1).close(); - Mockito.verify(channel2).close(); + verify(channel1).close(); + verify(channel2).close(); inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); // The closing channel was not closed again - Mockito.verify(channel3, never()).close(); + verify(channel3, never()).close(); // Complete the reconnecting channel channel4Future.complete(channel4); waitForPendingAdminTasks(); // It should be force-closed once we find out the pool was closed - Mockito.verify(channel4).forceClose(); + verify(channel4).forceClose(); // No events because the channel was never really associated to the pool inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node)); @@ -108,10 +109,9 @@ public void should_close_all_channels_when_closed() throws Exception { @Test public void should_force_close_all_channels_when_force_closed() throws Exception { - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); DriverChannel channel1 = newMockDriverChannel(1); DriverChannel channel2 = newMockDriverChannel(2); @@ -127,7 +127,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception // reconnection .pending(node, channel4Future) .build(); - InOrder inOrder = Mockito.inOrder(eventBus); + InOrder inOrder = inOrder(eventBus); CompletionStage poolFuture = ChannelPool.init(node, null, NodeDistance.LOCAL, context, "test"); @@ -145,16 +145,16 @@ public void should_force_close_all_channels_when_force_closed() throws Exception inOrder.verify(eventBus, times(1)).fire(ChannelEvent.channelClosed(node)); // Reconnection should have kicked in and started to open a channel, do not complete it yet - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); factoryHelper.waitForCalls(node, 1); CompletionStage closeFuture = pool.forceCloseAsync(); waitForPendingAdminTasks(); // The three original channels were force-closed - Mockito.verify(channel1).forceClose(); - Mockito.verify(channel2).forceClose(); - Mockito.verify(channel3).forceClose(); + verify(channel1).forceClose(); + verify(channel2).forceClose(); + verify(channel3).forceClose(); // Only two events because the one for channel3 was sent earlier inOrder.verify(eventBus, times(2)).fire(ChannelEvent.channelClosed(node)); @@ -163,7 +163,7 @@ public void should_force_close_all_channels_when_force_closed() throws Exception waitForPendingAdminTasks(); // It should be force-closed once we find out the pool was closed - Mockito.verify(channel4).forceClose(); + verify(channel4).forceClose(); // No events because the channel was never really associated to the pool inOrder.verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); inOrder.verify(eventBus, never()).fire(ChannelEvent.channelClosed(node)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 9593d60cc7b..0f569d1b00b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -17,6 +17,9 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.config.DriverConfig; @@ -44,7 +47,6 @@ import org.junit.After; import org.junit.Before; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; abstract class ChannelPoolTestBase { @@ -69,23 +71,22 @@ public void setup() { adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - this.eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); - Mockito.when(context.getChannelFactory()).thenReturn(channelFactory); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + this.eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); + when(context.getChannelFactory()).thenReturn(channelFactory); - Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(reconnectionPolicy.newNodeSchedule(any(Node.class))) - .thenReturn(reconnectionSchedule); + when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); + when(reconnectionPolicy.newNodeSchedule(any(Node.class))).thenReturn(reconnectionSchedule); // By default, set a large reconnection delay. Tests that care about reconnection will override // it. - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - Mockito.when(node.getConnectAddress()).thenReturn(ADDRESS); - Mockito.when(node.getMetricUpdater()).thenReturn(nodeMetricUpdater); + when(node.getConnectAddress()).thenReturn(ADDRESS); + when(node.getMetricUpdater()).thenReturn(nodeMetricUpdater); } @After @@ -94,18 +95,18 @@ public void teardown() { } DriverChannel newMockDriverChannel(int id) { - DriverChannel driverChannel = Mockito.mock(DriverChannel.class); + DriverChannel driverChannel = mock(DriverChannel.class); EventLoop adminExecutor = adminEventLoopGroup.next(); - Channel channel = Mockito.mock(Channel.class); + Channel channel = mock(Channel.class); DefaultChannelPromise closeFuture = new DefaultChannelPromise(channel, adminExecutor); DefaultChannelPromise closeStartedFuture = new DefaultChannelPromise(channel, adminExecutor); - Mockito.when(driverChannel.close()).thenReturn(closeFuture); - Mockito.when(driverChannel.forceClose()).thenReturn(closeFuture); - Mockito.when(driverChannel.closeFuture()).thenReturn(closeFuture); - Mockito.when(driverChannel.closeStartedFuture()).thenReturn(closeStartedFuture); - Mockito.when(driverChannel.setKeyspace(any(CqlIdentifier.class))) + when(driverChannel.close()).thenReturn(closeFuture); + when(driverChannel.forceClose()).thenReturn(closeFuture); + when(driverChannel.closeFuture()).thenReturn(closeFuture); + when(driverChannel.closeStartedFuture()).thenReturn(closeStartedFuture); + when(driverChannel.setKeyspace(any(CqlIdentifier.class))) .thenReturn(adminExecutor.newSucceededFuture(null)); - Mockito.when(driverChannel.toString()).thenReturn("channel" + id); + when(driverChannel.toString()).thenReturn("channel" + id); return driverChannel; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java index ab732d76fbd..5e1e12d13d8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelSetTest.java @@ -17,12 +17,13 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ChannelSetTest { @@ -49,15 +50,15 @@ public void should_return_element_when_single() { // Then assertThat(set.size()).isEqualTo(1); assertThat(set.next()).isEqualTo(channel1); - Mockito.verify(channel1, never()).getAvailableIds(); + verify(channel1, never()).getAvailableIds(); } @Test public void should_return_most_available_when_multiple() { // Given - Mockito.when(channel1.getAvailableIds()).thenReturn(2); - Mockito.when(channel2.getAvailableIds()).thenReturn(12); - Mockito.when(channel3.getAvailableIds()).thenReturn(8); + when(channel1.getAvailableIds()).thenReturn(2); + when(channel2.getAvailableIds()).thenReturn(12); + when(channel3.getAvailableIds()).thenReturn(8); // When set.add(channel1); @@ -67,12 +68,12 @@ public void should_return_most_available_when_multiple() { // Then assertThat(set.size()).isEqualTo(3); assertThat(set.next()).isEqualTo(channel2); - Mockito.verify(channel1).getAvailableIds(); - Mockito.verify(channel2).getAvailableIds(); - Mockito.verify(channel3).getAvailableIds(); + verify(channel1).getAvailableIds(); + verify(channel2).getAvailableIds(); + verify(channel3).getAvailableIds(); // When - Mockito.when(channel1.getAvailableIds()).thenReturn(15); + when(channel1.getAvailableIds()).thenReturn(15); // Then assertThat(set.next()).isEqualTo(channel1); @@ -81,9 +82,9 @@ public void should_return_most_available_when_multiple() { @Test public void should_remove_channels() { // Given - Mockito.when(channel1.getAvailableIds()).thenReturn(2); - Mockito.when(channel2.getAvailableIds()).thenReturn(12); - Mockito.when(channel3.getAvailableIds()).thenReturn(8); + when(channel1.getAvailableIds()).thenReturn(2); + when(channel2.getAvailableIds()).thenReturn(12); + when(channel3.getAvailableIds()).thenReturn(8); set.add(channel1); set.add(channel2); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index 5775d393af4..6f97c21ec87 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -20,7 +20,11 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -71,7 +75,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class DefaultSessionPoolsTest { @@ -109,47 +112,44 @@ public void setup() { MockitoAnnotations.initMocks(this); adminEventLoopGroup = new DefaultEventLoopGroup(1); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminEventLoopGroup); + when(context.getNettyOptions()).thenReturn(nettyOptions); // Config: - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) + when(defaultProfile.getBoolean(DefaultDriverOption.REQUEST_WARN_IF_SET_KEYSPACE)) .thenReturn(true); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) - .thenReturn(false); - Mockito.when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) + when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)).thenReturn(false); + when(defaultProfile.isDefined(DefaultDriverOption.PROTOCOL_VERSION)).thenReturn(true); + when(defaultProfile.getDuration(DefaultDriverOption.METADATA_TOPOLOGY_WINDOW)) .thenReturn(Duration.ZERO); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)) - .thenReturn(1); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.getConfig()).thenReturn(config); + when(defaultProfile.getInt(DefaultDriverOption.METADATA_TOPOLOGY_MAX_EVENTS)).thenReturn(1); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); // Init sequence: - Mockito.when(metadataManager.addContactPoints(anySet())) + when(metadataManager.addContactPoints(anySet())) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(metadataManager.refreshNodes()) + when(metadataManager.refreshNodes()).thenReturn(CompletableFuture.completedFuture(null)); + when(metadataManager.firstSchemaRefreshFuture()) .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(metadataManager.firstSchemaRefreshFuture()) - .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.getMetadataManager()).thenReturn(metadataManager); + when(context.getMetadataManager()).thenReturn(metadataManager); - Mockito.when(topologyMonitor.init()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.getTopologyMonitor()).thenReturn(topologyMonitor); + when(topologyMonitor.init()).thenReturn(CompletableFuture.completedFuture(null)); + when(context.getTopologyMonitor()).thenReturn(topologyMonitor); - Mockito.when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); + when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); - Mockito.when(context.getConfigLoader()).thenReturn(configLoader); + when(context.getConfigLoader()).thenReturn(configLoader); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(context.getMetricsFactory()).thenReturn(metricsFactory); // Runtime behavior: - Mockito.when(context.getSessionName()).thenReturn("test"); + when(context.getSessionName()).thenReturn("test"); - Mockito.when(context.getChannelPoolFactory()).thenReturn(channelPoolFactory); + when(context.getChannelPoolFactory()).thenReturn(channelPoolFactory); - eventBus = Mockito.spy(new EventBus("test")); - Mockito.when(context.getEventBus()).thenReturn(eventBus); + eventBus = spy(new EventBus("test")); + when(context.getEventBus()).thenReturn(eventBus); node1 = mockLocalNode(1); node2 = mockLocalNode(2); @@ -159,46 +159,41 @@ public void setup() { node1.getConnectAddress(), node1, node2.getConnectAddress(), node2, node3.getConnectAddress(), node3); - Mockito.when(metadata.getNodes()).thenReturn(nodes); - Mockito.when(metadataManager.getMetadata()).thenReturn(metadata); + when(metadata.getNodes()).thenReturn(nodes); + when(metadataManager.getMetadata()).thenReturn(metadata); PoolManager poolManager = new PoolManager(context); - Mockito.when(context.getPoolManager()).thenReturn(poolManager); + when(context.getPoolManager()).thenReturn(poolManager); // Shutdown sequence: - Mockito.when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); - Mockito.when(context.getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME)) - .thenReturn(retryPolicy); - Mockito.when(context.getSpeculativeExecutionPolicies()) + when(context.getReconnectionPolicy()).thenReturn(reconnectionPolicy); + when(context.getRetryPolicy(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(retryPolicy); + when(context.getSpeculativeExecutionPolicies()) .thenReturn( ImmutableMap.of(DriverExecutionProfile.DEFAULT_NAME, speculativeExecutionPolicy)); - Mockito.when(context.getAddressTranslator()).thenReturn(addressTranslator); - Mockito.when(context.getNodeStateListener()).thenReturn(nodeStateListener); - Mockito.when(context.getSchemaChangeListener()).thenReturn(schemaChangeListener); - Mockito.when(context.getRequestTracker()).thenReturn(requestTracker); + when(context.getAddressTranslator()).thenReturn(addressTranslator); + when(context.getNodeStateListener()).thenReturn(nodeStateListener); + when(context.getSchemaChangeListener()).thenReturn(schemaChangeListener); + when(context.getRequestTracker()).thenReturn(requestTracker); - Mockito.when(metadataManager.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(metadataManager.forceCloseAsync()) - .thenReturn(CompletableFuture.completedFuture(null)); + when(metadataManager.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); + when(metadataManager.forceCloseAsync()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(topologyMonitor.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(topologyMonitor.forceCloseAsync()) - .thenReturn(CompletableFuture.completedFuture(null)); + when(topologyMonitor.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); + when(topologyMonitor.forceCloseAsync()).thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(context.getControlConnection()).thenReturn(controlConnection); - Mockito.when(controlConnection.closeAsync()) - .thenReturn(CompletableFuture.completedFuture(null)); - Mockito.when(controlConnection.forceCloseAsync()) - .thenReturn(CompletableFuture.completedFuture(null)); + when(context.getControlConnection()).thenReturn(controlConnection); + when(controlConnection.closeAsync()).thenReturn(CompletableFuture.completedFuture(null)); + when(controlConnection.forceCloseAsync()).thenReturn(CompletableFuture.completedFuture(null)); DefaultPromise nettyCloseFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); nettyCloseFuture.setSuccess(null); - Mockito.when(nettyOptions.onClose()).thenAnswer(invocation -> nettyCloseFuture); + when(nettyOptions.onClose()).thenAnswer(invocation -> nettyCloseFuture); } @Test public void should_initialize_pools_with_distances() { - Mockito.when(node3.getDistance()).thenReturn(NodeDistance.REMOTE); + when(node3.getDistance()).thenReturn(NodeDistance.REMOTE); CompletableFuture pool1Future = new CompletableFuture<>(); CompletableFuture pool2Future = new CompletableFuture<>(); @@ -236,7 +231,7 @@ public void should_initialize_pools_with_distances() { @Test public void should_not_connect_to_ignored_nodes() { - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool3 = mockPool(node3); @@ -260,7 +255,7 @@ public void should_not_connect_to_ignored_nodes() { @Test public void should_not_connect_to_forced_down_nodes() { - Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); ChannelPool pool1 = mockPool(node1); ChannelPool pool3 = mockPool(node3); @@ -314,7 +309,7 @@ public void should_adjust_distance_if_changed_while_init() { pool3Future.complete(pool3); waitForPendingAdminTasks(); - Mockito.verify(pool2).resize(NodeDistance.REMOTE); + verify(pool2).resize(NodeDistance.REMOTE); assertThatStage(initFuture) .isSuccess( @@ -355,7 +350,7 @@ public void should_remove_pool_if_ignored_while_init() { pool3Future.complete(pool3); waitForPendingAdminTasks(); - Mockito.verify(pool2).closeAsync(); + verify(pool2).closeAsync(); assertThatStage(initFuture) .isSuccess( @@ -395,7 +390,7 @@ public void should_remove_pool_if_forced_down_while_init() { pool3Future.complete(pool3); waitForPendingAdminTasks(); - Mockito.verify(pool2).closeAsync(); + verify(pool2).closeAsync(); assertThatStage(initFuture) .isSuccess( @@ -424,7 +419,7 @@ public void should_resize_pool_if_distance_changes() { assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.REMOTE, node2)); - Mockito.verify(pool2, timeout(500)).resize(NodeDistance.REMOTE); + verify(pool2, timeout(500)).resize(NodeDistance.REMOTE); } @Test @@ -448,7 +443,7 @@ public void should_remove_pool_if_node_becomes_ignored() { assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); - Mockito.verify(pool2, timeout(500)).closeAsync(); + verify(pool2, timeout(500)).closeAsync(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -475,7 +470,7 @@ public void should_do_nothing_if_node_becomes_ignored_but_was_already_ignored() assertThatStage(initFuture).isSuccess(); eventBus.fire(new DistanceEvent(NodeDistance.IGNORED, node2)); - Mockito.verify(pool2, timeout(100)).closeAsync(); + verify(pool2, timeout(100)).closeAsync(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -488,7 +483,7 @@ public void should_do_nothing_if_node_becomes_ignored_but_was_already_ignored() @Test public void should_recreate_pool_if_node_becomes_not_ignored() { - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -539,7 +534,7 @@ public void should_remove_pool_if_node_is_forced_down() { assertThatStage(initFuture).isSuccess(); eventBus.fire(NodeStateEvent.changed(NodeState.UP, NodeState.FORCED_DOWN, node2)); - Mockito.verify(pool2, timeout(500)).closeAsync(); + verify(pool2, timeout(500)).closeAsync(); Session session = CompletableFutures.getCompleted(initFuture.toCompletableFuture()); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); @@ -547,7 +542,7 @@ public void should_remove_pool_if_node_is_forced_down() { @Test public void should_recreate_pool_if_node_is_forced_back_up() { - Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -578,8 +573,8 @@ public void should_recreate_pool_if_node_is_forced_back_up() { @Test public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { - Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -608,7 +603,7 @@ public void should_not_recreate_pool_if_node_is_forced_back_up_but_ignored() { @Test public void should_adjust_distance_if_changed_while_recreating() { - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -644,14 +639,14 @@ public void should_adjust_distance_if_changed_while_recreating() { waitForPendingAdminTasks(); // Pool should have been adjusted - Mockito.verify(pool2).resize(NodeDistance.REMOTE); + verify(pool2).resize(NodeDistance.REMOTE); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool2, pool3); } @Test public void should_remove_pool_if_ignored_while_recreating() { - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -687,14 +682,14 @@ public void should_remove_pool_if_ignored_while_recreating() { waitForPendingAdminTasks(); // Pool should have been closed - Mockito.verify(pool2).closeAsync(); + verify(pool2).closeAsync(); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @Test public void should_remove_pool_if_forced_down_while_recreating() { - Mockito.when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); + when(node2.getDistance()).thenReturn(NodeDistance.IGNORED); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -730,7 +725,7 @@ public void should_remove_pool_if_forced_down_while_recreating() { waitForPendingAdminTasks(); // Pool should have been closed - Mockito.verify(pool2).closeAsync(); + verify(pool2).closeAsync(); assertThat(((DefaultSession) session).getPools()).containsValues(pool1, pool3); } @@ -760,9 +755,9 @@ public void should_close_all_pools_when_closing() { waitForPendingAdminTasks(); assertThatStage(closeFuture).isSuccess(); - Mockito.verify(pool1).closeAsync(); - Mockito.verify(pool2).closeAsync(); - Mockito.verify(pool3).closeAsync(); + verify(pool1).closeAsync(); + verify(pool2).closeAsync(); + verify(pool3).closeAsync(); } @Test @@ -790,14 +785,14 @@ public void should_force_close_all_pools_when_force_closing() { waitForPendingAdminTasks(); assertThatStage(closeFuture).isSuccess(); - Mockito.verify(pool1).forceCloseAsync(); - Mockito.verify(pool2).forceCloseAsync(); - Mockito.verify(pool3).forceCloseAsync(); + verify(pool1).forceCloseAsync(); + verify(pool2).forceCloseAsync(); + verify(pool3).forceCloseAsync(); } @Test public void should_close_pool_if_recreated_while_closing() { - Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -835,7 +830,7 @@ public void should_close_pool_if_recreated_while_closing() { waitForPendingAdminTasks(); // Pool should have been closed - Mockito.verify(pool2).forceCloseAsync(); + verify(pool2).forceCloseAsync(); } @Test @@ -863,14 +858,14 @@ public void should_set_keyspace_on_all_pools() { ((DefaultSession) session).setKeyspace(newKeyspace); waitForPendingAdminTasks(); - Mockito.verify(pool1).setKeyspace(newKeyspace); - Mockito.verify(pool2).setKeyspace(newKeyspace); - Mockito.verify(pool3).setKeyspace(newKeyspace); + verify(pool1).setKeyspace(newKeyspace); + verify(pool2).setKeyspace(newKeyspace); + verify(pool3).setKeyspace(newKeyspace); } @Test public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() { - Mockito.when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); + when(node2.getState()).thenReturn(NodeState.FORCED_DOWN); ChannelPool pool1 = mockPool(node1); ChannelPool pool2 = mockPool(node2); @@ -903,32 +898,32 @@ public void should_set_keyspace_on_pool_if_recreated_while_switching_keyspace() CqlIdentifier newKeyspace = CqlIdentifier.fromInternal("newKeyspace"); session.setKeyspace(newKeyspace); waitForPendingAdminTasks(); - Mockito.verify(pool1).setKeyspace(newKeyspace); - Mockito.verify(pool3).setKeyspace(newKeyspace); + verify(pool1).setKeyspace(newKeyspace); + verify(pool3).setKeyspace(newKeyspace); // now pool init completes pool2Future.complete(pool2); waitForPendingAdminTasks(); // Pool should have been closed - Mockito.verify(pool2).setKeyspace(newKeyspace); + verify(pool2).setKeyspace(newKeyspace); } private ChannelPool mockPool(Node node) { - ChannelPool pool = Mockito.mock(ChannelPool.class); - Mockito.when(pool.getNode()).thenReturn(node); - Mockito.when(pool.getInitialKeyspaceName()).thenReturn(KEYSPACE); - Mockito.when(pool.setKeyspace(any(CqlIdentifier.class))) + ChannelPool pool = mock(ChannelPool.class); + when(pool.getNode()).thenReturn(node); + when(pool.getInitialKeyspaceName()).thenReturn(KEYSPACE); + when(pool.setKeyspace(any(CqlIdentifier.class))) .thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture closeFuture = new CompletableFuture<>(); - Mockito.when(pool.closeFuture()).thenReturn(closeFuture); - Mockito.when(pool.closeAsync()) + when(pool.closeFuture()).thenReturn(closeFuture); + when(pool.closeAsync()) .then( i -> { closeFuture.complete(null); return closeFuture; }); - Mockito.when(pool.forceCloseAsync()) + when(pool.forceCloseAsync()) .then( i -> { closeFuture.complete(null); @@ -942,10 +937,10 @@ private CompletionStage newSession() { } private static DefaultNode mockLocalNode(int i) { - DefaultNode node = Mockito.mock(DefaultNode.class); - Mockito.when(node.getConnectAddress()).thenReturn(new InetSocketAddress("127.0.0." + i, 9042)); - Mockito.when(node.getDistance()).thenReturn(NodeDistance.LOCAL); - Mockito.when(node.toString()).thenReturn("node" + i); + DefaultNode node = mock(DefaultNode.class); + when(node.getConnectAddress()).thenReturn(new InetSocketAddress("127.0.0." + i, 9042)); + when(node.getDistance()).thenReturn(NodeDistance.LOCAL); + when(node.toString()).thenReturn("node" + i); return node; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 83ea502283d..4aa7e414939 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -19,7 +19,10 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; @@ -41,7 +44,6 @@ import java.util.concurrent.CompletionStage; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import org.mockito.Mockito; import org.mockito.internal.util.MockUtil; import org.mockito.stubbing.OngoingStubbing; @@ -59,7 +61,7 @@ public static MockChannelPoolFactoryHelper.Builder builder( private MockChannelPoolFactoryHelper(ChannelPoolFactory channelPoolFactory) { this.channelPoolFactory = channelPoolFactory; - this.inOrder = Mockito.inOrder(channelPoolFactory); + this.inOrder = inOrder(channelPoolFactory); } public void waitForCall(Node node, CqlIdentifier keyspace, NodeDistance distance) { @@ -120,7 +122,7 @@ public static class Builder { private Builder(ChannelPoolFactory channelPoolFactory) { assertThat(MockUtil.isMock(channelPoolFactory)).as("expected a mock").isTrue(); - Mockito.verifyZeroInteractions(channelPoolFactory); + verifyZeroInteractions(channelPoolFactory); this.channelPoolFactory = channelPoolFactory; } @@ -175,13 +177,12 @@ private void stub() { if (results.size() > 0) { CompletionStage first = results.poll(); OngoingStubbing> ongoingStubbing = - Mockito.when( - channelPoolFactory.init( - eq(params.node), - eq(params.keyspace), - eq(params.distance), - any(InternalDriverContext.class), - eq("test"))) + when(channelPoolFactory.init( + eq(params.node), + eq(params.keyspace), + eq(params.distance), + any(InternalDriverContext.class), + eq("test"))) .thenReturn(first); for (CompletionStage result : results) { ongoingStubbing.thenReturn(result); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java index df338b531c1..6bb875d1dbd 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/ReprepareOnUpTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -55,7 +56,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ReprepareOnUpTest { @@ -75,22 +75,21 @@ public class ReprepareOnUpTest { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(pool.next()).thenReturn(channel); - Mockito.when(channel.eventLoop()).thenReturn(eventLoop); - Mockito.when(eventLoop.inEventLoop()).thenReturn(true); + when(pool.next()).thenReturn(channel); + when(channel.eventLoop()).thenReturn(eventLoop); + when(eventLoop.inEventLoop()).thenReturn(true); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(true); - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REPREPARE_TIMEOUT)) + when(defaultProfile.getDuration(DefaultDriverOption.REPREPARE_TIMEOUT)) .thenReturn(Duration.ofMillis(500)); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(0); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) - .thenReturn(100); - Mockito.when(context.getConfig()).thenReturn(config); + when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(0); + when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)).thenReturn(100); + when(context.getConfig()).thenReturn(config); - Mockito.when(context.getMetricsFactory()).thenReturn(metricsFactory); - Mockito.when(metricsFactory.getSessionUpdater()).thenReturn(metricUpdater); + when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(metricsFactory.getSessionUpdater()).thenReturn(metricUpdater); done = new CompletableFuture<>(); whenPrepared = () -> ((CompletableFuture) done).complete(null); @@ -112,7 +111,7 @@ public void should_complete_immediately_if_no_prepared_statements() { @Test public void should_complete_immediately_if_pool_empty() { // Given - Mockito.when(pool.next()).thenReturn(null); + when(pool.next()).thenReturn(null); MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp("test", pool, getMockPayloads('a'), context, whenPrepared); @@ -175,7 +174,7 @@ public void should_reprepare_all_if_system_table_empty() { @Test public void should_reprepare_all_if_system_query_disabled() { - Mockito.when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) + when(defaultProfile.getBoolean(DefaultDriverOption.REPREPARE_CHECK_SYSTEM_TABLE)) .thenReturn(false); MockReprepareOnUp reprepareOnUp = @@ -223,21 +222,21 @@ public void should_not_reprepare_already_known_statements() { @Test public void should_proceed_if_schema_agreement_not_reached() { - Mockito.when(topologyMonitor.checkSchemaAgreement()) + when(topologyMonitor.checkSchemaAgreement()) .thenReturn(CompletableFuture.completedFuture(false)); should_not_reprepare_already_known_statements(); } @Test public void should_proceed_if_schema_agreement_fails() { - Mockito.when(topologyMonitor.checkSchemaAgreement()) + when(topologyMonitor.checkSchemaAgreement()) .thenReturn(CompletableFutures.failedFuture(new RuntimeException("test"))); should_not_reprepare_already_known_statements(); } @Test public void should_limit_number_of_statements_to_reprepare() { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_STATEMENTS)).thenReturn(3); MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( @@ -265,8 +264,7 @@ public void should_limit_number_of_statements_to_reprepare() { @Test public void should_limit_number_of_statements_reprepared_in_parallel() { - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)) - .thenReturn(3); + when(defaultProfile.getInt(DefaultDriverOption.REPREPARE_MAX_PARALLELISM)).thenReturn(3); MockReprepareOnUp reprepareOnUp = new MockReprepareOnUp( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java index e5a660b8d1b..a9f4233513b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottlerTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -31,7 +32,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -45,13 +45,12 @@ public class ConcurrencyLimitingRequestThrottlerTest { @Before public void setup() { - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when( - defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS)) + when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS)) .thenReturn(5); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) + when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) .thenReturn(10); throttler = new ConcurrencyLimitingRequestThrottler(context); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java index 19f4c7b5522..26b52403e8f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/throttling/RateLimitingRequestThrottlerTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -34,7 +35,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.Silent.class) @@ -63,24 +63,23 @@ public class RateLimitingRequestThrottlerTest { @Before public void setup() { - Mockito.when(context.getConfig()).thenReturn(config); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when( - defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND)) + when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_REQUESTS_PER_SECOND)) .thenReturn(5); - Mockito.when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) + when(defaultProfile.getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE)) .thenReturn(10); // Set to match the time to reissue one permit. Although it does not matter in practice, since // the executor is mocked and we trigger tasks manually. - Mockito.when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL)) + when(defaultProfile.getDuration(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL)) .thenReturn(DRAIN_INTERVAL); - Mockito.when(context.getNettyOptions()).thenReturn(nettyOptions); - Mockito.when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminGroup); + when(context.getNettyOptions()).thenReturn(nettyOptions); + when(nettyOptions.adminEventExecutorGroup()).thenReturn(adminGroup); adminExecutor = new ScheduledTaskCapturingEventLoop(adminGroup); - Mockito.when(adminGroup.next()).thenReturn(adminExecutor); + when(adminGroup.next()).thenReturn(adminExecutor); throttler = new RateLimitingRequestThrottler(context, clock); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGeneratorTest.java index a7aadedba38..fa4adec9e6c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/AtomicTimestampGeneratorTest.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.fail; +import static org.mockito.Mockito.when; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; @@ -24,7 +25,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; public class AtomicTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @@ -36,7 +36,7 @@ protected MonotonicTimestampGenerator newInstance(Clock clock) { @Test public void should_share_timestamps_across_all_threads() throws Exception { // Prepare to generate 1000 timestamps with the clock frozen at 1 - OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + OngoingStubbing stub = when(clock.currentTimeMicros()); for (int i = 0; i < 1000; i++) { stub = stub.thenReturn(1L); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java index 25f6c73eb71..59324205872 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/MonotonicTimestampGeneratorTestBase.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.time; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -32,7 +34,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.OngoingStubbing; import org.slf4j.LoggerFactory; @@ -53,18 +54,15 @@ abstract class MonotonicTimestampGeneratorTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(config.getDefaultProfile()).thenReturn(defaultProfile); - Mockito.when(context.getConfig()).thenReturn(config); + when(config.getDefaultProfile()).thenReturn(defaultProfile); + when(context.getConfig()).thenReturn(config); // Disable warnings by default - Mockito.when( - defaultProfile.getDuration( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) + when(defaultProfile.getDuration( + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) .thenReturn(Duration.ZERO); // Actual value doesn't really matter since we only test the first warning - Mockito.when( - defaultProfile.getDuration( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) + when(defaultProfile.getDuration(DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_INTERVAL)) .thenReturn(Duration.ofSeconds(10)); logger = (Logger) LoggerFactory.getLogger(MonotonicTimestampGenerator.class); @@ -80,7 +78,7 @@ public void teardown() { @Test public void should_use_clock_if_it_keeps_increasing() { - OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + OngoingStubbing stub = when(clock.currentTimeMicros()); for (long l = 1; l < 5; l++) { stub = stub.thenReturn(l); } @@ -94,7 +92,7 @@ public void should_use_clock_if_it_keeps_increasing() { @Test public void should_increment_if_clock_does_not_increase() { - Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 5L); + when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 5L); MonotonicTimestampGenerator generator = newInstance(clock); @@ -106,11 +104,10 @@ public void should_increment_if_clock_does_not_increase() { @Test public void should_warn_if_timestamps_drift() { - Mockito.when( - defaultProfile.getDuration( - DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) + when(defaultProfile.getDuration( + DefaultDriverOption.TIMESTAMP_GENERATOR_DRIFT_WARNING_THRESHOLD, Duration.ZERO)) .thenReturn(Duration.ofNanos(2 * 1000)); - Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); + when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L); MonotonicTimestampGenerator generator = newInstance(clock); @@ -121,7 +118,7 @@ public void should_warn_if_timestamps_drift() { // Clock still at 1, last returned timestamp is 4 (> 1 + 2), should warn assertThat(generator.next()).isEqualTo(5); - Mockito.verify(appender).doAppend(loggingEventCaptor.capture()); + verify(appender).doAppend(loggingEventCaptor.capture()); ILoggingEvent log = loggingEventCaptor.getValue(); assertThat(log.getLevel()).isEqualTo(Level.WARN); assertThat(log.getMessage()).contains("Clock skew detected"); @@ -129,7 +126,7 @@ public void should_warn_if_timestamps_drift() { @Test public void should_go_back_to_clock_if_new_tick_high_enough() { - Mockito.when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L, 10L); + when(clock.currentTimeMicros()).thenReturn(1L, 1L, 1L, 1L, 1L, 10L); MonotonicTimestampGenerator generator = newInstance(clock); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java index 977279629b1..6de3a2b5e41 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/time/ThreadLocalTimestampGeneratorTest.java @@ -18,6 +18,7 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static com.datastax.oss.driver.Assertions.assertThatStage; import static com.datastax.oss.driver.Assertions.fail; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import java.util.List; @@ -28,7 +29,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; public class ThreadLocalTimestampGeneratorTest extends MonotonicTimestampGeneratorTestBase { @@ -42,7 +42,7 @@ public void should_confine_timestamps_to_thread() throws Exception { final int testThreadsCount = 2; // Prepare to generate 1000 timestamps for each thread, with the clock frozen at 1 - OngoingStubbing stub = Mockito.when(clock.currentTimeMicros()); + OngoingStubbing stub = when(clock.currentTimeMicros()); for (int i = 0; i < testThreadsCount * 1000; i++) { stub = stub.thenReturn(1L); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java index e3d2c5ec41a..160e5d04dd9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestLogFormatterTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.tracker; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -44,7 +45,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -57,8 +57,8 @@ public class RequestLogFormatterTest { @Before public void setup() { - Mockito.when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); - Mockito.when(context.getProtocolVersion()).thenReturn(protocolVersion); + when(context.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT); + when(context.getProtocolVersion()).thenReturn(protocolVersion); formatter = new RequestLogFormatter(context); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java index 7808c0fd93b..7260a2ee3ac 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/ListCodecTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -29,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ListCodecTest extends CodecTestBase> { @@ -40,8 +40,8 @@ public class ListCodecTest extends CodecTestBase> { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); - Mockito.when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); + when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); + when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); codec = TypeCodecs.listOf(elementCodec); } @@ -57,11 +57,9 @@ public void should_encode_empty_list() { @Test public void should_encode_non_empty_list() { - Mockito.when(elementCodec.encode(1, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x01")); - Mockito.when(elementCodec.encode(2, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x0002")); - Mockito.when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) + when(elementCodec.encode(1, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x01")); + when(elementCodec.encode(2, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x0002")); + when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) .thenReturn(Bytes.fromHexString("0x000003")); assertThat(encode(ImmutableList.of(1, 2, 3))) @@ -86,11 +84,9 @@ public void should_decode_empty_list() { @Test public void should_decode_non_empty_list() { - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) - .thenReturn(1); - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) - .thenReturn(2); - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) + when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)).thenReturn(1); + when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)).thenReturn(2); + when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) .thenReturn(3); assertThat(decode("0x" + "00000003" + "0000000101" + "000000020002" + "00000003000003")) @@ -109,9 +105,9 @@ public void should_format_empty_list() { @Test public void should_format_non_empty_list() { - Mockito.when(elementCodec.format(1)).thenReturn("a"); - Mockito.when(elementCodec.format(2)).thenReturn("b"); - Mockito.when(elementCodec.format(3)).thenReturn("c"); + when(elementCodec.format(1)).thenReturn("a"); + when(elementCodec.format(2)).thenReturn("b"); + when(elementCodec.format(3)).thenReturn("c"); assertThat(format(ImmutableList.of(1, 2, 3))).isEqualTo("[a,b,c]"); } @@ -129,9 +125,9 @@ public void should_parse_empty_list() { @Test public void should_parse_non_empty_list() { - Mockito.when(elementCodec.parse("a")).thenReturn(1); - Mockito.when(elementCodec.parse("b")).thenReturn(2); - Mockito.when(elementCodec.parse("c")).thenReturn(3); + when(elementCodec.parse("a")).thenReturn(1); + when(elementCodec.parse("b")).thenReturn(2); + when(elementCodec.parse("c")).thenReturn(3); assertThat(parse("[a,b,c]")).containsExactly(1, 2, 3); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java index 4c4978d2102..96de17f75e8 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/MapCodecTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -29,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class MapCodecTest extends CodecTestBase> { @@ -41,11 +41,11 @@ public class MapCodecTest extends CodecTestBase> { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(keyCodec.getCqlType()).thenReturn(DataTypes.TEXT); - Mockito.when(keyCodec.getJavaType()).thenReturn(GenericType.STRING); + when(keyCodec.getCqlType()).thenReturn(DataTypes.TEXT); + when(keyCodec.getJavaType()).thenReturn(GenericType.STRING); - Mockito.when(valueCodec.getCqlType()).thenReturn(DataTypes.INT); - Mockito.when(valueCodec.getJavaType()).thenReturn(GenericType.INTEGER); + when(valueCodec.getCqlType()).thenReturn(DataTypes.INT); + when(valueCodec.getJavaType()).thenReturn(GenericType.INTEGER); codec = TypeCodecs.mapOf(keyCodec, valueCodec); } @@ -61,19 +61,13 @@ public void should_encode_empty_map() { @Test public void should_encode_non_empty_map() { - Mockito.when(keyCodec.encode("a", ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x10")); - Mockito.when(keyCodec.encode("b", ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x2000")); - Mockito.when(keyCodec.encode("c", ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x300000")); - - Mockito.when(valueCodec.encode(1, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x01")); - Mockito.when(valueCodec.encode(2, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x0002")); - Mockito.when(valueCodec.encode(3, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x000003")); + when(keyCodec.encode("a", ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x10")); + when(keyCodec.encode("b", ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x2000")); + when(keyCodec.encode("c", ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x300000")); + + when(valueCodec.encode(1, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x01")); + when(valueCodec.encode(2, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x0002")); + when(valueCodec.encode(3, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x000003")); assertThat(encode(ImmutableMap.of("a", 1, "b", 2, "c", 3))) .isEqualTo( @@ -100,19 +94,13 @@ public void should_decode_empty_map() { @Test public void should_decode_non_empty_map() { - Mockito.when(keyCodec.decode(Bytes.fromHexString("0x10"), ProtocolVersion.DEFAULT)) - .thenReturn("a"); - Mockito.when(keyCodec.decode(Bytes.fromHexString("0x2000"), ProtocolVersion.DEFAULT)) - .thenReturn("b"); - Mockito.when(keyCodec.decode(Bytes.fromHexString("0x300000"), ProtocolVersion.DEFAULT)) - .thenReturn("c"); - - Mockito.when(valueCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) - .thenReturn(1); - Mockito.when(valueCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) - .thenReturn(2); - Mockito.when(valueCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) - .thenReturn(3); + when(keyCodec.decode(Bytes.fromHexString("0x10"), ProtocolVersion.DEFAULT)).thenReturn("a"); + when(keyCodec.decode(Bytes.fromHexString("0x2000"), ProtocolVersion.DEFAULT)).thenReturn("b"); + when(keyCodec.decode(Bytes.fromHexString("0x300000"), ProtocolVersion.DEFAULT)).thenReturn("c"); + + when(valueCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)).thenReturn(1); + when(valueCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)).thenReturn(2); + when(valueCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)).thenReturn(3); assertThat( decode( @@ -142,13 +130,13 @@ public void should_format_empty_map() { @Test public void should_format_non_empty_map() { - Mockito.when(keyCodec.format("a")).thenReturn("foo"); - Mockito.when(keyCodec.format("b")).thenReturn("bar"); - Mockito.when(keyCodec.format("c")).thenReturn("baz"); + when(keyCodec.format("a")).thenReturn("foo"); + when(keyCodec.format("b")).thenReturn("bar"); + when(keyCodec.format("c")).thenReturn("baz"); - Mockito.when(valueCodec.format(1)).thenReturn("qux"); - Mockito.when(valueCodec.format(2)).thenReturn("quux"); - Mockito.when(valueCodec.format(3)).thenReturn("quuz"); + when(valueCodec.format(1)).thenReturn("qux"); + when(valueCodec.format(2)).thenReturn("quux"); + when(valueCodec.format(3)).thenReturn("quuz"); assertThat(format(ImmutableMap.of("a", 1, "b", 2, "c", 3))) .isEqualTo("{foo:qux,bar:quux,baz:quuz}"); @@ -167,13 +155,13 @@ public void should_parse_empty_map() { @Test public void should_parse_non_empty_map() { - Mockito.when(keyCodec.parse("foo")).thenReturn("a"); - Mockito.when(keyCodec.parse("bar")).thenReturn("b"); - Mockito.when(keyCodec.parse("baz")).thenReturn("c"); + when(keyCodec.parse("foo")).thenReturn("a"); + when(keyCodec.parse("bar")).thenReturn("b"); + when(keyCodec.parse("baz")).thenReturn("c"); - Mockito.when(valueCodec.parse("qux")).thenReturn(1); - Mockito.when(valueCodec.parse("quux")).thenReturn(2); - Mockito.when(valueCodec.parse("quuz")).thenReturn(3); + when(valueCodec.parse("qux")).thenReturn(1); + when(valueCodec.parse("quux")).thenReturn(2); + when(valueCodec.parse("quuz")).thenReturn(3); assertThat(parse("{foo:qux,bar:quux,baz:quuz}")) .containsOnlyKeys("a", "b", "c") diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java index e05d2453681..9e6b590d2f4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/SetCodecTest.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core.type.codec; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -29,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class SetCodecTest extends CodecTestBase> { @@ -40,8 +40,8 @@ public class SetCodecTest extends CodecTestBase> { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); - Mockito.when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); + when(elementCodec.getCqlType()).thenReturn(DataTypes.INT); + when(elementCodec.getJavaType()).thenReturn(GenericType.INTEGER); codec = TypeCodecs.setOf(elementCodec); } @@ -57,11 +57,9 @@ public void should_encode_empty_set() { @Test public void should_encode_non_empty_set() { - Mockito.when(elementCodec.encode(1, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x01")); - Mockito.when(elementCodec.encode(2, ProtocolVersion.DEFAULT)) - .thenReturn(Bytes.fromHexString("0x0002")); - Mockito.when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) + when(elementCodec.encode(1, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x01")); + when(elementCodec.encode(2, ProtocolVersion.DEFAULT)).thenReturn(Bytes.fromHexString("0x0002")); + when(elementCodec.encode(3, ProtocolVersion.DEFAULT)) .thenReturn(Bytes.fromHexString("0x000003")); assertThat(encode(ImmutableSet.of(1, 2, 3))) @@ -86,11 +84,9 @@ public void should_decode_empty_set() { @Test public void should_decode_non_empty_set() { - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)) - .thenReturn(1); - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)) - .thenReturn(2); - Mockito.when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) + when(elementCodec.decode(Bytes.fromHexString("0x01"), ProtocolVersion.DEFAULT)).thenReturn(1); + when(elementCodec.decode(Bytes.fromHexString("0x0002"), ProtocolVersion.DEFAULT)).thenReturn(2); + when(elementCodec.decode(Bytes.fromHexString("0x000003"), ProtocolVersion.DEFAULT)) .thenReturn(3); assertThat(decode("0x" + "00000003" + "0000000101" + "000000020002" + "00000003000003")) @@ -109,9 +105,9 @@ public void should_format_empty_set() { @Test public void should_format_non_empty_set() { - Mockito.when(elementCodec.format(1)).thenReturn("a"); - Mockito.when(elementCodec.format(2)).thenReturn("b"); - Mockito.when(elementCodec.format(3)).thenReturn("c"); + when(elementCodec.format(1)).thenReturn("a"); + when(elementCodec.format(2)).thenReturn("b"); + when(elementCodec.format(3)).thenReturn("c"); assertThat(format(ImmutableSet.of(1, 2, 3))).isEqualTo("{a,b,c}"); } @@ -129,9 +125,9 @@ public void should_parse_empty_set() { @Test public void should_parse_non_empty_set() { - Mockito.when(elementCodec.parse("a")).thenReturn(1); - Mockito.when(elementCodec.parse("b")).thenReturn(2); - Mockito.when(elementCodec.parse("c")).thenReturn(3); + when(elementCodec.parse("a")).thenReturn(1); + when(elementCodec.parse("b")).thenReturn(2); + when(elementCodec.parse("c")).thenReturn(3); assertThat(parse("{a,b,c}")).containsExactly(1, 2, 3); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index af53d295260..95d5def804c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -16,6 +16,10 @@ package com.datastax.oss.driver.internal.core.type.codec; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; @@ -34,7 +38,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class TupleCodecTest extends CodecTestBase { @@ -51,23 +54,22 @@ public class TupleCodecTest extends CodecTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); - intCodec = Mockito.spy(TypeCodecs.INT); - doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); - textCodec = Mockito.spy(TypeCodecs.TEXT); + intCodec = spy(TypeCodecs.INT); + doubleCodec = spy(TypeCodecs.DOUBLE); + textCodec = spy(TypeCodecs.TEXT); // Called by the getters/setters - Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) - .thenAnswer(i -> doubleCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)).thenAnswer(i -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); // Called by format/parse - Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); + when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); tupleType = new DefaultTupleType( @@ -96,10 +98,10 @@ public void should_encode_tuple() { + ("00000001" + "61") // size and contents of field 2 ); - Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); // null values are handled directly in the tuple codec, without calling the child codec: - Mockito.verifyZeroInteractions(doubleCodec); - Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + verifyZeroInteractions(doubleCodec); + verify(textCodec).encode("a", ProtocolVersion.DEFAULT); } @Test @@ -115,10 +117,9 @@ public void should_decode_tuple() { assertThat(tuple.isNull(1)).isTrue(); assertThat(tuple.getString(2)).isEqualTo("a"); - Mockito.verify(intCodec) - .decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); - Mockito.verifyZeroInteractions(doubleCodec); - Mockito.verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); + verify(intCodec).decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); + verifyZeroInteractions(doubleCodec); + verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); } @Test @@ -135,9 +136,9 @@ public void should_format_tuple() { assertThat(format(tuple)).isEqualTo("(1,NULL,'a')"); - Mockito.verify(intCodec).format(1); - Mockito.verify(doubleCodec).format(null); - Mockito.verify(textCodec).format("a"); + verify(intCodec).format(1); + verify(doubleCodec).format(null); + verify(textCodec).format("a"); } @Test @@ -155,9 +156,9 @@ public void should_parse_tuple() { assertThat(tuple.isNull(1)).isTrue(); assertThat(tuple.getString(2)).isEqualTo("a"); - Mockito.verify(intCodec).parse("1"); - Mockito.verify(doubleCodec).parse("NULL"); - Mockito.verify(textCodec).parse("'a'"); + verify(intCodec).parse("1"); + verify(doubleCodec).parse("NULL"); + verify(textCodec).parse("'a'"); } @Test(expected = IllegalArgumentException.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index 88ea987d7ef..bca5f73e569 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -16,6 +16,10 @@ package com.datastax.oss.driver.internal.core.type.codec; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -35,7 +39,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class UdtCodecTest extends CodecTestBase { @@ -52,23 +55,22 @@ public class UdtCodecTest extends CodecTestBase { public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); - Mockito.when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); + when(attachmentPoint.getCodecRegistry()).thenReturn(codecRegistry); + when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT); - intCodec = Mockito.spy(TypeCodecs.INT); - doubleCodec = Mockito.spy(TypeCodecs.DOUBLE); - textCodec = Mockito.spy(TypeCodecs.TEXT); + intCodec = spy(TypeCodecs.INT); + doubleCodec = spy(TypeCodecs.DOUBLE); + textCodec = spy(TypeCodecs.TEXT); // Called by the getters/setters - Mockito.when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)) - .thenAnswer(i -> doubleCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); + when(codecRegistry.codecFor(DataTypes.INT, Integer.class)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE, Double.class)).thenAnswer(i -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.TEXT, String.class)).thenAnswer(i -> textCodec); // Called by format/parse - Mockito.when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); - Mockito.when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); + when(codecRegistry.codecFor(DataTypes.INT)).thenAnswer(i -> intCodec); + when(codecRegistry.codecFor(DataTypes.DOUBLE)).thenAnswer(i -> doubleCodec); + when(codecRegistry.codecFor(DataTypes.TEXT)).thenAnswer(i -> textCodec); userType = new DefaultUserDefinedType( @@ -105,10 +107,10 @@ public void should_encode_udt() { + ("00000001" + "61") // size and contents of field 2 ); - Mockito.verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); + verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); // null values are handled directly in the udt codec, without calling the child codec: - Mockito.verifyZeroInteractions(doubleCodec); - Mockito.verify(textCodec).encode("a", ProtocolVersion.DEFAULT); + verifyZeroInteractions(doubleCodec); + verify(textCodec).encode("a", ProtocolVersion.DEFAULT); } @Test @@ -124,10 +126,9 @@ public void should_decode_udt() { assertThat(udt.isNull(1)).isTrue(); assertThat(udt.getString(2)).isEqualTo("a"); - Mockito.verify(intCodec) - .decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); - Mockito.verifyZeroInteractions(doubleCodec); - Mockito.verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); + verify(intCodec).decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); + verifyZeroInteractions(doubleCodec); + verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); } @Test @@ -144,9 +145,9 @@ public void should_format_udt() { assertThat(format(udt)).isEqualTo("{field1:1,field2:NULL,field3:'a'}"); - Mockito.verify(intCodec).format(1); - Mockito.verify(doubleCodec).format(null); - Mockito.verify(textCodec).format("a"); + verify(intCodec).format(1); + verify(doubleCodec).format(null); + verify(textCodec).format("a"); } @Test @@ -164,9 +165,9 @@ public void should_parse_udt() { assertThat(udt.isNull(1)).isTrue(); assertThat(udt.getString(2)).isEqualTo("a"); - Mockito.verify(intCodec).parse("1"); - Mockito.verify(doubleCodec).parse("NULL"); - Mockito.verify(textCodec).parse("'a'"); + verify(intCodec).parse("1"); + verify(doubleCodec).parse("NULL"); + verify(textCodec).parse("'a'"); } @Test(expected = IllegalArgumentException.class) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 195a118dec1..72056f6860a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verifyZeroInteractions; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -63,7 +65,6 @@ import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class CachingCodecRegistryTest { @@ -99,7 +100,7 @@ public void should_find_primitive_codecs_for_types() { checkPrimitiveMappings(registry, TypeCodecs.INET); checkPrimitiveMappings(registry, TypeCodecs.DURATION); // Primitive mappings never hit the cache - Mockito.verifyZeroInteractions(mockCache); + verifyZeroInteractions(mockCache); } private void checkPrimitiveMappings(TestCachingCodecRegistry registry, TypeCodec codec) { @@ -134,7 +135,7 @@ public void should_find_primitive_codecs_for_value() throws Exception { assertThat(registry.codecFor(new UUID(2L, 1L))).isEqualTo(TypeCodecs.UUID); assertThat(registry.codecFor(InetAddress.getByName("127.0.0.1"))).isEqualTo(TypeCodecs.INET); assertThat(registry.codecFor(CqlDuration.newInstance(1, 2, 3))).isEqualTo(TypeCodecs.DURATION); - Mockito.verifyZeroInteractions(mockCache); + verifyZeroInteractions(mockCache); } @Test @@ -161,7 +162,7 @@ public void should_find_primitive_codecs_for_cql_type_and_value() throws Excepti .isEqualTo(TypeCodecs.INET); assertThat(registry.codecFor(DataTypes.DURATION, CqlDuration.newInstance(1, 2, 3))) .isEqualTo(TypeCodecs.DURATION); - Mockito.verifyZeroInteractions(mockCache); + verifyZeroInteractions(mockCache); } @Test @@ -182,7 +183,7 @@ public void should_find_user_codec_for_built_in_java_type() { assertThat(registry.codecFor(DataTypes.INT)).isSameAs(TypeCodecs.INT); assertThat(registry.codecFor("")).isSameAs(TypeCodecs.TEXT); - Mockito.verifyZeroInteractions(mockCache); + verifyZeroInteractions(mockCache); } @Test @@ -202,7 +203,7 @@ public void should_find_user_codec_for_custom_java_type() { // The search by CQL type only still returns the built-in codec assertThat(registry.codecFor(DataTypes.TEXT)).isSameAs(TypeCodecs.TEXT); - Mockito.verifyZeroInteractions(mockCache); + verifyZeroInteractions(mockCache); } @Test @@ -212,7 +213,7 @@ public void should_create_list_codec_for_cql_and_java_types() { List> value = ImmutableList.of(ImmutableList.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); @@ -233,7 +234,7 @@ public void should_create_list_codec_for_cql_type() { List> value = ImmutableList.of(ImmutableList.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); @@ -251,7 +252,7 @@ public void should_create_list_codec_for_cql_type_and_java_value() { List> value = ImmutableList.of(ImmutableList.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, value); assertThat(codec).isNotNull(); @@ -271,7 +272,7 @@ public void should_create_list_codec_for_java_value() { List> value = ImmutableList.of(ImmutableList.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -294,7 +295,7 @@ public void should_create_list_codec_for_java_value_when_first_element_is_a_subt List value = ImmutableList.of(address); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -312,7 +313,7 @@ public void should_create_set_codec_for_cql_and_java_types() { Set> value = ImmutableSet.of(ImmutableSet.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); @@ -333,7 +334,7 @@ public void should_create_set_codec_for_cql_type() { Set> value = ImmutableSet.of(ImmutableSet.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); @@ -351,7 +352,7 @@ public void should_create_set_codec_for_cql_type_and_java_value() { Set> value = ImmutableSet.of(ImmutableSet.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, value); assertThat(codec).isNotNull(); @@ -371,7 +372,7 @@ public void should_create_set_codec_for_java_value() { Set> value = ImmutableSet.of(ImmutableSet.of(1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -394,7 +395,7 @@ public void should_create_set_codec_for_java_value_when_first_element_is_a_subty Set value = ImmutableSet.of(address); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -413,7 +414,7 @@ public void should_create_map_codec_for_cql_and_java_types() { Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, javaType); assertThat(codec).isNotNull(); @@ -438,7 +439,7 @@ public void should_create_map_codec_for_cql_type() { Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); @@ -457,7 +458,7 @@ public void should_create_map_codec_for_java_type() { Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(javaType); assertThat(codec).isNotNull(); @@ -476,7 +477,7 @@ public void should_create_map_codec_for_cql_type_and_java_value() { Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(cqlType, value); assertThat(codec).isNotNull(); @@ -500,7 +501,7 @@ public void should_create_map_codec_for_java_value() { Map> value = ImmutableMap.of(1, ImmutableMap.of(1, 1)); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec>> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -526,7 +527,7 @@ public void should_create_map_codec_for_java_value_when_first_element_is_a_subty Map value = ImmutableMap.of(address, address); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec> codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -545,7 +546,7 @@ public void should_create_tuple_codec_for_cql_and_java_types() { TupleValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType, GenericType.TUPLE_VALUE); assertThat(codec).isNotNull(); @@ -564,7 +565,7 @@ public void should_create_tuple_codec_for_cql_type() { TupleValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); @@ -581,7 +582,7 @@ public void should_create_tuple_codec_for_cql_type_and_java_value() { TupleValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType, value); assertThat(codec).isNotNull(); @@ -600,7 +601,7 @@ public void should_create_tuple_codec_for_java_value() { TupleValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -626,7 +627,7 @@ public void should_create_udt_codec_for_cql_and_java_types() { UdtValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType, GenericType.UDT_VALUE); assertThat(codec).isNotNull(); @@ -650,7 +651,7 @@ public void should_create_udt_codec_for_cql_type() { UdtValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType); assertThat(codec).isNotNull(); @@ -672,7 +673,7 @@ public void should_create_udt_codec_for_cql_type_and_java_value() { UdtValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(cqlType, value); assertThat(codec).isNotNull(); @@ -696,7 +697,7 @@ public void should_create_udt_codec_for_java_value() { UdtValue value = cqlType.newValue(); TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); TypeCodec codec = registry.codecFor(value); assertThat(codec).isNotNull(); @@ -737,7 +738,7 @@ public void should_not_find_codec_if_java_type_unknown() { public void should_not_allow_covariance_for_lookups_by_java_type() { TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache, new ACodec()); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); // covariance not allowed @@ -759,7 +760,7 @@ public void should_not_allow_covariance_for_lookups_by_java_type() { public void should_allow_covariance_for_lookups_by_cql_type_and_value() { TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache, new ACodec()); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); // covariance allowed @@ -786,7 +787,7 @@ public void should_allow_covariance_for_lookups_by_cql_type_and_value() { public void should_allow_covariance_for_lookups_by_value() { TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache, new ACodec()); - InOrder inOrder = Mockito.inOrder(mockCache); + InOrder inOrder = inOrder(mockCache); // covariance allowed diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java index 6cb06654781..e6a878f7450 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -16,10 +16,12 @@ package com.datastax.oss.driver.internal.core.util; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.concurrent.ThreadLocalRandom; import org.junit.Test; -import org.mockito.Mockito; public class ArrayUtilsTest { @@ -82,8 +84,8 @@ public void should_not_bubble_down_when_target_index_lower() { @Test public void should_shuffle_head() { String[] array = {"a", "b", "c", "d", "e"}; - ThreadLocalRandom random = Mockito.mock(ThreadLocalRandom.class); - Mockito.when(random.nextInt(Mockito.anyInt())) + ThreadLocalRandom random = mock(ThreadLocalRandom.class); + when(random.nextInt(anyInt())) .thenAnswer( (invocation) -> { int i = invocation.getArgument(0); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java index 984ef1f2be3..a809e7b0c9b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ReflectionTest.java @@ -16,6 +16,8 @@ package com.datastax.oss.driver.internal.core.util; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -27,7 +29,6 @@ import com.typesafe.config.ConfigFactory; import java.util.Map; import org.junit.Test; -import org.mockito.Mockito; public class ReflectionTest { @@ -55,9 +56,9 @@ public void should_build_policies_per_profile() { + " advanced.speculative-execution-policy.class = NoSpeculativeExecutionPolicy\n" + " }\n" + "}\n"; - InternalDriverContext context = Mockito.mock(InternalDriverContext.class); + InternalDriverContext context = mock(InternalDriverContext.class); TypesafeDriverConfig config = new TypesafeDriverConfig(ConfigFactory.parseString(configSource)); - Mockito.when(context.getConfig()).thenReturn(config); + when(context.getConfig()).thenReturn(config); Map policies = Reflection.buildFromConfigProfiles( diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java index 9130961399b..2cd6c9eed21 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/DebouncerTest.java @@ -16,8 +16,14 @@ package com.datastax.oss.driver.internal.core.util.concurrent; import static com.datastax.oss.driver.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.shaded.guava.common.base.Joiner; import io.netty.util.concurrent.EventExecutor; @@ -31,7 +37,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class DebouncerTest { @@ -46,12 +51,9 @@ public class DebouncerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - Mockito.when(adminExecutor.inEventLoop()).thenReturn(true); - Mockito.when( - adminExecutor.schedule( - Mockito.any(Runnable.class), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS))) + when(adminExecutor.inEventLoop()).thenReturn(true); + when(adminExecutor.schedule( + any(Runnable.class), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS))) .thenAnswer((i) -> scheduledFuture); results = new ArrayList<>(); } @@ -73,8 +75,7 @@ public void should_flush_synchronously_if_window_is_zero() { debouncer.receive(1); debouncer.receive(2); - Mockito.verify(adminExecutor, never()) - .schedule(Mockito.any(Runnable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class)); + verify(adminExecutor, never()).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)); assertThat(results).containsExactly("1", "2"); } @@ -87,8 +88,7 @@ public void should_flush_synchronously_if_max_events_is_one() { debouncer.receive(1); debouncer.receive(2); - Mockito.verify(adminExecutor, never()) - .schedule(Mockito.any(Runnable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class)); + verify(adminExecutor, never()).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)); assertThat(results).containsExactly("1", "2"); } @@ -102,11 +102,8 @@ public void should_debounce_after_time_window_if_no_other_event() { // a task should have been scheduled, run it ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - Mockito.verify(adminExecutor) - .schedule( - captor.capture(), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); + verify(adminExecutor) + .schedule(captor.capture(), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); captor.getValue().run(); // the element should have been flushed @@ -121,25 +118,19 @@ public void should_reset_time_window_when_new_event() { debouncer.receive(1); debouncer.receive(2); - InOrder inOrder = Mockito.inOrder(adminExecutor, scheduledFuture); + InOrder inOrder = inOrder(adminExecutor, scheduledFuture); // a first task should have been scheduled, and then cancelled inOrder .verify(adminExecutor) - .schedule( - Mockito.any(Runnable.class), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); + .schedule(any(Runnable.class), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); inOrder.verify(scheduledFuture).cancel(true); // a second task should have been scheduled, run it ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); inOrder .verify(adminExecutor) - .schedule( - captor.capture(), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); + .schedule(captor.capture(), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); captor.getValue().run(); // both elements should have been flushed together @@ -154,12 +145,9 @@ public void should_force_flush_after_max_events() { for (int i = 0; i < 10; i++) { debouncer.receive(i); } - Mockito.verify(adminExecutor, times(9)) - .schedule( - Mockito.any(Runnable.class), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); - Mockito.verify(scheduledFuture, times(9)).cancel(true); + verify(adminExecutor, times(9)) + .schedule(any(Runnable.class), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); + verify(scheduledFuture, times(9)).cancel(true); assertThat(results).containsExactly("0,1,2,3,4,5,6,7,8,9"); } @@ -170,14 +158,11 @@ public void should_cancel_next_flush_when_stopped() { adminExecutor, this::coalesce, this::flush, DEFAULT_WINDOW, DEFAULT_MAX_EVENTS); debouncer.receive(1); - Mockito.verify(adminExecutor) - .schedule( - Mockito.any(Runnable.class), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); + verify(adminExecutor) + .schedule(any(Runnable.class), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); debouncer.stop(); - Mockito.verify(scheduledFuture).cancel(true); + verify(scheduledFuture).cancel(true); } @Test @@ -188,10 +173,7 @@ public void should_ignore_new_events_when_flushed() { debouncer.stop(); debouncer.receive(1); - Mockito.verify(adminExecutor, never()) - .schedule( - Mockito.any(Runnable.class), - Mockito.eq(DEFAULT_WINDOW.toNanos()), - Mockito.eq(TimeUnit.NANOSECONDS)); + verify(adminExecutor, never()) + .schedule(any(Runnable.class), eq(DEFAULT_WINDOW.toNanos()), eq(TimeUnit.NANOSECONDS)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java index 19ff1da3f4b..24bd02b1b73 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ReconnectionTest.java @@ -17,6 +17,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.TestDataProviders; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule; @@ -33,7 +36,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @RunWith(DataProviderRunner.class) @@ -74,38 +76,38 @@ public void should_start_out_not_running() { @Test public void should_schedule_first_attempt_on_start() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); // When reconnection.start(); // Then - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); assertThat(reconnection.isRunning()).isTrue(); - Mockito.verify(onStartCallback).run(); + verify(onStartCallback).run(); } @Test public void should_ignore_start_if_already_started() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(1)); reconnection.start(); - Mockito.verify(reconnectionSchedule).nextDelay(); - Mockito.verify(onStartCallback).run(); + verify(reconnectionSchedule).nextDelay(); + verify(onStartCallback).run(); // When reconnection.start(); // Then - Mockito.verifyNoMoreInteractions(reconnectionSchedule, onStartCallback); + verifyNoMoreInteractions(reconnectionSchedule, onStartCallback); } @Test public void should_stop_if_first_attempt_succeeds() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); reconnection.start(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); // When // the reconnection task is scheduled: @@ -117,15 +119,15 @@ public void should_stop_if_first_attempt_succeeds() { // Then assertThat(reconnection.isRunning()).isFalse(); - Mockito.verify(onStopCallback).run(); + verify(onStopCallback).run(); } @Test public void should_reschedule_if_first_attempt_fails() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); reconnection.start(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); // When // the reconnection task is scheduled: @@ -137,7 +139,7 @@ public void should_reschedule_if_first_attempt_fails() { // Then // schedule was called again - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + verify(reconnectionSchedule, times(2)).nextDelay(); runPendingTasks(); // task was called again assertThat(reconnectionTask.callCount()).isEqualTo(2); @@ -151,15 +153,15 @@ public void should_reschedule_if_first_attempt_fails() { // Then assertThat(reconnection.isRunning()).isFalse(); - Mockito.verify(onStopCallback).run(); + verify(onStopCallback).run(); } @Test public void should_reconnect_now_if_next_attempt_not_started() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); reconnection.start(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); // When reconnection.reconnectNow(false); @@ -171,13 +173,13 @@ public void should_reconnect_now_if_next_attempt_not_started() { // if that attempt fails, another reconnection should be scheduled reconnectionTask.complete(false); runPendingTasks(); - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + verify(reconnectionSchedule, times(2)).nextDelay(); } @Test public void should_reconnect_now_if_stopped_and_forced() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); assertThat(reconnection.isRunning()).isFalse(); // When @@ -190,14 +192,14 @@ public void should_reconnect_now_if_stopped_and_forced() { // if that attempt failed, another reconnection was scheduled reconnectionTask.complete(false); runPendingTasks(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); } @Test @UseDataProvider(location = TestDataProviders.class, value = "booleans") public void should_reconnect_now_when_attempt_in_progress(boolean force) { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); reconnection.start(); runPendingTasks(); // the next scheduled attempt has started, but not completed yet @@ -233,27 +235,27 @@ public void should_not_reconnect_now_if_stopped_and_not_forced() { @Test public void should_stop_between_attempts() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); reconnection.start(); runPendingTasks(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); // When reconnection.stop(); runPendingTasks(); // Then - Mockito.verify(onStopCallback).run(); + verify(onStopCallback).run(); assertThat(reconnection.isRunning()).isFalse(); } @Test public void should_restart_after_stopped_between_attempts() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofSeconds(10)); reconnection.start(); runPendingTasks(); - Mockito.verify(reconnectionSchedule).nextDelay(); + verify(reconnectionSchedule).nextDelay(); reconnection.stop(); runPendingTasks(); assertThat(reconnection.isRunning()).isFalse(); @@ -263,7 +265,7 @@ public void should_restart_after_stopped_between_attempts() { runPendingTasks(); // Then - Mockito.verify(reconnectionSchedule, times(2)).nextDelay(); + verify(reconnectionSchedule, times(2)).nextDelay(); assertThat(reconnection.isRunning()).isTrue(); } @@ -271,12 +273,12 @@ public void should_restart_after_stopped_between_attempts() { @UseDataProvider(location = TestDataProviders.class, value = "booleans") public void should_stop_while_attempt_in_progress(boolean outcome) { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); reconnection.start(); runPendingTasks(); // the next scheduled attempt has started, but not completed yet assertThat(reconnectionTask.callCount()).isEqualTo(1); - Mockito.verify(onStartCallback).run(); + verify(onStartCallback).run(); // When reconnection.stop(); @@ -285,22 +287,22 @@ public void should_stop_while_attempt_in_progress(boolean outcome) { // Then // should let the current attempt complete (whatever its outcome), and become stopped only then assertThat(reconnection.isRunning()).isTrue(); - Mockito.verifyNoMoreInteractions(onStopCallback); + verifyNoMoreInteractions(onStopCallback); reconnectionTask.complete(outcome); runPendingTasks(); - Mockito.verify(onStopCallback).run(); + verify(onStopCallback).run(); assertThat(reconnection.isRunning()).isFalse(); } @Test public void should_restart_after_stopped_while_attempt_in_progress() { // Given - Mockito.when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); + when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofNanos(1)); reconnection.start(); runPendingTasks(); // the next scheduled attempt has started, but not completed yet assertThat(reconnectionTask.callCount()).isEqualTo(1); - Mockito.verify(onStartCallback).run(); + verify(onStartCallback).run(); // now stop reconnection.stop(); runPendingTasks(); @@ -316,8 +318,8 @@ public void should_restart_after_stopped_while_attempt_in_progress() { assertThat(reconnectionTask.callCount()).isEqualTo(1); // because we were still in progress all the time, to the outside it's as if the stop/restart // had never happened - Mockito.verifyNoMoreInteractions(onStartCallback); - Mockito.verifyNoMoreInteractions(onStopCallback); + verifyNoMoreInteractions(onStartCallback); + verifyNoMoreInteractions(onStopCallback); // When reconnectionTask.complete(true); @@ -325,7 +327,7 @@ public void should_restart_after_stopped_while_attempt_in_progress() { // Then assertThat(reconnection.isRunning()).isFalse(); - Mockito.verify(onStopCallback).run(); + verify(onStopCallback).run(); } private void runPendingTasks() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java index 2b4a9d1e6ae..79f56fb3215 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/concurrent/ScheduledTaskCapturingEventLoop.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; import edu.umd.cs.findbugs.annotations.NonNull; @@ -32,7 +34,6 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.mockito.Mockito; /** * Extend Netty's default event loop to capture scheduled tasks instead of running them. The tasks @@ -129,7 +130,7 @@ public class CapturedTask { private final TimeUnit unit; @SuppressWarnings("unchecked") - private final ScheduledFuture scheduledFuture = Mockito.mock(ScheduledFuture.class); + private final ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); CapturedTask(Callable task, long initialDelay, TimeUnit unit) { this(task, initialDelay, -1, unit); @@ -142,14 +143,13 @@ public class CapturedTask { this.unit = unit; // If the code under test cancels the scheduled future, cancel our task - Mockito.when(scheduledFuture.cancel(anyBoolean())) + when(scheduledFuture.cancel(anyBoolean())) .thenAnswer(invocation -> futureTask.cancel(invocation.getArgument(0))); // Delegate methods of the scheduled future to our task (to be extended to more methods if // needed) - Mockito.when(scheduledFuture.isDone()).thenAnswer(invocation -> futureTask.isDone()); - Mockito.when(scheduledFuture.isCancelled()) - .thenAnswer(invocation -> futureTask.isCancelled()); + when(scheduledFuture.isDone()).thenAnswer(invocation -> futureTask.isDone()); + when(scheduledFuture.isCancelled()).thenAnswer(invocation -> futureTask.isCancelled()); } public void run() { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index aefaeb57f26..77ea21a84d3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -17,6 +17,7 @@ import static com.datastax.oss.driver.assertions.Assertions.assertThat; import static com.datastax.oss.driver.assertions.Assertions.fail; +import static org.mockito.Mockito.*; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; @@ -71,7 +72,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @Category(ParallelizableTests.class) @@ -80,7 +80,7 @@ public class NodeStateIT { private SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); - private NodeStateListener nodeStateListener = Mockito.mock(NodeStateListener.class); + private NodeStateListener nodeStateListener = mock(NodeStateListener.class); private InOrder inOrder; private SessionRule sessionRule = @@ -111,7 +111,7 @@ public class NodeStateIT { @Before public void setup() { - inOrder = Mockito.inOrder(nodeStateListener); + inOrder = inOrder(nodeStateListener); AtomicBoolean nonInitialEvent = new AtomicBoolean(false); driverContext = (InternalDriverContext) sessionRule.session().getContext(); @@ -172,7 +172,7 @@ public void setup() { @After public void teardown() { - Mockito.reset(nodeStateListener); + reset(nodeStateListener); } @Test @@ -336,7 +336,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) .withDuration(DefaultDriverOption.RECONNECTION_MAX_DELAY, Duration.ofHours(1)) .build(); - NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + NodeStateListener localNodeStateListener = mock(NodeStateListener.class); try (CqlSession session = SessionUtils.newSession(simulacron, null, localNodeStateListener, null, null, loader)) { @@ -347,7 +347,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() (DefaultNode) session.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); // UP fired a first time as part of the init process - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode); + verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode); localSimulacronNode.stop(); @@ -356,7 +356,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node going down") .before(10, TimeUnit.SECONDS) .becomesTrue(); - Mockito.verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode); + verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode); expect(NodeStateEvent.changed(NodeState.UP, NodeState.DOWN, localMetadataNode)); @@ -369,7 +369,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() .as("Node coming back up") .before(10, TimeUnit.SECONDS) .becomesTrue(); - Mockito.verify(localNodeStateListener, timeout(500).times(2)).onUp(localMetadataNode); + verify(localNodeStateListener, timeout(500).times(2)).onUp(localMetadataNode); expect(NodeStateEvent.changed(NodeState.DOWN, NodeState.UP, localMetadataNode)); } @@ -474,7 +474,7 @@ public void should_signal_non_contact_points_as_added() { Iterator contactPoints = simulacron.getContactPoints().iterator(); InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); - NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + NodeStateListener localNodeStateListener = mock(NodeStateListener.class); DriverConfigLoader loader = SessionUtils.configLoaderBuilder() .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) @@ -494,9 +494,9 @@ public void should_signal_non_contact_points_as_added() { Node localMetadataNode2 = nodes.get(address2); // Successful contact point goes to up directly - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); + verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); // Non-contact point only added since we don't have a connection or events for it yet - Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); + verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); } } @@ -506,7 +506,7 @@ public void should_remove_invalid_contact_point() { Iterator contactPoints = simulacron.getContactPoints().iterator(); InetSocketAddress address1 = contactPoints.next(); InetSocketAddress address2 = contactPoints.next(); - NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + NodeStateListener localNodeStateListener = mock(NodeStateListener.class); // Initialize the driver with 1 wrong address and 1 valid address InetSocketAddress wrongContactPoint = withUnusedPort(address1); @@ -531,10 +531,10 @@ public void should_remove_invalid_contact_point() { // The order of the calls is not deterministic because contact points are shuffled, but it // does not matter here since Mockito.verify does not enforce order. - Mockito.verify(localNodeStateListener, timeout(500)).onRemove(nodeCaptor.capture()); + verify(localNodeStateListener, timeout(500)).onRemove(nodeCaptor.capture()); assertThat(nodeCaptor.getValue().getConnectAddress()).isEqualTo(wrongContactPoint); - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); - Mockito.verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); + verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); + verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); // Note: there might be an additional onDown for wrongContactPoint if it was hit first at // init. This is hard to test since the node was removed later, so we simply don't call @@ -553,7 +553,7 @@ public void should_mark_unreachable_contact_point_down() { InetSocketAddress address1 = localSimulacronNode1.inetSocketAddress(); InetSocketAddress address2 = localSimulacronNode2.inetSocketAddress(); - NodeStateListener localNodeStateListener = Mockito.mock(NodeStateListener.class); + NodeStateListener localNodeStateListener = mock(NodeStateListener.class); localSimulacronNode2.stop(); try { @@ -579,18 +579,18 @@ public void should_mark_unreachable_contact_point_down() { Node localMetadataNode2 = nodes.get(address2); if (localMetadataNode2.getState() == NodeState.DOWN) { // Stopped node was tried first and marked down, that's our target scenario - Mockito.verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode2); - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); - Mockito.verifyNoMoreInteractions(localNodeStateListener); + verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode2); + verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); + verifyNoMoreInteractions(localNodeStateListener); return; } else { // Stopped node was not tried assertThat(localMetadataNode2).isUnknown(); - Mockito.verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); - Mockito.verifyNoMoreInteractions(localNodeStateListener); + verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); + verifyNoMoreInteractions(localNodeStateListener); } } - Mockito.reset(localNodeStateListener); + reset(localNodeStateListener); } fail("Couldn't get the driver to try stopped node first (tried 5 times)"); } finally { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index 0acfaf213a3..cdd90eae022 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.api.core.metadata; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -45,7 +46,6 @@ import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import org.mockito.Mockito; @Category(ParallelizableTests.class) public class SchemaChangesIT { @@ -91,7 +91,7 @@ public void should_handle_keyspace_creation() { .containsEntry("class", "org.apache.cassandra.locator.SimpleStrategy") .containsEntry("replication_factor", "1"); }, - (listener, keyspace) -> Mockito.verify(listener).onKeyspaceCreated(keyspace), + (listener, keyspace) -> verify(listener).onKeyspaceCreated(keyspace), newKeyspaceId); } @@ -106,7 +106,7 @@ public void should_handle_keyspace_drop() { newKeyspaceId.asCql(true))), String.format("DROP KEYSPACE %s", newKeyspaceId.asCql(true)), metadata -> metadata.getKeyspace(newKeyspaceId), - (listener, oldKeyspace) -> Mockito.verify(listener).onKeyspaceDropped(oldKeyspace), + (listener, oldKeyspace) -> verify(listener).onKeyspaceDropped(oldKeyspace), newKeyspaceId); } @@ -127,7 +127,7 @@ public void should_handle_keyspace_update() { metadata -> metadata.getKeyspace(newKeyspaceId), newKeyspace -> assertThat(newKeyspace.isDurableWrites()).isFalse(), (listener, oldKeyspace, newKeyspace) -> - Mockito.verify(listener).onKeyspaceUpdated(newKeyspace, oldKeyspace), + verify(listener).onKeyspaceUpdated(newKeyspace, oldKeyspace), newKeyspaceId); } @@ -154,7 +154,7 @@ public void should_handle_table_creation() { }); assertThat(table.getClusteringColumns()).isEmpty(); }, - (listener, table) -> Mockito.verify(listener).onTableCreated(table)); + (listener, table) -> verify(listener).onTableCreated(table)); } @Test @@ -166,7 +166,7 @@ public void should_handle_table_drop() { metadata .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getTable(CqlIdentifier.fromInternal("foo"))), - (listener, oldTable) -> Mockito.verify(listener).onTableDropped(oldTable)); + (listener, oldTable) -> verify(listener).onTableDropped(oldTable)); } @Test @@ -179,8 +179,7 @@ public void should_handle_table_update() { .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getTable(CqlIdentifier.fromInternal("foo"))), newTable -> assertThat(newTable.getColumn(CqlIdentifier.fromInternal("v"))).isPresent(), - (listener, oldTable, newTable) -> - Mockito.verify(listener).onTableUpdated(newTable, oldTable)); + (listener, oldTable, newTable) -> verify(listener).onTableUpdated(newTable, oldTable)); } @Test @@ -198,7 +197,7 @@ public void should_handle_type_creation() { assertThat(type.getFieldNames()).containsExactly(CqlIdentifier.fromInternal("i")); assertThat(type.getFieldTypes()).containsExactly(DataTypes.INT); }, - (listener, type) -> Mockito.verify(listener).onUserDefinedTypeCreated(type)); + (listener, type) -> verify(listener).onUserDefinedTypeCreated(type)); } @Test @@ -210,7 +209,7 @@ public void should_handle_type_drop() { metadata .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getUserDefinedType(CqlIdentifier.fromInternal("t"))), - (listener, oldType) -> Mockito.verify(listener).onUserDefinedTypeDropped(oldType)); + (listener, oldType) -> verify(listener).onUserDefinedTypeDropped(oldType)); } @Test @@ -226,7 +225,7 @@ public void should_handle_type_update() { assertThat(newType.getFieldNames()) .containsExactly(CqlIdentifier.fromInternal("i"), CqlIdentifier.fromInternal("j")), (listener, oldType, newType) -> - Mockito.verify(listener).onUserDefinedTypeUpdated(newType, oldType)); + verify(listener).onUserDefinedTypeUpdated(newType, oldType)); } @Test @@ -254,7 +253,7 @@ public void should_handle_view_creation() { CqlIdentifier.fromInternal("score"), CqlIdentifier.fromInternal("user")); }, - (listener, view) -> Mockito.verify(listener).onViewCreated(view)); + (listener, view) -> verify(listener).onViewCreated(view)); } @Test @@ -272,7 +271,7 @@ public void should_handle_view_drop() { metadata .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getView(CqlIdentifier.fromInternal("highscores"))), - (listener, oldView) -> Mockito.verify(listener).onViewDropped(oldView)); + (listener, oldView) -> verify(listener).onViewDropped(oldView)); } @Test @@ -293,7 +292,7 @@ public void should_handle_view_update() { newView -> assertThat(newView.getOptions().get(CqlIdentifier.fromInternal("comment"))) .isEqualTo("The best score for each game"), - (listener, oldView, newView) -> Mockito.verify(listener).onViewUpdated(newView, oldView)); + (listener, oldView, newView) -> verify(listener).onViewUpdated(newView, oldView)); } @Test @@ -316,7 +315,7 @@ public void should_handle_function_creation() { assertThat(function.isCalledOnNullInput()).isFalse(); assertThat(function.getBody()).isEqualTo("return i;"); }, - (listener, function) -> Mockito.verify(listener).onFunctionCreated(function)); + (listener, function) -> verify(listener).onFunctionCreated(function)); } @Test @@ -331,7 +330,7 @@ public void should_handle_function_drop() { metadata .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT)), - (listener, oldFunction) -> Mockito.verify(listener).onFunctionDropped(oldFunction)); + (listener, oldFunction) -> verify(listener).onFunctionDropped(oldFunction)); } @Test @@ -350,7 +349,7 @@ public void should_handle_function_update() { .flatMap(ks -> ks.getFunction(CqlIdentifier.fromInternal("id"), DataTypes.INT)), newFunction -> assertThat(newFunction.getBody()).isEqualTo("return j;"), (listener, oldFunction, newFunction) -> - Mockito.verify(listener).onFunctionUpdated(newFunction, oldFunction)); + verify(listener).onFunctionUpdated(newFunction, oldFunction)); } @Test @@ -375,7 +374,7 @@ public void should_handle_aggregate_creation() { assertThat(aggregate.getFinalFuncSignature()).isEmpty(); assertThat(aggregate.getInitCond()).hasValue(0); }, - (listener, aggregate) -> Mockito.verify(listener).onAggregateCreated(aggregate)); + (listener, aggregate) -> verify(listener).onAggregateCreated(aggregate)); } @Test @@ -391,7 +390,7 @@ public void should_handle_aggregate_drop() { metadata .getKeyspace(adminSessionRule.keyspace()) .flatMap(ks -> ks.getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT)), - (listener, oldAggregate) -> Mockito.verify(listener).onAggregateDropped(oldAggregate)); + (listener, oldAggregate) -> verify(listener).onAggregateDropped(oldAggregate)); } @Test @@ -410,7 +409,7 @@ public void should_handle_aggregate_update() { .flatMap(ks -> ks.getAggregate(CqlIdentifier.fromInternal("sum"), DataTypes.INT)), newAggregate -> assertThat(newAggregate.getInitCond()).hasValue(1), (listener, oldAggregate, newAggregate) -> - Mockito.verify(listener).onAggregateUpdated(newAggregate, oldAggregate)); + verify(listener).onAggregateUpdated(newAggregate, oldAggregate)); } private void should_handle_creation( @@ -425,8 +424,8 @@ private void should_handle_creation( adminSessionRule.session().execute(beforeStatement); } - SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + SchemaChangeListener listener1 = mock(SchemaChangeListener.class); + SchemaChangeListener listener2 = mock(SchemaChangeListener.class); // cluster1 executes the DDL query and gets a SCHEMA_CHANGE response. // cluster2 gets a SCHEMA_CHANGE push event on its control connection. @@ -478,8 +477,8 @@ private void should_handle_drop( adminSessionRule.session().execute(statement); } - SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + SchemaChangeListener listener1 = mock(SchemaChangeListener.class); + SchemaChangeListener listener2 = mock(SchemaChangeListener.class); List keyspaceList = Lists.newArrayList(); for (CqlIdentifier keyspace : keyspaces) { @@ -526,8 +525,8 @@ private void should_handle_update( adminSessionRule.session().execute(statement); } - SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + SchemaChangeListener listener1 = mock(SchemaChangeListener.class); + SchemaChangeListener listener2 = mock(SchemaChangeListener.class); List keyspaceList = Lists.newArrayList(); for (CqlIdentifier keyspace : keyspaces) { keyspaceList.add(keyspace.asInternal()); @@ -578,8 +577,8 @@ private void should_handle_update_via_drop_and_recreate( adminSessionRule.session().execute(statement); } - SchemaChangeListener listener1 = Mockito.mock(SchemaChangeListener.class); - SchemaChangeListener listener2 = Mockito.mock(SchemaChangeListener.class); + SchemaChangeListener listener1 = mock(SchemaChangeListener.class); + SchemaChangeListener listener2 = mock(SchemaChangeListener.class); List keyspaceList = Lists.newArrayList(); for (CqlIdentifier keyspace : keyspaces) { keyspaceList.add(keyspace.asInternal()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 3be0e2279c9..ab58b1ee737 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; @@ -56,7 +57,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.internal.verification.VerificationModeFactory; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.verification.Timeout; @@ -172,7 +172,7 @@ public void should_log_successful_request() { sessionRuleRequest.session().execute(QUERY); // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .contains("Success", "[0 values]", QUERY); } @@ -186,7 +186,7 @@ public void should_log_successful_request_with_defaults() { sessionRuleDefaults.session().execute(QUERY); // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .contains("Success", "[0 values]", QUERY); } @@ -205,7 +205,7 @@ public void should_log_failed_request_with_stack_trace() { } // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); ILoggingEvent log = loggingEventCaptor.getValue(); assertThat(log.getFormattedMessage()) .contains("Error", "[0 values]", QUERY) @@ -227,7 +227,7 @@ public void should_log_failed_request_with_stack_trace_with_defaults() { } // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); ILoggingEvent log = loggingEventCaptor.getValue(); assertThat(log.getFormattedMessage()) .contains("Error", "[0 values]", QUERY, ServerError.class.getName()); @@ -249,7 +249,7 @@ public void should_log_failed_request_without_stack_trace() { } // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); ILoggingEvent log = loggingEventCaptor.getValue(); assertThat(log.getFormattedMessage()) .contains("Error", "[0 values]", QUERY, ServerError.class.getName()); @@ -267,7 +267,7 @@ public void should_log_slow_request() { .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("low-threshold").build()); // Then - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .contains("Slow", "[0 values]", QUERY); } @@ -285,7 +285,7 @@ public void should_not_log_when_disabled() throws InterruptedException { // Then // We expect no messages. The request logger is invoked asynchronously, so simply wait a bit TimeUnit.MILLISECONDS.sleep(500); - Mockito.verify(appender, never()).doAppend(any(LoggingEvent.class)); + verify(appender, never()).doAppend(any(LoggingEvent.class)); } @Test @@ -308,7 +308,7 @@ public void should_log_failed_nodes_on_successful_request() { ResultSet set = sessionRuleNode.session().execute(QUERY); // Then - Mockito.verify(appender, new Timeout(500, VerificationModeFactory.times(3))) + verify(appender, new Timeout(500, VerificationModeFactory.times(3))) .doAppend(loggingEventCaptor.capture()); List events = loggingEventCaptor.getAllValues(); assertThat(events.get(0).getFormattedMessage()).contains("Error", "[0 values]", QUERY); @@ -335,7 +335,7 @@ public void should_log_successful_nodes_on_successful_request() { ResultSet set = sessionRuleNode.session().execute(QUERY); // Then - Mockito.verify(appender, new Timeout(500, VerificationModeFactory.times(2))) + verify(appender, new Timeout(500, VerificationModeFactory.times(2))) .doAppend(loggingEventCaptor.capture()); List events = loggingEventCaptor.getAllValues(); assertThat(events.get(0).getFormattedMessage()).contains("Success", "[0 values]", QUERY); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index 518e55a78ed..d418d66a2c9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; @@ -66,7 +67,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.slf4j.LoggerFactory; import org.slf4j.helpers.MessageFormatter; @@ -94,7 +94,7 @@ public class DefaultRetryPolicyIT { ArgumentCaptor.forClass(ILoggingEvent.class); @SuppressWarnings("unchecked") - private Appender appender = (Appender) Mockito.mock(Appender.class); + private Appender appender = (Appender) mock(Appender.class); private Logger logger; private Level oldLevel; @@ -146,7 +146,7 @@ public void should_not_retry_on_read_timeout_when_data_present() { counter.assertTotalCount(1); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } @Test @@ -172,7 +172,7 @@ public void should_not_retry_on_read_timeout_when_less_than_blockFor_received() counter.assertTotalCount(1); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } @Test @@ -199,7 +199,7 @@ public void should_retry_on_read_timeout_when_enough_responses_and_data_not_pres counter.assertNodeCounts(2, 0, 0); // verify log event was emitted as expected - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo( expectedMessage( @@ -242,7 +242,7 @@ public void should_retry_on_next_host_on_connection_error_if_idempotent() { counter.assertNodeCounts(1, 1, 0); // verify log event was emitted as expected - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo(expectedMessage(DefaultRetryPolicy.RETRYING_ON_ABORTED, logPrefix, 0)); } @@ -273,7 +273,7 @@ public void should_keep_retrying_on_next_host_on_connection_error() { counter.assertNodeCounts(1, 1, 1); // verify log event was emitted for each host as expected - Mockito.verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); + verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); // final log message should have 2 retries assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo(expectedMessage(DefaultRetryPolicy.RETRYING_ON_ABORTED, logPrefix, 2)); @@ -308,7 +308,7 @@ public void should_not_retry_on_connection_error_if_non_idempotent() { counter.assertTotalCount(1); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } @Test @@ -336,7 +336,7 @@ public void should_retry_on_write_timeout_if_write_type_batch_log() { counter.assertNodeCounts(2, 0, 0); // verify log event was emitted as expected - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo( expectedMessage( @@ -386,7 +386,7 @@ public void should_not_retry_on_write_timeout_if_write_type_non_batch_log( counter.assertTotalCount(1); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } @Test @@ -415,7 +415,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id counter.assertTotalCount(1); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } @Test @@ -442,7 +442,7 @@ public void should_retry_on_next_host_on_unavailable() { counter.assertNodeCounts(1, 1, 0); // verify log event was emitted as expected - Mockito.verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo( expectedMessage( @@ -496,7 +496,7 @@ public void should_keep_retrying_on_next_host_on_error_response() { counter.assertNodeCounts(1, 1, 1); // verify log event was emitted for each host as expected - Mockito.verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); + verify(appender, after(500).times(3)).doAppend(loggingEventCaptor.capture()); // final log message should have 2 retries assertThat(loggingEventCaptor.getValue().getFormattedMessage()) .isEqualTo(expectedMessage(DefaultRetryPolicy.RETRYING_ON_ERROR, logPrefix, 2)); @@ -523,7 +523,7 @@ public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() counter.assertNodeCounts(1, 0, 0); // expect no logging messages since there was no retry - Mockito.verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); + verify(appender, after(500).times(0)).doAppend(any(ILoggingEvent.class)); } private String expectedMessage(String template, Object... args) { diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java index c685ed7c6e3..b10f3aeb7a9 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java @@ -21,7 +21,6 @@ import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createAggregate; import com.datastax.oss.driver.api.core.type.DataTypes; -import org.assertj.core.api.Assertions; import org.junit.Test; public class CreateAggregateTest { @@ -135,18 +134,18 @@ public void should_create_or_replace() { @Test public void should_not_throw_on_toString_for_CreateAggregateStart() { - Assertions.assertThat(createAggregate("agg1").toString()).isEqualTo("CREATE AGGREGATE agg1 ()"); + assertThat(createAggregate("agg1").toString()).isEqualTo("CREATE AGGREGATE agg1 ()"); } @Test public void should_not_throw_on_toString_for_CreateAggregateWithParam() { - Assertions.assertThat(createAggregate("func1").withParameter(DataTypes.INT).toString()) + assertThat(createAggregate("func1").withParameter(DataTypes.INT).toString()) .isEqualTo("CREATE AGGREGATE func1 (int)"); } @Test public void should_not_throw_on_toString_for_NotExists_OrReplace() { - Assertions.assertThat(createAggregate("func1").ifNotExists().orReplace().toString()) + assertThat(createAggregate("func1").ifNotExists().orReplace().toString()) .isEqualTo("CREATE OR REPLACE AGGREGATE IF NOT EXISTS func1 ()"); } } From 2177de63e946f3de28437f71cdcf1a6b407f84b9 Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Tue, 12 Feb 2019 02:50:54 -0800 Subject: [PATCH 682/742] JAVA-2144: Expose internal API to hook into the session lifecycle (#1186) --- changelog/README.md | 1 + .../core/context/InternalDriverContext.java | 16 +++ .../core/context/LifecycleListener.java | 54 ++++++++ .../internal/core/session/DefaultSession.java | 18 +++ .../api/core/context/LifecycleListenerIT.java | 123 ++++++++++++++++++ 5 files changed, 212 insertions(+) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/context/LifecycleListener.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java diff --git a/changelog/README.md b/changelog/README.md index f5842f5c57e..244d57a27cd 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [new feature] JAVA-2144: Expose internal API to hook into the session lifecycle - [improvement] JAVA-2119: Add PagingIterable abstraction as a supertype of ResultSet - [bug] JAVA-2063: Normalize authentication logging - [documentation] JAVA-2034: Add performance recommendations in the manual diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index d7ff7588ef9..03c1e1df439 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -41,6 +41,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.buffer.ByteBuf; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -145,4 +147,18 @@ public interface InternalDriverContext extends DriverContext { */ @NonNull Map getStartupOptions(); + + /** + * A list of additional components to notify of session lifecycle events. + * + *

        The default implementation returns an empty list. Custom driver extensions might override + * this method to add their own components. + * + *

        Note that the driver assumes that the returned list is constant; there is no way to add + * listeners dynamically. + */ + @NonNull + default List getLifecycleListeners() { + return Collections.emptyList(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/LifecycleListener.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/LifecycleListener.java new file mode 100644 index 00000000000..31fcacfdcf1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/LifecycleListener.java @@ -0,0 +1,54 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.context; + +import com.datastax.oss.driver.api.core.session.SessionBuilder; + +/** + * A component that gets notified of certain events in the session's lifecycle. + * + *

        This is intended for third-party extensions, no built-in components implement this. + */ +public interface LifecycleListener extends AutoCloseable { + + /** + * Invoked when the session is ready to process user requests. + * + *

        This corresponds to the moment when the {@link SessionBuilder#build()} returns, or the + * future returned by {@link SessionBuilder#buildAsync()} completes. If the session initialization + * fails, this method will not get called. + * + *

        This method is invoked on a driver thread, it should complete relatively quickly and not + * block. + */ + void onSessionReady(); + + /** + * Invoked when the session shuts down. + * + *

        Implementations should perform any necessary cleanup, for example freeing resources or + * cancelling scheduled tasks. + * + *

        Note that this method gets called even if the shutdown results from a failed initialization. + * In that case, implementations should be ready to handle a call to this method even though + * {@link #onSessionReady()} hasn't been invoked. + * + *

        This method is invoked on a driver thread, it should complete relatively quickly and not + * block. + */ + @Override + void close() throws Exception; +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index ee032e90c94..f45f8a0a25c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.context.LifecycleListener; import com.datastax.oss.driver.internal.core.control.ControlConnection; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; @@ -394,6 +395,7 @@ private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { initFuture.completeExceptionally(error); } else { initFuture.complete(DefaultSession.this); + notifyLifecycleListeners(); } }); } catch (Throwable throwable) { @@ -405,6 +407,21 @@ private void afterInitialSchemaRefresh(CqlIdentifier keyspace) { } } + private void notifyLifecycleListeners() { + for (LifecycleListener lifecycleListener : context.getLifecycleListeners()) { + try { + lifecycleListener.onSessionReady(); + } catch (Throwable t) { + Loggers.warnWithException( + LOG, + "[{}] Error while notifying {} of session ready", + logPrefix, + lifecycleListener, + t); + } + } + } + private void onNodeStateChanged(NodeStateEvent event) { assert adminExecutor.inEventLoop(); if (event.newState == null) { @@ -537,6 +554,7 @@ private void closePolicies() { } catch (Throwable t) { // ignore } + policies.addAll(context.getLifecycleListeners()); // Finally we have a list of all the policies that initialized successfully, close them: for (AutoCloseable policy : policies) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java new file mode 100644 index 00000000000..47dfa73c3b1 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java @@ -0,0 +1,123 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.context; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.context.LifecycleListener; +import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.server.RejectScope; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Collections; +import java.util.List; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(ParallelizableTests.class) +public class LifecycleListenerIT { + + @ClassRule + public static SimulacronRule simulacronRule = + new SimulacronRule(ClusterSpec.builder().withNodes(1)); + + @Test + public void should_notify_listener_of_init_and_shutdown() { + TestLifecycleListener listener = new TestLifecycleListener(); + assertThat(listener.ready).isFalse(); + assertThat(listener.closed).isFalse(); + + try (CqlSession session = newSession(listener)) { + assertThat(listener.ready).isTrue(); + assertThat(listener.closed).isFalse(); + } + assertThat(listener.ready).isTrue(); + assertThat(listener.closed).isTrue(); + } + + @Test + public void should_not_notify_listener_when_init_fails() { + TestLifecycleListener listener = new TestLifecycleListener(); + assertThat(listener.ready).isFalse(); + assertThat(listener.closed).isFalse(); + + simulacronRule.cluster().rejectConnections(0, RejectScope.STOP); + try (CqlSession session = newSession(listener)) { + fail("Expected AllNodesFailedException"); + } catch (AllNodesFailedException ignored) { + } finally { + simulacronRule.cluster().acceptConnections(); + } + assertThat(listener.ready).isFalse(); + assertThat(listener.closed).isTrue(); + } + + private CqlSession newSession(TestLifecycleListener listener) { + TestContext context = new TestContext(new DefaultDriverConfigLoader(), listener); + return CompletableFutures.getUninterruptibly( + DefaultSession.init(context, simulacronRule.getContactPoints(), null)); + } + + public static class TestLifecycleListener implements LifecycleListener { + public volatile boolean ready; + public volatile boolean closed; + + @Override + public void onSessionReady() { + ready = true; + } + + @Override + public void close() throws Exception { + closed = true; + } + } + + public static class TestContext extends DefaultDriverContext { + + private final List listeners; + + public TestContext(DriverConfigLoader configLoader, TestLifecycleListener listener) { + super( + configLoader, + Collections.emptyList(), + null, + null, + null, + Collections.emptyMap(), + Collections.emptyMap(), + null); + this.listeners = ImmutableList.of(listener); + } + + @NonNull + @Override + public List getLifecycleListeners() { + return listeners; + } + } +} From 9150b02e86578d8bb93e37a63c0ab61707e8a7b7 Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Tue, 12 Feb 2019 05:35:56 -0800 Subject: [PATCH 683/742] JAVA-2151: Drop "Dsl" suffix from query builder main classes (#1188) --- changelog/README.md | 1 + .../driver/api/core/type/codec/TypeCodec.java | 4 +- .../datastax/oss/driver/osgi/OsgiBaseIT.java | 2 +- manual/query_builder/README.md | 12 +++--- manual/query_builder/condition/README.md | 2 +- manual/query_builder/delete/README.md | 8 ++-- manual/query_builder/insert/README.md | 6 +-- manual/query_builder/relation/README.md | 4 +- manual/query_builder/schema/README.md | 6 +-- .../query_builder/schema/aggregate/README.md | 8 ++-- .../query_builder/schema/function/README.md | 8 ++-- manual/query_builder/schema/index/README.md | 8 ++-- .../query_builder/schema/keyspace/README.md | 8 ++-- .../schema/materialized_view/README.md | 8 ++-- manual/query_builder/schema/table/README.md | 10 ++--- manual/query_builder/schema/type/README.md | 10 ++--- manual/query_builder/select/README.md | 8 ++-- manual/query_builder/term/README.md | 6 +-- manual/query_builder/update/README.md | 8 ++-- query-builder/revapi.json | 20 ++++++++++ ...QueryBuilderDsl.java => QueryBuilder.java} | 2 +- .../oss/driver/api/querybuilder/Raw.java | 2 +- ...hemaBuilderDsl.java => SchemaBuilder.java} | 4 +- .../condition/ConditionalStatement.java | 8 ++-- .../querybuilder/delete/DeleteSelection.java | 8 ++-- .../relation/InRelationBuilder.java | 4 +- .../relation/OngoingWhereClause.java | 4 +- .../schema/AlterTableAddColumn.java | 6 +-- .../querybuilder/schema/AlterTableStart.java | 4 +- .../querybuilder/schema/AlterTypeStart.java | 6 +-- .../schema/CreateAggregateStart.java | 4 +- .../schema/CreateAggregateStateFunc.java | 4 +- .../schema/CreateFunctionStart.java | 4 +- .../schema/CreateFunctionWithNullOption.java | 4 +- .../api/querybuilder/schema/CreateTable.java | 8 ++-- .../schema/OngoingCreateType.java | 4 +- .../schema/OngoingPartitionKey.java | 4 +- .../querybuilder/schema/RelationOptions.java | 10 ++--- .../querybuilder/select/OngoingSelection.java | 22 +++++------ .../api/querybuilder/select/Select.java | 10 ++--- .../api/querybuilder/select/Selector.java | 8 ++-- .../driver/api/querybuilder/term/Term.java | 7 ++-- .../api/querybuilder/update/Assignment.java | 6 +-- .../update/OngoingAssignment.java | 8 ++-- .../querybuilder/insert/DefaultInsert.java | 4 +- .../querybuilder/condition/ConditionTest.java | 6 +-- .../delete/DeleteFluentConditionTest.java | 6 +-- .../delete/DeleteFluentRelationTest.java | 4 +- .../delete/DeleteIdempotenceTest.java | 10 ++--- .../delete/DeleteSelectorTest.java | 6 +-- .../delete/DeleteTimestampTest.java | 4 +- .../insert/InsertIdempotenceTest.java | 12 +++--- .../querybuilder/insert/JsonInsertTest.java | 4 +- .../insert/RegularInsertTest.java | 6 +-- .../querybuilder/relation/RelationTest.java | 8 ++-- .../api/querybuilder/relation/TermTest.java | 38 +++++++++---------- .../schema/AlterKeyspaceTest.java | 2 +- .../schema/AlterMaterializedViewTest.java | 2 +- .../querybuilder/schema/AlterTableTest.java | 2 +- .../querybuilder/schema/AlterTypeTest.java | 2 +- .../schema/CreateAggregateTest.java | 6 +-- .../schema/CreateFunctionTest.java | 4 +- .../querybuilder/schema/CreateIndexTest.java | 2 +- .../schema/CreateKeyspaceTest.java | 2 +- .../schema/CreateMaterializedViewTest.java | 4 +- .../querybuilder/schema/CreateTableTest.java | 14 +++---- .../querybuilder/schema/CreateTypeTest.java | 4 +- .../schema/DropAggregateTest.java | 2 +- .../querybuilder/schema/DropFunctionTest.java | 2 +- .../querybuilder/schema/DropIndexTest.java | 2 +- .../querybuilder/schema/DropKeyspaceTest.java | 2 +- .../schema/DropMaterializedViewTest.java | 2 +- .../querybuilder/schema/DropTableTest.java | 2 +- .../api/querybuilder/schema/DropTypeTest.java | 2 +- .../select/SelectAllowFilteringTest.java | 2 +- .../select/SelectFluentRelationTest.java | 8 ++-- .../select/SelectGroupByTest.java | 4 +- .../querybuilder/select/SelectLimitTest.java | 4 +- .../select/SelectOrderingTest.java | 4 +- .../select/SelectSelectorTest.java | 6 +-- .../update/UpdateFluentAssignmentTest.java | 6 +-- .../update/UpdateFluentConditionTest.java | 6 +-- .../update/UpdateFluentRelationTest.java | 4 +- .../update/UpdateIdempotenceTest.java | 10 ++--- .../update/UpdateTimestampTest.java | 4 +- 85 files changed, 271 insertions(+), 251 deletions(-) rename query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/{QueryBuilderDsl.java => QueryBuilder.java} (99%) rename query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/{SchemaBuilderDsl.java => SchemaBuilder.java} (99%) diff --git a/changelog/README.md b/changelog/README.md index 244d57a27cd..30be47feb13 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2151: Drop "Dsl" suffix from query builder main classes - [new feature] JAVA-2144: Expose internal API to hook into the session lifecycle - [improvement] JAVA-2119: Add PagingIterable abstraction as a supertype of ResultSet - [bug] JAVA-2063: Normalize authentication logging diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java index c90b77e6383..777d35b25fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java @@ -204,8 +204,8 @@ default boolean accepts(@NonNull DataType cqlType) { *

      • to format the INITCOND in {@link AggregateMetadata#describe(boolean)}; *
      • in the {@code toString()} representation of some driver objects (such as {@link UdtValue} * and {@link TupleValue}), which is only used in driver logs; - *
      • for literal values in the query builder (see {@code QueryBuilderDsl#literal(Object, - * CodecRegistry)} and {@code QueryBuilderDsl#literal(Object, TypeCodec)}). + *
      • for literal values in the query builder (see {@code QueryBuilder#literal(Object, + * CodecRegistry)} and {@code QueryBuilder#literal(Object, TypeCodec)}). * * * If you choose not to implement this method, don't throw an exception but instead return a diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java index 16ee962c991..e173825bf74 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.osgi; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.CqlSession; diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index d21da93e8f5..f3f8e97ae16 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -21,7 +21,7 @@ To use it in your application, add the following dependency: Here is our canonical example rewritten with the query builder: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; try (CqlSession session = CqlSession.builder().build()) { @@ -38,11 +38,11 @@ try (CqlSession session = CqlSession.builder().build()) { #### Fluent API -All the starting methods are centralized in the [QueryBuilderDsl] class. To get started, add the +All the starting methods are centralized in the [QueryBuilder] class. To get started, add the following import: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; ``` Choose the method matching your desired statement, for example `selectFrom`. Then use your IDE's @@ -183,6 +183,6 @@ For a complete tour of the API, browse the child pages in this manual: * [Terms](term/) * [Idempotence](idempotence/) -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/SchemaBuilderDsl.html -[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/SchemaBuilder.html +[CqlIdentifier]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlIdentifier.html diff --git a/manual/query_builder/condition/README.md b/manual/query_builder/condition/README.md index a14b1b4bd66..ae5b489ea10 100644 --- a/manual/query_builder/condition/README.md +++ b/manual/query_builder/condition/README.md @@ -17,7 +17,7 @@ You can also create it manually with one of the factory methods in [Condition], `if_()`: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; Condition vCondition = Condition.column("v").isEqualTo(literal(1)); deleteFrom("user") diff --git a/manual/query_builder/delete/README.md b/manual/query_builder/delete/README.md index 19359af40bd..8117723962a 100644 --- a/manual/query_builder/delete/README.md +++ b/manual/query_builder/delete/README.md @@ -1,11 +1,11 @@ ## DELETE -To start a DELETE query, use one of the `deleteFrom` methods in [QueryBuilderDsl]. There are several +To start a DELETE query, use one of the `deleteFrom` methods in [QueryBuilder]. There are several variants depending on whether your table name is qualified, and whether you use case-sensitive identifiers or case-insensitive strings: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; DeleteSelection delete = deleteFrom("user"); ``` @@ -141,5 +141,5 @@ deleteFrom("user") Conditions are a common feature used by UPDATE and DELETE, so they have a [dedicated page](../condition) in this manual. -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html diff --git a/manual/query_builder/insert/README.md b/manual/query_builder/insert/README.md index 5606580bbd1..a6bf16dbbc9 100644 --- a/manual/query_builder/insert/README.md +++ b/manual/query_builder/insert/README.md @@ -1,11 +1,11 @@ ## INSERT -To start an INSERT query, use one of the `insertInto` methods in [QueryBuilderDsl]. There are +To start an INSERT query, use one of the `insertInto` methods in [QueryBuilder]. There are several variants depending on whether your table name is qualified, and whether you use case-sensitive identifiers or case-insensitive strings: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; InsertInto insert = insertInto("user"); ``` @@ -87,4 +87,4 @@ insertInto("user").json(bindMarker()).usingTimestamp(bindMarker()) If you call the method multiple times, the last value will be used. -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html diff --git a/manual/query_builder/relation/README.md b/manual/query_builder/relation/README.md index 80ed83fa274..5b03cb0bb4b 100644 --- a/manual/query_builder/relation/README.md +++ b/manual/query_builder/relation/README.md @@ -201,5 +201,5 @@ This should be used with caution, as it's possible to generate invalid CQL that execution time; on the other hand, it can be used as a workaround to handle new CQL features that are not yet covered by the query builder. -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[Relation]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/relation/Relation.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[Relation]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/relation/Relation.html diff --git a/manual/query_builder/schema/README.md b/manual/query_builder/schema/README.md index fba9e417f47..4a6691725e8 100644 --- a/manual/query_builder/schema/README.md +++ b/manual/query_builder/schema/README.md @@ -8,10 +8,10 @@ one to *generate CQL DDL queries programmatically**. For example it could be us * given a Java class that represents a table, view, or user defined type, generate representative schema DDL `CREATE` queries. -Here is an example that demonstrates creating a keyspace and a table using [SchemaBuilderDsl]: +Here is an example that demonstrates creating a keyspace and a table using [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; try (CqlSession session = CqlSession.builder().build()) { CreateKeyspace createKs = createKeyspace("cycling").withSimpleStrategy(1); @@ -44,4 +44,4 @@ element type: * [function](function/) * [aggregate](aggregate/) -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/schema/aggregate/README.md b/manual/query_builder/schema/aggregate/README.md index 09a8d835338..e6f23085b69 100644 --- a/manual/query_builder/schema/aggregate/README.md +++ b/manual/query_builder/schema/aggregate/README.md @@ -1,15 +1,15 @@ ## Aggregate Aggregates enable users to apply User-defined functions (UDF) to rows in a data set and combine -their values into a final result, for example average or standard deviation. [SchemaBuilderDsl] +their values into a final result, for example average or standard deviation. [SchemaBuilder] offers API methods for creating and dropping aggregates. ### Creating an aggregate (CREATE AGGREGATE) -To start a `CREATE AGGREGATE` query, use `createAggregate` in [SchemaBuilderDsl]: +To start a `CREATE AGGREGATE` query, use `createAggregate` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateAggregateStart create = createAggregate("average"); ``` @@ -76,4 +76,4 @@ dropAggregate("average").ifExists(); // DROP AGGREGATE IF EXISTS average ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/schema/function/README.md b/manual/query_builder/schema/function/README.md index 56717bfd96a..984b2e43518 100644 --- a/manual/query_builder/schema/function/README.md +++ b/manual/query_builder/schema/function/README.md @@ -1,15 +1,15 @@ ## Function User-defined functions (UDF) enable users to create user code written in JSR-232 compliant scripting -languages that can be evaluated in CQL queries. [SchemaBuilderDsl] offers API methods for creating +languages that can be evaluated in CQL queries. [SchemaBuilder] offers API methods for creating and dropping UDFs. ### Creating a Function (CREATE FUNCTION) -To start a `CREATE FUNCTION` query, use `createFunction` in [SchemaBuilderDsl]: +To start a `CREATE FUNCTION` query, use `createFunction` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateFunctionStart create = createFunction("log"); ``` @@ -92,4 +92,4 @@ dropFunction("log").ifExists(); // DROP FUNCTION IF EXISTS log ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/schema/index/README.md b/manual/query_builder/schema/index/README.md index 8624f2c8a30..dcb623ce5f7 100644 --- a/manual/query_builder/schema/index/README.md +++ b/manual/query_builder/schema/index/README.md @@ -1,15 +1,15 @@ # Index -An index provides a means of expanding the query capabilities of a table. [SchemaBuilderDsl] offers +An index provides a means of expanding the query capabilities of a table. [SchemaBuilder] offers API methods for creating and dropping indices. Unlike other schema members, there is no mechanism to alter an index. ### Creating an Index (CREATE INDEX) -To start a `CREATE INDEX` query, use `createIndex` in [SchemaBuilderDsl]: +To start a `CREATE INDEX` query, use `createIndex` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; // an index name is not required CreateIndexStart create = createIndex(); @@ -99,4 +99,4 @@ dropIndex("my_idx").ifExists(); // DROP INDEX IF EXISTS my_idx ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/schema/keyspace/README.md b/manual/query_builder/schema/keyspace/README.md index 5a8a434f6bf..90ea1a8deb4 100644 --- a/manual/query_builder/schema/keyspace/README.md +++ b/manual/query_builder/schema/keyspace/README.md @@ -1,14 +1,14 @@ ## Keyspace A keyspace is a top-level namespace that defines a name, replication strategy and configurable -options. [SchemaBuilderDsl] offers API methods for creating, altering and dropping keyspaces. +options. [SchemaBuilder] offers API methods for creating, altering and dropping keyspaces. ### Creating a Keyspace (CREATE KEYSPACE) -To start a `CREATE KEYSPACE` query, use `createKeyspace` in [SchemaBuilderDsl]: +To start a `CREATE KEYSPACE` query, use `createKeyspace` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateKeyspaceStart create = createKeyspace("cycling"); ``` @@ -83,6 +83,6 @@ dropKeyspace("cycling").ifExists(); // DROP KEYSPACE IF EXISTS cycling ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/schema/materialized_view/README.md b/manual/query_builder/schema/materialized_view/README.md index 59e289462c1..f6c126353ab 100644 --- a/manual/query_builder/schema/materialized_view/README.md +++ b/manual/query_builder/schema/materialized_view/README.md @@ -2,15 +2,15 @@ Materialized Views are an experimental feature introduced in Apache Cassandra 3.0 that provide a mechanism for server-side denormalization from a base table into a view that is updated when the -base table is updated. [SchemaBuilderDsl] offers API methods for creating, altering and dropping +base table is updated. [SchemaBuilder] offers API methods for creating, altering and dropping materialized views. ### Creating a Materialized View (CREATE MATERIALIZED VIEW) -To start a `CREATE MATERIALIZED VIEW` query, use `createMaterializedView` in [SchemaBuilderDsl]: +To start a `CREATE MATERIALIZED VIEW` query, use `createMaterializedView` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateMaterializedViewStart create = createMaterializedView("cycling", "cyclist_by_age"); ``` @@ -85,5 +85,5 @@ dropTable("cyclist_by_age").ifExists(); // DROP MATERIALIZED VIEW IF EXISTS cyclist_by_age ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html [RelationStructure]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.html diff --git a/manual/query_builder/schema/table/README.md b/manual/query_builder/schema/table/README.md index cf30d41bec8..c35c6932fd3 100644 --- a/manual/query_builder/schema/table/README.md +++ b/manual/query_builder/schema/table/README.md @@ -1,14 +1,14 @@ ## Table -Data in Apache Cassandra is stored in tables. [SchemaBuilderDsl] offers API methods for creating, +Data in Apache Cassandra is stored in tables. [SchemaBuilder] offers API methods for creating, altering, and dropping tables. ### Creating a Table (CREATE TABLE) -To start a `CREATE TABLE` query, use `createTable` in [SchemaBuilderDsl]: +To start a `CREATE TABLE` query, use `createTable` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateTableStart create = createTable("cycling", "cyclist_name"); ``` @@ -107,6 +107,6 @@ dropTable("cyclist_name").ifExists(); // DROP TABLE IF EXISTS cyclist_name ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html [CreateTableWithOptions]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/CreateTableWithOptions.html -[AlterTableWithOptions]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.html +[AlterTableWithOptions]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/schema/AlterTableWithOptions.html diff --git a/manual/query_builder/schema/type/README.md b/manual/query_builder/schema/type/README.md index 6642e70d336..dfa236d7d91 100644 --- a/manual/query_builder/schema/type/README.md +++ b/manual/query_builder/schema/type/README.md @@ -1,14 +1,14 @@ ## Type User-defined types are special types that can associate multiple named fields to a single column. -[SchemaBuilderDsl] offers API methods for creating, altering, and dropping types. +[SchemaBuilder] offers API methods for creating, altering, and dropping types. ### Creating a Type (CREATE TYPE) -To start a `CREATE TYPE` query, use `createType` in [SchemaBuilderDsl]: +To start a `CREATE TYPE` query, use `createType` in [SchemaBuilder]: ```java -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.*; CreateTypeStart create = createType("mykeyspace", "address"); ``` @@ -43,7 +43,7 @@ CreateType create = createType("mykeyspace", "address") ### Using a created Type in Schema Builder API After creating a UDT, one may wonder how to use it in other schema statements. To do so, utilize -`udt(name,frozen)` from [SchemaBuilderDsl], i.e: +`udt(name,frozen)` from [SchemaBuilder], i.e: ```java CreateTable users = createTable("mykeyspace", "users") @@ -88,4 +88,4 @@ dropTable("address").ifExists(); // DROP TYPE IF EXISTS address ``` -[SchemaBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.html +[SchemaBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.html diff --git a/manual/query_builder/select/README.md b/manual/query_builder/select/README.md index b3bc49f9353..6151585de5d 100644 --- a/manual/query_builder/select/README.md +++ b/manual/query_builder/select/README.md @@ -1,11 +1,11 @@ ## SELECT -Start your SELECT with the `selectFrom` method in [QueryBuilderDsl]. There are several variants +Start your SELECT with the `selectFrom` method in [QueryBuilder]. There are several variants depending on whether your table name is qualified, and whether you use case-sensitive identifiers or case-insensitive strings: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; SelectFrom selectUser = selectFrom("user"); ``` @@ -391,5 +391,5 @@ selectFrom("user").all().allowFiltering(); // SELECT * FROM user ALLOW FILTERING ``` -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[Selector]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/select/Selector.html diff --git a/manual/query_builder/term/README.md b/manual/query_builder/term/README.md index 0214e3361d8..160572f2a3b 100644 --- a/manual/query_builder/term/README.md +++ b/manual/query_builder/term/README.md @@ -6,7 +6,7 @@ A term is an expression that does not involve the value of a column. It is used: selectors; * as the right operand of [relations](../relation). -To create a term, call one of the factory methods in [QueryBuilderDsl]: +To create a term, call one of the factory methods in [QueryBuilder]: ### Literals @@ -105,5 +105,5 @@ This should be used with caution, as it's possible to generate invalid CQL that execution time; on the other hand, it can be used as a workaround to handle new CQL features that are not yet covered by the query builder. -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[CodecRegistry]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.html \ No newline at end of file +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[CodecRegistry]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.html \ No newline at end of file diff --git a/manual/query_builder/update/README.md b/manual/query_builder/update/README.md index a57708c81a6..19d0b43d1e8 100644 --- a/manual/query_builder/update/README.md +++ b/manual/query_builder/update/README.md @@ -1,11 +1,11 @@ ## UPDATE -To start an UPDATE query, use one of the `update` methods in [QueryBuilderDsl]. There are several +To start an UPDATE query, use one of the `update` methods in [QueryBuilder]. There are several variants depending on whether your table name is qualified, and whether you use case-sensitive identifiers or case-insensitive strings: ```java -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.*; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; UpdateStart update = update("user"); ``` @@ -222,5 +222,5 @@ update("foo") Conditions are a common feature used by UPDATE and DELETE, so they have a [dedicated page](../condition) in this manual. -[QueryBuilderDsl]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilderDsl.html -[Assignment]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/update/Assignment.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +[Assignment]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/update/Assignment.html diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 5567afe6dcc..07d232e1493 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -104,6 +104,26 @@ "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", "elementKind": "parameter", "justification": "Fix nullability annotations on query builder range selectors" + }, + { + "code": "java.class.removed", + "old": "class com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", + "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" + }, + { + "code": "java.class.removed", + "old": "class com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", + "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition===)", + "new": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition===)", + "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-rc1-SNAPSHOT", + "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" } ] } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java similarity index 99% rename from query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java rename to query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java index c71f9a53ba3..62ade2a2de0 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilderDsl.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java @@ -49,7 +49,7 @@ import java.util.Arrays; /** A Domain-Specific Language to build CQL queries using Java code. */ -public class QueryBuilderDsl { +public class QueryBuilder { /** Starts a SELECT query for a qualified table. */ @NonNull diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java index 4a31d4c2277..453ee2f3b14 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/Raw.java @@ -25,7 +25,7 @@ * A raw CQL snippet that will be appended to the query as-is, without any syntax checking or * escaping. * - *

        To build an instance of this type, use {@link QueryBuilderDsl#raw(String)}. + *

        To build an instance of this type, use {@link QueryBuilder#raw(String)}. * *

        It should be used with caution, as it's possible to generate invalid CQL that will fail at * execution time; on the other hand, it can be used as a workaround to handle new CQL features that diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.java similarity index 99% rename from query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java rename to query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.java index 1205dc3cc96..e6f63fe1702 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilderDsl.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/SchemaBuilder.java @@ -58,7 +58,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; /** A Domain-Specific Language to build CQL DDL queries using Java code. */ -public class SchemaBuilderDsl { +public class SchemaBuilder { /** Starts a CREATE KEYSPACE query. */ @NonNull @@ -661,7 +661,7 @@ public static UserDefinedType udt(@NonNull String name, boolean frozen) { /** * Specifies the rows_per_partition configuration for table caching options. * - * @see RelationOptions#withCaching(boolean, SchemaBuilderDsl.RowsPerPartition) + * @see RelationOptions#withCaching(boolean, SchemaBuilder.RowsPerPartition) */ public static class RowsPerPartition { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java index 7ee88052368..e193e221d01 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.condition; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.condition.DefaultConditionBuilder; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; @@ -147,7 +147,7 @@ default ConditionBuilder ifElement(@NonNull String columnName, @NonNull T /** * Adds a raw CQL snippet as a condition. * - *

        This is the equivalent of creating a condition with {@link QueryBuilderDsl#raw(String)} and + *

        This is the equivalent of creating a condition with {@link QueryBuilder#raw(String)} and * passing it to {@link #if_(Condition)}. * *

        The contents will be appended to the query as-is, without any syntax checking or escaping. @@ -155,10 +155,10 @@ default ConditionBuilder ifElement(@NonNull String columnName, @NonNull T * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL * features that are not yet covered by the query builder. * - * @see QueryBuilderDsl#raw(String) + * @see QueryBuilder#raw(String) */ @NonNull default SelfT ifRaw(@NonNull String raw) { - return if_(QueryBuilderDsl.raw(raw)); + return if_(QueryBuilder.raw(raw)); } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java index ae01f198a18..8f325ced6e7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelection.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.querybuilder.BindMarker; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; import com.datastax.oss.driver.api.querybuilder.select.Selector; import com.datastax.oss.driver.api.querybuilder.term.Term; @@ -143,18 +143,18 @@ default DeleteSelection element(@NonNull String collectionName, @NonNull Term in /** * Specifies an element to delete as a raw CQL snippet. * - *

        This is a shortcut for {@link #selector(Selector) selector(QueryBuilderDsl.raw(raw))}. + *

        This is a shortcut for {@link #selector(Selector) selector(QueryBuilder.raw(raw))}. * *

        The contents will be appended to the query as-is, without any syntax checking or escaping. * This method should be used with caution, as it's possible to generate invalid CQL that will * fail at execution time; on the other hand, it can be used as a workaround to handle new CQL * features that are not yet covered by the query builder. * - * @see QueryBuilderDsl#raw(String) + * @see QueryBuilder#raw(String) */ @NonNull default DeleteSelection raw(@NonNull String raw) { - return selector(QueryBuilderDsl.raw(raw)); + return selector(QueryBuilder.raw(raw)); } /** diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java index 80c86870894..626ea69c5e2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.relation; import com.datastax.oss.driver.api.querybuilder.BindMarker; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -39,7 +39,7 @@ default ResultT in(@NonNull BindMarker bindMarker) { */ @NonNull default ResultT in(@NonNull Iterable alternatives) { - return build(" IN ", QueryBuilderDsl.tuple(alternatives)); + return build(" IN ", QueryBuilder.tuple(alternatives)); } /** Var-arg equivalent of {@link #in(Iterable)} . */ diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java index cf7ac645aa3..917ef937138 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.relation; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.core.CqlIdentifiers; import com.datastax.oss.driver.internal.querybuilder.DefaultRaw; @@ -234,7 +234,7 @@ default SelfT whereCustomIndex(@NonNull String indexName, @NonNull Term expressi /** * Adds a raw CQL snippet as a relation. * - *

        This is the equivalent of creating a relation with {@link QueryBuilderDsl#raw(String)} and + *

        This is the equivalent of creating a relation with {@link QueryBuilder#raw(String)} and * passing it to {@link #where(Relation)}. * *

        The contents will be appended to the query as-is, without any syntax checking or escaping. diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java index fad26f12ca0..4aae7083fdf 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableAddColumn.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface AlterTableAddColumn { @@ -26,7 +26,7 @@ public interface AlterTableAddColumn { * Adds a column definition in the ALTER TABLE statement. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull AlterTableAddColumnEnd addColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); @@ -44,7 +44,7 @@ default AlterTableAddColumnEnd addColumn(@NonNull String columnName, @NonNull Da * Adds a static column definition in the ALTER TABLE statement. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull AlterTableAddColumnEnd addStaticColumn( diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java index d641ce65e0a..7306508b295 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableStart.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface AlterTableStart @@ -36,7 +36,7 @@ public interface AlterTableStart * Completes ALTER TABLE specifying the the type of a column should be changed. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull BuildableQuery alterColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java index 27701eeac89..67c2c744af7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeStart.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface AlterTypeStart extends AlterTypeRenameField { @@ -28,7 +28,7 @@ public interface AlterTypeStart extends AlterTypeRenameField { * Completes ALTER TYPE specifying the the type of a field should be changed. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull BuildableQuery alterField(@NonNull CqlIdentifier fieldName, @NonNull DataType dataType); @@ -46,7 +46,7 @@ default BuildableQuery alterField(@NonNull String fieldName, @NonNull DataType d * Completes ALTER TYPE by adding a field definition in the ALTER TYPE statement. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull BuildableQuery addField(@NonNull CqlIdentifier fieldName, @NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java index 39de627569d..bd615c43650 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStart.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface CreateAggregateStart { @@ -42,7 +42,7 @@ public interface CreateAggregateStart { *

        Parameter keys are added in the order of their declaration. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateAggregateStart withParameter(@NonNull DataType paramType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java index 31d81024825..4a128ba1b9c 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateStateFunc.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface CreateAggregateStateFunc { @@ -28,7 +28,7 @@ public interface CreateAggregateStateFunc { * the state function. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateAggregateEnd withSType(@NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java index 3e6903226df..9ece2c1bb09 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionStart.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface CreateFunctionStart { @@ -43,7 +43,7 @@ public interface CreateFunctionStart { *

        Parameter keys are added in the order of their declaration. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateFunctionStart withParameter(@NonNull CqlIdentifier paramName, @NonNull DataType paramType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java index 8beba9f4862..f08522806cc 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionWithNullOption.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface CreateFunctionWithNullOption { @@ -27,7 +27,7 @@ public interface CreateFunctionWithNullOption { * returned from the function. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateFunctionWithType returnsType(@NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java index 5be5271fb3c..78c1268b966 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTable.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface CreateTable extends BuildableQuery, OngoingPartitionKey, CreateTableWithOptions { @@ -33,7 +33,7 @@ public interface CreateTable extends BuildableQuery, OngoingPartitionKey, Create *

        Clustering key columns are added in the order of their declaration. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateTable withClusteringColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); @@ -51,7 +51,7 @@ default CreateTable withClusteringColumn(@NonNull String columnName, @NonNull Da * Adds a column definition in the CREATE TABLE statement. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateTable withColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); @@ -72,7 +72,7 @@ default CreateTable withColumn(@NonNull String columnName, @NonNull DataType dat * #withColumn(CqlIdentifier, DataType) addColumn} call). * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateTable withStaticColumn(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java index b6be3b9946b..dc5b7241eef 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingCreateType.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface OngoingCreateType { @@ -29,7 +29,7 @@ public interface OngoingCreateType { *

        Fields keys are added in the order of their declaration. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateType withField(@NonNull CqlIdentifier identifier, @NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java index 1987fd85060..631463859a2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OngoingPartitionKey.java @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import edu.umd.cs.findbugs.annotations.NonNull; public interface OngoingPartitionKey { @@ -32,7 +32,7 @@ public interface OngoingPartitionKey { *

        Partition keys are added in the order of their declaration. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link SchemaBuilderDsl#udt(CqlIdentifier, boolean)}. + * {@link SchemaBuilder#udt(CqlIdentifier, boolean)}. */ @NonNull CreateTable withPartitionKey(@NonNull CqlIdentifier columnName, @NonNull DataType dataType); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java index 022cee26f4a..271fa9951f5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.api.querybuilder.schema; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; @@ -79,9 +79,9 @@ default SelfT withComment(@NonNull String comment) { /** * Defines the compaction strategy to use. * - * @see SchemaBuilderDsl#sizeTieredCompactionStrategy() - * @see SchemaBuilderDsl#leveledCompactionStrategy() - * @see SchemaBuilderDsl#timeWindowCompactionStrategy() + * @see SchemaBuilder#sizeTieredCompactionStrategy() + * @see SchemaBuilder#leveledCompactionStrategy() + * @see SchemaBuilder#timeWindowCompactionStrategy() */ @NonNull default SelfT withCompaction(@NonNull CompactionStrategy compactionStrategy) { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java index f4f5ed47c1e..df58d757f1f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OngoingSelection.java @@ -22,7 +22,7 @@ import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; import edu.umd.cs.findbugs.annotations.NonNull; @@ -272,7 +272,7 @@ default Select field(@NonNull Selector udt, @NonNull String fieldName) { * selection, like a nested UDT). * *

        In other words, this is a shortcut for {{@link #field(Selector, CqlIdentifier) - * field(QueryBuilderDsl.column(udtColumnId), fieldId)}. + * field(QueryBuilder.column(udtColumnId), fieldId)}. * * @see Selector#field(CqlIdentifier, CqlIdentifier) */ @@ -311,7 +311,7 @@ default Select element(@NonNull Selector collection, @NonNull Term index) { * Shortcut for element selection when the target collection is a simple column. * *

        In other words, this is the equivalent of {@link #element(Selector, Term) - * element(QueryBuilderDsl.column(collection), index)}. + * element(QueryBuilder.column(collection), index)}. * * @see Selector#element(CqlIdentifier, Term) */ @@ -481,7 +481,7 @@ default Select mapOf(@NonNull Map elementSelectors) { * (map){a:b,c:d}}. * *

        To create the data types, use the constants and static methods in {@link DataTypes}, or - * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * {@link QueryBuilder#udt(CqlIdentifier)}. * *

        This is a shortcut for {@link #selector(Selector) selector(Selector.mapOf(elementSelectors, * keyType, valueType))}. @@ -666,7 +666,7 @@ default Select ttl(@NonNull String columnName) { * Casts a selector to a type, as in {@code SELECT CAST(a AS double)}. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * {@link QueryBuilder#udt(CqlIdentifier)}. * *

        This is a shortcut for {@link #selector(Selector) selector(Selector.function(keyspaceId, * functionId, arguments))}. @@ -747,7 +747,7 @@ default Select toUnixTimestamp(@NonNull String columnName) { * * @throws CodecNotFoundException if there is no default CQL mapping for the Java type of {@code * value}. - * @see QueryBuilderDsl#literal(Object) + * @see QueryBuilder#literal(Object) */ @NonNull default Select literal(@Nullable Object value) { @@ -764,7 +764,7 @@ default Select literal(@Nullable Object value) { * @see DriverContext#getCodecRegistry() * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can * handle {@code value}. - * @see QueryBuilderDsl#literal(Object, CodecRegistry) + * @see QueryBuilder#literal(Object, CodecRegistry) */ @NonNull default Select literal(@Nullable Object value, @NonNull CodecRegistry codecRegistry) { @@ -777,11 +777,11 @@ default Select literal(@Nullable Object value, @NonNull CodecRegistry codecRegis *

        This is an alternative to {@link #literal(Object)} for custom type mappings. The value will * be turned into a string with {@link TypeCodec#format(Object)}, and inlined in the query. * - * @see QueryBuilderDsl#literal(Object, TypeCodec) + * @see QueryBuilder#literal(Object, TypeCodec) */ @NonNull default Select literal(@Nullable T value, @Nullable TypeCodec codec) { - return selector(QueryBuilderDsl.literal(value, codec)); + return selector(QueryBuilder.literal(value, codec)); } /** @@ -793,11 +793,11 @@ default Select literal(@Nullable T value, @Nullable TypeCodec codec) { * features that are not yet covered by the query builder. * *

        This is a shortcut for {@link #selector(Selector) - * selector(QueryBuilderDsl.raw(rawExpression))}. + * selector(QueryBuilder.raw(rawExpression))}. */ @NonNull default Select raw(@NonNull String rawExpression) { - return selector(QueryBuilderDsl.raw(rawExpression)); + return selector(QueryBuilder.raw(rawExpression)); } /** diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java index af3da19eaca..2b8f008d5b0 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Select.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.api.querybuilder.BindMarker; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause; import com.datastax.oss.driver.internal.core.CqlIdentifiers; import com.datastax.oss.driver.shaded.guava.common.collect.Iterables; @@ -156,8 +156,8 @@ default Select orderBy(@NonNull String columnName, @NonNull ClusteringOrder orde /** * Adds a LIMIT clause to this query with a bind marker. * - *

        To create the argument, use one of the factory methods in {@link QueryBuilderDsl}, for - * example {@link QueryBuilderDsl#bindMarker() bindMarker()}. + *

        To create the argument, use one of the factory methods in {@link QueryBuilder}, for example + * {@link QueryBuilder#bindMarker() bindMarker()}. * *

        If this method or {@link #limit(int)} is called multiple times, the last value is used. * {@code null} can be passed to cancel a previous limit. @@ -177,8 +177,8 @@ default Select orderBy(@NonNull String columnName, @NonNull ClusteringOrder orde /** * Adds a PER PARTITION LIMIT clause to this query with a bind marker. * - *

        To create the argument, use one of the factory methods in {@link QueryBuilderDsl}, for - * example {@link QueryBuilderDsl#bindMarker() bindMarker()}. + *

        To create the argument, use one of the factory methods in {@link QueryBuilder}, for example + * {@link QueryBuilder#bindMarker() bindMarker()}. * *

        If this method or {@link #perPartitionLimit(int)} is called multiple times, the last value * is used. {@code null} can be passed to cancel a previous limit. diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java index b0a6ae30588..f3654f74e6f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/Selector.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.querybuilder.CqlSnippet; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.ArithmeticOperator; import com.datastax.oss.driver.internal.querybuilder.select.AllSelector; @@ -351,7 +351,7 @@ static Selector mapOf(@NonNull Map elementSelectors) { * (map){a:b,c:d}}. * *

        To create the data types, use the constants and static methods in {@link DataTypes}, or - * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * {@link QueryBuilder#udt(CqlIdentifier)}. * * @see #mapOf(Map) */ @@ -367,7 +367,7 @@ static Selector mapOf( * Provides a type hint for a selector, as in {@code SELECT (double)1/3}. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * {@link QueryBuilder#udt(CqlIdentifier)}. */ @NonNull static Selector typeHint(@NonNull Selector selector, @NonNull DataType targetType) { @@ -490,7 +490,7 @@ static Selector ttl(@NonNull String columnName) { * Casts a selector to a type, as in {@code SELECT CAST(a AS double)}. * *

        To create the data type, use the constants and static methods in {@link DataTypes}, or - * {@link QueryBuilderDsl#udt(CqlIdentifier)}. + * {@link QueryBuilder#udt(CqlIdentifier)}. * * @throws IllegalArgumentException if the selector is aliased. */ diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java index 1e3c1c8c68b..5daa05a11f0 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; import com.datastax.oss.driver.api.querybuilder.CqlSnippet; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.relation.ArithmeticRelationBuilder; import com.datastax.oss.driver.api.querybuilder.select.Selector; @@ -33,9 +33,8 @@ *

      • as the right operand of a {@link ArithmeticRelationBuilder#isEqualTo(Term) relation}. * * - * To build instances of this type, use the factory methods in {@link QueryBuilderDsl}, such as - * {@link QueryBuilderDsl#literal(Object) literal}, {@link QueryBuilderDsl#tuple(Iterable) tuple}, - * etc. + * To build instances of this type, use the factory methods in {@link QueryBuilder}, such as {@link + * QueryBuilder#literal(Object) literal}, {@link QueryBuilder#tuple(Iterable) tuple}, etc. */ public interface Term extends CqlSnippet { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java index f52104a92f2..d918c8aba42 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/Assignment.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; import com.datastax.oss.driver.api.querybuilder.CqlSnippet; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnLeftOperand; @@ -109,7 +109,7 @@ static Assignment increment(@NonNull String columnName, @NonNull Term amount) { /** Increments a counter by 1, as in {@code SET c+=1} . */ @NonNull static Assignment increment(@NonNull CqlIdentifier columnId) { - return increment(columnId, QueryBuilderDsl.literal(1)); + return increment(columnId, QueryBuilder.literal(1)); } /** Shortcut for {@link #increment(CqlIdentifier) CqlIdentifier.fromCql(columnName)}. */ @@ -136,7 +136,7 @@ static Assignment decrement(@NonNull String columnName, @NonNull Term amount) { /** Decrements a counter by 1, as in {@code SET c-=1} . */ @NonNull static Assignment decrement(@NonNull CqlIdentifier columnId) { - return decrement(columnId, QueryBuilderDsl.literal(1)); + return decrement(columnId, QueryBuilder.literal(1)); } /** Shortcut for {@link #decrement(CqlIdentifier) CqlIdentifier.fromCql(columnName)}. */ diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java index 8ac57142489..67af1f09e34 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/OngoingAssignment.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.update; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.term.Term; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; @@ -156,13 +156,13 @@ default UpdateWithAssignments increment(@NonNull String columnName, @NonNull Ter * Increments a counter by 1, as in {@code SET c+=1} . * *

        This is a shortcut for {@link #increment(CqlIdentifier, Term)} increment(columnId, - * QueryBuilderDsl.literal(1))}. + * QueryBuilder.literal(1))}. * * @see Assignment#increment(CqlIdentifier) */ @NonNull default UpdateWithAssignments increment(@NonNull CqlIdentifier columnId) { - return increment(columnId, QueryBuilderDsl.literal(1)); + return increment(columnId, QueryBuilder.literal(1)); } /** @@ -207,7 +207,7 @@ default UpdateWithAssignments decrement(@NonNull String columnName, @NonNull Ter */ @NonNull default UpdateWithAssignments decrement(@NonNull CqlIdentifier columnId) { - return decrement(columnId, QueryBuilderDsl.literal(1)); + return decrement(columnId, QueryBuilder.literal(1)); } /** diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index 68e41a8a091..32c81cd9906 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder; import com.datastax.oss.driver.api.querybuilder.BindMarker; -import com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.insert.Insert; import com.datastax.oss.driver.api.querybuilder.insert.InsertInto; import com.datastax.oss.driver.api.querybuilder.insert.JsonInsert; @@ -80,7 +80,7 @@ public JsonInsert json(@NonNull String json) { return new DefaultInsert( keyspace, table, - QueryBuilderDsl.literal(json), + QueryBuilder.literal(json), missingJsonBehavior, ImmutableMap.of(), timestamp, diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionTest.java index b395eecebd2..06296960a69 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.condition; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentConditionTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentConditionTest.java index e8a0a8a0567..2a75bbaa8d7 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentConditionTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentConditionTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.delete; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentRelationTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentRelationTest.java index 0403193b044..fa8afdf5a67 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentRelationTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteFluentRelationTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.delete; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; import com.datastax.oss.driver.api.querybuilder.relation.RelationTest; import com.datastax.oss.driver.api.querybuilder.select.SelectFluentRelationTest; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteIdempotenceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteIdempotenceTest.java index 4a495b16f17..5d9cb05a914 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteIdempotenceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteIdempotenceTest.java @@ -16,11 +16,11 @@ package com.datastax.oss.driver.api.querybuilder.delete; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.function; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelectorTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelectorTest.java index f59879f9328..7c3f7685bb5 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelectorTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteSelectorTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.delete; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteTimestampTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteTimestampTest.java index 01c6ac6f37b..27c343694c7 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteTimestampTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/delete/DeleteTimestampTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.delete; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/InsertIdempotenceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/InsertIdempotenceTest.java index 990e0e799b6..57fd40152fa 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/InsertIdempotenceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/InsertIdempotenceTest.java @@ -16,12 +16,12 @@ package com.datastax.oss.driver.api.querybuilder.insert; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.add; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.function; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.insertInto; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.tuple; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.add; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsertTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsertTest.java index e78a6462ffb..5bfd7eea59a 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsertTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/JsonInsertTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.insert; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java index 671afa550d1..c445411b88c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.insert; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.insertInto; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java index a00732f7ffb..f3ff81ed188 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java @@ -16,10 +16,10 @@ package com.datastax.oss.driver.api.querybuilder.relation; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.tuple; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java index 11c7ea14f98..d46bd691c4c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java @@ -16,25 +16,25 @@ package com.datastax.oss.driver.api.querybuilder.relation; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.add; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.currentDate; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.currentTime; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.currentTimeUuid; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.currentTimestamp; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.function; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.maxTimeUuid; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.minTimeUuid; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.multiply; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.negate; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.now; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.remainder; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.subtract; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.toDate; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.toTimestamp; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.toUnixTimestamp; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.typeHint; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.add; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.currentDate; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.currentTime; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.currentTimeUuid; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.currentTimestamp; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.maxTimeUuid; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.minTimeUuid; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.multiply; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.negate; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.now; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.remainder; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.subtract; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.toDate; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.toTimestamp; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.toUnixTimestamp; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.typeHint; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.TupleValue; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceTest.java index 43ed98a8ae2..6d56be76c29 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterKeyspaceTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.alterKeyspace; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.alterKeyspace; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewTest.java index 9a4da7272cc..360b6f35183 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterMaterializedViewTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.alterMaterializedView; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.alterMaterializedView; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableTest.java index 907e8ef9919..16db985ba9c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTableTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.alterTable; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.alterTable; import com.datastax.oss.driver.api.core.type.DataTypes; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeTest.java index 3e5bdbb1050..6ae49c8533c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/AlterTypeTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.alterType; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.alterType; import com.datastax.oss.driver.api.core.type.DataTypes; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java index b10f3aeb7a9..f9dcf41d41a 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateAggregateTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.tuple; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createAggregate; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createAggregate; import com.datastax.oss.driver.api.core.type.DataTypes; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionTest.java index ec58a5491df..02a91fd627d 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateFunctionTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createFunction; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.udt; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createFunction; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.udt; import com.datastax.oss.driver.api.core.type.DataTypes; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexTest.java index df0eda36024..d654219f23c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateIndexTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createIndex; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createIndex; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceTest.java index 89e0edd2f72..3c067aa9e3c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateKeyspaceTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createKeyspace; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createKeyspace; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewTest.java index 8a8912aa86b..88ff1dafd8f 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateMaterializedViewTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createMaterializedView; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createMaterializedView; import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java index 0db25b0416a..16f4c2e0d10 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java @@ -16,13 +16,13 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createTable; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.udt; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createTable; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.udt; import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl; -import com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition; import com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.CompactionWindowUnit; import com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.TimestampResolution; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; @@ -261,7 +261,7 @@ public void should_generate_create_table_size_tiered_compaction() { .withPartitionKey("k", DataTypes.INT) .withColumn("v", DataTypes.TEXT) .withCompaction( - SchemaBuilderDsl.sizeTieredCompactionStrategy() + SchemaBuilder.sizeTieredCompactionStrategy() .withBucketHigh(1.6) .withBucketLow(0.6) .withColdReadsToOmit(0.1) @@ -284,7 +284,7 @@ public void should_generate_create_table_leveled_compaction() { .withPartitionKey("k", DataTypes.INT) .withColumn("v", DataTypes.TEXT) .withCompaction( - SchemaBuilderDsl.leveledCompactionStrategy() + SchemaBuilder.leveledCompactionStrategy() .withSSTableSizeInMB(110) .withTombstoneCompactionIntervalInSeconds(3600))) .hasCql( @@ -298,7 +298,7 @@ public void should_generate_create_table_time_window_compaction() { .withPartitionKey("k", DataTypes.INT) .withColumn("v", DataTypes.TEXT) .withCompaction( - SchemaBuilderDsl.timeWindowCompactionStrategy() + SchemaBuilder.timeWindowCompactionStrategy() .withCompactionWindow(10, CompactionWindowUnit.DAYS) .withTimestampResolution(TimestampResolution.MICROSECONDS) .withUnsafeAggressiveSSTableExpiration(false))) diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeTest.java index 7ac79433a47..7015d49067c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTypeTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.createType; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.udt; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.createType; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.udt; import com.datastax.oss.driver.api.core.type.DataTypes; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropAggregateTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropAggregateTest.java index ae622c49c1d..5af944238ff 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropAggregateTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropAggregateTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropAggregate; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropAggregate; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropFunctionTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropFunctionTest.java index 10b81c0515e..6a50f126805 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropFunctionTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropFunctionTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropFunction; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropFunction; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropIndexTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropIndexTest.java index 55c435b1115..46efe7c5f37 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropIndexTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropIndexTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropIndex; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropIndex; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropKeyspaceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropKeyspaceTest.java index 6e12000a4d0..fa84c4e6783 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropKeyspaceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropKeyspaceTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropKeyspace; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropKeyspace; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropMaterializedViewTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropMaterializedViewTest.java index 94a059ae529..a323de03c4c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropMaterializedViewTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropMaterializedViewTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropMaterializedView; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropMaterializedView; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTableTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTableTest.java index a0fe53e87c3..0a986077f1c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTableTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTableTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropTable; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropTable; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTypeTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTypeTest.java index 43cb72a3713..cb9613f94b4 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTypeTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/DropTypeTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.schema; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.dropType; +import static com.datastax.oss.driver.api.querybuilder.SchemaBuilder.dropType; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectAllowFilteringTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectAllowFilteringTest.java index ae5874422ce..79c89feeb30 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectAllowFilteringTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectAllowFilteringTest.java @@ -16,7 +16,7 @@ package com.datastax.oss.driver.api.querybuilder.select; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectFluentRelationTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectFluentRelationTest.java index 78693ad4475..884454d91f9 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectFluentRelationTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectFluentRelationTest.java @@ -16,10 +16,10 @@ package com.datastax.oss.driver.api.querybuilder.select; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.tuple; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple; import com.datastax.oss.driver.api.querybuilder.relation.RelationTest; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectGroupByTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectGroupByTest.java index 39125502473..12d6d2e3223 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectGroupByTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectGroupByTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.select; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectLimitTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectLimitTest.java index 53f8369b792..cba2e83d04d 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectLimitTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectLimitTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.select; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java index d5b11470039..348c735764c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java @@ -18,8 +18,8 @@ import static com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder.ASC; import static com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder.DESC; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import com.datastax.oss.driver.api.querybuilder.relation.Relation; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java index c5d981bd5d5..bd3263441e0 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.select; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentAssignmentTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentAssignmentTest.java index 22872e043a7..184ad2e2dbf 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentAssignmentTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentAssignmentTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.update; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.update; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; import com.datastax.oss.driver.api.querybuilder.Literal; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentConditionTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentConditionTest.java index efbcd666271..8a562431510 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentConditionTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentConditionTest.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.querybuilder.update; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.update; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentRelationTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentRelationTest.java index f726056c0c5..86559ea65f1 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentRelationTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateFluentRelationTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.update; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.update; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; import com.datastax.oss.driver.api.querybuilder.relation.RelationTest; import com.datastax.oss.driver.api.querybuilder.select.SelectFluentRelationTest; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateIdempotenceTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateIdempotenceTest.java index 869f5d21afe..1f424484ee6 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateIdempotenceTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateIdempotenceTest.java @@ -16,11 +16,11 @@ package com.datastax.oss.driver.api.querybuilder.update; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.function; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.literal; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.raw; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.update; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; import java.util.Arrays; import org.junit.Test; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java index f808a9fc744..6ae165bfa19 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java @@ -16,8 +16,8 @@ package com.datastax.oss.driver.api.querybuilder.update; import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl.update; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; import org.junit.Test; From a4c33b0af0f866137b83a7f6c0a33f686f8890e6 Mon Sep 17 00:00:00 2001 From: tomekl007 Date: Fri, 1 Feb 2019 14:36:32 +0100 Subject: [PATCH 684/742] Expose maxAttempts property in ExponentialReconnectionPolicy --- .../core/connection/ExponentialReconnectionPolicy.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java index 1d90f6f803f..2320dee255a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/connection/ExponentialReconnectionPolicy.java @@ -160,4 +160,8 @@ private long calculateDelayWithJitter() { return delay; } } + + public long getMaxAttempts() { + return maxAttempts; + } } From f8b429e95cefcaf031bf88c85868e3af9f41d84a Mon Sep 17 00:00:00 2001 From: tomekl007 Date: Mon, 4 Feb 2019 17:06:49 +0100 Subject: [PATCH 685/742] Add method for creating AdminRequestHandler instances from an existing Query object This commit also modifies the logic in AdminRequestHandler to treat all Result instances as success. --- .../adminrequest/AdminRequestHandler.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java index 4f112f24974..6ccf1651e1f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/adminrequest/AdminRequestHandler.java @@ -27,7 +27,7 @@ import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.request.Query; import com.datastax.oss.protocol.internal.request.query.QueryOptions; -import com.datastax.oss.protocol.internal.response.result.Prepared; +import com.datastax.oss.protocol.internal.response.Result; import com.datastax.oss.protocol.internal.response.result.Rows; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ScheduledFuture; @@ -49,6 +49,11 @@ public class AdminRequestHandler implements ResponseCallback { private static final Logger LOG = LoggerFactory.getLogger(AdminRequestHandler.class); + public static AdminRequestHandler query( + DriverChannel channel, Query query, Duration timeout, String logPrefix) { + return createAdminRequestHandler(channel, query, Collections.emptyMap(), timeout, logPrefix); + } + public static AdminRequestHandler query( DriverChannel channel, String query, @@ -60,7 +65,17 @@ public static AdminRequestHandler query( new Query( query, buildQueryOptions(pageSize, serialize(parameters, channel.protocolVersion()), null)); - String debugString = "query '" + query + "'"; + return createAdminRequestHandler(channel, message, parameters, timeout, logPrefix); + } + + private static AdminRequestHandler createAdminRequestHandler( + DriverChannel channel, + Query message, + Map parameters, + Duration timeout, + String logPrefix) { + + String debugString = "query '" + message.query + "'"; if (!parameters.isEmpty()) { debugString += " with parameters " + parameters; } @@ -148,7 +163,8 @@ public void onResponse(Frame responseFrame) { ByteBuffer pagingState = rows.getMetadata().pagingState; AdminRequestHandler nextHandler = (pagingState == null) ? null : this.copy(pagingState); setFinalResult(new AdminResult(rows, nextHandler, channel.protocolVersion())); - } else if (message instanceof Prepared) { + } else if (message instanceof Result) { + // Internal prepares are only "reprepare on up" types of queries, where we only care about // success, not the actual result, so this is good enough: setFinalResult(null); From d17ac777b8d8a6e6b9f1661d0764001a73414a00 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Wed, 13 Feb 2019 05:18:50 -0600 Subject: [PATCH 686/742] JAVA-2106: Log server side warnings returned from a query (#1184) --- changelog/README.md | 1 + .../api/core/config/DefaultDriverOption.java | 2 + .../core/context/DefaultDriverContext.java | 13 ++ .../core/context/InternalDriverContext.java | 10 + .../internal/core/cql/CqlRequestHandler.java | 33 +++ .../internal/core/tracker/RequestLogger.java | 35 +++- core/src/main/resources/reference.conf | 13 ++ .../api/core/cql/ExecutionInfoWarningsIT.java | 192 ++++++++++++++++++ .../api/core/tracker/RequestLoggerIT.java | 32 ++- 9 files changed, 315 insertions(+), 16 deletions(-) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java diff --git a/changelog/README.md b/changelog/README.md index 30be47feb13..01a7ae4f1a9 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0-rc1 (in progress) +- [improvement] JAVA-2106: Log server side warnings returned from a query - [improvement] JAVA-2151: Drop "Dsl" suffix from query builder main classes - [new feature] JAVA-2144: Expose internal API to hook into the session lifecycle - [improvement] JAVA-2119: Add PagingIterable abstraction as a supertype of ResultSet diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index 793131d5627..89d8365de78 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -171,6 +171,8 @@ public enum DefaultDriverOption implements DriverOption { NETTY_TIMER_TICK_DURATION("advanced.netty.timer.tick-duration"), NETTY_TIMER_TICKS_PER_WHEEL("advanced.netty.timer.ticks-per-wheel"), + + REQUEST_LOG_WARNINGS("advanced.request.log-warnings"), ; private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index b930ec2cb58..2b3d03d22f7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -67,6 +67,7 @@ import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; +import com.datastax.oss.driver.internal.core.tracker.RequestLogFormatter; import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry; import com.datastax.oss.driver.internal.core.util.Reflection; import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector; @@ -195,6 +196,8 @@ public class DefaultDriverContext implements InternalDriverContext { private final Map localDatacentersFromBuilder; private final Map> nodeFiltersFromBuilder; private final ClassLoader classLoader; + private final LazyReference requestLogFormatterRef = + new LazyReference<>("requestLogFormatter", this::buildRequestLogFormatter, cycleDetector); public DefaultDriverContext( DriverConfigLoader configLoader, @@ -757,4 +760,14 @@ public ProtocolVersion getProtocolVersion() { public Map getStartupOptions() { return startupOptionsRef.get(); } + + protected RequestLogFormatter buildRequestLogFormatter() { + return new RequestLogFormatter(this); + } + + @NonNull + @Override + public RequestLogFormatter getRequestLogFormatter() { + return requestLogFormatterRef.get(); + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java index 03c1e1df439..afc5dbce92e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/InternalDriverContext.java @@ -36,6 +36,7 @@ import com.datastax.oss.driver.internal.core.session.PoolManager; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory; +import com.datastax.oss.driver.internal.core.tracker.RequestLogFormatter; import com.datastax.oss.protocol.internal.Compressor; import com.datastax.oss.protocol.internal.FrameCodec; import edu.umd.cs.findbugs.annotations.NonNull; @@ -161,4 +162,13 @@ public interface InternalDriverContext extends DriverContext { default List getLifecycleListeners() { return Collections.emptyList(); } + + /** + * A {@link RequestLogFormatter} instance based on this {@link DriverContext}. + * + *

        The {@link RequestLogFormatter} instance returned here will use the settings in + * advanced.request-tracker when formatting requests. + */ + @NonNull + RequestLogFormatter getRequestLogFormatter(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index f44ca955d2d..63d8770d5b3 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -54,6 +54,7 @@ import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.tracker.NoopRequestTracker; +import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.collection.QueryPlan; import com.datastax.oss.protocol.internal.Frame; @@ -356,11 +357,43 @@ private void setFinalResult( TimeUnit.NANOSECONDS); } } + // log the warnings if they have NOT been disabled + if (!executionInfo.getWarnings().isEmpty() + && executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOG_WARNINGS) + && LOG.isWarnEnabled()) { + logServerWarnings(executionInfo.getWarnings()); + } } catch (Throwable error) { setFinalError(error, callback.node, -1); } } + private void logServerWarnings(List warnings) { + // use the RequestLogFormatter to format the query + StringBuilder statementString = new StringBuilder(); + context + .getRequestLogFormatter() + .appendRequest( + statement, + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH), + executionProfile.getBoolean( + DefaultDriverOption.REQUEST_LOGGER_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_SHOW_VALUES), + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUES), + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH), + statementString); + // log each warning separately + warnings.forEach( + (warning) -> + LOG.warn("Query '{}' generated server side warning(s): {}", statementString, warning)); + } + private ExecutionInfo buildExecutionInfo( NodeResponseCallback callback, Result resultMessage, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index 8805edcd9ca..220a2222d89 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -63,6 +63,11 @@ public class RequestLogger implements RequestTracker { private static final Logger LOG = LoggerFactory.getLogger(RequestLogger.class); + public static final int DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH = 500; + public static final boolean DEFAULT_REQUEST_LOGGER_SHOW_VALUES = true; + public static final int DEFAULT_REQUEST_LOGGER_MAX_VALUES = 50; + public static final int DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH = 50; + private final String logPrefix; private final RequestLogFormatter formatter; @@ -100,12 +105,19 @@ public void onSuccess( } int maxQueryLength = - executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH); boolean showValues = - executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); - int maxValues = executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); + executionProfile.getBoolean( + DefaultDriverOption.REQUEST_LOGGER_VALUES, DEFAULT_REQUEST_LOGGER_SHOW_VALUES); + int maxValues = + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, DEFAULT_REQUEST_LOGGER_MAX_VALUES); int maxValueLength = - executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, + DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH); logSuccess( request, latencyNanos, isSlow, node, maxQueryLength, showValues, maxValues, maxValueLength); @@ -124,13 +136,20 @@ public void onError( } int maxQueryLength = - executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500); + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH); boolean showValues = - executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, false); - int maxValues = executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 0); + executionProfile.getBoolean( + DefaultDriverOption.REQUEST_LOGGER_VALUES, DEFAULT_REQUEST_LOGGER_SHOW_VALUES); + int maxValues = + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, DEFAULT_REQUEST_LOGGER_MAX_VALUES); int maxValueLength = - executionProfile.getInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 0); + executionProfile.getInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, + DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH); boolean showStackTraces = executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, false); diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index cf9b12417e7..2620963bd80 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -759,6 +759,19 @@ datastax-java-driver { # Overridable in a profile: yes consistency = ONE } + + # Whether logging of server warnings generated during query execution should be disabled by the + # driver. All server generated warnings will be available programmatically via the ExecutionInfo + # object on the executed statement's ResultSet. If set to "false", this will prevent the driver + # from logging these warnings. + # + # NOTE: The log formatting for these warning messages will reuse the options defined for + # advanced.request-tracker. + # + # Required: yes + # Modifiable at runtime: yes, the new value will be used for query warnings received after the change. + # Overridable in a profile: yes + log-warnings = true } advanced.metrics { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java new file mode 100644 index 00000000000..b39e215e29a --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java @@ -0,0 +1,192 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.datastax.oss.driver.api.core.cql; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.verify; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoaderBuilder; +import com.datastax.oss.driver.internal.core.cql.CqlRequestHandler; +import com.datastax.oss.driver.internal.core.tracker.RequestLogger; +import com.google.common.base.Strings; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.LoggerFactory; + +@RunWith(MockitoJUnitRunner.class) +public class ExecutionInfoWarningsIT { + + private static final CustomCcmRule CCM = new CustomCcmRule.Builder().build(); + private static final SessionRule SESSION_RULE = + SessionRule.builder(CCM) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 20) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH) + .withProfile( + "log-disabled", + DefaultDriverConfigLoaderBuilder.profileBuilder() + .withString(DefaultDriverOption.REQUEST_LOG_WARNINGS, "false") + .build()) + .build()) + .build(); + private static final String KEY = "test"; + + @ClassRule public static final TestRule CCM_RULE = RuleChain.outerRule(CCM).around(SESSION_RULE); + + @Mock private Appender appender; + @Captor private ArgumentCaptor loggingEventCaptor; + private Logger logger; + private Level originalLoggerLevel; + + @BeforeClass + public static void setupSchema() { + // table with simple primary key, single cell. + SESSION_RULE + .session() + .execute( + SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k int primary key, v text)") + .withExecutionProfile(SESSION_RULE.slowProfile()) + .build()); + for (int i = 0; i < 100; i++) { + SESSION_RULE + .session() + .execute( + SimpleStatement.builder("INSERT INTO test (k, v) VALUES (?, ?)") + .addPositionalValues(KEY, i) + .build()); + } + } + + @Before + public void setupLogger() { + // setup the log appender + logger = (Logger) LoggerFactory.getLogger(CqlRequestHandler.class); + originalLoggerLevel = logger.getLevel(); + logger.setLevel(Level.WARN); + logger.addAppender(appender); + } + + @After + public void cleanupLogger() { + logger.setLevel(originalLoggerLevel); + logger.detachAppender(appender); + } + + @Test + public void should_execute_query_and_log_server_side_warnings() { + final String query = "SELECT count(*) FROM test;"; + Statement st = SimpleStatement.builder(String.format(query)).build(); + ResultSet result = SESSION_RULE.session().execute(st); + + ExecutionInfo executionInfo = result.getExecutionInfo(); + assertThat(executionInfo).isNotNull(); + List warnings = executionInfo.getWarnings(); + assertThat(warnings).isNotNull(); + assertThat(warnings).isNotEmpty(); + String warning = warnings.get(0); + assertThat(warning).isNotNull(); + assertThat(warning).isEqualTo("Aggregation query used without partition key"); + // verify the log was generated + verify(appender, after(500).times(1)).doAppend(loggingEventCaptor.capture()); + assertThat(loggingEventCaptor.getValue().getMessage()).isNotNull(); + String logMessage = loggingEventCaptor.getValue().getFormattedMessage(); + assertThat(logMessage) + .startsWith( + "Query '[0 values] " + + query + + "' generated server side warning(s): Aggregation query used without partition key"); + } + + @Test + public void should_execute_query_and_not_log_server_side_warnings() { + final String query = "SELECT count(*) FROM test;"; + Statement st = + SimpleStatement.builder(String.format(query)) + .withExecutionProfileName("log-disabled") + .build(); + ResultSet result = SESSION_RULE.session().execute(st); + + ExecutionInfo executionInfo = result.getExecutionInfo(); + assertThat(executionInfo).isNotNull(); + List warnings = executionInfo.getWarnings(); + assertThat(warnings).isNotNull(); + assertThat(warnings).isNotEmpty(); + String warning = warnings.get(0); + assertThat(warning).isNotNull(); + assertThat(warning).isEqualTo("Aggregation query used without partition key"); + // verify the log was NOT generated + verify(appender, after(500).times(0)).doAppend(loggingEventCaptor.capture()); + } + + @Test + public void should_expose_warnings_on_execution_info() { + // the default batch size warn threshold is 5 * 1024 bytes, but after CASSANDRA-10876 there must + // be multiple mutations in a batch to trigger this warning so the batch includes 2 different + // inserts. + final String query = + String.format( + "BEGIN UNLOGGED BATCH\n" + + "INSERT INTO test (k, v) VALUES (1, '%s')\n" + + "INSERT INTO test (k, v) VALUES (2, '%s')\n" + + "APPLY BATCH", + Strings.repeat("1", 2 * 1024), Strings.repeat("1", 3 * 1024)); + Statement st = SimpleStatement.builder(String.format(query)).build(); + ResultSet result = SESSION_RULE.session().execute(st); + ExecutionInfo executionInfo = result.getExecutionInfo(); + assertThat(executionInfo).isNotNull(); + List warnings = executionInfo.getWarnings(); + assertThat(warnings).isNotEmpty(); + // verify the log was generated + verify(appender, after(500).times(1)).doAppend(loggingEventCaptor.capture()); + assertThat(loggingEventCaptor.getValue().getMessage()).isNotNull(); + String logMessage = loggingEventCaptor.getValue().getFormattedMessage(); + assertThat(logMessage) + .startsWith("Query '") + // query will only be logged up to MAX_QUERY_LENGTH + // characters + .contains(query.substring(0, RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH)) + .contains("' generated server side warning(s): ") + .contains( + String.format( + "Batch for [%s.test] is of size 5152, exceeding specified threshold of 5120 by 32.", + SESSION_RULE.keyspace().asCql(true))); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index ab58b1ee737..6eeda1ae4d3 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -92,10 +92,18 @@ public class RequestLoggerIT { .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, true) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, true) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, true) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500) - .withBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, true) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 50) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 50) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH) + .withBoolean( + DefaultDriverOption.REQUEST_LOGGER_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_SHOW_VALUES) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUES) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, true) .withProfile("low-threshold", lowThresholdProfile) .withProfile("no-logs", noLogsProfile) @@ -111,10 +119,18 @@ public class RequestLoggerIT { .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, true) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_SLOW_ENABLED, true) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, true) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, 500) - .withBoolean(DefaultDriverOption.REQUEST_LOGGER_VALUES, true) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, 50) - .withInt(DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, 50) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_QUERY_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH) + .withBoolean( + DefaultDriverOption.REQUEST_LOGGER_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_SHOW_VALUES) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUE_LENGTH, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUE_LENGTH) + .withInt( + DefaultDriverOption.REQUEST_LOGGER_MAX_VALUES, + RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_VALUES) .withBoolean(DefaultDriverOption.REQUEST_LOGGER_STACK_TRACES, true) .withProfile("low-threshold", lowThresholdProfile) .withProfile("no-logs", noLogsProfile) From bb04e9254ee1b7702c8002f075d49a326f7ca57f Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 8 Feb 2019 15:19:33 -0800 Subject: [PATCH 687/742] Improve manual on prepared statements --- manual/core/statements/prepared/README.md | 73 +++++++++++++++++------ 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md index 6be868bce70..344554c173d 100644 --- a/manual/core/statements/prepared/README.md +++ b/manual/core/statements/prepared/README.md @@ -55,35 +55,69 @@ client driver Cassandra |<--------------------------------| | ``` +Beyond saving a bit of parsing overhead on the server, prepared statements have other advantages; +the `PREPARED` response also contains useful metadata about the CQL query: + +* information about the result set that will be produced when the statement gets executed. The + driver caches this, so that the server doesn't need to include it with every response. This saves + a bit of bandwidth, and the resources it would take to decode it every time. +* the CQL types of the bound variables. This allows bound statements' `set` methods to perform + better checks, and fail fast (without a server round-trip) if the types are wrong. +* which bound variables are part of the partition key. This allows bound statements to automatically + compute their [routing key](../../load_balancing/#token-aware). +* more optimizations might get added in the future. For example, [CASSANDRA-10813] suggests adding + an "[idempotent](../../idempotence)" flag to the response. + +If you have a unique query that is executed only once, a [simple statement](../simple/) will be more +efficient. But note that this should be pretty rare: most client applications typically repeat the +same queries over and over, and a parameterized version can be extracted and prepared. + +### Preparing + +`Session.prepare()` accepts either a plain query string, or a `SimpleStatement` object. If you use a +`SimpleStatement`, its execution parameters will propagate to bound statements: + +```java +SimpleStatement simpleStatement = + SimpleStatement.builder("SELECT * FROM product WHERE sku = ?") + .withConsistencyLevel(DefaultConsistencyLevel.QUORUM) + .build(); +PreparedStatement preparedStatement = session.prepare(simpleStatement); +BoundStatement boundStatement = preparedStatement.bind(); +assert boundStatement.getConsistencyLevel() == DefaultConsistencyLevel.QUORUM; +``` + +For more details, including the complete list of attributes that are copied, refer to +[API docs][Session.prepare]. + The driver caches prepared statements: if you call `prepare()` multiple times with the same query -string (or a `SimpleStatement` with the same execution characteristics), you will get the same -`PreparedStatement` instance. We still recommend keeping a reference to it (for example by caching -it as a field in a DAO); if that's not possible (e.g. if query strings are generated dynamically), -it's OK to call `prepare()` every time: there will just be a small performance overhead to check the -internal cache. Note that caching is based on: +string (or a `SimpleStatement` with the same execution parameters), you will get the same +`PreparedStatement` instance: + +```java +PreparedStatement ps1 = session.prepare("SELECT * FROM product WHERE sku = ?"); +// The second call hits the cache, nothing is sent to the server: +PreparedStatement ps2 = session.prepare("SELECT * FROM product WHERE sku = ?"); +assert ps1 == ps2; +``` + +We still recommend avoiding repeated calls to `prepare()`; if that's not possible (e.g. if query +strings are generated dynamically), there will just be a small performance overhead to check the +cache on every call. + +Note that caching is based on: * the query string exactly as you provided it: the driver does not perform any kind of trimming or sanitizing. * all other execution parameters: for example, preparing two statements with identical query strings - but different consistency levels will yield distinct prepared statements. + but different consistency levels will yield two distinct prepared statements (that each produce + bound statements with their respective consistency level). The size of the cache is exposed as a session-level [metric](../../metrics/) `cql-prepared-cache-size`. The cache uses [weak values]([guava eviction]) eviction, so this represents the number of `PreparedStatement` instances that your application has created, and is still holding a reference to. -If you execute a query only once, a prepared statement is inefficient because it requires two round -trips. Consider a [simple statement](../simple/) instead. - -### Preparing - -The `Session.prepare` method accepts either a query string or a `SimpleStatement` object. If you use -the object variant, both the initial prepare request and future bound statements will share some of -the options of that simple statement: - -* initial prepare request: configuration profile name (or instance) and custom payload. -* bound statements: configuration profile name (or instance) and custom payload, idempotent flag. - ### Parameters and binding The prepared query string will usually contain placeholders, which can be either anonymous or named: @@ -279,6 +313,7 @@ new version with the response; the driver updates its local cache transparently, observe the new columns in the result set. [BoundStatement]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/cql/BoundStatement.html - +[Session.prepare]: https://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/core/CqlSession.html#prepare-com.datastax.oss.driver.api.core.cql.SimpleStatement- [CASSANDRA-10786]: https://issues.apache.org/jira/browse/CASSANDRA-10786 +[CASSANDRA-10813]: https://issues.apache.org/jira/browse/CASSANDRA-10813 [guava eviction]: https://github.com/google/guava/wiki/CachesExplained#reference-based-eviction \ No newline at end of file From e58ea4844581ae6073bc1e60259c4e1d4f021131 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 19 Feb 2019 09:57:56 +0100 Subject: [PATCH 688/742] Update version in docs --- changelog/README.md | 2 +- core/revapi.json | 4 ++-- manual/core/README.md | 2 +- manual/core/compression/README.md | 2 +- manual/core/integration/README.md | 16 ++++++++-------- manual/core/shaded_jar/README.md | 6 +++--- manual/query_builder/README.md | 2 +- query-builder/revapi.json | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 01a7ae4f1a9..98b16b95a25 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0-rc1 (in progress) +### 4.0.0-rc1 - [improvement] JAVA-2106: Log server side warnings returned from a query - [improvement] JAVA-2151: Drop "Dsl" suffix from query builder main classes diff --git a/core/revapi.json b/core/revapi.json index 340e5a90f5a..598e24478cd 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -695,7 +695,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.TokenMap", "classSimpleName": "TokenMap", "methodName": "getPartitionerName", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "elementKind": "method", "justification": "JAVA-2103: Expose partitioner name in TokenMap API" }, @@ -707,7 +707,7 @@ "classQualifiedName": "com.datastax.oss.driver.api.core.connection.ReconnectionPolicy", "classSimpleName": "ReconnectionPolicy", "methodName": "newControlConnectionSchedule", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "elementKind": "method", "justification": "JAVA-2077: Allow reconnection policy to detect first connection attempt" } diff --git a/manual/core/README.md b/manual/core/README.md index 092a4d93c51..0417714d24f 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 ``` diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index 2ac913a17f0..b5c06e7d54c 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -70,4 +70,4 @@ Dependency: Always double-check the exact Snappy version needed; you can find it in the driver's [parent POM]. -[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-beta3%7Cpom \ No newline at end of file +[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-rc1%7Cpom \ No newline at end of file diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 7ae32ee7677..5c28d5e83a4 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -39,7 +39,7 @@ dependencies, and tell Maven that we're going to use Java 8: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 ch.qos.logback @@ -144,7 +144,7 @@ You should see output similar to: [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3.1:java (default-cli) @ yourapp --- -11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-beta3 +11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-rc1 11:39:45.648 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision 11:39:45.649 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 3.11.2 @@ -176,7 +176,7 @@ repositories { } dependencies { - compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-beta3' + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-rc1' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } ``` @@ -260,7 +260,7 @@ In that case, you can exclude the dependency: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 com.typesafe @@ -288,7 +288,7 @@ are not available on your platform, you can exclude the following dependencies: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 com.github.jnr @@ -322,7 +322,7 @@ and never call [Session.getMetrics] anywhere in your application, you can remove com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 io.dropwizard.metrics @@ -343,7 +343,7 @@ If all of these metrics are disabled, you can remove the dependency: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 org.hdrhistogram @@ -369,7 +369,7 @@ exclude them: com.datastax.oss java-driver-core - 4.0.0-beta3 + 4.0.0-rc1 com.github.stephenc.jcip diff --git a/manual/core/shaded_jar/README.md b/manual/core/shaded_jar/README.md index 9633b8bdd3f..c7007a3b3b4 100644 --- a/manual/core/shaded_jar/README.md +++ b/manual/core/shaded_jar/README.md @@ -12,7 +12,7 @@ package name: com.datastax.oss java-driver-core-shaded - 4.0.0-beta3 + 4.0.0-rc1 ``` @@ -23,12 +23,12 @@ dependency to the non-shaded JAR: com.datastax.oss java-driver-core-shaded - 4.0.0-beta3 + 4.0.0-rc1 com.datastax.oss java-driver-query-builder - 4.0.0-beta3 + 4.0.0-rc1 com.datastax.oss diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index f3f8e97ae16..09b0f8c369c 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -14,7 +14,7 @@ To use it in your application, add the following dependency: com.datastax.oss java-driver-query-builder - 4.0.0-beta3 + 4.0.0-rc1 ``` diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 07d232e1493..9539281f7e2 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -122,7 +122,7 @@ "old": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition===)", "new": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition===)", "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-rc1-SNAPSHOT", + "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-rc1", "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" } ] From be56e7ec18740c22f294d1ef6a23284d8c351995 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 19 Feb 2019 10:03:22 +0100 Subject: [PATCH 689/742] [maven-release-plugin] prepare release 4.0.0-rc1 --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index dab7a2bbd9a..2aead28d090 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index b016372a1b8..3f5784b3d49 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index 24846fe3c9f..d5d75873f22 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 9235b6e5a17..bffb6b962c4 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index 18dd9e8956a..f23e86ebf96 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 pom DataStax Java driver for Apache Cassandra(R) @@ -609,7 +609,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0-rc1 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index a9f8d1b244e..e3bbe0fcb5c 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 494a9249adf..da3049c15b5 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1-SNAPSHOT + 4.0.0-rc1 java-driver-test-infra From f4ca4d73387a35715c8f5eb93fd06db2d7bf7819 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 19 Feb 2019 10:03:38 +0100 Subject: [PATCH 690/742] [maven-release-plugin] prepare for next development iteration --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 2aead28d090..91f8a68377b 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 3f5784b3d49..0052882d1f1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index d5d75873f22..c95458f001d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-distribution diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index bffb6b962c4..3a64ad2567f 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-integration-tests diff --git a/pom.xml b/pom.xml index f23e86ebf96..13eba2a5f7f 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT pom DataStax Java driver for Apache Cassandra(R) @@ -609,7 +609,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - 4.0.0-rc1 + HEAD diff --git a/query-builder/pom.xml b/query-builder/pom.xml index e3bbe0fcb5c..95e9ecb241f 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index da3049c15b5..d89c09d45d8 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc1 + 4.0.0-rc2-SNAPSHOT java-driver-test-infra From c6054fba784346005e199315d11ba1707b5a1379 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 14:21:31 -0800 Subject: [PATCH 691/742] Prepare changelog for 4.0.0 --- changelog/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 98b16b95a25..6614d3587ad 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,6 +2,8 @@ +### 4.0.0 (in progress) + ### 4.0.0-rc1 - [improvement] JAVA-2106: Log server side warnings returned from a query From b49c51722902dbdd5ea8c18411972d28e60a00f9 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 20 Feb 2019 13:24:00 +0100 Subject: [PATCH 692/742] Update driver version in main README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9089777e3a..6c0ff613555 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-beta2](https://github.com/datastax/java-driver/tree/4.0.0-beta2).* +[4.0.0-rc1](https://github.com/datastax/java-driver/tree/4.0.0-rc1).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and [DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query From 5f6cdf877ebf17c223a10b24e99d9a633e28c360 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 20 Feb 2019 13:24:47 +0100 Subject: [PATCH 693/742] Change Travis badge URL form travis-ci.org to travis-ci.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c0ff613555..06493ace680 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Java Driver for Apache Cassandra® -[![Build Status](https://travis-ci.org/datastax/java-driver.svg?branch=4.x)](https://travis-ci.org/datastax/java-driver) +[![Build Status](https://travis-ci.com/datastax/java-driver.svg?branch=4.x)](https://travis-ci.com/datastax/java-driver) *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the From 26083e502a4c22ae3f0c5985a5d57c6db6dd8c6f Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 20 Feb 2019 12:54:05 -0800 Subject: [PATCH 694/742] Reinitialize Revapi ignores So that `revapi.json` files only document breaking changes between RCs and GA. What changed across beta versions is less relevant to end users, so don't pollute the files with that information. --- core/revapi.json | 696 -------------------------------------- query-builder/revapi.json | 108 ------ 2 files changed, 804 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 598e24478cd..e34ef7a45ac 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -15,702 +15,6 @@ } }, "ignore": [ - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::isVirtual()", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata", - "classSimpleName": "KeyspaceMetadata", - "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.method.returnTypeChanged", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "oldType": "java.util.UUID", - "newType": "java.util.Optional", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", - "classSimpleName": "RelationMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId()", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata", - "classSimpleName": "RelationMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "classSimpleName": "TableMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::isVirtual()", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.TableMetadata", - "classSimpleName": "TableMetadata", - "methodName": "isVirtual", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.annotation.removed", - "old": "method java.util.UUID com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getId() @ com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.core.metadata.schema", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.schema.ViewMetadata", - "classSimpleName": "ViewMetadata", - "methodName": "getId", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Adding virtual tables" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator com.datastax.oss.driver.api.core.context.DriverContext::addressTranslator()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "addressTranslator", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::authProvider()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "authProvider", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.config.DriverConfig com.datastax.oss.driver.api.core.context.DriverContext::config()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "config", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.config.DriverConfigLoader com.datastax.oss.driver.api.core.context.DriverContext::configLoader()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "configLoader", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator com.datastax.oss.driver.api.core.context.DriverContext::getAddressTranslator()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getAddressTranslator", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::getAuthProvider()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getAuthProvider", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.config.DriverConfig com.datastax.oss.driver.api.core.context.DriverContext::getConfig()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getConfig", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.config.DriverConfigLoader com.datastax.oss.driver.api.core.context.DriverContext::getConfigLoader()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getConfigLoader", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getLoadBalancingPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getLoadBalancingPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.metadata.NodeStateListener com.datastax.oss.driver.api.core.context.DriverContext::getNodeStateListener()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getNodeStateListener", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy com.datastax.oss.driver.api.core.context.DriverContext::getReconnectionPolicy()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getReconnectionPolicy", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.session.throttling.RequestThrottler com.datastax.oss.driver.api.core.context.DriverContext::getRequestThrottler()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getRequestThrottler", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.tracker.RequestTracker com.datastax.oss.driver.api.core.context.DriverContext::getRequestTracker()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getRequestTracker", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getRetryPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getRetryPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener com.datastax.oss.driver.api.core.context.DriverContext::getSchemaChangeListener()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getSchemaChangeListener", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.lang.String com.datastax.oss.driver.api.core.context.DriverContext::getSessionName()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getSessionName", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::getSpeculativeExecutionPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getSpeculativeExecutionPolicies", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::getSslEngineFactory()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getSslEngineFactory", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.time.TimestampGenerator com.datastax.oss.driver.api.core.context.DriverContext::getTimestampGenerator()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "getTimestampGenerator", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::loadBalancingPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "loadBalancingPolicies", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy com.datastax.oss.driver.api.core.context.DriverContext::loadBalancingPolicy(java.lang.String)", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "loadBalancingPolicy", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.metadata.NodeStateListener com.datastax.oss.driver.api.core.context.DriverContext::nodeStateListener()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "nodeStateListener", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy com.datastax.oss.driver.api.core.context.DriverContext::reconnectionPolicy()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "reconnectionPolicy", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.session.throttling.RequestThrottler com.datastax.oss.driver.api.core.context.DriverContext::requestThrottler()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "requestThrottler", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.tracker.RequestTracker com.datastax.oss.driver.api.core.context.DriverContext::requestTracker()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "requestTracker", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::retryPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "retryPolicies", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.retry.RetryPolicy com.datastax.oss.driver.api.core.context.DriverContext::retryPolicy(java.lang.String)", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "retryPolicy", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener com.datastax.oss.driver.api.core.context.DriverContext::schemaChangeListener()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "schemaChangeListener", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.lang.String com.datastax.oss.driver.api.core.context.DriverContext::sessionName()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "sessionName", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.util.Map com.datastax.oss.driver.api.core.context.DriverContext::speculativeExecutionPolicies()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "speculativeExecutionPolicies", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy com.datastax.oss.driver.api.core.context.DriverContext::speculativeExecutionPolicy(java.lang.String)", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "speculativeExecutionPolicy", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::sslEngineFactory()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "sslEngineFactory", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.time.TimestampGenerator com.datastax.oss.driver.api.core.context.DriverContext::timestampGenerator()", - "package": "com.datastax.oss.driver.api.core.context", - "classQualifiedName": "com.datastax.oss.driver.api.core.context.DriverContext", - "classSimpleName": "DriverContext", - "methodName": "timestampGenerator", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry com.datastax.oss.driver.api.core.detach.AttachmentPoint::codecRegistry()", - "package": "com.datastax.oss.driver.api.core.detach", - "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", - "classSimpleName": "AttachmentPoint", - "methodName": "codecRegistry", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry com.datastax.oss.driver.api.core.detach.AttachmentPoint::getCodecRegistry()", - "package": "com.datastax.oss.driver.api.core.detach", - "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", - "classSimpleName": "AttachmentPoint", - "methodName": "getCodecRegistry", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.ProtocolVersion com.datastax.oss.driver.api.core.detach.AttachmentPoint::getProtocolVersion()", - "package": "com.datastax.oss.driver.api.core.detach", - "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", - "classSimpleName": "AttachmentPoint", - "methodName": "getProtocolVersion", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.removed", - "old": "method com.datastax.oss.driver.api.core.ProtocolVersion com.datastax.oss.driver.api.core.detach.AttachmentPoint::protocolVersion()", - "package": "com.datastax.oss.driver.api.core.detach", - "classQualifiedName": "com.datastax.oss.driver.api.core.detach.AttachmentPoint", - "classSimpleName": "AttachmentPoint", - "methodName": "protocolVersion", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "Renamed context getters for uniformity" - }, - { - "code": "java.method.addedToInterface", - "new": "method com.datastax.oss.driver.api.core.metadata.Node com.datastax.oss.driver.api.core.session.Request::getNode()", - "package": "com.datastax.oss.driver.api.core.session", - "classQualifiedName": "com.datastax.oss.driver.api.core.session.Request", - "classSimpleName": "Request", - "methodName": "getNode", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Add ability to query specific nodes for virtual tables" - }, - { - "code": "java.method.addedToInterface", - "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node)", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.Statement", - "classSimpleName": "Statement", - "methodName": "setNode", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Add ability to query specific nodes for virtual tables" - }, - { - "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", - "oldType": "java.util.concurrent.CompletionStage", - "newType": "java.util.concurrent.CompletionStage", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", - "classSimpleName": "CqlSession", - "methodName": "executeAsync", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Return covariant future types from session async methods" - }, - { - "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", - "oldType": "java.util.concurrent.CompletionStage", - "newType": "java.util.concurrent.CompletionStage", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", - "classSimpleName": "CqlSession", - "methodName": "executeAsync", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Return covariant future types from session async methods" - }, - { - "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", - "oldType": "java.util.concurrent.CompletionStage", - "newType": "java.util.concurrent.CompletionStage", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", - "classSimpleName": "CqlSession", - "methodName": "prepareAsync", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Return covariant future types from session async methods" - }, - { - "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", - "oldType": "java.util.concurrent.CompletionStage", - "newType": "java.util.concurrent.CompletionStage", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", - "classSimpleName": "CqlSession", - "methodName": "prepareAsync", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Return covariant future types from session async methods" - }, - { - "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", - "oldType": "java.util.concurrent.CompletionStage", - "newType": "java.util.concurrent.CompletionStage", - "package": "com.datastax.oss.driver.api.core", - "classQualifiedName": "com.datastax.oss.driver.api.core.CqlSession", - "classSimpleName": "CqlSession", - "methodName": "prepareAsync", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "elementKind": "method", - "justification": "Return covariant future types from session async methods" - }, - { - "code": "java.method.removed", - "old": "method void com.datastax.oss.driver.api.core.cql.ResultSet::fetchNextPage()", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", - "classSimpleName": "ResultSet", - "methodName": "fetchNextPage", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "JAVA-1988: remove pre-fetching from ResultSet API" - }, - { - "code": "java.method.removed", - "old": "method int com.datastax.oss.driver.api.core.cql.ResultSet::getAvailableWithoutFetching()", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", - "classSimpleName": "ResultSet", - "methodName": "getAvailableWithoutFetching", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "JAVA-1988: remove pre-fetching from ResultSet API" - }, - { - "code": "java.method.removed", - "old": "method boolean com.datastax.oss.driver.api.core.cql.ResultSet::isFullyFetched()", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", - "classSimpleName": "ResultSet", - "methodName": "isFullyFetched", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta1", - "elementKind": "method", - "justification": "JAVA-1988: remove pre-fetching from ResultSet API" - }, - { - "code": "java.method.addedToInterface", - "new": "method int com.datastax.oss.driver.api.core.cql.ResultSet::getAvailableWithoutFetching()", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", - "classSimpleName": "ResultSet", - "methodName": "getAvailableWithoutFetching", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", - "elementKind": "method", - "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" - }, - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.cql.ResultSet::isFullyFetched()", - "package": "com.datastax.oss.driver.api.core.cql", - "classQualifiedName": "com.datastax.oss.driver.api.core.cql.ResultSet", - "classSimpleName": "ResultSet", - "methodName": "isFullyFetched", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", - "elementKind": "method", - "justification": "JAVA-2011: Re-add ResultSet.getAvailableWithoutFetching() and isFullyFetched()" - }, - { - "code": "java.method.numberOfParametersChanged", - "old": "method com.datastax.oss.driver.api.core.context.DriverContext com.datastax.oss.driver.api.core.session.SessionBuilder::buildContext(com.datastax.oss.driver.api.core.config.DriverConfigLoader, java.util.List>, com.datastax.oss.driver.api.core.metadata.NodeStateListener, com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener, com.datastax.oss.driver.api.core.tracker.RequestTracker, java.util.Map>, java.lang.ClassLoader)", - "new": "method com.datastax.oss.driver.api.core.context.DriverContext com.datastax.oss.driver.api.core.session.SessionBuilder::buildContext(com.datastax.oss.driver.api.core.config.DriverConfigLoader, java.util.List>, com.datastax.oss.driver.api.core.metadata.NodeStateListener, com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener, com.datastax.oss.driver.api.core.tracker.RequestTracker, java.util.Map, java.util.Map>, java.lang.ClassLoader)", - "package": "com.datastax.oss.driver.api.core.session", - "classQualifiedName": "com.datastax.oss.driver.api.core.session.SessionBuilder", - "classSimpleName": "SessionBuilder", - "methodName": "buildContext", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta2", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", - "elementKind": "method", - "justification": "JAVA-2049: Add shorthand method to SessionBuilder to specify local DC" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.config.DriverConfigLoader::reload()", - "package": "com.datastax.oss.driver.api.core.config", - "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", - "classSimpleName": "DriverConfigLoader", - "methodName": "reload", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", - "elementKind": "method", - "justification": "JAVA-2058: Make programmatic config reloading part of the public API" - }, - { - "code": "java.method.addedToInterface", - "new": "method boolean com.datastax.oss.driver.api.core.config.DriverConfigLoader::supportsReloading()", - "package": "com.datastax.oss.driver.api.core.config", - "classQualifiedName": "com.datastax.oss.driver.api.core.config.DriverConfigLoader", - "classSimpleName": "DriverConfigLoader", - "methodName": "supportsReloading", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-beta3", - "elementKind": "method", - "justification": "JAVA-2058: Make programmatic config reloading part of the public API" - }, - { - "code": "java.method.addedToInterface", - "new": "method java.lang.String com.datastax.oss.driver.api.core.metadata.TokenMap::getPartitionerName()", - "package": "com.datastax.oss.driver.api.core.metadata", - "classQualifiedName": "com.datastax.oss.driver.api.core.metadata.TokenMap", - "classSimpleName": "TokenMap", - "methodName": "getPartitionerName", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", - "elementKind": "method", - "justification": "JAVA-2103: Expose partitioner name in TokenMap API" - }, - { - "code": "java.method.numberOfParametersChanged", - "old": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule com.datastax.oss.driver.api.core.connection.ReconnectionPolicy::newControlConnectionSchedule()", - "new": "method com.datastax.oss.driver.api.core.connection.ReconnectionPolicy.ReconnectionSchedule com.datastax.oss.driver.api.core.connection.ReconnectionPolicy::newControlConnectionSchedule(boolean)", - "package": "com.datastax.oss.driver.api.core.connection", - "classQualifiedName": "com.datastax.oss.driver.api.core.connection.ReconnectionPolicy", - "classSimpleName": "ReconnectionPolicy", - "methodName": "newControlConnectionSchedule", - "newArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", - "elementKind": "method", - "justification": "JAVA-2077: Allow reconnection policy to detect first connection attempt" - } ] } } diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 9539281f7e2..2fde1deda35 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -17,114 +17,6 @@ } }, "ignore": [ - { - "regex": true, - "code": "java\\.annotation\\.removed", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.querybuilder.*", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.added", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", - "package": "com.datastax.oss.driver.api.querybuilder.*", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.removed", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.querybuilder.*", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.added", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Selector::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", - "package": "com.datastax.oss.driver.api.querybuilder.*", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.removed", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.querybuilder.select", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.added", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", - "package": "com.datastax.oss.driver.api.querybuilder.select", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.removed", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.NonNull", - "package": "com.datastax.oss.driver.api.querybuilder.select", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta2", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "regex": true, - "code": "java\\.annotation\\.added", - "old": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "new": "parameter com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.Select com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.select\\.OngoingSelection::range\\(.*, com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term, ===com\\.datastax\\.oss\\.driver\\.api\\.querybuilder\\.term\\.Term===\\).*", - "annotationType": "edu.umd.cs.findbugs.annotations.Nullable", - "package": "com.datastax.oss.driver.api.querybuilder.select", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "elementKind": "parameter", - "justification": "Fix nullability annotations on query builder range selectors" - }, - { - "code": "java.class.removed", - "old": "class com.datastax.oss.driver.api.querybuilder.QueryBuilderDsl", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" - }, - { - "code": "java.class.removed", - "old": "class com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" - }, - { - "code": "java.method.parameterTypeChanged", - "old": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilderDsl.RowsPerPartition===)", - "new": "parameter SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, ===com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition===)", - "oldArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-beta3", - "newArchive": "com.datastax.oss:java-driver-query-builder:jar:4.0.0-rc1", - "justification": "JAVA-2151: Drop \"Dsl\" suffix from query builder main classes" - } ] } } From 71d732bf5bd521da32232755c35add64aed251c3 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sat, 23 Feb 2019 15:01:36 +0100 Subject: [PATCH 695/742] Remove Travis CI badge --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 06493ace680..31e9bdcc065 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Datastax Java Driver for Apache Cassandra® -[![Build Status](https://travis-ci.com/datastax/java-driver.svg?branch=4.x)](https://travis-ci.com/datastax/java-driver) - *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. From e0e0d42f05cb780060c1499cb0fac334805c9ae0 Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Thu, 28 Feb 2019 01:50:38 -0800 Subject: [PATCH 696/742] JAVA-2149: Improve Term javadocs in the query builder (#1191) --- changelog/README.md | 2 ++ .../driver/api/querybuilder/QueryBuilder.java | 4 +-- .../relation/ArithmeticRelationBuilder.java | 36 ++++--------------- .../driver/api/querybuilder/term/Term.java | 27 ++++++++++---- 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 6614d3587ad..7192d799982 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,8 @@ ### 4.0.0 (in progress) +- [documentation] JAVA-2149: Improve Term javadocs in the query builder + ### 4.0.0-rc1 - [improvement] JAVA-2106: Log server side warnings returned from a query diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java index 62ade2a2de0..b9996fd1d23 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java @@ -219,7 +219,7 @@ public static Term negate(@NonNull Term argument) { return new OppositeTerm(argument); } - /** A function call as a term, as in {@code WHERE = f(arguments)}. */ + /** A function call as a term, as in {@code WHERE k = f(arguments)}. */ @NonNull public static Term function( @NonNull CqlIdentifier functionId, @NonNull Iterable arguments) { @@ -250,7 +250,7 @@ public static Term function(@NonNull String functionName, @NonNull Term... argum return function(CqlIdentifier.fromCql(functionName), arguments); } - /** A function call as a term, as in {@code WHERE = ks.f(arguments)}. */ + /** A function call as a term, as in {@code WHERE k = ks.f(arguments)}. */ @NonNull public static Term function( @Nullable CqlIdentifier keyspaceId, diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java index cfac305594c..26ff62dcf4c 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/ArithmeticRelationBuilder.java @@ -21,61 +21,37 @@ public interface ArithmeticRelationBuilder { - /** - * Builds an '=' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds an '=' relation with the given term. */ @NonNull default ResultT isEqualTo(@NonNull Term rightOperand) { return build("=", rightOperand); } - /** - * Builds a '<' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds a '<' relation with the given term. */ @NonNull default ResultT isLessThan(@NonNull Term rightOperand) { return build("<", rightOperand); } - /** - * Builds a '<=' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds a '<=' relation with the given term. */ @NonNull default ResultT isLessThanOrEqualTo(@NonNull Term rightOperand) { return build("<=", rightOperand); } - /** - * Builds a '>' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds a '>' relation with the given term. */ @NonNull default ResultT isGreaterThan(@NonNull Term rightOperand) { return build(">", rightOperand); } - /** - * Builds a '>=' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds a '>=' relation with the given term. */ @NonNull default ResultT isGreaterThanOrEqualTo(@NonNull Term rightOperand) { return build(">=", rightOperand); } - /** - * Builds a '!=' relation with the given term. - * - *

        Use one of the static factory method in {@link Term} to create the argument. - */ + /** Builds a '!=' relation with the given term. */ @NonNull default ResultT isNotEqualTo(@NonNull Term rightOperand) { return build("!=", rightOperand); diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java index 5daa05a11f0..2cf33b856ca 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/term/Term.java @@ -17,24 +17,39 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.querybuilder.BuildableQuery; import com.datastax.oss.driver.api.querybuilder.CqlSnippet; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; -import com.datastax.oss.driver.api.querybuilder.relation.ArithmeticRelationBuilder; +import com.datastax.oss.driver.api.querybuilder.relation.Relation; +import com.datastax.oss.driver.api.querybuilder.select.OngoingSelection; import com.datastax.oss.driver.api.querybuilder.select.Selector; /** * A simple expression that doesn't reference columns. * - *

        For example, it can be used: + *

        It is used as an argument to certain {@linkplain Selector selectors} (for example the indices + * in a {@linkplain OngoingSelection#range(Selector, Term, Term) range}), or as the right operand of + * {@linkplain Relation relations}. + * + *

        To create a term, call one of the static factory methods in {@link QueryBuilder}: * *

          - *
        • for the indices in a {@link Selector#range(CqlIdentifier, Term, Term) range selection}; - *
        • as the right operand of a {@link ArithmeticRelationBuilder#isEqualTo(Term) relation}. + *
        • {@link QueryBuilder#literal(Object) literal()} to inline a Java object into the query + * string; + *
        • {@link QueryBuilder#function(CqlIdentifier, CqlIdentifier, Iterable) function()} to invoke + * a built-in or user-defined function; + *
        • an arithmetic operator combining other terms: {@link QueryBuilder#add(Term, Term) add()}, + * {@link QueryBuilder#subtract(Term, Term) subtract()}, {@link QueryBuilder#negate(Term) + * negate()}, {@link QueryBuilder#multiply(Term, Term) multiply()}, {@link + * QueryBuilder#divide(Term, Term) divide()} or {@link QueryBuilder#remainder(Term, Term) + * remainder()}; + *
        • {@link QueryBuilder#typeHint(Term, DataType) typeHint()} to coerce another term to a + * particular CQL type; + *
        • {@link QueryBuilder#raw(String) raw()} for a raw CQL snippet. *
        * - * To build instances of this type, use the factory methods in {@link QueryBuilder}, such as {@link - * QueryBuilder#literal(Object) literal}, {@link QueryBuilder#tuple(Iterable) tuple}, etc. + * Note that some of these methods have multiple overloads. */ public interface Term extends CqlSnippet { From 4ad1b1bc90eeb2a96cfd3893ba763ba2a58bda6a Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Thu, 28 Feb 2019 01:56:27 -0800 Subject: [PATCH 697/742] JAVA-2150: Improve query builder error message on unsupported literal type (#1194) --- changelog/README.md | 1 + .../oss/driver/api/querybuilder/QueryBuilder.java | 13 ++++++++++++- .../driver/api/querybuilder/relation/TermTest.java | 12 ++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 7192d799982..1d6ca98c4a5 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2150: Improve query builder error message on unsupported literal type - [documentation] JAVA-2149: Improve Term javadocs in the query builder ### 4.0.0-rc1 diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java index b9996fd1d23..c23c7f63ef4 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/QueryBuilder.java @@ -400,7 +400,18 @@ public static Literal literal(@Nullable Object value) { */ @NonNull public static Literal literal(@Nullable Object value, @NonNull CodecRegistry codecRegistry) { - return literal(value, (value == null) ? null : codecRegistry.codecFor(value)); + try { + return literal(value, (value == null) ? null : codecRegistry.codecFor(value)); + } catch (CodecNotFoundException e) { + assert value != null; + throw new IllegalArgumentException( + String.format( + "Could not inline literal of type %s. " + + "This happens because the driver doesn't know how to map it to a CQL type. " + + "Try passing a TypeCodec or CodecRegistry to literal().", + value.getClass().getName()), + e); + } } /** diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java index d46bd691c4c..320b7c827b8 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/TermTest.java @@ -35,6 +35,7 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.toTimestamp; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.toUnixTimestamp; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.typeHint; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.TupleValue; @@ -47,6 +48,7 @@ import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import java.util.Date; import org.junit.Test; public class TermTest { @@ -112,8 +114,14 @@ public void should_generate_literal_terms() { assertThat(literal(Charsets.UTF_8, CharsetCodec.TEST_REGISTRY)).hasCql("'UTF-8'"); } - @Test(expected = CodecNotFoundException.class) + @Test public void should_fail_when_no_codec_for_literal() { - literal(Charsets.UTF_8); + assertThatThrownBy(() -> literal(new Date(2018, 10, 10))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Could not inline literal of type java.util.Date. " + + "This happens because the driver doesn't know how to map it to a CQL type. " + + "Try passing a TypeCodec or CodecRegistry to literal().") + .hasCauseInstanceOf(CodecNotFoundException.class); } } From a2d3a4ec25acdfb643d653b847233d92b23d8df1 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 14:25:12 -0800 Subject: [PATCH 698/742] JAVA-2080: Fix internal manual links in upgrade guide --- upgrade_guide/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 30799c05c6e..e339107db45 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -121,8 +121,8 @@ will find more information about asynchronous iterations in the manual pages abo programming][4.x async programming] and [paging][4.x paging]. [3.x async paging]: http://docs.datastax.com/en/developer/java-driver/3.2/manual/async/#async-paging -[4.x async programming]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/async/ -[4.x paging]: http://docs.datastax.com/en/developer/java-driver/4.0/manual/core/paging/ +[4.x async programming]: ../manual/core/async/ +[4.x paging]: ../manual/core/paging/ #### CQL to Java type mappings From 68815e06c8dd87bcdd707f3b6572ff01efbb60ed Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 14:35:18 -0800 Subject: [PATCH 699/742] JAVA-2142: Update FAQ entry to mention statement builders --- faq/README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/faq/README.md b/faq/README.md index 62e3c66e7c2..489134ade8b 100644 --- a/faq/README.md +++ b/faq/README.md @@ -8,12 +8,23 @@ sure you don't accidentally ignore their result: ```java BoundStatement boundSelect = preparedSelect.bind(); -// This doesn't work: setInt doesn't modify boundSelect in place: +// This doesn't work: setInt and setPageSize don't modify boundSelect in place: boundSelect.setInt("k", key); +boundSelect.setPageSize(1000); session.execute(boundSelect); // Instead, do this: -boundSelect = boundSelect.setInt("k", key); +boundSelect = boundSelect.setInt("k", key).setPageSize(1000); +``` + +The driver also provides builders: + +```java +BoundStatement boundSelect = + preparedSelect.boundStatementBuilder() + .setInt("k", key) + .withPageSize(1000) + .build(); ``` ### Why do asynchronous methods return `CompletionStage` instead of `CompletableFuture`? From 101a4c2d82b61b667f85cb747da79f052b855973 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 14:45:15 -0800 Subject: [PATCH 700/742] JAVA-2159: Update code example in API conventions manual page --- manual/api_conventions/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manual/api_conventions/README.md b/manual/api_conventions/README.md index e55a4cc80c3..a76067ebef2 100644 --- a/manual/api_conventions/README.md +++ b/manual/api_conventions/README.md @@ -27,14 +27,15 @@ generally have to go through an explicit cast: import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.config.ForceReloadConfigEvent; +import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; // Public API: DriverContext context = session.getContext(); -// Switch to the internal API to force a reload of the configuration: +// Switch to the internal API to force a node down: InternalDriverContext internalContext = (InternalDriverContext) context; -internalContext.getEventBus().fire(ForceReloadConfigEvent.INSTANCE); +InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9042); +internalContext.getEventBus().fire(TopologyEvent.forceDown(address)); ``` So the risk of unintentionally using the internal API is very low. To double-check, you can always From 5856eaab620be51ee9337a9f23ffe2a28388edb7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 15:56:29 -0800 Subject: [PATCH 701/742] JAVA-1876: Improve contribution guidelines on integration tests --- CONTRIBUTING.md | 112 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37a65653a8a..c2cabf1cd02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -237,55 +237,115 @@ factor some common code in a parent abstract class named with "XxxTestBase", and different families of tests into separate child classes. For example, `CqlRequestHandlerTestBase`, `CqlRequestHandlerRetryTest`, `CqlRequestHandlerSpeculativeExecutionTest`... -Unit tests live in their respective production code's module. They should be fast and not start any +### Unit tests + +They live in the same module as the code they are testing. They should be fast and not start any external process. They usually target one specific component and mock the rest of the driver context. -Integration tests live in the aptly-named `integration-tests` module. They exercise the whole driver -stack against an external process -- either simulated with -[Simulacron](https://github.com/datastax/simulacron), or a live Cassandra cluster run with -[CCM](https://github.com/pcmanus/ccm) (the `ccm` executable must be in the path). They are -classified into categories that determine how they will be run during the build: +### Integration tests -* `@Category(ParallelizableTests.class)`: for tests that use Simulacron or `CcmRule`. They will be - run in parallel. -* No annotation: for tests that use `CustomCcmRule`. They will be run one after the other. -* `@Category(IsolatedTests.class)`: for tests that require specific environment tweaks, typically - system properties that need to be set before initialization. They will be run one after the other, - each in its own JVM fork. +They live in the `integration-tests` module, and exercise the whole driver stack against an external +process, which can be either one of: +* [Simulacron](https://github.com/datastax/simulacron): simulates Cassandra nodes on loopback + addresses; your test must "prime" data, i.e. tell the nodes what results to return for + pre-determined queries. + + For an example of a Simulacron-based test, see `NodeTargetingIT`. +* [CCM](https://github.com/pcmanus/ccm): launches actual Cassandra nodes locally. The `ccm` + executable must be in the path. + + You can pass a `-Dccm.version` system property to the build to target a particular Cassandra + version (it defaults to 3.11.0). `-Dccm.directory` allows you to point to a local installation + -- this can be a checkout of the Cassandra codebase, as long as it's built. See `CcmBridge` in + the driver codebase for more details. + + For an example of a CCM-based test, see `PlainTextAuthProviderIT`. -Simulacron relies on loopback aliases to simulate multiple nodes. On Linux or Windows, you shouldn't -have anything to do. On MacOS, run this script: +Integration tests are divided into three categories: -``` -#!/bin/bash -for sub in {0..4}; do - echo "Opening for 127.0.$sub" - for i in {0..255}; do sudo ifconfig lo0 alias 127.0.$sub.$i up; done -done -``` +#### Parallelizable tests -Note that this is known to cause temporary increased CPU usage in OS X initially while mDNSResponder -acclimates itself to the presence of added IP addresses. This lasts several minutes. Also, this does -not survive reboots. +These tests can be run in parallel, to speed up the build. They either use: +* dedicated Simulacron instances. These are lightweight, and Simulacron will manage the ports to + make sure that there are no collisions. +* a shared, one-node CCM cluster. Each test works in its own keyspace. + +The build runs them with a configurable degree of parallelism (currently 8). The shared CCM cluster +is initialized the first time it's used, and stopped before moving on to serial tests. + +To make an integration test parallelizable, annotate it with `@Category(ParallelizableTests.class)`. +If you use CCM, it **must** be with `CcmRule`. + +For an example of a Simulacron-based parallelizable test, see `NodeTargetingIT`. For a CCM-based +test, see `DirectCompressionIT`. + +#### Serial tests + +These tests cannot run in parallel, in general because they require CCM clusters of different sizes, +or with a specific configuration (we never run more than one CCM cluster simultaneously: it would be +too resource-intensive, and too complicated to manage all the ports). + +The build runs them one by one, after the parallelizable tests. + +To make an integration test serial, do not annotate it with `@Category`. The CCM rule **must** be +`CustomCcmRule`. + +For an example, see `DefaultLoadBalancingPolicyIT`. + +Note: if multiple serial tests have a common "base" class, do not pull up `CustomCcmRule`, each +child class must have its own instance. Otherwise they share the same CCM instance, and the first +one destroys it on teardown. See `TokenITBase` for how to organize code in those cases. + +#### Isolated tests + +Not only can those tests not run in parallel, they also require specific environment tweaks, +typically system properties that need to be set before initialization. + +The build runs them one by one, *each in its own JVM fork*, after the serial tests. + +To isolate an integration test, annotate it with `@Category(IsolatedTests.class)`. The CCM rule +**must** be `CustomCcmRule`. + +For an example, see `HeapCompressionIT`. ## Running the tests -#### Unit tests +### Unit tests mvn clean test This currently takes about 30 seconds. The goal is to keep it within a couple of minutes (it runs for each commit if you enable the pre-commit hook -- see below). -#### Integration tests +### Integration tests mvn clean verify This currently takes about 9 minutes. We don't have a hard limit, but ideally it should stay within 30 minutes to 1 hour. +You can skip test categories individually with `-DskipParallelizableITs`, `-DskipSerialITs` and +`-DskipIsolatedITs`. + +### Configuring MacOS for Simulacron + +Simulacron (used in integration tests) relies on loopback aliases to simulate multiple nodes. On +Linux or Windows, you shouldn't have anything to do. On MacOS, run this script: + +``` +#!/bin/bash +for sub in {0..4}; do + echo "Opening for 127.0.$sub" + for i in {0..255}; do sudo ifconfig lo0 alias 127.0.$sub.$i up; done +done +``` + +Note that this is known to cause temporary increased CPU usage in OS X initially while mDNSResponder +acclimates itself to the presence of added IP addresses. This lasts several minutes. Also, this does +not survive reboots. + ## License headers From 16eb2b18d1b77011113072a0cd829d47173bff7f Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 16:05:42 -0800 Subject: [PATCH 702/742] Remove Java import contribution guidelines The Google formatter now handles imports. --- CONTRIBUTING.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2cabf1cd02..ddb2cb81d76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,6 @@ Some aspects are not covered by the formatter: empty or contains only a single statement. * XML files: indent with two spaces and wrap to respect the column limit of 100 characters. -Also, if your IDE sorts import statements automatically, make sure it follows the same order as the -formatter: all static imports in ASCII sort order, followed by a blank line, followed by all regular -imports in ASCII sort order. - ## Coding style -- production code Do not use static imports. They make things harder to understand when you look at the code From 89cb84565aea46b92954735e4c62602370d2dbdb Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Feb 2019 16:18:56 -0800 Subject: [PATCH 703/742] Improve PR guidelines --- CONTRIBUTING.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddb2cb81d76..d8d7a4c778a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -423,11 +423,11 @@ Like commits, pull requests should be focused on a single, clearly stated goal. Don't base a pull request onto another one, it's too complicated to follow two branches that evolve at the same time. If a ticket depends on another, wait for the first one to be merged. -If you have to address feedback, avoid rebasing your branch and force-pushing (this makes the -reviewers' job harder, because they have to re-read the full diff and figure out where your new -changes are). Instead, push a new commit on top of the existing history; it will be squashed later -when the PR gets merged. If the history is complex, it's a good idea to indicate in the message -where the changes should be squashed: +If you have to address feedback, avoid rewriting the history (e.g. squashing or amending commits): +this makes the reviewers' job harder, because they have to re-read the full diff and figure out +where your new changes are. Instead, push a new commit on top of the existing history; it will be +squashed later when the PR gets merged. If the history is complex, it's a good idea to indicate in +the message where the changes should be squashed: ``` * 20c88f4 - Address feedback (to squash with "Add metadata parsing logic") (36 minutes ago) @@ -438,7 +438,6 @@ where the changes should be squashed: (Note that the message refers to the other commit's subject line, not the SHA-1. This way it's still relevant if there are intermediary rebases.) -If you *really* need a newer commit from the base branch, or if the history is getting too -complicated, it can be OK to force-push occasionally, provided that **all current reviewers agree**. - -Don't push a merge commit to a pull request under any circumstance. +If you need new stuff from the base branch, it's fine to rebase and force-push, as long as you don't +rewrite the history. Just give a heads up to the reviewers beforehand. Don't push a merge commit to +a pull request. From 3f92fb78856710275672850b0bd21d4643a21cf0 Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Thu, 28 Feb 2019 02:10:19 -0800 Subject: [PATCH 704/742] JAVA-2158: Allow BuildableQuery to build statement with values (#1193) --- changelog/README.md | 1 + query-builder/pom.xml | 5 + .../api/querybuilder/BuildableQuery.java | 44 ++++- .../querybuilder/delete/DefaultDelete.java | 17 ++ .../querybuilder/insert/DefaultInsert.java | 17 ++ .../querybuilder/select/DefaultSelect.java | 16 ++ .../querybuilder/update/DefaultUpdate.java | 17 ++ .../api/querybuilder/BuildableQueryTest.java | 153 ++++++++++++++++++ 8 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/BuildableQueryTest.java diff --git a/changelog/README.md b/changelog/README.md index 1d6ca98c4a5..832c0be7cad 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2158: Allow BuildableQuery to build statement with values - [improvement] JAVA-2150: Improve query builder error message on unsupported literal type - [documentation] JAVA-2149: Improve Term javadocs in the query builder diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 95e9ecb241f..ff9f4390c0b 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -52,6 +52,11 @@ junit test + + com.tngtech.java + junit-dataprovider + test + org.assertj assertj-core diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java index b1ffddb6cab..3536c9c676b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder; import com.datastax.oss.driver.api.core.cql.Statement; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Map; /** * End state for the query builder DSL, which allows the generation of a CQL query. @@ -47,14 +48,50 @@ public interface BuildableQuery { * SimpleStatement.newInstance(asCql()) * } * - * In addition, some query implementation might try to infer additional statement properties (such - * as {@link Statement#isIdempotent()}). + * In addition, some implementations might try to infer additional statement properties (such as + * {@link Statement#isIdempotent()}). */ @NonNull default SimpleStatement build() { return SimpleStatement.newInstance(asCql()); } + /** + * Builds the CQL query and wraps it in a simple statement, also providing positional values for + * bind markers. + * + *

        This is a similar to: + * + *

        {@code
        +   * SimpleStatement.newInstance(asCql(), values)
        +   * }
        + * + * In addition, some implementations might try to infer additional statement properties (such as + * {@link Statement#isIdempotent()}). + */ + @NonNull + default SimpleStatement build(@NonNull Object... values) { + return SimpleStatement.newInstance(asCql(), values); + } + + /** + * Builds the CQL query and wraps it in a simple statement, also providing named values for bind + * markers. + * + *

        This is a similar to: + * + *

        {@code
        +   * SimpleStatement.newInstance(asCql(), namedValues)
        +   * }
        + * + * In addition, some implementations might try to infer additional statement properties (such as + * {@link Statement#isIdempotent()}). + */ + @NonNull + default SimpleStatement build(@NonNull Map namedValues) { + return SimpleStatement.newInstance(asCql(), namedValues); + } + /** * Builds the CQL query and wraps it in a simple statement builder. * @@ -71,6 +108,9 @@ default SimpleStatement build() { * SimpleStatement statement = * builder.addNamedValue("k", 1).addNamedValue("c", 2).withTracing().build(); * } + * + * In addition, some implementations might try to infer additional statement properties (such as + * {@link Statement#isIdempotent()}). */ @NonNull default SimpleStatementBuilder builder() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java index 1c6872f1a59..485dde13d0e 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Map; import net.jcip.annotations.Immutable; @Immutable @@ -174,6 +175,22 @@ public SimpleStatement build() { return builder().build(); } + @NonNull + @Override + public SimpleStatement build(@NonNull Object... values) { + return builder().addPositionalValues(values).build(); + } + + @NonNull + @Override + public SimpleStatement build(@NonNull Map namedValues) { + SimpleStatementBuilder builder = builder(); + for (Map.Entry entry : namedValues.entrySet()) { + builder.addNamedValue(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + @NonNull @Override public SimpleStatementBuilder builder() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index 32c81cd9906..dca9ff1ab56 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Map; import net.jcip.annotations.Immutable; @Immutable @@ -186,6 +187,22 @@ public SimpleStatement build() { return builder().build(); } + @NonNull + @Override + public SimpleStatement build(@NonNull Object... values) { + return builder().addPositionalValues(values).build(); + } + + @NonNull + @Override + public SimpleStatement build(@NonNull Map namedValues) { + SimpleStatementBuilder builder = builder(); + for (Map.Entry entry : namedValues.entrySet()) { + builder.addNamedValue(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + @NonNull @Override public SimpleStatementBuilder builder() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java index b5f3bd25f1f..3259dc237b7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java @@ -431,6 +431,22 @@ public SimpleStatement build() { return builder().build(); } + @NonNull + @Override + public SimpleStatement build(@NonNull Object... values) { + return builder().addPositionalValues(values).build(); + } + + @NonNull + @Override + public SimpleStatement build(@NonNull Map namedValues) { + SimpleStatementBuilder builder = builder(); + for (Map.Entry entry : namedValues.entrySet()) { + builder.addNamedValue(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + @NonNull @Override public SimpleStatementBuilder builder() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java index 278b1376536..659702a42bf 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Map; import net.jcip.annotations.Immutable; @Immutable @@ -170,6 +171,22 @@ public SimpleStatement build() { return builder().build(); } + @NonNull + @Override + public SimpleStatement build(@NonNull Object... values) { + return builder().addPositionalValues(values).build(); + } + + @NonNull + @Override + public SimpleStatement build(@NonNull Map namedValues) { + SimpleStatementBuilder builder = builder(); + for (Map.Entry entry : namedValues.entrySet()) { + builder.addNamedValue(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + @NonNull @Override public SimpleStatementBuilder builder() { diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/BuildableQueryTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/BuildableQueryTest.java new file mode 100644 index 00000000000..a9ba444072b --- /dev/null +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/BuildableQueryTest.java @@ -0,0 +1,153 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DataProviderRunner.class) +public class BuildableQueryTest { + + @DataProvider + public static Object[][] sampleQueries() { + // query | values | expected CQL | expected idempotence + return new Object[][] { + { + selectFrom("foo").all().whereColumn("k").isEqualTo(bindMarker("k")), + ImmutableMap.of("k", 1), + "SELECT * FROM foo WHERE k=:k", + true + }, + { + deleteFrom("foo").whereColumn("k").isEqualTo(bindMarker("k")), + ImmutableMap.of("k", 1), + "DELETE FROM foo WHERE k=:k", + true + }, + { + deleteFrom("foo").whereColumn("k").isEqualTo(bindMarker("k")).ifExists(), + ImmutableMap.of("k", 1), + "DELETE FROM foo WHERE k=:k IF EXISTS", + false + }, + { + insertInto("foo").value("a", bindMarker("a")).value("b", bindMarker("b")), + ImmutableMap.of("a", 1, "b", "b"), + "INSERT INTO foo (a,b) VALUES (:a,:b)", + true + }, + { + insertInto("foo").value("k", tuple(bindMarker("field1"), function("generate_id"))), + ImmutableMap.of("field1", 1), + "INSERT INTO foo (k) VALUES ((:field1,generate_id()))", + false + }, + { + update("foo").setColumn("v", bindMarker("v")).whereColumn("k").isEqualTo(bindMarker("k")), + ImmutableMap.of("v", 3, "k", 1), + "UPDATE foo SET v=:v WHERE k=:k", + true + }, + { + update("foo") + .setColumn("v", function("non_idempotent_func")) + .whereColumn("k") + .isEqualTo(bindMarker("k")), + ImmutableMap.of("k", 1), + "UPDATE foo SET v=non_idempotent_func() WHERE k=:k", + false + }, + }; + } + + @Test + @UseDataProvider("sampleQueries") + public void should_build_statement_without_values( + BuildableQuery query, + @SuppressWarnings("unused") Map boundValues, + String expectedQueryString, + boolean expectedIdempotence) { + SimpleStatement statement = query.build(); + assertThat(statement.getQuery()).isEqualTo(expectedQueryString); + assertThat(statement.isIdempotent()).isEqualTo(expectedIdempotence); + assertThat(statement.getPositionalValues()).isEmpty(); + assertThat(statement.getNamedValues()).isEmpty(); + } + + @Test + @UseDataProvider("sampleQueries") + public void should_build_statement_with_positional_values( + BuildableQuery query, + Map boundValues, + String expectedQueryString, + boolean expectedIdempotence) { + Object[] positionalValues = boundValues.values().toArray(); + SimpleStatement statement = query.build(positionalValues); + assertThat(statement.getQuery()).isEqualTo(expectedQueryString); + assertThat(statement.isIdempotent()).isEqualTo(expectedIdempotence); + assertThat(statement.getPositionalValues()).containsExactly(positionalValues); + assertThat(statement.getNamedValues()).isEmpty(); + } + + @Test + @UseDataProvider("sampleQueries") + public void should_build_statement_with_named_values( + BuildableQuery query, + Map boundValues, + String expectedQueryString, + boolean expectedIdempotence) { + SimpleStatement statement = query.build(boundValues); + assertThat(statement.getQuery()).isEqualTo(expectedQueryString); + assertThat(statement.isIdempotent()).isEqualTo(expectedIdempotence); + assertThat(statement.getPositionalValues()).isEmpty(); + assertThat(statement.getNamedValues()).hasSize(boundValues.size()); + for (Map.Entry entry : boundValues.entrySet()) { + assertThat(statement.getNamedValues().get(CqlIdentifier.fromCql(entry.getKey()))) + .isEqualTo(entry.getValue()); + } + } + + @Test + @UseDataProvider("sampleQueries") + public void should_convert_to_statement_builder( + BuildableQuery query, + Map boundValues, + String expectedQueryString, + boolean expectedIdempotence) { + Object[] positionalValues = boundValues.values().toArray(); + SimpleStatement statement = query.builder().addPositionalValues(positionalValues).build(); + assertThat(statement.getQuery()).isEqualTo(expectedQueryString); + assertThat(statement.isIdempotent()).isEqualTo(expectedIdempotence); + assertThat(statement.getPositionalValues()).containsExactly(positionalValues); + assertThat(statement.getNamedValues()).isEmpty(); + } +} From 6e64a9cdbaf0e25a9e16a94c59ff69e215b23fa5 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Thu, 28 Feb 2019 14:24:07 +0100 Subject: [PATCH 705/742] guard call to jnr Platform.getCPU via Native wrapper --- .../oss/driver/internal/core/os/Native.java | 48 +++++++++++++++++++ .../driver/internal/core/os/NativeTest.java | 31 ++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/os/NativeTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java index 4a8e6df9bcf..25df2d5d23a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/os/Native.java @@ -15,7 +15,9 @@ */ package com.datastax.oss.driver.internal.core.os; +import java.lang.reflect.Method; import jnr.ffi.LibraryLoader; +import jnr.ffi.Platform; import jnr.ffi.Pointer; import jnr.ffi.Runtime; import jnr.ffi.Struct; @@ -75,6 +77,34 @@ public static int getProcessId() { return PosixLoader.POSIX.getpid(); } + /** + * Returns {@code true} if JNR {@link Platform} class is loaded, and {@code false} otherwise. + * + * @return {@code true} if JNR {@link Platform} class is loaded. + */ + public static boolean isPlatformAvailable() { + try { + return PlatformLoader.PLATFORM != null; + } catch (NoClassDefFoundError e) { + return false; + } + } + + /** + * Returns the current processor architecture the JVM is running on, as reported by {@link + * Platform#getCPU()}. + * + * @return the current processor architecture. + * @throws IllegalStateException if JNR Platform library is not loaded. + */ + public static String getCPU() { + if (!isPlatformAvailable()) + throw new IllegalStateException( + "JNR Platform class not loaded. " + + "Check isPlatformAvailable() before calling this method."); + return PlatformLoader.PLATFORM.getCPU().toString(); + } + /** * If jnr-ffi is not in the classpath at runtime, we'll fail to initialize the static fields * below, but we still want {@link Native} to initialize successfully, so use an inner class. @@ -150,4 +180,22 @@ private static class PosixLoader { GET_PID_AVAILABLE = getPidAvailable; } } + + private static class PlatformLoader { + + private static final Platform PLATFORM; + + static { + Platform platform; + try { + Class platformClass = Class.forName("jnr.ffi.Platform"); + Method getNativePlatform = platformClass.getMethod("getNativePlatform"); + platform = (Platform) getNativePlatform.invoke(null); + } catch (Throwable t) { + platform = null; + LOG.debug("Error loading jnr.ffi.Platform class, this class will not be available.", t); + } + PLATFORM = platform; + } + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/os/NativeTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/os/NativeTest.java new file mode 100644 index 00000000000..b34015f31aa --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/os/NativeTest.java @@ -0,0 +1,31 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.os; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +import org.junit.Test; + +public class NativeTest { + + /** Verifies that {@link Native#getCPU()} returns non-empty cpu architecture */ + @Test + public void should_return_cpu_if_call_is_available() { + if (Native.isPlatformAvailable()) { + assertThat(Native.getCPU()).isNotEmpty(); + } + } +} From 1892fa1a671494ba7cb19dbfa1811a2bdd1cccc8 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Fri, 1 Mar 2019 15:18:37 +0100 Subject: [PATCH 706/742] JAVA-2170 fix non-deterministic LifecycleListenerIT (#1202) --- .../driver/api/core/context/LifecycleListenerIT.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java index 47dfa73c3b1..02a9c6bb2c4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/context/LifecycleListenerIT.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.core.context; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -22,6 +23,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; @@ -52,11 +54,11 @@ public void should_notify_listener_of_init_and_shutdown() { assertThat(listener.closed).isFalse(); try (CqlSession session = newSession(listener)) { - assertThat(listener.ready).isTrue(); + ConditionChecker.checkThat(() -> listener.ready).before(1, SECONDS).becomesTrue(); assertThat(listener.closed).isFalse(); } assertThat(listener.ready).isTrue(); - assertThat(listener.closed).isTrue(); + ConditionChecker.checkThat(() -> listener.closed).before(1, SECONDS).becomesTrue(); } @Test @@ -83,8 +85,8 @@ private CqlSession newSession(TestLifecycleListener listener) { } public static class TestLifecycleListener implements LifecycleListener { - public volatile boolean ready; - public volatile boolean closed; + volatile boolean ready; + volatile boolean closed; @Override public void onSessionReady() { @@ -101,7 +103,7 @@ public static class TestContext extends DefaultDriverContext { private final List listeners; - public TestContext(DriverConfigLoader configLoader, TestLifecycleListener listener) { + TestContext(DriverConfigLoader configLoader, TestLifecycleListener listener) { super( configLoader, Collections.emptyList(), From 75eff01637417e029f8ad7bd41a6d01e4d474efb Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 5 Mar 2019 22:05:47 +0100 Subject: [PATCH 707/742] Remove dead code in GuavaDriverContext --- .../oss/driver/example/guava/internal/GuavaDriverContext.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index 045f376e9c8..c44b0dc071a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -68,8 +68,6 @@ public GuavaDriverContext( public RequestProcessorRegistry buildRequestProcessorRegistry() { // Register the typical request processors, except instead of the normal async processors, // use GuavaRequestAsyncProcessor to return ListenableFutures in async methods. - ConcurrentMap preparedStatementsCache = - new MapMaker().weakValues().makeMap(); CqlRequestAsyncProcessor cqlRequestAsyncProcessor = new CqlRequestAsyncProcessor(); CqlPrepareAsyncProcessor cqlPrepareAsyncProcessor = new CqlPrepareAsyncProcessor(); From 7edd38b1808ebedaa2ef1b2f20f98bfd128fda94 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 5 Mar 2019 22:39:38 +0100 Subject: [PATCH 708/742] Remove dead code in GuavaDriverContext (fix formatting issues) --- .../oss/driver/example/guava/internal/GuavaDriverContext.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java index c44b0dc071a..3ecf6a1b128 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/example/guava/internal/GuavaDriverContext.java @@ -29,13 +29,9 @@ import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlRequestAsyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor; -import com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; -import com.google.common.collect.MapMaker; -import java.nio.ByteBuffer; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentMap; import java.util.function.Predicate; /** From 32d8b64d98763e56d0511d822b718d1555eb9e77 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Wed, 6 Mar 2019 15:12:53 +0100 Subject: [PATCH 709/742] Java 2178: QueryBuilder: Alias after function column is not included in a query (#1206) --- changelog/README.md | 1 + .../select/CollectionSelector.java | 3 + .../querybuilder/select/MapSelector.java | 4 + .../select/SelectSelectorTest.java | 73 +++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 832c0be7cad..51c54fe3379 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [bug] Java 2178: QueryBuilder: Alias after function column is not included in a query - [improvement] JAVA-2158: Allow BuildableQuery to build statement with values - [improvement] JAVA-2150: Improve query builder error message on unsupported literal type - [documentation] JAVA-2149: Improve Term javadocs in the query builder diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java index 5c76bf659c0..ccec72cebee 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/CollectionSelector.java @@ -53,6 +53,9 @@ protected CollectionSelector( @Override public void appendTo(@NonNull StringBuilder builder) { CqlHelper.append(elementSelectors, builder, opening, ",", closing); + if (alias != null) { + builder.append(" AS ").append(alias.asCql(true)); + } } @NonNull diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java index ccad55b2247..f7fde80d5bd 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/MapSelector.java @@ -88,6 +88,10 @@ public void appendTo(@NonNull StringBuilder builder) { entry.getValue().appendTo(builder); } builder.append("}"); + + if (alias != null) { + builder.append(" AS ").append(alias.asCql(true)); + } } @NonNull diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java index bd3263441e0..42fa35bcd86 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectSelectorTest.java @@ -252,4 +252,77 @@ public void should_keep_last_alias_if_aliased_twice() { assertThat(selectFrom("foo").countAll().as("allthethings").as("total")) .hasCql("SELECT count(*) AS total FROM foo"); } + + @Test + public void should_alias_function_selector() { + assertThat(selectFrom("foo").function("bar", Selector.column("col")).as("alias_1")) + .hasCql("SELECT bar(col) AS alias_1 FROM foo"); + + assertThat( + selectFrom("foo") + .function("bar", Selector.column("col")) + .as("alias_1") + .function("baz", Selector.column("col")) + .as("alias_2")) + .hasCql("SELECT bar(col) AS alias_1,baz(col) AS alias_2 FROM foo"); + } + + @Test + public void should_alias_list_selector() { + assertThat(selectFrom("foo").listOf(Selector.column("col")).as("alias_1")) + .hasCql("SELECT [col] AS alias_1 FROM foo"); + + assertThat( + selectFrom("foo") + .listOf(Selector.column("col")) + .as("alias_1") + .listOf(Selector.column("col2")) + .as("alias_2")) + .hasCql("SELECT [col] AS alias_1,[col2] AS alias_2 FROM foo"); + } + + @Test + public void should_alias_set_selector() { + assertThat(selectFrom("foo").setOf(Selector.column("col")).as("alias_1")) + .hasCql("SELECT {col} AS alias_1 FROM foo"); + + assertThat( + selectFrom("foo") + .setOf(Selector.column("col")) + .as("alias_1") + .setOf(Selector.column("col2")) + .as("alias_2")) + .hasCql("SELECT {col} AS alias_1,{col2} AS alias_2 FROM foo"); + } + + @Test + public void should_alias_tuple_selector() { + assertThat(selectFrom("foo").tupleOf(Selector.column("col")).as("alias_1")) + .hasCql("SELECT (col) AS alias_1 FROM foo"); + + assertThat( + selectFrom("foo") + .tupleOf(Selector.column("col")) + .as("alias_1") + .tupleOf(Selector.column("col2")) + .as("alias_2")) + .hasCql("SELECT (col) AS alias_1,(col2) AS alias_2 FROM foo"); + } + + @Test + public void should_alias_map_selector() { + assertThat( + selectFrom("foo") + .mapOf(ImmutableMap.of(Selector.column("a"), Selector.column("b"))) + .as("alias_1")) + .hasCql("SELECT {a:b} AS alias_1 FROM foo"); + + assertThat( + selectFrom("foo") + .mapOf(ImmutableMap.of(Selector.column("a"), Selector.column("b"))) + .as("alias_1") + .mapOf(ImmutableMap.of(Selector.column("c"), Selector.column("d"))) + .as("alias_2")) + .hasCql("SELECT {a:b} AS alias_1,{c:d} AS alias_2 FROM foo"); + } } From 0e51bcb5fe086549e71416c6dcd1e59e779afef7 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 6 Mar 2019 09:49:58 -0800 Subject: [PATCH 710/742] Fix changelog formatting --- changelog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 51c54fe3379..ee4834936c5 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,7 +4,7 @@ ### 4.0.0 (in progress) -- [bug] Java 2178: QueryBuilder: Alias after function column is not included in a query +- [bug] JAVA-2178: QueryBuilder: Alias after function column is not included in a query - [improvement] JAVA-2158: Allow BuildableQuery to build statement with values - [improvement] JAVA-2150: Improve query builder error message on unsupported literal type - [documentation] JAVA-2149: Improve Term javadocs in the query builder From 3b7d300e32e738aada14bfae9999c46d3a53bb33 Mon Sep 17 00:00:00 2001 From: tomekl007 Date: Fri, 1 Mar 2019 10:33:43 +0100 Subject: [PATCH 711/742] JAVA-2164: Rename statement builder methods to setXxx --- changelog/README.md | 1 + core/revapi.json | 120 ++++++++++++++++++ .../api/core/cql/BatchStatementBuilder.java | 10 +- .../api/core/cql/SimpleStatementBuilder.java | 12 +- .../driver/api/core/cql/StatementBuilder.java | 36 +++--- .../api/core/session/SessionBuilder.java | 2 +- .../internal/core/cql/QueryTraceFetcher.java | 6 +- .../core/cql/CqlRequestHandlerTestBase.java | 4 +- faq/README.md | 2 +- .../core/config/DriverExecutionProfileIT.java | 14 +- .../DriverExecutionProfileReloadIT.java | 8 +- .../driver/api/core/cql/AsyncResultSetIT.java | 6 +- .../driver/api/core/cql/BoundStatementIT.java | 58 ++++----- .../api/core/cql/ExecutionInfoWarningsIT.java | 4 +- .../api/core/cql/PerRequestKeyspaceIT.java | 8 +- .../api/core/cql/PreparedStatementIT.java | 6 +- .../oss/driver/api/core/cql/QueryTraceIT.java | 2 +- .../api/core/cql/SimpleStatementIT.java | 22 ++-- .../oss/driver/api/core/data/DataTypeIT.java | 4 +- .../driver/api/core/metadata/SchemaIT.java | 2 +- .../api/core/session/RequestProcessorIT.java | 2 +- .../api/core/tracker/RequestLoggerIT.java | 6 +- .../type/codec/registry/CodecRegistryIT.java | 4 +- .../core/retry/DefaultRetryPolicyIT.java | 6 +- manual/core/configuration/README.md | 4 +- manual/core/idempotence/README.md | 2 +- manual/core/paging/README.md | 2 +- manual/core/performance/README.md | 6 +- manual/core/query_timestamps/README.md | 2 +- manual/core/statements/prepared/README.md | 6 +- manual/core/statements/simple/README.md | 2 +- manual/core/tracing/README.md | 2 +- .../api/querybuilder/BuildableQuery.java | 2 +- .../querybuilder/delete/DefaultDelete.java | 2 +- .../querybuilder/insert/DefaultInsert.java | 2 +- .../querybuilder/select/DefaultSelect.java | 2 +- .../querybuilder/update/DefaultUpdate.java | 2 +- .../api/testinfra/session/SessionUtils.java | 4 +- 38 files changed, 253 insertions(+), 132 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index ee4834936c5..a438c9ff677 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2164: Rename statement builder methods to setXxx - [bug] JAVA-2178: QueryBuilder: Alias after function column is not included in a query - [improvement] JAVA-2158: Allow BuildableQuery to build statement with values - [improvement] JAVA-2150: Improve query builder error message on unsupported literal type diff --git a/core/revapi.json b/core/revapi.json index e34ef7a45ac..9a15005be51 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -15,6 +15,126 @@ } }, "ignore": [ + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.cql.BatchStatementBuilder com.datastax.oss.driver.api.core.cql.BatchStatementBuilder::withKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.cql.BatchStatementBuilder com.datastax.oss.driver.api.core.cql.BatchStatementBuilder::withKeyspace(java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withKeyspace(java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withQuery(java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withExecutionProfileName(java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withIdempotence(java.lang.Boolean)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withNode(com.datastax.oss.driver.api.core.metadata.Node)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withPageSize(int)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withPagingState(java.nio.ByteBuffer)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKey(java.nio.ByteBuffer)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKeyspace(java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTimeout(java.time.Duration)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTimestamp(long)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTracing()", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2164: Rename statement builder methods to setXxx" + } ] } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java index f4ac0fefeb7..de3283b4a36 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatementBuilder.java @@ -51,20 +51,20 @@ public BatchStatementBuilder(@NonNull BatchStatement template) { * @see BatchStatement#getKeyspace() */ @NonNull - public BatchStatementBuilder withKeyspace(@NonNull CqlIdentifier keyspace) { + public BatchStatementBuilder setKeyspace(@NonNull CqlIdentifier keyspace) { this.keyspace = keyspace; return this; } /** - * Sets the CQL keyspace to execute this batch in. Shortcut for {@link - * #withKeyspace(CqlIdentifier) withKeyspace(CqlIdentifier.fromCql(keyspaceName))}. + * Sets the CQL keyspace to execute this batch in. Shortcut for {@link #setKeyspace(CqlIdentifier) + * setKeyspace(CqlIdentifier.fromCql(keyspaceName))}. * * @return this builder; never {@code null}. */ @NonNull - public BatchStatementBuilder withKeyspace(@NonNull String keyspaceName) { - return withKeyspace(CqlIdentifier.fromCql(keyspaceName)); + public BatchStatementBuilder setKeyspace(@NonNull String keyspaceName) { + return setKeyspace(CqlIdentifier.fromCql(keyspaceName)); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java index 24c2a883dfb..4a1a9e32233 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatementBuilder.java @@ -61,25 +61,25 @@ public SimpleStatementBuilder(@NonNull SimpleStatement template) { /** @see SimpleStatement#getQuery() */ @NonNull - public SimpleStatementBuilder withQuery(@NonNull String query) { + public SimpleStatementBuilder setQuery(@NonNull String query) { this.query = query; return this; } /** @see SimpleStatement#getKeyspace() */ @NonNull - public SimpleStatementBuilder withKeyspace(@Nullable CqlIdentifier keyspace) { + public SimpleStatementBuilder setKeyspace(@Nullable CqlIdentifier keyspace) { this.keyspace = keyspace; return this; } /** - * Shortcut for {@link #withKeyspace(CqlIdentifier) - * withKeyspace(CqlIdentifier.fromCql(keyspaceName))}. + * Shortcut for {@link #setKeyspace(CqlIdentifier) + * setKeyspace(CqlIdentifier.fromCql(keyspaceName))}. */ @NonNull - public SimpleStatementBuilder withKeyspace(@Nullable String keyspaceName) { - return withKeyspace(keyspaceName == null ? null : CqlIdentifier.fromCql(keyspaceName)); + public SimpleStatementBuilder setKeyspace(@Nullable String keyspaceName) { + return setKeyspace(keyspaceName == null ? null : CqlIdentifier.fromCql(keyspaceName)); } /** @see SimpleStatement#setPositionalValues(List) */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index b723fafc4b5..030b932139a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -86,14 +86,14 @@ protected StatementBuilder(StatementT template) { /** @see Statement#setExecutionProfileName(String) */ @NonNull - public SelfT withExecutionProfileName(@Nullable String executionProfileName) { + public SelfT setExecutionProfileName(@Nullable String executionProfileName) { this.executionProfileName = executionProfileName; return self; } /** @see Statement#setExecutionProfile(DriverExecutionProfile) */ @NonNull - public SelfT withExecutionProfile(@Nullable DriverExecutionProfile executionProfile) { + public SelfT setExecutionProfile(@Nullable DriverExecutionProfile executionProfile) { this.executionProfile = executionProfile; this.executionProfileName = null; return self; @@ -101,31 +101,31 @@ public SelfT withExecutionProfile(@Nullable DriverExecutionProfile executionProf /** @see Statement#setRoutingKeyspace(CqlIdentifier) */ @NonNull - public SelfT withRoutingKeyspace(@Nullable CqlIdentifier routingKeyspace) { + public SelfT setRoutingKeyspace(@Nullable CqlIdentifier routingKeyspace) { this.routingKeyspace = routingKeyspace; return self; } /** - * Shortcut for {@link #withRoutingKeyspace(CqlIdentifier) - * withRoutingKeyspace(CqlIdentifier.fromCql(routingKeyspaceName))}. + * Shortcut for {@link #setRoutingKeyspace(CqlIdentifier) + * setRoutingKeyspace(CqlIdentifier.fromCql(routingKeyspaceName))}. */ @NonNull - public SelfT withRoutingKeyspace(@Nullable String routingKeyspaceName) { - return withRoutingKeyspace( + public SelfT setRoutingKeyspace(@Nullable String routingKeyspaceName) { + return setRoutingKeyspace( routingKeyspaceName == null ? null : CqlIdentifier.fromCql(routingKeyspaceName)); } /** @see Statement#setRoutingKey(ByteBuffer) */ @NonNull - public SelfT withRoutingKey(@Nullable ByteBuffer routingKey) { + public SelfT setRoutingKey(@Nullable ByteBuffer routingKey) { this.routingKey = routingKey; return self; } /** @see Statement#setRoutingToken(Token) */ @NonNull - public SelfT withRoutingToken(@Nullable Token routingToken) { + public SelfT setRoutingToken(@Nullable Token routingToken) { this.routingToken = routingToken; return self; } @@ -149,62 +149,62 @@ public SelfT clearCustomPayload() { /** @see Statement#setIdempotent(Boolean) */ @NonNull - public SelfT withIdempotence(@Nullable Boolean idempotent) { + public SelfT setIdempotence(@Nullable Boolean idempotent) { this.idempotent = idempotent; return self; } /** @see Statement#setTracing(boolean) */ @NonNull - public SelfT withTracing() { + public SelfT setTracing() { this.tracing = true; return self; } /** @see Statement#setTimestamp(long) */ @NonNull - public SelfT withTimestamp(long timestamp) { + public SelfT setTimestamp(long timestamp) { this.timestamp = timestamp; return self; } /** @see Statement#setPagingState(ByteBuffer) */ @NonNull - public SelfT withPagingState(@Nullable ByteBuffer pagingState) { + public SelfT setPagingState(@Nullable ByteBuffer pagingState) { this.pagingState = pagingState; return self; } /** @see Statement#setPageSize(int) */ @NonNull - public SelfT withPageSize(int pageSize) { + public SelfT setPageSize(int pageSize) { this.pageSize = pageSize; return self; } /** @see Statement#setConsistencyLevel(ConsistencyLevel) */ @NonNull - public SelfT withConsistencyLevel(@Nullable ConsistencyLevel consistencyLevel) { + public SelfT setConsistencyLevel(@Nullable ConsistencyLevel consistencyLevel) { this.consistencyLevel = consistencyLevel; return self; } /** @see Statement#setSerialConsistencyLevel(ConsistencyLevel) */ @NonNull - public SelfT withSerialConsistencyLevel(@Nullable ConsistencyLevel serialConsistencyLevel) { + public SelfT setSerialConsistencyLevel(@Nullable ConsistencyLevel serialConsistencyLevel) { this.serialConsistencyLevel = serialConsistencyLevel; return self; } /** @see Statement#setTimeout(Duration) */ @NonNull - public SelfT withTimeout(@Nullable Duration timeout) { + public SelfT setTimeout(@Nullable Duration timeout) { this.timeout = timeout; return self; } /** @see Statement#setNode(Node) */ - public SelfT withNode(@Nullable Node node) { + public SelfT setNode(@Nullable Node node) { this.node = node; return self; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 898cbad790a..3f41c9de233 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -259,7 +259,7 @@ public SelfT withKeyspace(@Nullable CqlIdentifier keyspace) { /** * Shortcut for {@link #withKeyspace(CqlIdentifier) - * withKeyspace(CqlIdentifier.fromCql(keyspaceName))} + * setKeyspace(CqlIdentifier.fromCql(keyspaceName))} */ @NonNull public SelfT withKeyspace(@Nullable String keyspaceName) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java index ef746460570..ebe7f906c25 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcher.java @@ -78,7 +78,7 @@ private void querySession(int remainingAttempts) { .executeAsync( SimpleStatement.builder("SELECT * FROM system_traces.sessions WHERE session_id = ?") .addPositionalValue(tracingId) - .withExecutionProfile(config) + .setExecutionProfile(config) .build()) .whenComplete( (rs, error) -> { @@ -112,8 +112,8 @@ private void queryEvents(Row sessionRow, List events, ByteBuffer pagingStat .executeAsync( SimpleStatement.builder("SELECT * FROM system_traces.events WHERE session_id = ?") .addPositionalValue(tracingId) - .withPagingState(pagingState) - .withExecutionProfile(config) + .setPagingState(pagingState) + .setExecutionProfile(config) .build()) .whenComplete( (rs, error) -> { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java index 09b43e09b23..54fb1e3a7b3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerTestBase.java @@ -53,9 +53,9 @@ public abstract class CqlRequestHandlerTestBase { protected static final SimpleStatement UNDEFINED_IDEMPOTENCE_STATEMENT = SimpleStatement.newInstance("mock query"); protected static final SimpleStatement IDEMPOTENT_STATEMENT = - SimpleStatement.builder("mock query").withIdempotence(true).build(); + SimpleStatement.builder("mock query").setIdempotence(true).build(); protected static final SimpleStatement NON_IDEMPOTENT_STATEMENT = - SimpleStatement.builder("mock query").withIdempotence(false).build(); + SimpleStatement.builder("mock query").setIdempotence(false).build(); protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); protected static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); diff --git a/faq/README.md b/faq/README.md index 489134ade8b..762480d9347 100644 --- a/faq/README.md +++ b/faq/README.md @@ -23,7 +23,7 @@ The driver also provides builders: BoundStatement boundSelect = preparedSelect.boundStatementBuilder() .setInt("k", key) - .withPageSize(1000) + .setPageSize(1000) .build(); ``` diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index c26d4247535..bcc1c58e014 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -65,7 +65,7 @@ public void should_fail_if_config_profile_specified_doesnt_exist() { try (CqlSession session = SessionUtils.newSession(simulacron)) { SimpleStatement statement = SimpleStatement.builder("select * from system.local") - .withExecutionProfileName("IDONTEXIST") + .setExecutionProfileName("IDONTEXIST") .build(); thrown.expect(IllegalArgumentException.class); @@ -99,7 +99,7 @@ public void should_use_profile_request_timeout() { } // Execute query with profile, should not timeout since waits up to 10 seconds. - session.execute(SimpleStatement.builder(query).withExecutionProfileName("olap").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("olap").build()); } } @@ -128,7 +128,7 @@ public void should_use_profile_default_idempotence() { // Execute query with profile, should retry on all hosts since query is idempotent. thrown.expect(AllNodesFailedException.class); - session.execute(SimpleStatement.builder(query).withExecutionProfileName("idem").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("idem").build()); } } @@ -169,7 +169,7 @@ public void should_use_profile_consistency() { simulacron.cluster().clearLogs(); // Execute query with profile, should use profile CLs - session.execute(SimpleStatement.builder(query).withExecutionProfileName("cl").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("cl").build()); log = simulacron @@ -213,11 +213,11 @@ public void should_use_profile_page_size() { session.execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k int, v int, PRIMARY KEY (k,v))") - .withExecutionProfile(slowProfile) + .setExecutionProfile(slowProfile) .build()); PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (0, ?)"); BatchStatementBuilder bs = - BatchStatement.builder(DefaultBatchType.UNLOGGED).withExecutionProfile(slowProfile); + BatchStatement.builder(DefaultBatchType.UNLOGGED).setExecutionProfile(slowProfile); for (int i = 0; i < 500; i++) { bs.addStatement(prepared.bind(i)); } @@ -235,7 +235,7 @@ public void should_use_profile_page_size() { // Execute query with profile, should use profile page size future = session.executeAsync( - SimpleStatement.builder(query).withExecutionProfileName("smallpages").build()); + SimpleStatement.builder(query).setExecutionProfileName("smallpages").build()); result = CompletableFutures.getUninterruptibly(future); assertThat(result.remaining()).isEqualTo(10); // next fetch should also be 10 pages. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index a4690792dbc..85dec009ec9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -142,7 +142,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { // Expect failure because profile doesn't exist. try { - session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("slow").build()); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { // expected. @@ -155,7 +155,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { // Execute again, should expect to fail again because doesn't allow to dynamically define // profile. thrown.expect(IllegalArgumentException.class); - session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("slow").build()); } } @@ -184,7 +184,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio // Expect failure because profile doesn't exist. try { - session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("slow").build()); fail("Expected DriverTimeoutException"); } catch (DriverTimeoutException e) { // expected. @@ -195,7 +195,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio waitForConfigChange(session, 3, TimeUnit.SECONDS); // Execute again, should succeed because profile timeout was increased. - session.execute(SimpleStatement.builder(query).withExecutionProfileName("slow").build()); + session.execute(SimpleStatement.builder(query).setExecutionProfileName("slow").build()); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index f32823557dd..7c890639696 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -62,7 +62,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k0 text, k1 int, v int, PRIMARY KEY(k0, k1))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); PreparedStatement prepared = @@ -78,10 +78,10 @@ public static void setupSchema() { sessionRule .session() - .execute(batchPart1.withExecutionProfile(sessionRule.slowProfile()).build()); + .execute(batchPart1.setExecutionProfile(sessionRule.slowProfile()).build()); sessionRule .session() - .execute(batchPart2.withExecutionProfile(sessionRule.slowProfile()).build()); + .execute(batchPart2.setExecutionProfile(sessionRule.slowProfile()).build()); } @Test diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 5027d8b637b..dafb80fa67b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -103,7 +103,7 @@ public void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule @@ -119,7 +119,7 @@ public void setupSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v0 int)") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); // table with composite partition key @@ -130,7 +130,7 @@ public void setupSchema() { "CREATE TABLE IF NOT EXISTS test3 " + "(pk1 int, pk2 int, v int, " + "PRIMARY KEY ((pk1, pk2)))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -255,7 +255,7 @@ public void should_allow_custom_codecs_when_setting_values_in_bulk() { @Test public void should_use_page_size_from_simple_statement() { try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { - SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); PreparedStatement prepared = session.prepare(st); CompletionStage future = session.executeAsync(prepared.bind()); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); @@ -270,7 +270,7 @@ public void should_use_page_size() { try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { // set page size on simple statement, but will be unused since // overridden by bound statement. - SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); PreparedStatement prepared = session.prepare(st); CompletionStage future = session.executeAsync(prepared.bind().setPageSize(12)); @@ -286,8 +286,8 @@ public void should_use_consistencies_from_simple_statement() { try (CqlSession session = SessionUtils.newSession(simulacron)) { SimpleStatement st = SimpleStatement.builder("SELECT * FROM test where k = ?") - .withConsistencyLevel(DefaultConsistencyLevel.TWO) - .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setConsistencyLevel(DefaultConsistencyLevel.TWO) + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) .build(); PreparedStatement prepared = session.prepare(st); simulacron.cluster().clearLogs(); @@ -317,8 +317,8 @@ public void should_use_consistencies() { // overridden by bound statement. SimpleStatement st = SimpleStatement.builder("SELECT * FROM test where k = ?") - .withConsistencyLevel(DefaultConsistencyLevel.TWO) - .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setConsistencyLevel(DefaultConsistencyLevel.TWO) + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) .build(); PreparedStatement prepared = session.prepare(st); simulacron.cluster().clearLogs(); @@ -327,8 +327,8 @@ public void should_use_consistencies() { session.execute( prepared .boundStatementBuilder("0") - .withConsistencyLevel(DefaultConsistencyLevel.THREE) - .withSerialConsistencyLevel(DefaultConsistencyLevel.SERIAL) + .setConsistencyLevel(DefaultConsistencyLevel.THREE) + .setSerialConsistencyLevel(DefaultConsistencyLevel.SERIAL) .build()); List logs = simulacron.cluster().getLogs().getQueryLogs(); @@ -364,8 +364,8 @@ public void should_use_timeout_from_simple_statement() { .delay(1500, TimeUnit.MILLISECONDS)); SimpleStatement st = SimpleStatement.builder("mock query") - .withTimeout(Duration.ofSeconds(1)) - .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .setTimeout(Duration.ofSeconds(1)) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) .build(); PreparedStatement prepared = session.prepare(st); @@ -395,8 +395,8 @@ public void should_use_timeout() { .delay(1500, TimeUnit.MILLISECONDS)); SimpleStatement st = SimpleStatement.builder("mock query") - .withTimeout(Duration.ofSeconds(1)) - .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .setTimeout(Duration.ofSeconds(1)) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) .build(); PreparedStatement prepared = session.prepare(st); @@ -434,20 +434,20 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { SimpleStatementBuilder simpleStatementBuilder = SimpleStatement.builder("SELECT release_version FROM system.local") - .withExecutionProfile(mockProfile) - .withExecutionProfileName(mockConfigProfileName) - .withPagingState(mockPagingState) - .withKeyspace(mockKeyspace) - .withRoutingKeyspace(mockRoutingKeyspace) - .withRoutingKey(mockRoutingKey) - .withRoutingToken(mockRoutingToken) - .withTimestamp(42) - .withIdempotence(true) - .withTracing() - .withTimeout(mockTimeout) - .withConsistencyLevel(mockCl) - .withSerialConsistencyLevel(mockSerialCl) - .withPageSize(mockPageSize); + .setExecutionProfile(mockProfile) + .setExecutionProfileName(mockConfigProfileName) + .setPagingState(mockPagingState) + .setKeyspace(mockKeyspace) + .setRoutingKeyspace(mockRoutingKeyspace) + .setRoutingKey(mockRoutingKey) + .setRoutingToken(mockRoutingToken) + .setTimestamp(42) + .setIdempotence(true) + .setTracing() + .setTimeout(mockTimeout) + .setConsistencyLevel(mockCl) + .setSerialConsistencyLevel(mockSerialCl) + .setPageSize(mockPageSize); if (atLeastV4) { simpleStatementBuilder = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java index b39e215e29a..cf9a278f8e9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java @@ -83,7 +83,7 @@ public static void setupSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k int primary key, v text)") - .withExecutionProfile(SESSION_RULE.slowProfile()) + .setExecutionProfile(SESSION_RULE.slowProfile()) .build()); for (int i = 0; i < 100; i++) { SESSION_RULE @@ -140,7 +140,7 @@ public void should_execute_query_and_not_log_server_side_warnings() { final String query = "SELECT count(*) FROM test;"; Statement st = SimpleStatement.builder(String.format(query)) - .withExecutionProfileName("log-disabled") + .setExecutionProfileName("log-disabled") .build(); ResultSet result = SESSION_RULE.session().execute(st); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java index be492d4f691..1cd46307efe 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PerRequestKeyspaceIT.java @@ -61,7 +61,7 @@ public void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS foo (k text, cc int, v int, PRIMARY KEY(k, cc))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -80,7 +80,7 @@ public void should_reject_batch_statement_with_explicit_keyspace_in_protocol_v4( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1); should_reject_statement_with_keyspace_in_protocol_v4( BatchStatement.builder(DefaultBatchType.LOGGED) - .withKeyspace(sessionRule.keyspace()) + .setKeyspace(sessionRule.keyspace()) .addStatement(statementWithoutKeyspace) .build()); } @@ -134,7 +134,7 @@ public void should_execute_batch_with_explicit_keyspace() { CqlSession session = sessionRule.session(); session.execute( BatchStatement.builder(DefaultBatchType.LOGGED) - .withKeyspace(sessionRule.keyspace()) + .setKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", nameRule.getMethodName(), 1, 1), @@ -158,7 +158,7 @@ public void should_execute_batch_with_inferred_keyspace() { CqlSession session = sessionRule.session(); session.execute( BatchStatement.builder(DefaultBatchType.LOGGED) - .withKeyspace(sessionRule.keyspace()) + .setKeyspace(sessionRule.keyspace()) .addStatements( SimpleStatement.newInstance( "INSERT INTO foo (k, cc, v) VALUES (?, ?, ?)", diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index 050973004bd..97966f8ac6c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -87,7 +87,7 @@ public void setupSchema() { .session() .execute( SimpleStatement.builder(query) - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } } @@ -159,7 +159,7 @@ public void should_update_metadata_when_schema_changed_across_executions() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_test ADD d int") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); BoundStatement bs = ps.bind(1); ResultSet rows = session.execute(bs); @@ -203,7 +203,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { // When session.execute( SimpleStatement.builder("ALTER TABLE prepared_statement_test ADD d int") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); // Then diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index d859947ecc9..60ba77c08b8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -81,7 +81,7 @@ public void should_fetch_trace_when_tracing_enabled() { .session() .execute( SimpleStatement.builder("SELECT release_version FROM system.local") - .withTracing() + .setTracing() .build()) .getExecutionInfo(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 18bc12ba6b9..b9d33f5e2c5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -89,7 +89,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v int, PRIMARY KEY(k, v))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule @@ -105,7 +105,7 @@ public static void setupSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test2 (k text primary key, v int)") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -142,7 +142,7 @@ public void should_use_paging_state_when_provided_to_new_statement() { // response. st = SimpleStatement.builder(String.format("SELECT v FROM test where k='%s'", KEY)) - .withPagingState(result.getExecutionInfo().getPagingState()) + .setPagingState(result.getExecutionInfo().getPagingState()) .build(); // when executing that query. @@ -164,7 +164,7 @@ public void should_fail_if_using_paging_state_from_different_query() { // given a new different query and providing the paging state from the previous query // then an exception should be thrown indicating incompatible paging state SimpleStatement.builder("SELECT v FROM test") - .withPagingState(result.getExecutionInfo().getPagingState()) + .setPagingState(result.getExecutionInfo().getPagingState()) .build(); } @@ -175,7 +175,7 @@ public void should_use_timestamp_when_set() { SimpleStatement insert = SimpleStatement.builder("INSERT INTO test2 (k, v) values (?, ?)") .addPositionalValues(name.getMethodName(), 0) - .withTimestamp(timestamp) + .setTimestamp(timestamp) .build(); sessionRule.session().execute(insert); @@ -203,7 +203,7 @@ public void should_use_tracing_when_set() { ResultSet result = sessionRule .session() - .execute(SimpleStatement.builder("select * from test").withTracing().build()); + .execute(SimpleStatement.builder("select * from test").setTracing().build()); } @Test @@ -384,7 +384,7 @@ public void should_use_positional_value_with_case_sensitive_id() { @Test public void should_use_page_size() { - Statement st = SimpleStatement.builder("SELECT v FROM test").withPageSize(10).build(); + Statement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); CompletionStage future = sessionRule.session().executeAsync(st); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); @@ -396,8 +396,8 @@ public void should_use_page_size() { public void should_use_consistencies() { SimpleStatement st = SimpleStatement.builder("SELECT * FROM test where k = ?") - .withConsistencyLevel(DefaultConsistencyLevel.TWO) - .withSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setConsistencyLevel(DefaultConsistencyLevel.TWO) + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) .build(); simulacronSessionRule.session().execute(st); @@ -421,8 +421,8 @@ public void should_use_timeout() { .prime(when("mock query").then(noRows()).delay(1500, TimeUnit.MILLISECONDS)); SimpleStatement st = SimpleStatement.builder("mock query") - .withTimeout(Duration.ofSeconds(1)) - .withConsistencyLevel(DefaultConsistencyLevel.ONE) + .setTimeout(Duration.ofSeconds(1)) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) .build(); thrown.expect(DriverTimeoutException.class); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 954c042d58d..580aed22f25 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -289,7 +289,7 @@ public static void createTable() { String.format( "CREATE TABLE IF NOT EXISTS %s (k int primary key, %s)", tableName, String.join(",", columnData))) - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } @@ -771,7 +771,7 @@ private static String typeFor(DataType dataType) { String.format( "CREATE TYPE IF NOT EXISTS %s (%s)", udt.getName().asCql(false), String.join(",", fieldParts))) - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); // Chances are the UDT isn't labeled as frozen in the context we're given, so we add it as diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 457cb576cb0..874dbb37580 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -139,7 +139,7 @@ public void should_disable_schema_programmatically_when_enabled_in_config() { .session() .execute( SimpleStatement.builder("CREATE TABLE foo(k int primary key)") - .withExecutionProfile(slowProfile) + .setExecutionProfile(slowProfile) .build()); assertThat(session.getMetadata().getKeyspace(sessionRule.keyspace()).get().getTables()) .doesNotContainKey(CqlIdentifier.fromInternal("foo")); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index deb16b6df06..4a18c6ef23a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -83,7 +83,7 @@ public static void setupSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test (k text, v0 int, v1 int, PRIMARY KEY(k, v0))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { sessionRule diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java index 6eeda1ae4d3..18034a2cb7b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/tracker/RequestLoggerIT.java @@ -258,7 +258,7 @@ public void should_log_failed_request_without_stack_trace() { try { sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("no-traces").build()); + .execute(SimpleStatement.builder(QUERY).setExecutionProfileName("no-traces").build()); fail("Expected a ServerError"); } catch (ServerError error) { // expected @@ -280,7 +280,7 @@ public void should_log_slow_request() { // When sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("low-threshold").build()); + .execute(SimpleStatement.builder(QUERY).setExecutionProfileName("low-threshold").build()); // Then verify(appender, timeout(500)).doAppend(loggingEventCaptor.capture()); @@ -296,7 +296,7 @@ public void should_not_log_when_disabled() throws InterruptedException { // When sessionRuleRequest .session() - .execute(SimpleStatement.builder(QUERY).withExecutionProfileName("no-logs").build()); + .execute(SimpleStatement.builder(QUERY).setExecutionProfileName("no-logs").build()); // Then // We expect no messages. The request logger is invoked asynchronously, so simply wait a bit diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 4fbf5f20a17..5d4412bc6b6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -75,7 +75,7 @@ public static void createSchema() { .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k text primary key, v int)") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); // table with map value sessionRule @@ -83,7 +83,7 @@ public static void createSchema() { .execute( SimpleStatement.builder( "CREATE TABLE IF NOT EXISTS test2 (k0 text, k1 int, v map, primary key (k0, k1))") - .withExecutionProfile(sessionRule.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index d418d66a2c9..517b24a8e04 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -293,7 +293,7 @@ public void should_not_retry_on_connection_error_if_non_idempotent() { // when executing a non-idempotent query. sessionRule .session() - .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + .execute(SimpleStatement.builder(queryStr).setIdempotence(false).build()); fail("ClosedConnectionException expected"); } catch (ClosedConnectionException ex) { // then a ClosedConnectionException should be raised, indicating that the connection closed @@ -401,7 +401,7 @@ public void should_not_retry_on_write_timeout_if_write_type_batch_log_but_non_id // when executing a non-idempotent query. sessionRule .session() - .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + .execute(SimpleStatement.builder(queryStr).setIdempotence(false).build()); fail("WriteTimeoutException expected"); } catch (WriteTimeoutException wte) { // then a write timeout exception is thrown @@ -511,7 +511,7 @@ public void should_not_retry_on_next_host_on_error_response_if_non_idempotent() // when executing a query that is not idempotent sessionRule .session() - .execute(SimpleStatement.builder(queryStr).withIdempotence(false).build()); + .execute(SimpleStatement.builder(queryStr).setIdempotence(false).build()); fail("Expected a ServerError"); } catch (ServerError e) { // then should get a server error from first host. diff --git a/manual/core/configuration/README.md b/manual/core/configuration/README.md index d6ace2ef6c7..ce152e6a6ae 100644 --- a/manual/core/configuration/README.md +++ b/manual/core/configuration/README.md @@ -44,7 +44,7 @@ Now each request only needs a profile name: ```java SimpleStatement s = SimpleStatement.builder("SELECT name FROM user WHERE id = 1") - .withExecutionProfileName("oltp") + .setExecutionProfileName("oltp") .build(); session.execute(s); ``` @@ -185,7 +185,7 @@ DriverExecutionProfile dynamicProfile = DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.EACH_QUORUM.name()); SimpleStatement s = SimpleStatement.builder("SELECT name FROM user WHERE id = 1") - .withConfigProfile(dynamicProfile) + .setExecutionProfile(dynamicProfile) .build(); session.execute(s); ``` diff --git a/manual/core/idempotence/README.md b/manual/core/idempotence/README.md index a2cd4d140c9..59d45d5113f 100644 --- a/manual/core/idempotence/README.md +++ b/manual/core/idempotence/README.md @@ -32,7 +32,7 @@ SimpleStatement statement = // Or with a builder: SimpleStatement statement = SimpleStatement.builder("SELECT first_name FROM user WHERE id=1") - .withIdempotence(true) + .setIdempotence(true) .build(); ``` diff --git a/manual/core/paging/README.md b/manual/core/paging/README.md index b81d196081a..ac11c5c804e 100644 --- a/manual/core/paging/README.md +++ b/manual/core/paging/README.md @@ -131,7 +131,7 @@ ByteBuffer pagingState = rs.getExecutionInfo().getPagingState(); // Later: SimpleStatement statement = - SimpleStatement.builder("your query").withPagingState(pagingState).build(); + SimpleStatement.builder("your query").setPagingState(pagingState).build(); session.execute(statement); ``` diff --git a/manual/core/performance/README.md b/manual/core/performance/README.md index 9a81d01df5b..86ba9b02235 100644 --- a/manual/core/performance/README.md +++ b/manual/core/performance/README.md @@ -17,9 +17,9 @@ an intermediary copy. If you have multiple attributes to set, use a builder inst ```java SimpleStatement statement = SimpleStatement.builder("SELECT * FROM foo") - .withPageSize(20) - .withConsistencyLevel(DefaultConsistencyLevel.QUORUM) - .withIdempotence(true) + .setPageSize(20) + .setConsistencyLevel(DefaultConsistencyLevel.QUORUM) + .setIdempotence(true) .build(); ``` diff --git a/manual/core/query_timestamps/README.md b/manual/core/query_timestamps/README.md index 6c751e72811..0ad0761de28 100644 --- a/manual/core/query_timestamps/README.md +++ b/manual/core/query_timestamps/README.md @@ -145,7 +145,7 @@ Finally, you can assign a timestamp to a statement directly from application cod ```java Statement statement = SimpleStatement.builder("UPDATE users SET email = 'x@y.com' where id = 1") - .withTimestamp(1432815430948040L) + .setTimestamp(1432815430948040L) .build(); session.execute(statement); ``` diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md index 344554c173d..e88354456d0 100644 --- a/manual/core/statements/prepared/README.md +++ b/manual/core/statements/prepared/README.md @@ -80,7 +80,7 @@ same queries over and over, and a parameterized version can be extracted and pre ```java SimpleStatement simpleStatement = SimpleStatement.builder("SELECT * FROM product WHERE sku = ?") - .withConsistencyLevel(DefaultConsistencyLevel.QUORUM) + .setConsistencyLevel(DefaultConsistencyLevel.QUORUM) .build(); PreparedStatement preparedStatement = session.prepare(simpleStatement); BoundStatement boundStatement = preparedStatement.bind(); @@ -160,8 +160,8 @@ BoundStatement bound = .boundStatementBuilder() .setString(0, "324378") .setString(1, "LCD screen") - .withConfigProfileName("oltp") - .withTimestamp(123456789L) + .setExecutionProfileName("oltp") + .setTimestamp(123456789L) .build(); ``` diff --git a/manual/core/statements/simple/README.md b/manual/core/statements/simple/README.md index d3a864be485..ea4db0602d6 100644 --- a/manual/core/statements/simple/README.md +++ b/manual/core/statements/simple/README.md @@ -62,7 +62,7 @@ If you have many options to set, you can use a builder to avoid creating interme ```java SimpleStatement statement = SimpleStatement.builder("SELECT value FROM application_params WHERE name = 'greeting_message'") - .withIdempotence(true) + .setIdempotence(true) .build(); ``` diff --git a/manual/core/tracing/README.md b/manual/core/tracing/README.md index cd1d051d35b..3523b2544e9 100644 --- a/manual/core/tracing/README.md +++ b/manual/core/tracing/README.md @@ -19,7 +19,7 @@ Statement statement = // Builder-based: Statement statement = - SimpleStatement.builder("SELECT * FROM users WHERE id = 1234").withTracing().build(); + SimpleStatement.builder("SELECT * FROM users WHERE id = 1234").setTracing().build(); ``` Tracing is supposed to be run on a small percentage of requests only. Do not enable it on every diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java index 3536c9c676b..05f874a9c6b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/BuildableQuery.java @@ -106,7 +106,7 @@ default SimpleStatement build(@NonNull Map namedValues) { * .whereColumn("c").isLessThan(bindMarker("c")) * .builder(); * SimpleStatement statement = - * builder.addNamedValue("k", 1).addNamedValue("c", 2).withTracing().build(); + * builder.addNamedValue("k", 1).addNamedValue("c", 2).setTracing().build(); * } * * In addition, some implementations might try to infer additional statement properties (such as diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java index 485dde13d0e..319acf238a9 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/delete/DefaultDelete.java @@ -194,7 +194,7 @@ public SimpleStatement build(@NonNull Map namedValues) { @NonNull @Override public SimpleStatementBuilder builder() { - return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); + return SimpleStatement.builder(asCql()).setIdempotence(isIdempotent()); } public boolean isIdempotent() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index dca9ff1ab56..c1996dac624 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -206,7 +206,7 @@ public SimpleStatement build(@NonNull Map namedValues) { @NonNull @Override public SimpleStatementBuilder builder() { - return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); + return SimpleStatement.builder(asCql()).setIdempotence(isIdempotent()); } public boolean isIdempotent() { diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java index 3259dc237b7..6ab7c8a4065 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java @@ -451,7 +451,7 @@ public SimpleStatement build(@NonNull Map namedValues) { @Override public SimpleStatementBuilder builder() { // SELECT statements are always idempotent - return SimpleStatement.builder(asCql()).withIdempotence(true); + return SimpleStatement.builder(asCql()).setIdempotence(true); } @Nullable diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java index 659702a42bf..5428f5b3b01 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java @@ -190,7 +190,7 @@ public SimpleStatement build(@NonNull Map namedValues) { @NonNull @Override public SimpleStatementBuilder builder() { - return SimpleStatement.builder(asCql()).withIdempotence(isIdempotent()); + return SimpleStatement.builder(asCql()).setIdempotence(isIdempotent()); } public boolean isIdempotent() { diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 18d63830a0e..73d7d908a64 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -192,7 +192,7 @@ public static void createKeyspace( String.format( "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", keyspace.asCql(false))) - .withExecutionProfile(profile) + .setExecutionProfile(profile) .build(); session.execute(createKeyspace, Statement.SYNC); } @@ -214,7 +214,7 @@ public static void dropKeyspace( Session session, CqlIdentifier keyspace, DriverExecutionProfile profile) { session.execute( SimpleStatement.builder(String.format("DROP KEYSPACE IF EXISTS %s", keyspace.asCql(false))) - .withExecutionProfile(profile) + .setExecutionProfile(profile) .build(), Statement.SYNC); } From b28eb62d36c46e43096e6b00cc2ee25df6bdaff8 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Fri, 22 Feb 2019 09:52:16 -0600 Subject: [PATCH 712/742] JAVA-2090: Add support for additional_write_policy and read_repair table options --- changelog/README.md | 1 + .../schema/parsing/RelationParser.java | 2 + .../api/core/metadata/TableOptionsIT.java | 77 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TableOptionsIT.java diff --git a/changelog/README.md b/changelog/README.md index a438c9ff677..6c9e0a58535 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2090: Add support for additional_write_policy and read_repair table options - [improvement] JAVA-2164: Rename statement builder methods to setXxx - [bug] JAVA-2178: QueryBuilder: Alias after function column is not included in a query - [improvement] JAVA-2158: Allow BuildableQuery to build statement with values diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java index 85db273d12e..43b942b1669 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RelationParser.java @@ -115,6 +115,7 @@ public static void appendOptions(Map options, ScriptBuild */ public static final ImmutableMap> OPTION_CODECS = ImmutableMap.>builder() + .put("additional_write_policy", TypeCodecs.TEXT) .put("bloom_filter_fp_chance", TypeCodecs.DOUBLE) // In C* <= 2.2, this is a string, not a map (this is special-cased in parseOptions): .put("caching", MAP_OF_TEXT_TO_TEXT) @@ -136,6 +137,7 @@ public static void appendOptions(Map options, ScriptBuild .put("max_index_interval", TypeCodecs.INT) .put("memtable_flush_period_in_ms", TypeCodecs.INT) .put("min_index_interval", TypeCodecs.INT) + .put("read_repair", TypeCodecs.TEXT) .put("read_repair_chance", TypeCodecs.DOUBLE) .put("speculative_retry", TypeCodecs.TEXT) .build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TableOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TableOptionsIT.java new file mode 100644 index 00000000000..13e4e314df3 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/TableOptionsIT.java @@ -0,0 +1,77 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import java.time.Duration; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +@Category(ParallelizableTests.class) +public class TableOptionsIT { + private static final CqlIdentifier READ_REPAIR_KEY = CqlIdentifier.fromCql("read_repair"); + private static final CqlIdentifier ADDITIONAL_WRITE_POLICY_KEY = + CqlIdentifier.fromCql("additional_write_policy"); + + private static CcmRule ccmRule = CcmRule.getInstance(); + // disable debouncer to speed up test. + private static SessionRule sessionRule = + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .withDuration(DefaultDriverOption.METADATA_SCHEMA_WINDOW, Duration.ofSeconds(0)) + .build()) + .build(); + + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + + @Test + @CassandraRequirement(min = "4.0", description = "This test covers Cassandra 4+ features") + public void should_handle_cassandra4_table_options() { + CqlSession session = sessionRule.session(); + + // A simple table with read_repair and additional_write_policy options + session.execute( + "CREATE TABLE foo(k int, a text, PRIMARY KEY(k)) " + + "WITH read_repair='NONE' AND additional_write_policy='40p'"); + + TableMetadata fooMetadata = + session + .getMetadata() + .getKeyspace(sessionRule.keyspace()) + .orElseThrow(AssertionError::new) + .getTable("foo") + .orElseThrow(AssertionError::new); + + assertThat(fooMetadata.getOptions()) + .containsEntry(READ_REPAIR_KEY, "NONE") + .containsEntry(ADDITIONAL_WRITE_POLICY_KEY, "40p"); + } +} From c6d3e0a3de7dfffcf510a5483e626fb7619a0149 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 8 Mar 2019 08:41:28 -0800 Subject: [PATCH 713/742] Force the version of versions-maven-plugin through pluginManagement In version 2.7, the `display-dependency-updates` goal has useful new `-DallowXxxUpdates` filters. --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 13eba2a5f7f..61ebcc2e882 100644 --- a/pom.xml +++ b/pom.xml @@ -300,6 +300,11 @@
        + + org.codehaus.mojo + versions-maven-plugin + 2.7 + From c7958201f7c1aea45e8077695685c062edf03dd6 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 8 Mar 2019 11:43:13 -0800 Subject: [PATCH 714/742] JAVA-2187: Update dependency versions --- core/revapi.json | 3 ++- .../core/util/collection/QueryPlanTest.java | 10 ++++---- pom.xml | 24 +++++++++---------- query-builder/revapi.json | 1 + test-infra/revapi.json | 1 + 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 9a15005be51..e91c759a153 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -9,7 +9,8 @@ "exclude": [ "com\\.datastax\\.oss\\.protocol\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", - "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?" + "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", + "org\\.assertj(\\..+)?" ] } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java index ea6e2a7c6cf..8157a2662ee 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlanTest.java @@ -68,10 +68,10 @@ public void should_return_iterator() { queryPlan.poll(); Iterator iterator00 = queryPlan.iterator(); - assertThat(iterator3).containsExactly(node1, node2, node3); - assertThat(iterator2).containsExactly(node2, node3); - assertThat(iterator1).containsExactly(node3); - assertThat(iterator0).isEmpty(); - assertThat(iterator00).isEmpty(); + assertThat(iterator3).toIterable().containsExactly(node1, node2, node3); + assertThat(iterator2).toIterable().containsExactly(node2, node3); + assertThat(iterator1).toIterable().containsExactly(node3); + assertThat(iterator0).toIterable().isEmpty(); + assertThat(iterator00).toIterable().isEmpty(); } } diff --git a/pom.xml b/pom.xml index 61ebcc2e882..09e087ee8ec 100644 --- a/pom.xml +++ b/pom.xml @@ -46,16 +46,16 @@ UTF-8 1.3.3 25.1-jre - 2.1.10 - 4.0.2 + 2.1.11 + 4.0.5 1.4.4 - 4.1.27.Final - 1.7.25 + 4.1.34.Final + 1.7.26 1.1.7.2 - 1.4.1 + 1.5.1 - 3.10.0 + 3.12.1 1.3 2.9.5 4.12 @@ -105,7 +105,7 @@ com.github.jnr jnr-ffi - 2.1.8 + 2.1.9 org.xerial.snappy @@ -120,7 +120,7 @@ com.github.jnr jnr-posix - 3.0.46 + 3.0.49 io.dropwizard.metrics @@ -140,7 +140,7 @@ com.github.spotbugs spotbugs-annotations - 3.1.5 + 3.1.12 junit @@ -160,7 +160,7 @@ org.mockito mockito-core - 2.19.0 + 2.25.0 com.datastax.oss.simulacron @@ -291,12 +291,12 @@ org.revapi revapi-maven-plugin - 0.10.4 + 0.10.5 org.revapi revapi-java - 0.18.0 + 0.18.2 diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 2fde1deda35..709f06a1130 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -10,6 +10,7 @@ "com\\.datastax\\.oss\\.protocol\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", + "org\\.assertj(\\..+)?", // Don't re-check sibling modules that this module depends on "com\\.datastax\\.oss\\.driver\\.api\\.core(\\..+)?" ] diff --git a/test-infra/revapi.json b/test-infra/revapi.json index a4dc5b63664..11e4609560d 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -11,6 +11,7 @@ "com\\.datastax\\.oss\\.driver\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", "com\\.datastax\\.oss\\.simulacron(\\..+)?", + "org\\.assertj(\\..+)?", // Don't re-check sibling modules that this module depends on "com\\.datastax\\.oss\\.driver\\.api\\.core(\\..+)?" ] From 6194b1d9bbdd5522997bfb2ffcef0642e40dfc87 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 8 Mar 2019 14:15:41 -0800 Subject: [PATCH 715/742] Output minimal ignore suggestions when API problems found --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 09e087ee8ec..a3b60dedc8a 100644 --- a/pom.xml +++ b/pom.xml @@ -292,6 +292,9 @@ org.revapi revapi-maven-plugin 0.10.5 + + false + org.revapi From 5b2db904cf51f4c7745cb08951a7254efd31bc60 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 4 Mar 2019 12:47:22 -0800 Subject: [PATCH 716/742] JAVA-2165: Abstract node connection information Motivation: Until now, the only information used to connect to a node was its socket address (host+port). Internally, the driver also used this address as a unique node identifier (it was the key in the Metadata.nodes map). However, there are more complex deployment scenarios where the address might not be sufficient: for example, if nodes are accessed through a proxy that uses SNI routing, the connection information becomes host+port+sni_server_name, and the host+port part is no longer unique. We need to support those alternative connection methods, and use a different unique node identifier. Modifications: - Introduce EndPoint as an abstract wrapper around connection information. EndPoint.resolve() returns the socket address that will be used to open connections, but an endpoint may also contain additional pieces of information that will be used in other places (for example a custom SslHandlerFactory). - Add Node.getEndPoint(), and modify DefaultTopologyMonitor to fill it. Allow contact points to be specified as EndPoints (preserving legacy methods). - Replace InetSocketAddress by EndPoint everywhere that it was used to identify a node. - Index Metadata.nodes by the true unique node identifier: host_id from system tables. This has two consequences: - we can't put contact points in Metadata.nodes directly, because we don't know their host_id yet. Store them separately (MetadataManager.contactPoints) and transfer them during the first node list refresh. - when we receive a status event, we can't use a simple map lookup anymore, so do a linear traversal to find the node with a matching broadcast_rpc_address (side effect: we don't need to translate it). Result: We no longer depend on nodes having a unique socket address. We can plug in custom EndPoint implementations. --- changelog/README.md | 1 + core/revapi.json | 99 ++++++++++++++++ .../UnsupportedProtocolVersionException.java | 30 ++--- .../driver/api/core/auth/AuthProvider.java | 8 +- .../core/auth/AuthenticationException.java | 18 +-- .../loadbalancing/LoadBalancingPolicy.java | 16 +-- .../driver/api/core/metadata/EndPoint.java | 47 ++++++++ .../driver/api/core/metadata/Metadata.java | 41 ++++++- .../oss/driver/api/core/metadata/Node.java | 32 +++-- .../api/core/session/SessionBuilder.java | 36 +++++- .../driver/api/core/ssl/SslEngineFactory.java | 4 +- .../CassandraProtocolVersionRegistry.java | 11 +- .../driver/internal/core/ContactPoints.java | 26 ++--- .../core/auth/PlainTextAuthProvider.java | 8 +- .../internal/core/channel/ChannelFactory.java | 27 ++--- .../channel/ClusterNameMismatchException.java | 10 +- .../internal/core/channel/DriverChannel.java | 20 ++-- .../core/channel/ProtocolInitHandler.java | 35 +++--- .../core/control/ControlConnection.java | 15 +-- .../DefaultLoadBalancingPolicy.java | 26 ++--- .../core/metadata/AddNodeRefresh.java | 14 +-- .../core/metadata/DefaultEndPoint.java | 84 ++++++++++++++ .../core/metadata/DefaultMetadata.java | 12 +- .../internal/core/metadata/DefaultNode.java | 26 +++-- .../core/metadata/DefaultNodeInfo.java | 36 +++--- .../core/metadata/DefaultTopologyMonitor.java | 100 ++++++++++------ .../internal/core/metadata/DistanceEvent.java | 7 +- .../core/metadata/FullNodeListRefresh.java | 40 +++---- .../metadata/InitContactPointsRefresh.java | 59 ---------- .../core/metadata/InitialNodeListRefresh.java | 109 ++++++++++++++++++ .../metadata/LoadBalancingPolicyWrapper.java | 21 ++-- .../core/metadata/MetadataManager.java | 78 ++++++++----- .../internal/core/metadata/NodeInfo.java | 25 ++-- .../core/metadata/NodeStateEvent.java | 6 +- .../core/metadata/NodeStateManager.java | 88 ++++++++------ .../internal/core/metadata/NodesRefresh.java | 3 +- .../core/metadata/RemoveNodeRefresh.java | 40 ++++--- .../core/metadata/SchemaAgreementChecker.java | 85 +++++++------- .../internal/core/metadata/TopologyEvent.java | 48 +++++--- .../core/metadata/TopologyMonitor.java | 8 +- .../queries/DefaultSchemaQueriesFactory.java | 19 +-- .../metrics/DropwizardNodeMetricUpdater.java | 24 +--- .../internal/core/pool/ChannelPool.java | 9 +- .../internal/core/session/DefaultSession.java | 19 ++- .../internal/core/session/PoolManager.java | 28 +++-- .../core/ssl/DefaultSslEngineFactory.java | 10 +- .../core/ssl/JdkSslHandlerFactory.java | 4 +- .../internal/core/ssl/SslHandlerFactory.java | 4 +- .../core/channel/ChannelFactoryTestBase.java | 13 +-- .../core/channel/DriverChannelTest.java | 2 +- .../core/channel/EmbeddedEndPoint.java | 40 +++++++ .../internal/core/channel/LocalEndPoint.java | 40 +++++++ .../core/channel/ProtocolInitHandlerTest.java | 41 +++++-- .../control/ControlConnectionEventsTest.java | 2 - .../control/ControlConnectionTestBase.java | 14 +-- .../DefaultLoadBalancingPolicyEventsTest.java | 7 +- .../DefaultLoadBalancingPolicyInitTest.java | 51 ++++---- ...faultLoadBalancingPolicyQueryPlanTest.java | 19 ++- .../DefaultLoadBalancingPolicyTestBase.java | 22 ++-- .../core/metadata/AddNodeRefreshTest.java | 35 +++--- .../metadata/DefaultMetadataTokenMapTest.java | 55 ++++++--- .../metadata/DefaultTopologyMonitorTest.java | 74 ++++++------ .../metadata/FullNodeListRefreshTest.java | 47 ++++---- .../InitContactPointsRefreshTest.java | 58 ---------- .../LoadBalancingPolicyWrapperTest.java | 58 ++++------ .../core/metadata/MetadataManagerTest.java | 93 ++++++++++----- .../core/metadata/NodeStateManagerTest.java | 51 ++++---- .../core/metadata/RemoveNodeRefreshTest.java | 23 ++-- .../metadata/SchemaAgreementCheckerTest.java | 101 ++++++---------- .../core/metadata/TestNodeFactory.java | 35 ++++++ .../core/pool/ChannelPoolInitTest.java | 4 +- .../core/pool/ChannelPoolTestBase.java | 14 ++- .../core/session/DefaultSessionPoolsTest.java | 21 ++-- .../oss/driver/api/core/ConnectIT.java | 4 +- .../core/ProtocolVersionMixedClusterIT.java | 2 +- .../DriverExecutionProfileReloadIT.java | 8 +- .../driver/api/core/cql/BoundStatementIT.java | 2 +- .../oss/driver/api/core/cql/QueryTraceIT.java | 5 +- .../DefaultLoadBalancingPolicyIT.java | 27 +++-- .../core/loadbalancing/NodeTargetingIT.java | 9 +- .../api/core/metadata/NodeMetadataIT.java | 14 +-- .../driver/api/core/metadata/NodeStateIT.java | 103 +++++++++-------- .../driver/api/core/session/ExceptionIT.java | 4 +- .../api/core/session/RequestProcessorIT.java | 2 +- .../type/codec/registry/CodecRegistryIT.java | 4 +- .../core/retry/DefaultRetryPolicyIT.java | 13 ++- .../datastax/oss/driver/osgi/OsgiBaseIT.java | 2 +- manual/core/metrics/README.md | 4 +- pom.xml | 2 +- test-infra/revapi.json | 25 +++- .../api/testinfra/CassandraResourceRule.java | 6 +- .../SortingLoadBalancingPolicy.java | 6 +- .../api/testinfra/session/SessionUtils.java | 2 +- .../testinfra/simulacron/SimulacronRule.java | 8 +- 94 files changed, 1632 insertions(+), 1056 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/metadata/EndPoint.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultEndPoint.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitialNodeListRefresh.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/EmbeddedEndPoint.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/channel/LocalEndPoint.java delete mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/metadata/TestNodeFactory.java diff --git a/changelog/README.md b/changelog/README.md index 6c9e0a58535..b19a2678d73 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2165: Abstract node connection information - [improvement] JAVA-2090: Add support for additional_write_policy and read_repair table options - [improvement] JAVA-2164: Rename statement builder methods to setXxx - [bug] JAVA-2178: QueryBuilder: Alias after function column is not included in a query diff --git a/core/revapi.json b/core/revapi.json index e91c759a153..c8669069303 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -135,6 +135,105 @@ "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTracing()", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::(===java.net.SocketAddress===, java.lang.String, java.util.List)", + "new": "parameter void com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String, java.util.List)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forNegotiation(===java.net.SocketAddress===, java.util.List)", + "new": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forNegotiation(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.util.List)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forSingleAttempt(===java.net.SocketAddress===, com.datastax.oss.driver.api.core.ProtocolVersion)", + "new": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forSingleAttempt(===com.datastax.oss.driver.api.core.metadata.EndPoint===, com.datastax.oss.driver.api.core.ProtocolVersion)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.removed", + "old": "method java.net.SocketAddress com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::getAddress()", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter com.datastax.oss.driver.api.core.auth.Authenticator com.datastax.oss.driver.api.core.auth.AuthProvider::newAuthenticator(===java.net.SocketAddress===, java.lang.String) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", + "new": "parameter com.datastax.oss.driver.api.core.auth.Authenticator com.datastax.oss.driver.api.core.auth.AuthProvider::newAuthenticator(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthProvider::onMissingChallenge(===java.net.SocketAddress===) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", + "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthProvider::onMissingChallenge(===com.datastax.oss.driver.api.core.metadata.EndPoint===) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===java.net.SocketAddress===, java.lang.String)", + "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===java.net.SocketAddress===, java.lang.String, java.lang.Throwable)", + "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String, java.lang.Throwable)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.removed", + "old": "method java.net.SocketAddress com.datastax.oss.driver.api.core.auth.AuthenticationException::getAddress()", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.numberOfParametersChanged", + "old": "method void com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter, java.util.Set)", + "new": "method void com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getNodes()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getNodes()", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Node::getBroadcastRpcAddress()", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.removed", + "old": "method java.net.InetSocketAddress com.datastax.oss.driver.api.core.metadata.Node::getConnectAddress()", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.core.metadata.EndPoint com.datastax.oss.driver.api.core.metadata.Node::getEndPoint()", + "package": "com.datastax.oss.driver.api.core.metadata", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.parameterTypeChanged", + "old": "parameter javax.net.ssl.SSLEngine com.datastax.oss.driver.api.core.ssl.SslEngineFactory::newSslEngine(===java.net.SocketAddress===)", + "new": "parameter javax.net.ssl.SSLEngine com.datastax.oss.driver.api.core.ssl.SslEngineFactory::newSslEngine(===com.datastax.oss.driver.api.core.metadata.EndPoint===)", + "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java index 5430b0ad941..d80eba55514 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/UnsupportedProtocolVersionException.java @@ -16,10 +16,10 @@ package com.datastax.oss.driver.api.core; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.SocketAddress; import java.util.Collections; import java.util.List; @@ -34,51 +34,51 @@ public class UnsupportedProtocolVersionException extends DriverException { private static final long serialVersionUID = 0; - private final SocketAddress address; + private final EndPoint endPoint; private final List attemptedVersions; @NonNull public static UnsupportedProtocolVersionException forSingleAttempt( - @NonNull SocketAddress address, @NonNull ProtocolVersion attemptedVersion) { + @NonNull EndPoint endPoint, @NonNull ProtocolVersion attemptedVersion) { String message = - String.format("[%s] Host does not support protocol version %s", address, attemptedVersion); + String.format("[%s] Host does not support protocol version %s", endPoint, attemptedVersion); return new UnsupportedProtocolVersionException( - address, message, Collections.singletonList(attemptedVersion), null); + endPoint, message, Collections.singletonList(attemptedVersion), null); } @NonNull public static UnsupportedProtocolVersionException forNegotiation( - @NonNull SocketAddress address, @NonNull List attemptedVersions) { + @NonNull EndPoint endPoint, @NonNull List attemptedVersions) { String message = String.format( "[%s] Protocol negotiation failed: could not find a common version (attempted: %s). " + "Note that the driver does not support Cassandra 2.0 or lower.", - address, attemptedVersions); + endPoint, attemptedVersions); return new UnsupportedProtocolVersionException( - address, message, ImmutableList.copyOf(attemptedVersions), null); + endPoint, message, ImmutableList.copyOf(attemptedVersions), null); } public UnsupportedProtocolVersionException( - @Nullable SocketAddress address, // technically nullable, but should never be in real life + @Nullable EndPoint endPoint, // technically nullable, but should never be in real life @NonNull String message, @NonNull List attemptedVersions) { - this(address, message, attemptedVersions, null); + this(endPoint, message, attemptedVersions, null); } private UnsupportedProtocolVersionException( - SocketAddress address, + EndPoint endPoint, String message, List attemptedVersions, ExecutionInfo executionInfo) { super(message, executionInfo, null, true); - this.address = address; + this.endPoint = endPoint; this.attemptedVersions = attemptedVersions; } /** The address of the node that threw the error. */ @Nullable - public SocketAddress getAddress() { - return address; + public EndPoint getEndPoint() { + return endPoint; } /** The versions that were attempted. */ @@ -91,6 +91,6 @@ public List getAttemptedVersions() { @Override public DriverException copy() { return new UnsupportedProtocolVersionException( - address, getMessage(), attemptedVersions, getExecutionInfo()); + endPoint, getMessage(), attemptedVersions, getExecutionInfo()); } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java index 9e078542e05..e85b90e3b04 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthProvider.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.core.auth; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.auth.PlainTextAuthProvider; import edu.umd.cs.findbugs.annotations.NonNull; -import java.net.SocketAddress; /** * Provides {@link Authenticator} instances to use when connecting to Cassandra nodes. @@ -31,12 +31,12 @@ public interface AuthProvider extends AutoCloseable { /** * The authenticator to use when connecting to {@code host}. * - * @param host the Cassandra host to connect to. + * @param endPoint the Cassandra host to connect to. * @param serverAuthenticator the configured authenticator on the host. * @return the authentication implementation to use. */ @NonNull - Authenticator newAuthenticator(@NonNull SocketAddress host, @NonNull String serverAuthenticator) + Authenticator newAuthenticator(@NonNull EndPoint endPoint, @NonNull String serverAuthenticator) throws AuthenticationException; /** @@ -55,5 +55,5 @@ Authenticator newAuthenticator(@NonNull SocketAddress host, @NonNull String serv * will be retried according to the {@link ReconnectionPolicy}). * */ - void onMissingChallenge(@NonNull SocketAddress host) throws AuthenticationException; + void onMissingChallenge(@NonNull EndPoint endPoint) throws AuthenticationException; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java index 7f0bef26195..abf77e293d5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/AuthenticationException.java @@ -16,9 +16,9 @@ package com.datastax.oss.driver.api.core.auth; import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.SocketAddress; /** * Indicates an error during the authentication phase while connecting to a node. @@ -30,21 +30,21 @@ public class AuthenticationException extends RuntimeException { private static final long serialVersionUID = 0; - private final SocketAddress address; + private final EndPoint endPoint; - public AuthenticationException(@NonNull SocketAddress address, @NonNull String message) { - this(address, message, null); + public AuthenticationException(@NonNull EndPoint endPoint, @NonNull String message) { + this(endPoint, message, null); } public AuthenticationException( - @NonNull SocketAddress address, @NonNull String message, @Nullable Throwable cause) { - super(String.format("Authentication error on host %s: %s", address, message), cause); - this.address = address; + @NonNull EndPoint endPoint, @NonNull String message, @Nullable Throwable cause) { + super(String.format("Authentication error on node %s: %s", endPoint, message), cause); + this.endPoint = endPoint; } /** The address of the node that encountered the error. */ @NonNull - public SocketAddress getAddress() { - return address; + public EndPoint getEndPoint() { + return endPoint; } } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index 5b191fd3672..c45050aa5b8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -19,14 +19,11 @@ import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; -import com.datastax.oss.driver.api.core.session.SessionBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetSocketAddress; -import java.util.Collection; import java.util.Map; import java.util.Queue; -import java.util.Set; +import java.util.UUID; /** Decides which Cassandra nodes to contact for each query. */ public interface LoadBalancingPolicy extends AutoCloseable { @@ -42,17 +39,8 @@ public interface LoadBalancingPolicy extends AutoCloseable { * NodeState#UNKNOWN}. Node states may be updated concurrently while this method executes, but * if so you will receive a notification. * @param distanceReporter an object that will be used by the policy to signal distance changes. - * @param contactPoints the set of contact points that the driver was initialized with (see {@link - * SessionBuilder#addContactPoints(Collection)}). This is provided for reference, in case the - * policy needs to handle those nodes in a particular way. Each address in this set should - * normally have a corresponding entry in {@code nodes}, except for contact points that were - * down or invalid. If no contact points were provided, the driver defaults to 127.0.0.1:9042, - * but the set will be empty. */ - void init( - @NonNull Map nodes, - @NonNull DistanceReporter distanceReporter, - @NonNull Set contactPoints); + void init(@NonNull Map nodes, @NonNull DistanceReporter distanceReporter); /** * Returns the coordinators to use for a new query. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/EndPoint.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/EndPoint.java new file mode 100644 index 00000000000..d09253e099a --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/EndPoint.java @@ -0,0 +1,47 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.metadata; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * Encapsulates the information needed to open connections to a node. + * + *

        By default, the driver assumes plain TCP connections, and this is just a wrapper around an + * {@link InetSocketAddress}. However, more complex deployment scenarios might use a custom + * implementation that contains additional information; for example, if the nodes are accessed + * through a proxy with SNI routing, an SNI server name is needed in addition to the proxy address. + */ +public interface EndPoint { + + /** + * Resolves this instance to a socket address. + * + *

        This will be called each time the driver opens a new connection to the node. The returned + * address cannot be null. + */ + SocketAddress resolve(); + + /** + * Returns an alternate string representation for use in node-level metric names. + * + *

        Because metrics names are path-like, dot-separated strings, raw IP addresses don't make very + * good identifiers. So this method will typically replace the dots by another character, for + * example {@code 127_0_0_1_9042}. + */ + String asMetricPrefix(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index 449893e4d80..af841f60633 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.util.Map; import java.util.Optional; +import java.util.UUID; /** * The metadata of the Cassandra cluster that this driver instance is connected to. @@ -36,11 +37,45 @@ */ public interface Metadata { /** - * The nodes known to the driver, indexed by the address that it uses to connect to them. This - * might include nodes that are currently viewed as down, or ignored by the load balancing policy. + * The nodes known to the driver, indexed by their unique identifier ({@code host_id} in {@code + * system.local}/{@code system.peers}). This might include nodes that are currently viewed as + * down, or ignored by the load balancing policy. */ @NonNull - Map getNodes(); + Map getNodes(); + + /** + * Finds the node with the given {@linkplain Node#getEndPoint() connection information}, if it + * exists. + * + *

        Note that this method performs a linear search of {@link #getNodes()}. + */ + @NonNull + default Optional findNode(@NonNull EndPoint endPoint) { + for (Node node : getNodes().values()) { + if (node.getEndPoint().equals(endPoint)) { + return Optional.of(node); + } + } + return Optional.empty(); + } + + /** + * Finds the node with the given untranslated {@linkplain Node#getBroadcastRpcAddress() + * broadcast RPC address}, if it exists. + * + *

        Note that this method performs a linear search of {@link #getNodes()}. + */ + @NonNull + default Optional findNode(@NonNull InetSocketAddress broadcastRpcAddress) { + for (Node node : getNodes().values()) { + Optional o = node.getBroadcastRpcAddress(); + if (o.isPresent() && o.get().equals(broadcastRpcAddress)) { + return Optional.of(node); + } + } + return Optional.empty(); + } /** * The keyspaces defined in this cluster. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java index 031d3474062..019e2955848 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Node.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.api.core.metadata; import com.datastax.oss.driver.api.core.Version; -import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import edu.umd.cs.findbugs.annotations.NonNull; @@ -33,23 +32,40 @@ * state of the node. */ public interface Node { + + /** The information that the driver uses to connect to the node. */ + @NonNull + EndPoint getEndPoint(); + /** - * The address that the driver uses to connect to the node. This is the node's broadcast RPC - * address, transformed by the {@link AddressTranslator} if one is configured. + * The node's broadcast RPC address. * - *

        The driver also uses this to uniquely identify a node. + *

        This is the address that the node expects clients to connect to, as reported in {@code + * system.peers.rpc_address} (Cassandra 3) or {@code system.peers_v2.native_address/native_port} + * (Cassandra 4+). However, it might not be what the driver uses directly, if the node is accessed + * through a proxy. + * + *

        This may not be known at all times. In particular, some Cassandra versions (less than + * 2.0.16, 2.1.6 or 2.2.0-rc1) don't store it in the {@code system.local} table, so this will be + * unknown for the control node, until the control connection reconnects to another node. + * + * @see CASSANDRA-9436 (where the + * information was added to system.local) */ @NonNull - InetSocketAddress getConnectAddress(); + Optional getBroadcastRpcAddress(); /** * The node's broadcast address. That is, the address that other nodes use to communicate with * that node. This is also the value of the {@code peer} column in {@code system.peers}. If the * port is set to 0 it is unknown. * - *

        This may not be known at all times. In particular, some Cassandra versions don't store it in - * the {@code system.local} table, so this will be unknown for the control node, until the control - * connection reconnects to another node. + *

        This may not be known at all times. In particular, some Cassandra versions (less than + * 2.0.16, 2.1.6 or 2.2.0-rc1) don't store it in the {@code system.local} table, so this will be + * unknown for the control node, until the control connection reconnects to another node. + * + * @see CASSANDRA-9436 (where the + * information was added to system.local) */ @NonNull Optional getBroadcastAddress(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 3f41c9de233..66e3bac2df7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; @@ -31,6 +32,7 @@ import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -63,7 +65,7 @@ public abstract class SessionBuilder { protected final SelfT self = (SelfT) this; protected DriverConfigLoader configLoader; - protected Set programmaticContactPoints = new HashSet<>(); + protected Set programmaticContactPoints = new HashSet<>(); protected List> typeCodecs = new ArrayList<>(); private NodeStateListener nodeStateListener; private SchemaChangeListener schemaChangeListener; @@ -131,7 +133,9 @@ protected DriverConfigLoader defaultConfigLoader() { */ @NonNull public SelfT addContactPoints(@NonNull Collection contactPoints) { - this.programmaticContactPoints.addAll(contactPoints); + for (InetSocketAddress contactPoint : contactPoints) { + addContactPoint(contactPoint); + } return self; } @@ -142,6 +146,32 @@ public SelfT addContactPoints(@NonNull Collection contactPoin */ @NonNull public SelfT addContactPoint(@NonNull InetSocketAddress contactPoint) { + this.programmaticContactPoints.add(new DefaultEndPoint(contactPoint)); + return self; + } + + /** + * Adds contact points to use for the initial connection to the cluster. + * + *

        You only need this method if you use a custom {@link EndPoint} implementation. Otherwise, + * use {@link #addContactPoints(Collection)}. + */ + @NonNull + public SelfT addContactEndPoints(@NonNull Collection contactPoints) { + for (EndPoint contactPoint : contactPoints) { + addContactEndPoint(contactPoint); + } + return self; + } + + /** + * Adds a contact point to use for the initial connection to the cluster. + * + *

        You only need this method if you use a custom {@link EndPoint} implementation. Otherwise, + * use {@link #addContactPoint(InetSocketAddress)}. + */ + @NonNull + public SelfT addContactEndPoint(@NonNull EndPoint contactPoint) { this.programmaticContactPoints.add(contactPoint); return self; } @@ -319,7 +349,7 @@ protected final CompletionStage buildDefaultSessionAsync() { boolean resolveAddresses = defaultConfig.getBoolean(DefaultDriverOption.RESOLVE_CONTACT_POINTS, true); - Set contactPoints = + Set contactPoints = ContactPoints.merge(programmaticContactPoints, configContactPoints, resolveAddresses); if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java index 1a716c0e111..a001c696fe0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java @@ -15,8 +15,8 @@ */ package com.datastax.oss.driver.api.core.ssl; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import edu.umd.cs.findbugs.annotations.NonNull; -import java.net.SocketAddress; import javax.net.ssl.SSLEngine; /** @@ -34,5 +34,5 @@ public interface SslEngineFactory extends AutoCloseable { * node). */ @NonNull - SSLEngine newSslEngine(@NonNull SocketAddress remoteEndpoint); + SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java index 337868d9441..f76f2d9b0fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/CassandraProtocolVersionRegistry.java @@ -130,25 +130,22 @@ public ProtocolVersion highestCommon(Collection nodes) { "[{}] Node {} reports null Cassandra version, " + "ignoring it from optimal protocol version computation", logPrefix, - node.getConnectAddress()); + node.getEndPoint()); continue; } version = version.nextStable(); if (version.compareTo(Version.V2_1_0) < 0) { throw new UnsupportedProtocolVersionException( - node.getConnectAddress(), + node.getEndPoint(), String.format( "Node %s reports Cassandra version %s, " + "but the driver only supports 2.1.0 and above", - node.getConnectAddress(), version), + node.getEndPoint(), version), ImmutableList.of(DefaultProtocolVersion.V3, DefaultProtocolVersion.V4)); } LOG.debug( - "[{}] Node {} reports Cassandra version {}", - logPrefix, - node.getConnectAddress(), - version); + "[{}] Node {} reports Cassandra version {}", logPrefix, node.getEndPoint(), version); if (version.compareTo(Version.V2_2_0) < 0 && candidates.remove(DefaultProtocolVersion.V4)) { LOG.debug("[{}] Excluding protocol V4", logPrefix); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java index e88dbcd7edc..dcbbcac3248 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -15,6 +15,8 @@ */ package com.datastax.oss.driver.internal.core; +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.shaded.guava.common.base.Splitter; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import com.datastax.oss.driver.shaded.guava.common.collect.Sets; @@ -33,28 +35,14 @@ public class ContactPoints { private static final Logger LOG = LoggerFactory.getLogger(ContactPoints.class); - public static Set merge( - Set programmaticContactPoints, - List configContactPoints, - boolean resolve) { + public static Set merge( + Set programmaticContactPoints, List configContactPoints, boolean resolve) { - if (!resolve) { - for (InetSocketAddress address : programmaticContactPoints) { - if (!address.isUnresolved()) { - LOG.warn( - "You configured the driver to not resolve addresses," - + " but at least one of your programmatic contact points is resolved ({})," - + " this is probably a mistake", - address); - break; - } - } - } - - Set result = Sets.newHashSet(programmaticContactPoints); + Set result = Sets.newHashSet(programmaticContactPoints); for (String spec : configContactPoints) { for (InetSocketAddress address : extract(spec, resolve)) { - boolean wasNew = result.add(address); + DefaultEndPoint endPoint = new DefaultEndPoint(address); + boolean wasNew = result.add(endPoint); if (!wasNew) { LOG.warn("Duplicate contact point {}", address); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java index 2983c5b2b11..cf0bbfcb917 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/auth/PlainTextAuthProvider.java @@ -21,9 +21,9 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import edu.umd.cs.findbugs.annotations.NonNull; -import java.net.SocketAddress; import java.nio.ByteBuffer; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; @@ -65,19 +65,19 @@ public PlainTextAuthProvider(DriverContext context) { @NonNull @Override public Authenticator newAuthenticator( - @NonNull SocketAddress host, @NonNull String serverAuthenticator) { + @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) { String username = config.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME); String password = config.getString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD); return new PlainTextAuthenticator(username, password); } @Override - public void onMissingChallenge(@NonNull SocketAddress host) { + public void onMissingChallenge(@NonNull EndPoint endPoint) { LOG.warn( "[{}] {} did not send an authentication challenge; " + "This is suspicious because the driver expects authentication", logPrefix, - host); + endPoint); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index f38ea39113b..9d5caa03b8f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; @@ -39,7 +40,6 @@ import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.FixedRecvByteBufAllocator; -import java.net.SocketAddress; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -100,12 +100,12 @@ public CompletionStage connect(Node node, DriverChannelOptions op } else { nodeMetricUpdater = NoopNodeMetricUpdater.INSTANCE; } - return connect(node.getConnectAddress(), options, nodeMetricUpdater); + return connect(node.getEndPoint(), options, nodeMetricUpdater); } @VisibleForTesting CompletionStage connect( - SocketAddress address, DriverChannelOptions options, NodeMetricUpdater nodeMetricUpdater) { + EndPoint endPoint, DriverChannelOptions options, NodeMetricUpdater nodeMetricUpdater) { CompletableFuture resultFuture = new CompletableFuture<>(); ProtocolVersion currentVersion; @@ -120,7 +120,7 @@ CompletionStage connect( } connect( - address, + endPoint, options, nodeMetricUpdater, currentVersion, @@ -131,7 +131,7 @@ CompletionStage connect( } private void connect( - SocketAddress address, + EndPoint endPoint, DriverChannelOptions options, NodeMetricUpdater nodeMetricUpdater, ProtocolVersion currentVersion, @@ -147,7 +147,7 @@ private void connect( .channel(nettyOptions.channelClass()) .option(ChannelOption.ALLOCATOR, nettyOptions.allocator()) .handler( - initializer(address, currentVersion, options, nodeMetricUpdater, resultFuture)); + initializer(endPoint, currentVersion, options, nodeMetricUpdater, resultFuture)); DriverExecutionProfile config = context.getConfig().getDefaultProfile(); @@ -180,14 +180,14 @@ private void connect( nettyOptions.afterBootstrapInitialized(bootstrap); - ChannelFuture connectFuture = bootstrap.connect(address); + ChannelFuture connectFuture = bootstrap.connect(endPoint.resolve()); connectFuture.addListener( cf -> { if (connectFuture.isSuccess()) { Channel channel = connectFuture.channel(); DriverChannel driverChannel = - new DriverChannel(address, channel, context.getWriteCoalescer(), currentVersion); + new DriverChannel(endPoint, channel, context.getWriteCoalescer(), currentVersion); // If this is the first successful connection, remember the protocol version and // cluster name for future connections. if (isNegotiating) { @@ -210,7 +210,7 @@ private void connect( currentVersion, downgraded.get()); connect( - address, + endPoint, options, nodeMetricUpdater, downgraded.get(), @@ -219,7 +219,8 @@ private void connect( resultFuture); } else { resultFuture.completeExceptionally( - UnsupportedProtocolVersionException.forNegotiation(address, attemptedVersions)); + UnsupportedProtocolVersionException.forNegotiation( + endPoint, attemptedVersions)); } } else { // Note: might be completed already if the failure happened in initializer(), this is @@ -232,7 +233,7 @@ private void connect( @VisibleForTesting ChannelInitializer initializer( - SocketAddress address, + EndPoint endPoint, ProtocolVersion protocolVersion, DriverChannelOptions options, NodeMetricUpdater nodeMetricUpdater, @@ -266,12 +267,12 @@ protected void initChannel(Channel channel) { HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultConfig); ProtocolInitHandler initHandler = new ProtocolInitHandler( - context, protocolVersion, clusterName, options, heartbeatHandler); + context, protocolVersion, clusterName, endPoint, options, heartbeatHandler); ChannelPipeline pipeline = channel.pipeline(); context .getSslHandlerFactory() - .map(f -> f.newSslHandler(channel, address)) + .map(f -> f.newSslHandler(channel, endPoint)) .map(h -> pipeline.addLast("ssl", h)); // Only add meter handlers on the pipeline if metrics are enabled. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java index 42ae63fa2c1..04abdfb0368 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ClusterNameMismatchException.java @@ -15,7 +15,7 @@ */ package com.datastax.oss.driver.internal.core.channel; -import java.net.SocketAddress; +import com.datastax.oss.driver.api.core.metadata.EndPoint; /** * Indicates that we've attempted to connect to a node with a cluster name that doesn't match that @@ -39,18 +39,18 @@ public class ClusterNameMismatchException extends RuntimeException { private static final long serialVersionUID = 0; - public final SocketAddress address; + public final EndPoint endPoint; public final String expectedClusterName; public final String actualClusterName; public ClusterNameMismatchException( - SocketAddress address, String actualClusterName, String expectedClusterName) { + EndPoint endPoint, String actualClusterName, String expectedClusterName) { super( String.format( "Node %s reports cluster name '%s' that doesn't match our cluster name '%s'. " + "It will be forced down.", - address, actualClusterName, expectedClusterName)); - this.address = address; + endPoint, actualClusterName, expectedClusterName)); + this.endPoint = endPoint; this.expectedClusterName = expectedClusterName; this.actualClusterName = actualClusterName; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index 7830d170992..59978777b98 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions; import com.datastax.oss.protocol.internal.Message; import io.netty.channel.Channel; @@ -46,7 +47,7 @@ public class DriverChannel { @SuppressWarnings("RedundantStringConstructorCall") static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); - private final SocketAddress connectAddress; + private final EndPoint endPoint; private final Channel channel; private final InFlightHandler inFlightHandler; private final WriteCoalescer writeCoalescer; @@ -55,11 +56,11 @@ public class DriverChannel { private final AtomicBoolean forceClosing = new AtomicBoolean(); DriverChannel( - SocketAddress connectAddress, + EndPoint endPoint, Channel channel, WriteCoalescer writeCoalescer, ProtocolVersion protocolVersion) { - this.connectAddress = connectAddress; + this.endPoint = endPoint; this.channel = channel; this.inFlightHandler = channel.pipeline().get(InFlightHandler.class); this.writeCoalescer = writeCoalescer; @@ -153,16 +154,9 @@ public ProtocolVersion protocolVersion() { return protocolVersion; } - /** - * The address that was used to establish the connection. - * - *

        Note that it might be unresolved, and therefore not equal to the underlying Netty channel's - * remote address. - * - *

        See {@code advanced.resolve-contact-points} in the configuration. - */ - public SocketAddress connectAddress() { - return connectAddress; + /** The endpoint that was used to establish the connection. */ + public EndPoint getEndPoint() { + return endPoint; } public SocketAddress localAddress() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java index 240e474709c..54cb427e365 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ConnectionInitException; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.util.ProtocolUtils; @@ -42,7 +43,6 @@ import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.SetKeyspace; import io.netty.channel.ChannelHandlerContext; -import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.List; import net.jcip.annotations.NotThreadSafe; @@ -65,6 +65,7 @@ class ProtocolInitHandler extends ConnectInitHandler { private final DriverChannelOptions options; // might be null if this is the first channel to this cluster private final String expectedClusterName; + private final EndPoint endPoint; private final HeartbeatHandler heartbeatHandler; private String logPrefix; private ChannelHandlerContext ctx; @@ -73,10 +74,12 @@ class ProtocolInitHandler extends ConnectInitHandler { InternalDriverContext context, ProtocolVersion protocolVersion, String expectedClusterName, + EndPoint endPoint, DriverChannelOptions options, HeartbeatHandler heartbeatHandler) { this.context = context; + this.endPoint = endPoint; DriverExecutionProfile defaultConfig = context.getConfig().getDefaultProfile(); @@ -165,14 +168,12 @@ void onResponse(Message response) { ProtocolUtils.opcodeString(response.opcode)); try { if (step == Step.STARTUP && response instanceof Ready) { - context - .getAuthProvider() - .ifPresent(provider -> provider.onMissingChallenge(channel.remoteAddress())); + context.getAuthProvider().ifPresent(provider -> provider.onMissingChallenge(endPoint)); step = Step.GET_CLUSTER_NAME; send(); } else if (step == Step.STARTUP && response instanceof Authenticate) { Authenticate authenticate = (Authenticate) response; - authenticator = buildAuthenticator(channel.remoteAddress(), authenticate.authenticator); + authenticator = buildAuthenticator(endPoint, authenticate.authenticator); authenticator .initialResponse() .whenCompleteAsync( @@ -180,7 +181,7 @@ void onResponse(Message response) { if (error != null) { fail( new AuthenticationException( - channel.remoteAddress(), "authenticator threw an exception", error)); + endPoint, "authenticator threw an exception", error)); } else { step = Step.AUTH_RESPONSE; authReponseToken = token; @@ -198,7 +199,7 @@ void onResponse(Message response) { if (error != null) { fail( new AuthenticationException( - channel.remoteAddress(), "authenticator threw an exception", error)); + endPoint, "authenticator threw an exception", error)); } else { step = Step.AUTH_RESPONSE; authReponseToken = token; @@ -216,7 +217,7 @@ void onResponse(Message response) { if (error != null) { fail( new AuthenticationException( - channel.remoteAddress(), "authenticator threw an exception", error)); + endPoint, "authenticator threw an exception", error)); } else { step = Step.GET_CLUSTER_NAME; send(); @@ -229,16 +230,14 @@ void onResponse(Message response) { && ((Error) response).code == ProtocolConstants.ErrorCode.AUTH_ERROR) { fail( new AuthenticationException( - channel.remoteAddress(), - String.format("server replied '%s'", ((Error) response).message))); + endPoint, String.format("server replied '%s'", ((Error) response).message))); } else if (step == Step.GET_CLUSTER_NAME && response instanceof Rows) { Rows rows = (Rows) response; List row = rows.getData().poll(); String actualClusterName = getString(row, 0); if (expectedClusterName != null && !expectedClusterName.equals(actualClusterName)) { fail( - new ClusterNameMismatchException( - channel.remoteAddress(), actualClusterName, expectedClusterName)); + new ClusterNameMismatchException(endPoint, actualClusterName, expectedClusterName)); } else { if (expectedClusterName == null) { // Store the actual name so that it can be retrieved from the factory @@ -275,7 +274,7 @@ void onResponse(Message response) { && error.message.contains("Invalid or unsupported protocol version")) { fail( UnsupportedProtocolVersionException.forSingleAttempt( - channel.remoteAddress(), initialProtocolVersion)); + endPoint, initialProtocolVersion)); } else if (step == Step.SET_KEYSPACE && error.code == ProtocolConstants.ErrorCode.INVALID) { fail(new InvalidKeyspaceException(error.message)); @@ -299,17 +298,17 @@ void fail(String message, Throwable cause) { setConnectFailure(finalException); } - private Authenticator buildAuthenticator(SocketAddress address, String authenticator) { + private Authenticator buildAuthenticator(EndPoint endPoint, String authenticator) { return context .getAuthProvider() - .map(p -> p.newAuthenticator(address, authenticator)) + .map(p -> p.newAuthenticator(endPoint, authenticator)) .orElseThrow( () -> new AuthenticationException( - address, + endPoint, String.format( - "Host %s requires authentication (%s), but no authenticator configured", - address, authenticator))); + "Node %s requires authentication (%s), but no authenticator configured", + endPoint, authenticator))); } @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java index 9d229f89f10..50b6ffe90f0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/control/ControlConnection.java @@ -48,7 +48,6 @@ import com.datastax.oss.protocol.internal.response.event.TopologyChangeEvent; import edu.umd.cs.findbugs.annotations.NonNull; import io.netty.util.concurrent.EventExecutor; -import java.net.InetSocketAddress; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -199,13 +198,12 @@ public void onEvent(Message eventMessage) { private void processTopologyChange(Event event) { TopologyChangeEvent tce = (TopologyChangeEvent) event; - InetSocketAddress address = context.getAddressTranslator().translate(tce.address); switch (tce.changeType) { case ProtocolConstants.TopologyChangeType.NEW_NODE: - context.getEventBus().fire(TopologyEvent.suggestAdded(address)); + context.getEventBus().fire(TopologyEvent.suggestAdded(tce.address)); break; case ProtocolConstants.TopologyChangeType.REMOVED_NODE: - context.getEventBus().fire(TopologyEvent.suggestRemoved(address)); + context.getEventBus().fire(TopologyEvent.suggestRemoved(tce.address)); break; default: LOG.warn("[{}] Unsupported topology change type: {}", logPrefix, tce.changeType); @@ -214,13 +212,12 @@ private void processTopologyChange(Event event) { private void processStatusChange(Event event) { StatusChangeEvent sce = (StatusChangeEvent) event; - InetSocketAddress address = context.getAddressTranslator().translate(sce.address); switch (sce.changeType) { case ProtocolConstants.StatusChangeType.UP: - context.getEventBus().fire(TopologyEvent.suggestUp(address)); + context.getEventBus().fire(TopologyEvent.suggestUp(sce.address)); break; case ProtocolConstants.StatusChangeType.DOWN: - context.getEventBus().fire(TopologyEvent.suggestDown(address)); + context.getEventBus().fire(TopologyEvent.suggestDown(sce.address)); break; default: LOG.warn("[{}] Unsupported status change type: {}", logPrefix, sce.changeType); @@ -501,7 +498,7 @@ private void onDistanceEvent(DistanceEvent event) { if (event.distance == NodeDistance.IGNORED && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.connectAddress())) { + && event.node.getEndPoint().equals(channel.getEndPoint())) { LOG.debug( "[{}] Control node {} became IGNORED, reconnecting to a different node", logPrefix, @@ -516,7 +513,7 @@ private void onStateEvent(NodeStateEvent event) { if ((event.newState == null /*(removed)*/ || event.newState == NodeState.FORCED_DOWN) && channel != null && !channel.closeFuture().isDone() - && event.node.getConnectAddress().equals(channel.connectAddress())) { + && event.node.getEndPoint().equals(channel.getEndPoint())) { LOG.debug( "[{}] Control node {} was removed or forced down, reconnecting to a different node", logPrefix, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 4c528423228..f975755fc57 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.util.ArrayUtils; import com.datastax.oss.driver.internal.core.util.Reflection; @@ -35,7 +36,6 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; @@ -43,6 +43,7 @@ import java.util.Optional; import java.util.Queue; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntUnaryOperator; @@ -119,17 +120,15 @@ public DefaultLoadBalancingPolicy(@NonNull DriverContext context, @NonNull Strin } @Override - public void init( - @NonNull Map nodes, - @NonNull DistanceReporter distanceReporter, - @NonNull Set contactPoints) { + public void init(@NonNull Map nodes, @NonNull DistanceReporter distanceReporter) { this.distanceReporter = distanceReporter; + Set contactPoints = metadataManager.getContactPoints(); if (localDc == null) { - if (contactPoints.isEmpty()) { - // No explicit contact points provided => the driver used the default (127.0.0.1:9042), and - // we allow inferring the local DC in this case - Node contactPoint = nodes.get(MetadataManager.DEFAULT_CONTACT_POINT); + if (metadataManager.wasImplicitContactPoint()) { + // We allow automatic inference of the local DC in this case + assert contactPoints.size() == 1; + Node contactPoint = contactPoints.iterator().next(); localDc = contactPoint.getDatacenter(); LOG.debug("[{}] Local DC set from contact point {}: {}", logPrefix, contactPoint, localDc); } else { @@ -139,17 +138,16 @@ public void init( + " in the config)"); } } else { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (InetSocketAddress address : contactPoints) { - Node node = nodes.get(address); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Node node : contactPoints) { if (node != null) { String datacenter = node.getDatacenter(); if (!Objects.equals(localDc, datacenter)) { - builder.put(address, (datacenter == null) ? "" : datacenter); + builder.put(node, (datacenter == null) ? "" : datacenter); } } } - ImmutableMap badContactPoints = builder.build(); + ImmutableMap badContactPoints = builder.build(); if (isDefaultPolicy && !badContactPoints.isEmpty()) { LOG.warn( "[{}] You specified {} as the local DC, but some contact points are from a different DC ({})", diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java index 4b7dda81111..088d5d0ea68 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefresh.java @@ -20,8 +20,8 @@ import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import java.net.InetSocketAddress; import java.util.Map; +import java.util.UUID; import net.jcip.annotations.ThreadSafe; @ThreadSafe @@ -36,16 +36,16 @@ public class AddNodeRefresh extends NodesRefresh { @Override public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - Map oldNodes = oldMetadata.getNodes(); - if (oldNodes.containsKey(newNodeInfo.getConnectAddress())) { + Map oldNodes = oldMetadata.getNodes(); + if (oldNodes.containsKey(newNodeInfo.getHostId())) { return new Result(oldMetadata); } else { - DefaultNode newNode = new DefaultNode(newNodeInfo.getConnectAddress(), context); + DefaultNode newNode = new DefaultNode(newNodeInfo.getEndPoint(), context); copyInfos(newNodeInfo, newNode, null, context.getSessionName()); - Map newNodes = - ImmutableMap.builder() + Map newNodes = + ImmutableMap.builder() .putAll(oldNodes) - .put(newNode.getConnectAddress(), newNode) + .put(newNode.getHostId(), newNode) .build(); return new Result( oldMetadata.withNodes(newNodes, tokenMapEnabled, false, null, context), diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultEndPoint.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultEndPoint.java new file mode 100644 index 00000000000..754497798f9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultEndPoint.java @@ -0,0 +1,84 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +public class DefaultEndPoint implements EndPoint { + + private final InetSocketAddress address; + private final String metricPrefix; + + public DefaultEndPoint(InetSocketAddress address) { + Preconditions.checkNotNull(address); + this.address = address; + this.metricPrefix = buildMetricPrefix(address); + } + + @Override + public InetSocketAddress resolve() { + return address; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof DefaultEndPoint) { + DefaultEndPoint that = (DefaultEndPoint) other; + return this.address.equals(that.address); + } else { + return false; + } + } + + @Override + public int hashCode() { + return address.hashCode(); + } + + @Override + public String toString() { + return address.toString(); + } + + @Override + public String asMetricPrefix() { + return metricPrefix; + } + + private static String buildMetricPrefix(InetSocketAddress addressAndPort) { + StringBuilder prefix = new StringBuilder(); + InetAddress address = addressAndPort.getAddress(); + int port = addressAndPort.getPort(); + if (address instanceof Inet4Address) { + // Metrics use '.' as a delimiter, replace so that the IP is a single path component + // (127.0.0.1 => 127_0_0_1) + prefix.append(address.getHostAddress().replace('.', '_')); + } else { + assert address instanceof Inet6Address; + // IPv6 only uses '%' and ':' as separators, so no replacement needed + prefix.append(address.getHostAddress()); + } + // Append the port since Cassandra 4 supports nodes with different ports + return prefix.append(':').append(port).toString(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index 9c22ed40709..f4677cebac7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -29,10 +29,10 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetSocketAddress; import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.UUID; import net.jcip.annotations.Immutable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,12 +48,12 @@ public class DefaultMetadata implements Metadata { public static DefaultMetadata EMPTY = new DefaultMetadata(Collections.emptyMap(), Collections.emptyMap(), null); - protected final Map nodes; + protected final Map nodes; protected final Map keyspaces; protected final TokenMap tokenMap; protected DefaultMetadata( - Map nodes, + Map nodes, Map keyspaces, TokenMap tokenMap) { this.nodes = nodes; @@ -63,7 +63,7 @@ protected DefaultMetadata( @NonNull @Override - public Map getNodes() { + public Map getNodes() { return nodes; } @@ -91,7 +91,7 @@ public Optional getTokenMap() { * @return the new metadata. */ public DefaultMetadata withNodes( - Map newNodes, + Map newNodes, boolean tokenMapEnabled, boolean tokensChanged, TokenFactory tokenFactory, @@ -119,7 +119,7 @@ public DefaultMetadata withSchema( @Nullable protected TokenMap rebuildTokenMap( - Map newNodes, + Map newNodes, Map newKeyspaces, boolean tokenMapEnabled, boolean forceFullRebuild, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java index b1693248cda..c8e6abc466c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNode.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -37,9 +38,10 @@ @ThreadSafe public class DefaultNode implements Node { - private final InetSocketAddress connectAddress; + private final EndPoint endPoint; private final NodeMetricUpdater metricUpdater; + volatile InetSocketAddress broadcastRpcAddress; volatile InetSocketAddress broadcastAddress; volatile InetSocketAddress listenAddress; volatile String datacenter; @@ -59,8 +61,8 @@ public class DefaultNode implements Node { volatile NodeDistance distance; - public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext context) { - this.connectAddress = connectAddress; + public DefaultNode(EndPoint endPoint, InternalDriverContext context) { + this.endPoint = endPoint; this.state = NodeState.UNKNOWN; this.distance = NodeDistance.IGNORED; this.rawTokens = Collections.emptySet(); @@ -73,8 +75,14 @@ public DefaultNode(InetSocketAddress connectAddress, InternalDriverContext conte @NonNull @Override - public InetSocketAddress getConnectAddress() { - return connectAddress; + public EndPoint getEndPoint() { + return endPoint; + } + + @NonNull + @Override + public Optional getBroadcastRpcAddress() { + return Optional.ofNullable(broadcastRpcAddress); } @NonNull @@ -162,7 +170,9 @@ public boolean equals(Object other) { return true; } else if (other instanceof Node) { Node that = (Node) other; - return this.connectAddress.equals(that.getConnectAddress()); + // hostId is the natural identifier, but unfortunately we don't know it for contact points + // until the driver has opened the first connection. + return this.endPoint.equals(that.getEndPoint()); } else { return false; } @@ -170,12 +180,12 @@ public boolean equals(Object other) { @Override public int hashCode() { - return connectAddress.hashCode(); + return endPoint.hashCode(); } @Override public String toString() { - return connectAddress.toString(); + return endPoint.toString(); } /** Note: deliberately not exposed by the public interface. */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java index 45d9ca0a442..2bc3b8dfa54 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultNodeInfo.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.internal.core.metadata; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashMap; @@ -31,7 +32,8 @@ public static Builder builder() { return new Builder(); } - private final InetSocketAddress connectAddress; + private final EndPoint endPoint; + private final InetSocketAddress broadcastRpcAddress; private final InetSocketAddress broadcastAddress; private final InetSocketAddress listenAddress; private final String datacenter; @@ -44,7 +46,8 @@ public static Builder builder() { private final UUID schemaVersion; private DefaultNodeInfo(Builder builder) { - this.connectAddress = builder.connectAddress; + this.endPoint = builder.endPoint; + this.broadcastRpcAddress = builder.broadcastRpcAddress; this.broadcastAddress = builder.broadcastAddress; this.listenAddress = builder.listenAddress; this.datacenter = builder.datacenter; @@ -58,8 +61,13 @@ private DefaultNodeInfo(Builder builder) { } @Override - public InetSocketAddress getConnectAddress() { - return connectAddress; + public EndPoint getEndPoint() { + return endPoint; + } + + @Override + public Optional getBroadcastRpcAddress() { + return Optional.ofNullable(broadcastRpcAddress); } @Override @@ -114,7 +122,8 @@ public UUID getSchemaVersion() { @NotThreadSafe public static class Builder { - private InetSocketAddress connectAddress; + private EndPoint endPoint; + private InetSocketAddress broadcastRpcAddress; private InetSocketAddress broadcastAddress; private InetSocketAddress listenAddress; private String datacenter; @@ -126,22 +135,23 @@ public static class Builder { private UUID hostId; private UUID schemaVersion; - public Builder withConnectAddress(InetSocketAddress address) { - this.connectAddress = address; + public Builder withEndPoint(EndPoint endPoint) { + this.endPoint = endPoint; + return this; + } + + public Builder withBroadcastRpcAddress(InetSocketAddress address) { + this.broadcastRpcAddress = address; return this; } public Builder withBroadcastAddress(InetSocketAddress address) { - if (address != null) { - this.broadcastAddress = address; - } + this.broadcastAddress = address; return this; } public Builder withListenAddress(InetSocketAddress address) { - if (address != null) { - this.listenAddress = address; - } + this.listenAddress = address; return this; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index 84eee637fbf..e658dc21642 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; @@ -34,6 +35,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import net.jcip.annotations.ThreadSafe; @@ -104,7 +107,7 @@ public CompletionStage> refreshNode(Node node) { } LOG.debug("[{}] Refreshing info for {}", logPrefix, node); DriverChannel channel = controlConnection.channel(); - if (node.getConnectAddress().equals(channel.connectAddress())) { + if (node.getEndPoint().equals(channel.getEndPoint())) { // refreshNode is called for nodes that just came up. If the control node just came up, it // means the control connection just reconnected, which means we did a full node refresh. So // we don't need to process this call. @@ -134,19 +137,19 @@ public CompletionStage> refreshNode(Node node) { return query.thenApply(this::firstRowAsNodeInfo); } else { return query(channel, "SELECT * FROM " + retrievePeerTableName()) - .thenApply(result -> this.findInPeers(result, node.getConnectAddress())); + .thenApply(result -> this.findInPeers(result, node.getHostId())); } } @Override - public CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress) { + public CompletionStage> getNewNodeInfo(InetSocketAddress broadcastRpcAddress) { if (closeFuture.isDone()) { return CompletableFutures.failedFuture(new IllegalStateException("closed")); } - LOG.debug("[{}] Fetching info for new node {}", logPrefix, connectAddress); + LOG.debug("[{}] Fetching info for new node {}", logPrefix, broadcastRpcAddress); DriverChannel channel = controlConnection.channel(); return query(channel, "SELECT * FROM " + retrievePeerTableName()) - .thenApply(result -> this.findInPeers(result, connectAddress)); + .thenApply(result -> this.findInPeers(result, broadcastRpcAddress)); } @Override @@ -159,7 +162,8 @@ public CompletionStage> refreshNodeList() { // This cast always succeeds in production. The only way it could fail is in a test that uses a // local channel, and we don't have such tests at the moment. - InetSocketAddress controlAddress = (InetSocketAddress) channel.connectAddress(); + InetSocketAddress controlBroadcastRpcAddress = + (InetSocketAddress) channel.getEndPoint().resolve(); savePort(channel); @@ -199,7 +203,8 @@ public CompletionStage> refreshNodeList() { // reports the normal RPC address instead of the broadcast one (CASSANDRA-11181). We // already know the address since we've just used it to query. nodeInfos.add( - nodeInfoBuilder(controlNodeResult.iterator().next(), controlAddress).build()); + nodeInfoBuilder(controlNodeResult.iterator().next(), controlBroadcastRpcAddress) + .build()); for (AdminRow row : peersResult) { nodeInfos.add(asNodeInfo(row)); } @@ -255,12 +260,7 @@ private String retrievePeerTableName() { } private NodeInfo asNodeInfo(AdminRow row) { - InetSocketAddress connectAddress = getNativeAddressFromPeers(row); - if (connectAddress == null) { - throw new IllegalArgumentException( - "Missing rpc_address or native in system row, can't refresh node"); - } - return nodeInfoBuilder(row, connectAddress).build(); + return nodeInfoBuilder(row, getBroadcastRpcAddress(row)).build(); } private Optional firstRowAsNodeInfo(AdminResult result) { @@ -272,10 +272,23 @@ private Optional firstRowAsNodeInfo(AdminResult result) { } } + /** + * @param broadcastRpcAddress this is a parameter only because we already have it when we come + * from {@link #findInPeers(AdminResult, InetSocketAddress)}. Callers that don't already have + * it can use {@link #getBroadcastRpcAddress}. + */ protected DefaultNodeInfo.Builder nodeInfoBuilder( - AdminRow row, InetSocketAddress connectAddress) { - DefaultNodeInfo.Builder builder = DefaultNodeInfo.builder().withConnectAddress(connectAddress); + AdminRow row, InetSocketAddress broadcastRpcAddress) { + + // Deployments that use a custom EndPoint implementation will need their own TopologyMonitor. + // One simple approach is to extend this class and override this method. + EndPoint endPoint = + new DefaultEndPoint(context.getAddressTranslator().translate(broadcastRpcAddress)); + DefaultNodeInfo.Builder builder = + DefaultNodeInfo.builder() + .withEndPoint(endPoint) + .withBroadcastRpcAddress(broadcastRpcAddress); InetAddress broadcastAddress = row.getInetAddress("broadcast_address"); // in system.local if (broadcastAddress == null) { broadcastAddress = row.getInetAddress("peer"); // in system.peers @@ -301,16 +314,28 @@ protected DefaultNodeInfo.Builder nodeInfoBuilder( return builder; } - private Optional findInPeers(AdminResult result, InetSocketAddress connectAddress) { - // The peers table is keyed by broadcast_address, but we only have the translated - // broadcast_rpc_address, so we have to traverse the whole table and check the rows one by one. + private Optional findInPeers( + AdminResult result, InetSocketAddress broadcastRpcAddressToFind) { + // The peers table is keyed by broadcast_address, but we only have the broadcast_rpc_address, so + // we have to traverse the whole table and check the rows one by one. + for (AdminRow row : result) { + InetSocketAddress broadcastRpcAddress = getBroadcastRpcAddress(row); + if (broadcastRpcAddress != null && broadcastRpcAddress.equals(broadcastRpcAddressToFind)) { + return Optional.of(nodeInfoBuilder(row, broadcastRpcAddress).build()); + } + } + LOG.debug("[{}] Could not find any peer row matching {}", logPrefix, broadcastRpcAddressToFind); + return Optional.empty(); + } + + private Optional findInPeers(AdminResult result, UUID hostIdToFind) { for (AdminRow row : result) { - InetSocketAddress nativeAddress = getNativeAddressFromPeers(row); - if (nativeAddress != null && nativeAddress.equals(connectAddress)) { - return Optional.of(nodeInfoBuilder(row, nativeAddress).build()); + UUID hostId = row.getUuid("host_id"); + if (hostId != null && hostId.equals(hostIdToFind)) { + return Optional.of(nodeInfoBuilder(row, getBroadcastRpcAddress(row)).build()); } } - LOG.debug("[{}] Could not find any peer row matching {}", logPrefix, connectAddress); + LOG.debug("[{}] Could not find any peer row matching {}", logPrefix, hostIdToFind); return Optional.empty(); } @@ -318,23 +343,30 @@ private Optional findInPeers(AdminResult result, InetSocketAddress con // nodes. As a consequence, the port is not stored in system tables. // We save it the first time we get a control connection channel. private void savePort(DriverChannel channel) { - if (port < 0 && channel.connectAddress() instanceof InetSocketAddress) { - port = ((InetSocketAddress) channel.connectAddress()).getPort(); + if (port < 0) { + SocketAddress address = channel.getEndPoint().resolve(); + if (address instanceof InetSocketAddress) { + port = ((InetSocketAddress) address).getPort(); + } } } - private InetSocketAddress getNativeAddressFromPeers(AdminRow row) { + private InetSocketAddress getBroadcastRpcAddress(AdminRow row) { InetAddress nativeAddress = row.getInetAddress("native_address"); if (nativeAddress == null) { - nativeAddress = row.getInetAddress("rpc_address"); - } - if (nativeAddress == null) { - return null; - } - Integer rowPort = row.getInteger("native_port"); - if (rowPort == null || rowPort == 0) { - rowPort = port; + // Cassandra < 4 + InetAddress rpcAddress = row.getInetAddress("rpc_address"); + if (rpcAddress == null) { + // This could only happen if system.peers is corrupted, but handle gracefully + return null; + } + return new InetSocketAddress(rpcAddress, port); + } else { + Integer rowPort = row.getInteger("native_port"); + if (rowPort == null || rowPort == 0) { + rowPort = port; + } + return new InetSocketAddress(nativeAddress, rowPort); } - return addressTranslator.translate(new InetSocketAddress(nativeAddress, rowPort)); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java index c4eda2afdbb..638d4f3db99 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DistanceEvent.java @@ -40,8 +40,7 @@ public boolean equals(Object other) { return true; } else if (other instanceof DistanceEvent) { DistanceEvent that = (DistanceEvent) other; - return this.distance == that.distance - && Objects.equals(this.node.getConnectAddress(), that.node.getConnectAddress()); + return this.distance == that.distance && Objects.equals(this.node, that.node); } else { return false; } @@ -49,11 +48,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(this.distance, this.node.getConnectAddress()); + return Objects.hash(this.distance, this.node); } @Override public String toString() { - return "DistanceEvent(" + distance + ", " + node.getConnectAddress() + ")"; + return "DistanceEvent(" + distance + ", " + node + ")"; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java index 61216af2a4a..7b6aeae48e2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefresh.java @@ -24,11 +24,11 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.Sets; -import java.net.InetSocketAddress; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.UUID; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,27 +51,23 @@ public Result compute( String logPrefix = context.getSessionName(); TokenFactoryRegistry tokenFactoryRegistry = context.getTokenFactoryRegistry(); - Map oldNodes = oldMetadata.getNodes(); + Map oldNodes = oldMetadata.getNodes(); - Map added = new HashMap<>(); - Set seen = new HashSet<>(); + Map added = new HashMap<>(); + Set seen = new HashSet<>(); TokenFactory tokenFactory = oldMetadata.getTokenMap().map(m -> ((DefaultTokenMap) m).getTokenFactory()).orElse(null); boolean tokensChanged = false; for (NodeInfo nodeInfo : nodeInfos) { - InetSocketAddress address = nodeInfo.getConnectAddress(); - if (address == null) { - LOG.warn("[{}] Got node info with no connect address, ignoring", logPrefix); - continue; - } - seen.add(address); - DefaultNode node = (DefaultNode) oldNodes.get(address); + UUID id = nodeInfo.getHostId(); + seen.add(id); + DefaultNode node = (DefaultNode) oldNodes.get(id); if (node == null) { - node = new DefaultNode(address, context); + node = new DefaultNode(nodeInfo.getEndPoint(), context); LOG.debug("[{}] Adding new node {}", logPrefix, node); - added.put(address, node); + added.put(id, node); } if (tokenFactory == null && nodeInfo.getPartitioner() != null) { tokenFactory = tokenFactoryRegistry.tokenFactoryFor(nodeInfo.getPartitioner()); @@ -79,25 +75,25 @@ public Result compute( tokensChanged |= copyInfos(nodeInfo, node, tokenFactory, logPrefix); } - Set removed = Sets.difference(oldNodes.keySet(), seen); + Set removed = Sets.difference(oldNodes.keySet(), seen); - if (added.isEmpty() && removed.isEmpty()) { - // Edge case: if all the nodes of the cluster were listed as contact points, and this is the - // first refresh, we get here so we need to set the token factory and trigger a token map - // rebuild: + if (added.isEmpty() && removed.isEmpty()) { // The list didn't change if (!oldMetadata.getTokenMap().isPresent() && tokenFactory != null) { + // First time we found out what the partitioner is => set the token factory and trigger a + // token map rebuild: return new Result( oldMetadata.withNodes( oldMetadata.getNodes(), tokenMapEnabled, true, tokenFactory, context)); } else { + // No need to create a new metadata instance return new Result(oldMetadata); } } else { - ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); ImmutableList.Builder eventsBuilder = ImmutableList.builder(); newNodesBuilder.putAll(added); - for (Map.Entry entry : oldNodes.entrySet()) { + for (Map.Entry entry : oldNodes.entrySet()) { if (!removed.contains(entry.getKey())) { newNodesBuilder.put(entry.getKey(), entry.getValue()); } @@ -106,8 +102,8 @@ public Result compute( for (Node node : added.values()) { eventsBuilder.add(NodeStateEvent.added((DefaultNode) node)); } - for (InetSocketAddress address : removed) { - Node node = oldNodes.get(address); + for (UUID id : removed) { + Node node = oldNodes.get(id); eventsBuilder.add(NodeStateEvent.removed((DefaultNode) node)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java deleted file mode 100644 index 02b744ac770..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefresh.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.metadata; - -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import java.net.InetSocketAddress; -import java.util.Set; -import net.jcip.annotations.ThreadSafe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** Creates minimal node info about the contact points, before the first connection. */ -@ThreadSafe -class InitContactPointsRefresh implements MetadataRefresh { - private static final Logger LOG = LoggerFactory.getLogger(InitContactPointsRefresh.class); - - @VisibleForTesting final Set contactPoints; - - InitContactPointsRefresh(Set contactPoints) { - this.contactPoints = contactPoints; - } - - @Override - public Result compute( - DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { - - String logPrefix = context.getSessionName(); - LOG.debug("[{}] Initializing node metadata with contact points {}", logPrefix, contactPoints); - - ImmutableMap.Builder newNodes = ImmutableMap.builder(); - for (InetSocketAddress address : contactPoints) { - newNodes.put(address, new DefaultNode(address, context)); - } - return new Result( - oldMetadata.withNodes( - newNodes.build(), - // At this stage there is no token map and we don't have the info to refresh it yet - false, - false, - null, - context)); - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitialNodeListRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitialNodeListRefresh.java new file mode 100644 index 00000000000..96ed3b0d19e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/InitialNodeListRefresh.java @@ -0,0 +1,109 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.token.DefaultTokenMap; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactory; +import com.datastax.oss.driver.internal.core.metadata.token.TokenFactoryRegistry; +import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import java.util.Set; +import java.util.UUID; +import net.jcip.annotations.ThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The first node list refresh: contact points are not in the metadata yet, we need to copy them + * over. + */ +@ThreadSafe +class InitialNodeListRefresh extends NodesRefresh { + + private static final Logger LOG = LoggerFactory.getLogger(InitialNodeListRefresh.class); + + @VisibleForTesting final Iterable nodeInfos; + @VisibleForTesting final Set contactPoints; + + InitialNodeListRefresh(Iterable nodeInfos, Set contactPoints) { + this.nodeInfos = nodeInfos; + this.contactPoints = contactPoints; + } + + @Override + public Result compute( + DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { + + String logPrefix = context.getSessionName(); + TokenFactoryRegistry tokenFactoryRegistry = context.getTokenFactoryRegistry(); + + assert oldMetadata.getNodes().isEmpty(); + + TokenFactory tokenFactory = + oldMetadata.getTokenMap().map(m -> ((DefaultTokenMap) m).getTokenFactory()).orElse(null); + boolean tokensChanged = false; + + ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + + for (NodeInfo nodeInfo : nodeInfos) { + EndPoint endPoint = nodeInfo.getEndPoint(); + DefaultNode node = findIn(contactPoints, endPoint); + if (node == null) { + node = new DefaultNode(endPoint, context); + LOG.debug("[{}] Adding new node {}", logPrefix, node); + } else { + LOG.debug("[{}] Copying contact point {}", logPrefix, node); + } + if (tokenFactory == null && nodeInfo.getPartitioner() != null) { + tokenFactory = tokenFactoryRegistry.tokenFactoryFor(nodeInfo.getPartitioner()); + } + tokensChanged |= copyInfos(nodeInfo, node, tokenFactory, logPrefix); + newNodesBuilder.put(node.getHostId(), node); + } + + ImmutableMap newNodes = newNodesBuilder.build(); + ImmutableList.Builder eventsBuilder = ImmutableList.builder(); + + for (DefaultNode newNode : newNodes.values()) { + if (!contactPoints.contains(newNode)) { + eventsBuilder.add(NodeStateEvent.added(newNode)); + } + } + for (DefaultNode contactPoint : contactPoints) { + if (findIn(newNodes.values(), contactPoint.getEndPoint()) == null) { + eventsBuilder.add(NodeStateEvent.removed(contactPoint)); + } + } + + return new Result( + oldMetadata.withNodes( + ImmutableMap.copyOf(newNodes), tokenMapEnabled, tokensChanged, tokenFactory, context), + eventsBuilder.build()); + } + + private DefaultNode findIn(Iterable nodes, EndPoint endPoint) { + for (Node node : nodes) { + if (node.getEndPoint().equals(endPoint)) { + return (DefaultNode) node; + } + } + return null; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index 351e83b2ff2..d1d932eb7c8 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -29,7 +29,6 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -37,6 +36,7 @@ import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; @@ -119,8 +119,7 @@ public void init() { MetadataManager metadataManager = context.getMetadataManager(); Metadata metadata = metadataManager.getMetadata(); for (LoadBalancingPolicy policy : policies) { - policy.init( - excludeDownHosts(metadata), reporters.get(policy), metadataManager.getContactPoints()); + policy.init(excludeDownHosts(metadata), reporters.get(policy)); } if (stateRef.compareAndSet(State.DURING_INIT, State.RUNNING)) { eventFilter.markReady(); @@ -145,10 +144,8 @@ public Queue newQueryPlan( switch (stateRef.get()) { case BEFORE_INIT: case DURING_INIT: - // Retrieve nodes from the metadata (at this stage it's the contact points). The only time - // when this can happen is during control connection initialization. - List nodes = new ArrayList<>(); - nodes.addAll(context.getMetadataManager().getMetadata().getNodes().values()); + // The contact points are not stored in the metadata yet: + List nodes = new ArrayList<>(context.getMetadataManager().getContactPoints()); Collections.shuffle(nodes); return new ConcurrentLinkedQueue<>(nodes); case RUNNING: @@ -198,11 +195,13 @@ private void processNodeStateEvent(NodeStateEvent event) { } } - private static Map excludeDownHosts(Metadata metadata) { - ImmutableMap.Builder nodes = ImmutableMap.builder(); - for (Node node : metadata.getNodes().values()) { + private static Map excludeDownHosts(Metadata metadata) { + ImmutableMap.Builder nodes = ImmutableMap.builder(); + for (Map.Entry entry : metadata.getNodes().entrySet()) { + UUID id = entry.getKey(); + Node node = entry.getValue(); if (node.getState() == NodeState.UP || node.getState() == NodeState.UNKNOWN) { - nodes.put(node.getConnectAddress(), node); + nodes.put(id, node); } } return nodes.build(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java index d7b711bc9e5..2c4dcfa7f3e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/MetadataManager.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.AsyncAutoCloseable; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent; @@ -51,8 +52,8 @@ public class MetadataManager implements AsyncAutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class); - public static final InetSocketAddress DEFAULT_CONTACT_POINT = - new InetSocketAddress("127.0.0.1", 9042); + static final EndPoint DEFAULT_CONTACT_POINT = + new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042)); private final InternalDriverContext context; private final String logPrefix; @@ -66,7 +67,8 @@ public class MetadataManager implements AsyncAutoCloseable { private volatile List refreshedKeyspaces; private volatile Boolean schemaEnabledProgrammatically; private volatile boolean tokenMapEnabled; - private volatile Set providedContactPoints; + private volatile Set contactPoints; + private volatile boolean wasImplicitContactPoint; public MetadataManager(InternalDriverContext context) { this(context, DefaultMetadata.EMPTY); @@ -112,24 +114,38 @@ public Metadata getMetadata() { return this.metadata; } - public CompletionStage addContactPoints(Set providedContactPoints) { - this.providedContactPoints = providedContactPoints; - Set contactPoints; + public void addContactPoints(Set providedContactPoints) { + // Convert the EndPoints to Nodes, but we can't put them into the Metadata yet, because we + // don't know their host_id. So store them in a volatile field instead, they will get copied + // during the first node refresh. + ImmutableSet.Builder contactPointsBuilder = ImmutableSet.builder(); if (providedContactPoints == null || providedContactPoints.isEmpty()) { LOG.info( "[{}] No contact points provided, defaulting to {}", logPrefix, DEFAULT_CONTACT_POINT); - contactPoints = ImmutableSet.of(DEFAULT_CONTACT_POINT); + this.wasImplicitContactPoint = true; + contactPointsBuilder.add(new DefaultNode(DEFAULT_CONTACT_POINT, context)); } else { - contactPoints = providedContactPoints; + for (EndPoint endPoint : providedContactPoints) { + contactPointsBuilder.add(new DefaultNode(endPoint, context)); + } } + this.contactPoints = contactPointsBuilder.build(); LOG.debug("[{}] Adding initial contact points {}", logPrefix, contactPoints); - CompletableFuture initNodesFuture = new CompletableFuture<>(); - RunOrSchedule.on(adminExecutor, () -> singleThreaded.initNodes(contactPoints, initNodesFuture)); - return initNodesFuture; } - public Set getContactPoints() { - return providedContactPoints; + /** + * The contact points that were used by the driver to initialize. If none were provided + * explicitly, this will be the default (127.0.0.1:9042). + * + * @see #wasImplicitContactPoint() + */ + public Set getContactPoints() { + return contactPoints; + } + + /** Whether the default contact point was used (because none were provided explicitly). */ + public boolean wasImplicitContactPoint() { + return wasImplicitContactPoint; } public CompletionStage refreshNodes() { @@ -162,10 +178,10 @@ public CompletionStage refreshNode(Node node) { adminExecutor); } - public void addNode(InetSocketAddress address) { + public void addNode(InetSocketAddress broadcastRpcAddress) { context .getTopologyMonitor() - .getNewNodeInfo(address) + .getNewNodeInfo(broadcastRpcAddress) .whenCompleteAsync( (info, error) -> { if (error != null) { @@ -173,17 +189,17 @@ public void addNode(InetSocketAddress address) { "[{}] Error refreshing node info for {}, " + "this will be retried on the next full refresh", logPrefix, - address, + broadcastRpcAddress, error); } else { - singleThreaded.addNode(address, info.orElse(null)); + singleThreaded.addNode(broadcastRpcAddress, info.orElse(null)); } }, adminExecutor); } - public void removeNode(InetSocketAddress address) { - RunOrSchedule.on(adminExecutor, () -> singleThreaded.removeNode(address)); + public void removeNode(InetSocketAddress broadcastRpcAddress) { + RunOrSchedule.on(adminExecutor, () -> singleThreaded.removeNode(broadcastRpcAddress)); } /** @@ -274,28 +290,26 @@ private SingleThreaded(InternalDriverContext context, DriverExecutionProfile con this.schemaParserFactory = context.getSchemaParserFactory(); } - private void initNodes( - Set addresses, CompletableFuture initNodesFuture) { - apply(new InitContactPointsRefresh(addresses)); - initNodesFuture.complete(null); - } - private Void refreshNodes(Iterable nodeInfos) { + MetadataRefresh refresh = + didFirstNodeListRefresh + ? new FullNodeListRefresh(nodeInfos) + : new InitialNodeListRefresh(nodeInfos, contactPoints); didFirstNodeListRefresh = true; - return apply(new FullNodeListRefresh(nodeInfos)); + return apply(refresh); } private void addNode(InetSocketAddress address, NodeInfo info) { try { if (info != null) { - if (!address.equals(info.getConnectAddress())) { + if (!address.equals(info.getBroadcastRpcAddress().orElse(null))) { // This would be a bug in the TopologyMonitor, protect against it LOG.warn( - "[{}] Received a request to add a node for {}, " - + "but the provided info uses the connect address {}, ignoring it", + "[{}] Received a request to add a node for broadcast RPC address {}, " + + "but the provided info reports {}, ignoring it", logPrefix, address, - info.getConnectAddress()); + info.getBroadcastAddress()); } else { apply(new AddNodeRefresh(info)); } @@ -311,8 +325,8 @@ private void addNode(InetSocketAddress address, NodeInfo info) { } } - private void removeNode(InetSocketAddress address) { - apply(new RemoveNodeRefresh(address)); + private void removeNode(InetSocketAddress broadcastRpcAddress) { + apply(new RemoveNodeRefresh(broadcastRpcAddress)); } private void refreshSchema( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java index 1a5ecf9853a..11aaf2c00ea 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeInfo.java @@ -15,9 +15,9 @@ */ package com.datastax.oss.driver.internal.core.metadata; -import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import java.net.InetSocketAddress; @@ -32,17 +32,21 @@ *

        This information will be copied to the corresponding {@link Node} in the metadata. */ public interface NodeInfo { + + /** The endpoint that the driver will use to connect to the node. */ + EndPoint getEndPoint(); + /** - * The address that the driver uses to connect to the node. This is the node's broadcast RPC - * address, transformed by the {@link AddressTranslator} if one is configured. + * The node's broadcast RPC address. * - *

        The driver uses this to uniquely identify a node. + *

        This is used to match status events coming in on the control connection. Note that it's not + * possible to fill it for the control node for some Cassandra versions, but that's less important + * because the control node doesn't receive events for itself. * - *

        This must not be null. If this instance is the reponse to a {@link - * TopologyMonitor#refreshNode(Node) refresh node} request, it must also match the address with - * which the request was made, otherwise the new node will be ignored. + * @see Node#getBroadcastRpcAddress() */ - InetSocketAddress getConnectAddress(); + Optional getBroadcastRpcAddress(); + /** * The node's broadcast address and port. That is, the address that other nodes use to communicate * with that node. @@ -51,6 +55,7 @@ public interface NodeInfo { * don't need this information, you can leave it empty. */ Optional getBroadcastAddress(); + /** * The node's listen address and port. That is, the address that the Cassandra process binds to. * @@ -111,8 +116,8 @@ public interface NodeInfo { Map getExtras(); /** - * The host ID that is assigned to this host by cassandra. This value can be used to uniquely - * identify a host even when the underling ip address changes. + * The host ID that is assigned to this host by cassandra. The driver uses this to uniquely + * identify a node. */ UUID getHostId(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java index cbb367afb22..8a5d9e54f48 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateEvent.java @@ -54,7 +54,7 @@ public boolean equals(Object other) { NodeStateEvent that = (NodeStateEvent) other; return this.oldState == that.oldState && this.newState == that.newState - && Objects.equals(this.node.getConnectAddress(), that.node.getConnectAddress()); + && Objects.equals(this.node, that.node); } else { return false; } @@ -62,11 +62,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(oldState, newState, node.getConnectAddress()); + return Objects.hash(oldState, newState, node); } @Override public String toString() { - return "NodeStateEvent(" + oldState + "=>" + newState + ", " + node.getConnectAddress() + ")"; + return "NodeStateEvent(" + oldState + "=>" + newState + ", " + node + ")"; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java index 528a40f3f84..b2f264f30f9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManager.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.internal.core.channel.ChannelEvent; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -33,6 +34,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import net.jcip.annotations.ThreadSafe; @@ -170,75 +172,87 @@ private void onDebouncedTopologyEvent(TopologyEvent event) { return; } LOG.debug("[{}] Processing {}", logPrefix, event); - DefaultNode node = (DefaultNode) metadataManager.getMetadata().getNodes().get(event.address); + Optional maybeNode = metadataManager.getMetadata().findNode(event.broadcastRpcAddress); switch (event.type) { case SUGGEST_UP: - if (node == null) { + if (maybeNode.isPresent()) { + DefaultNode node = (DefaultNode) maybeNode.get(); + if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("[{}] Not setting {} UP because it is FORCED_DOWN", logPrefix, node); + } else if (node.distance == NodeDistance.IGNORED) { + setState(node, NodeState.UP, "it is IGNORED and an UP topology event was received"); + } + } else { LOG.debug( - "[{}] Received UP event for unknown node {}, adding it", logPrefix, event.address); - metadataManager.addNode(event.address); - } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("[{}] Not setting {} UP because it is FORCED_DOWN", logPrefix, node); - } else if (node.distance == NodeDistance.IGNORED) { - setState(node, NodeState.UP, "it is IGNORED and an UP topology event was received"); + "[{}] Received UP event for unknown node {}, adding it", + logPrefix, + event.broadcastRpcAddress); + metadataManager.addNode(event.broadcastRpcAddress); } break; case SUGGEST_DOWN: - if (node == null) { + if (maybeNode.isPresent()) { + DefaultNode node = (DefaultNode) maybeNode.get(); + if (node.openConnections > 0) { + LOG.debug( + "[{}] Not setting {} DOWN because it still has active connections", + logPrefix, + node); + } else if (node.state == NodeState.FORCED_DOWN) { + LOG.debug("[{}] Not setting {} DOWN because it is FORCED_DOWN", logPrefix, node); + } else if (node.distance == NodeDistance.IGNORED) { + setState( + node, NodeState.DOWN, "it is IGNORED and a DOWN topology event was received"); + } + } else { LOG.debug( "[{}] Received DOWN event for unknown node {}, ignoring it", logPrefix, - event.address); - } else if (node.openConnections > 0) { - LOG.debug( - "[{}] Not setting {} DOWN because it still has active connections", - logPrefix, - node); - } else if (node.state == NodeState.FORCED_DOWN) { - LOG.debug("[{}] Not setting {} DOWN because it is FORCED_DOWN", logPrefix, node); - } else if (node.distance == NodeDistance.IGNORED) { - setState(node, NodeState.DOWN, "it is IGNORED and a DOWN topology event was received"); + event.broadcastRpcAddress); } break; case FORCE_UP: - if (node == null) { + if (maybeNode.isPresent()) { + DefaultNode node = (DefaultNode) maybeNode.get(); + setState(node, NodeState.UP, "a FORCE_UP topology event was received"); + } else { LOG.debug( "[{}] Received FORCE_UP event for unknown node {}, adding it", logPrefix, - event.address); - metadataManager.addNode(event.address); - } else { - setState(node, NodeState.UP, "a FORCE_UP topology event was received"); + event.broadcastRpcAddress); + metadataManager.addNode(event.broadcastRpcAddress); } break; case FORCE_DOWN: - if (node == null) { + if (maybeNode.isPresent()) { + DefaultNode node = (DefaultNode) maybeNode.get(); + setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); + } else { LOG.debug( "[{}] Received FORCE_DOWN event for unknown node {}, ignoring it", logPrefix, - event.address); - } else { - setState(node, NodeState.FORCED_DOWN, "a FORCE_DOWN topology event was received"); + event.broadcastRpcAddress); } break; case SUGGEST_ADDED: - if (node != null) { + if (maybeNode.isPresent()) { + DefaultNode node = (DefaultNode) maybeNode.get(); LOG.debug( "[{}] Received ADDED event for {} but it is already in our metadata, ignoring", logPrefix, node); } else { - metadataManager.addNode(event.address); + metadataManager.addNode(event.broadcastRpcAddress); } break; case SUGGEST_REMOVED: - if (node == null) { + if (maybeNode.isPresent()) { + metadataManager.removeNode(event.broadcastRpcAddress); + } else { LOG.debug( "[{}] Received REMOVED event for {} but it is not in our metadata, ignoring", logPrefix, - event.address); - } else { - metadataManager.removeNode(event.address); + event.broadcastRpcAddress); } break; } @@ -261,9 +275,9 @@ private Collection coalesceTopologyEvents(List eve Map last = Maps.newHashMapWithExpectedSize(events.size()); for (TopologyEvent event : events) { if (event.isForceEvent() - || !last.containsKey(event.address) - || !last.get(event.address).isForceEvent()) { - last.put(event.address, event); + || !last.containsKey(event.broadcastRpcAddress) + || !last.get(event.broadcastRpcAddress).isForceEvent()) { + last.put(event.broadcastRpcAddress, event); } } result = last.values(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java index 956d8fca38b..04817eaa2ae 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/NodesRefresh.java @@ -34,6 +34,7 @@ abstract class NodesRefresh implements MetadataRefresh { */ protected static boolean copyInfos( NodeInfo nodeInfo, DefaultNode node, TokenFactory tokenFactory, String logPrefix) { + node.broadcastRpcAddress = nodeInfo.getBroadcastRpcAddress().orElse(null); node.broadcastAddress = nodeInfo.getBroadcastAddress().orElse(null); node.listenAddress = nodeInfo.getListenAddress().orElse(null); node.datacenter = nodeInfo.getDatacenter(); @@ -48,7 +49,7 @@ protected static boolean copyInfos( "[{}] Error converting Cassandra version '{}' for {}", logPrefix, versionString, - node.getConnectAddress()); + node.getEndPoint()); } boolean tokensChanged = tokenFactory != null && !node.rawTokens.equals(nodeInfo.getTokens()); if (tokensChanged) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java index 58f0c2c5133..7ee741664ef 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefresh.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import java.net.InetSocketAddress; import java.util.Map; +import java.util.UUID; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,10 +32,10 @@ public class RemoveNodeRefresh extends NodesRefresh { private static final Logger LOG = LoggerFactory.getLogger(RemoveNodeRefresh.class); - @VisibleForTesting final InetSocketAddress toRemove; + @VisibleForTesting final InetSocketAddress broadcastRpcAddressToRemove; - RemoveNodeRefresh(InetSocketAddress toRemove) { - this.toRemove = toRemove; + RemoveNodeRefresh(InetSocketAddress broadcastRpcAddressToRemove) { + this.broadcastRpcAddressToRemove = broadcastRpcAddressToRemove; } @Override @@ -43,23 +44,30 @@ public Result compute( String logPrefix = context.getSessionName(); - Map oldNodes = oldMetadata.getNodes(); - Node node = oldNodes.get(toRemove); - if (node == null) { - // Normally this should already be checked before calling MetadataManager, but it doesn't - // hurt to fail gracefully just in case + Map oldNodes = oldMetadata.getNodes(); + + ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); + Node removedNode = null; + for (Node node : oldNodes.values()) { + if (node.getBroadcastRpcAddress().isPresent() + && node.getBroadcastRpcAddress().get().equals(broadcastRpcAddressToRemove)) { + removedNode = node; + } else { + assert node.getHostId() != null; // nodes in metadata.getNodes() always have their id set + newNodesBuilder.put(node.getHostId(), node); + } + } + + if (removedNode == null) { + // This should never happen because we already check the event in NodeStateManager, but handle + // just in case. + LOG.debug("[{}] Couldn't find node {} to remove", broadcastRpcAddressToRemove); return new Result(oldMetadata); } else { - LOG.debug("[{}] Removing node {}", logPrefix, node); - ImmutableMap.Builder newNodesBuilder = ImmutableMap.builder(); - for (Map.Entry entry : oldNodes.entrySet()) { - if (!entry.getKey().equals(toRemove)) { - newNodesBuilder.put(entry.getKey(), entry.getValue()); - } - } + LOG.debug("[{}] Removing node {}", logPrefix, removedNode); return new Result( oldMetadata.withNodes(newNodesBuilder.build(), tokenMapEnabled, false, null, context), - ImmutableList.of(NodeStateEvent.removed((DefaultNode) node))); + ImmutableList.of(NodeStateEvent.removed((DefaultNode) removedNode))); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java index 787a60c8504..5ddd2f0806e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementChecker.java @@ -28,7 +28,6 @@ import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.time.Duration; import java.util.Collections; @@ -111,7 +110,7 @@ private void sendQueries() { CompletionStage localQuery = query("SELECT schema_version FROM system.local WHERE key='local'"); CompletionStage peersQuery = - query("SELECT peer, rpc_address, schema_version FROM system.peers"); + query("SELECT host_id, schema_version FROM system.peers"); localQuery .thenCombine(peersQuery, this::extractSchemaVersions) @@ -120,52 +119,60 @@ private void sendQueries() { } private Set extractSchemaVersions(AdminResult controlNodeResult, AdminResult peersResult) { - ImmutableSet.Builder uuids = ImmutableSet.builder(); + // Gather the versions of all the nodes that are UP + ImmutableSet.Builder schemaVersions = ImmutableSet.builder(); - UUID uuid; + // Control node (implicitly UP, we've just queried it) Iterator iterator = controlNodeResult.iterator(); - if (iterator.hasNext() && (uuid = iterator.next().getUuid("schema_version")) != null) { - uuids.add(uuid); + if (iterator.hasNext()) { + AdminRow localRow = iterator.next(); + UUID schemaVersion = localRow.getUuid("schema_version"); + if (schemaVersion == null) { + LOG.warn( + "[{}] Missing schema_version for control node {}, " + + "excluding from schema agreement check", + logPrefix, + channel.getEndPoint()); + } else { + schemaVersions.add(schemaVersion); + } + } else { + LOG.warn( + "[{}] Missing system.local row for control node {}, " + + "excluding from schema agreement check", + logPrefix, + channel.getEndPoint()); } - Map nodes = context.getMetadataManager().getMetadata().getNodes(); + Map nodes = context.getMetadataManager().getMetadata().getNodes(); for (AdminRow peerRow : peersResult) { - InetSocketAddress connectAddress = getConnectAddress(peerRow); - Node node = nodes.get(connectAddress); - if (node == null || node.getState() != NodeState.UP) { + UUID hostId = peerRow.getUuid("host_id"); + if (hostId == null) { + LOG.warn( + "[{}] Missing host_id in system.peers row, excluding from schema agreement check", + logPrefix); continue; } - uuid = peerRow.getUuid("schema_version"); - if (uuid != null) { - uuids.add(uuid); + UUID schemaVersion = peerRow.getUuid("schema_version"); + if (schemaVersion == null) { + LOG.warn( + "[{}] Missing schema_version in system.peers row for {}, " + + "excluding from schema agreement check", + logPrefix, + hostId); + continue; } + Node node = nodes.get(hostId); + if (node == null) { + LOG.warn("[{}] Unknown peer {}, excluding from schema agreement check", logPrefix, hostId); + continue; + } else if (node.getState() != NodeState.UP) { + LOG.debug("[{}] Peer {} is down, excluding from schema agreement check", logPrefix, hostId); + continue; + } + schemaVersions.add(schemaVersion); } - return uuids.build(); - } - - private InetSocketAddress getConnectAddress(AdminRow peerRow) { - // This is actually broadcast_address - InetAddress broadcastAddress = peerRow.getInetAddress("peer"); - // The address we are looking for (this corresponds to broadcast_rpc_address in the peer's - // cassandra yaml file; if this setting if unset, it defaults to the value for rpc_address or - // rpc_interface - InetAddress rpcAddress = peerRow.getInetAddress("rpc_address"); - - if (rpcAddress == null) { - LOG.warn( - "[{}] Found corrupted row with null rpc_address in system.peers (peer = {}), " - + "excluding from schema agreement", - logPrefix, - broadcastAddress); - return null; - } else if (rpcAddress.equals(BIND_ALL_ADDRESS)) { - LOG.warn( - "[{}] Found peer with 0.0.0.0 as rpc_address in system.peers, using peer ({}) instead", - logPrefix, - broadcastAddress); - rpcAddress = broadcastAddress; - } - return context.getAddressTranslator().translate(new InetSocketAddress(rpcAddress, port)); + return schemaVersions.build(); } private void completeOrReschedule(Set uuids, Throwable error) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java index 5c90c7ec0a1..4f11fa4b182 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyEvent.java @@ -24,6 +24,10 @@ * An event emitted from the {@link TopologyMonitor}, indicating a change in the topology of the * Cassandra cluster. * + *

        Internally, the driver uses this to handle {@code TOPOLOGY_CHANGE} and {@code STATUS_CHANGE} + * events received on the control connection; for historical reasons, those protocol events identify + * nodes by their (untranslated) {@linkplain Node#getBroadcastRpcAddress() broadcast RPC address}. + * *

        As shown by the names, most of these events are mere suggestions, that the driver might choose * to ignore if they contradict other information it has about the nodes; see the documentation of * each factory method for detailed explanations. @@ -56,8 +60,8 @@ public enum Type { * * */ - public static TopologyEvent suggestUp(InetSocketAddress address) { - return new TopologyEvent(Type.SUGGEST_UP, address); + public static TopologyEvent suggestUp(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.SUGGEST_UP, broadcastRpcAddress); } /** @@ -73,8 +77,8 @@ public static TopologyEvent suggestUp(InetSocketAddress address) { * #forceDown(InetSocketAddress)}. * */ - public static TopologyEvent suggestDown(InetSocketAddress address) { - return new TopologyEvent(Type.SUGGEST_DOWN, address); + public static TopologyEvent suggestDown(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.SUGGEST_DOWN, broadcastRpcAddress); } /** @@ -95,8 +99,8 @@ public static TopologyEvent suggestDown(InetSocketAddress address) { * when it detects an unrecoverable error, such as a node that does not support the current * protocol version. */ - public static TopologyEvent forceDown(InetSocketAddress address) { - return new TopologyEvent(Type.FORCE_DOWN, address); + public static TopologyEvent forceDown(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.FORCE_DOWN, broadcastRpcAddress); } /** @@ -105,8 +109,8 @@ public static TopologyEvent forceDown(InetSocketAddress address) { *

        The node will be set back UP. If it is not ignored by the load balancing policy, a * connection pool will be reopened. */ - public static TopologyEvent forceUp(InetSocketAddress address) { - return new TopologyEvent(Type.FORCE_UP, address); + public static TopologyEvent forceUp(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.FORCE_UP, broadcastRpcAddress); } /** @@ -116,8 +120,8 @@ public static TopologyEvent forceUp(InetSocketAddress address) { * information about the node can't be refreshed (i.e. {@link * TopologyMonitor#getNewNodeInfo(InetSocketAddress)} fails). */ - public static TopologyEvent suggestAdded(InetSocketAddress address) { - return new TopologyEvent(Type.SUGGEST_ADDED, address); + public static TopologyEvent suggestAdded(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.SUGGEST_ADDED, broadcastRpcAddress); } /** @@ -125,17 +129,24 @@ public static TopologyEvent suggestAdded(InetSocketAddress address) { * *

        The driver ignore this event if the node does not exist in its metadata. */ - public static TopologyEvent suggestRemoved(InetSocketAddress address) { - return new TopologyEvent(Type.SUGGEST_REMOVED, address); + public static TopologyEvent suggestRemoved(InetSocketAddress broadcastRpcAddress) { + return new TopologyEvent(Type.SUGGEST_REMOVED, broadcastRpcAddress); } public final Type type; - public final InetSocketAddress address; + + /** + * Note that this is the untranslated broadcast RPC address, as it was received in the + * protocol event. + * + * @see Node#getBroadcastRpcAddress() + */ + public final InetSocketAddress broadcastRpcAddress; /** Builds a new instance (the static methods in this class are a preferred alternative). */ - public TopologyEvent(Type type, InetSocketAddress address) { + public TopologyEvent(Type type, InetSocketAddress broadcastRpcAddress) { this.type = type; - this.address = address; + this.broadcastRpcAddress = broadcastRpcAddress; } public boolean isForceEvent() { @@ -148,7 +159,8 @@ public boolean equals(Object other) { return true; } else if (other instanceof TopologyEvent) { TopologyEvent that = (TopologyEvent) other; - return this.type == that.type && Objects.equals(this.address, that.address); + return this.type == that.type + && Objects.equals(this.broadcastRpcAddress, that.broadcastRpcAddress); } else { return false; } @@ -156,11 +168,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(this.type, this.address); + return Objects.hash(this.type, this.broadcastRpcAddress); } @Override public String toString() { - return "TopologyEvent(" + type + ", " + address + ")"; + return "TopologyEvent(" + type + ", " + broadcastRpcAddress + ")"; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java index b9430e4cc35..d01ae3d954f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/TopologyMonitor.java @@ -16,7 +16,6 @@ package com.datastax.oss.driver.internal.core.metadata; import com.datastax.oss.driver.api.core.AsyncAutoCloseable; -import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.session.Session; @@ -86,13 +85,12 @@ public interface TopologyMonitor extends AsyncAutoCloseable { *

        This will be invoked directly from a driver's internal thread; if the refresh involves * blocking I/O or heavy computations, it should be scheduled on a separate thread. * - * @param connectAddress the address that the driver uses to connect to the node. This is the - * node's broadcast RPC address, transformed by the {@link AddressTranslator} if one is - * configured. + * @param broadcastRpcAddress the node's broadcast RPC address,. * @return a future that completes with the information. If the monitor doesn't know any node with * this address, it should reply with {@link Optional#empty()}; the new node will be ignored. + * @see Node#getBroadcastRpcAddress() */ - CompletionStage> getNewNodeInfo(InetSocketAddress connectAddress); + CompletionStage> getNewNodeInfo(InetSocketAddress broadcastRpcAddress); /** * Invoked when the driver needs to refresh information about all the nodes. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java index aeed089327d..aee7ccaa5cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/DefaultSchemaQueriesFactory.java @@ -45,14 +45,17 @@ public SchemaQueries newInstance(CompletableFuture refreshFuture) { if (channel == null || channel.closeFuture().isDone()) { throw new IllegalStateException("Control channel not available, aborting schema refresh"); } - @SuppressWarnings("SuspiciousMethodCalls") - Node node = context.getMetadataManager().getMetadata().getNodes().get(channel.connectAddress()); - if (node == null) { - throw new IllegalStateException( - "Could not find control node metadata " - + channel.connectAddress() - + ", aborting schema refresh"); - } + Node node = + context + .getMetadataManager() + .getMetadata() + .findNode(channel.getEndPoint()) + .orElseThrow( + () -> + new IllegalStateException( + "Could not find control node metadata " + + channel.getEndPoint() + + ", aborting schema refresh")); return newInstance(node, channel, refreshFuture); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java index 016259da3b4..a4322393e29 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/DropwizardNodeMetricUpdater.java @@ -19,15 +19,12 @@ import com.codahale.metrics.MetricRegistry; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import com.datastax.oss.driver.api.core.metrics.NodeMetric; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.pool.ChannelPool; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.util.Set; import java.util.function.Function; import net.jcip.annotations.ThreadSafe; @@ -44,7 +41,7 @@ public DropwizardNodeMetricUpdater( MetricRegistry registry, InternalDriverContext context) { super(enabledMetrics, registry); - this.metricNamePrefix = buildPrefix(context.getSessionName(), node.getConnectAddress()); + this.metricNamePrefix = buildPrefix(context.getSessionName(), node.getEndPoint()); DriverExecutionProfile config = context.getConfig().getDefaultProfile(); @@ -92,21 +89,8 @@ public String buildFullName(NodeMetric metric, String profileName) { return metricNamePrefix + metric.getPath(); } - private String buildPrefix(String sessionName, InetSocketAddress addressAndPort) { - StringBuilder prefix = new StringBuilder(sessionName).append(".nodes."); - InetAddress address = addressAndPort.getAddress(); - int port = addressAndPort.getPort(); - if (address instanceof Inet4Address) { - // Metrics use '.' as a delimiter, replace so that the IP is a single path component - // (127.0.0.1 => 127_0_0_1) - prefix.append(address.getHostAddress().replace('.', '_')); - } else { - assert address instanceof Inet6Address; - // IPv6 only uses '%' and ':' as separators, so no replacement needed - prefix.append(address.getHostAddress()); - } - // Append the port in anticipation of when C* will support nodes on different ports - return prefix.append('_').append(port).append('.').toString(); + private String buildPrefix(String sessionName, EndPoint endPoint) { + return sessionName + ".nodes." + endPoint.asMetricPrefix() + "."; } private void initializePoolGauge( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java index c4078a57398..24891972763 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/pool/ChannelPool.java @@ -109,7 +109,7 @@ private ChannelPool( this.initialKeyspaceName = keyspaceName; this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); this.sessionLogPrefix = sessionLogPrefix; - this.logPrefix = sessionLogPrefix + "|" + node.getConnectAddress(); + this.logPrefix = sessionLogPrefix + "|" + node.getEndPoint(); this.singleThreaded = new SingleThreaded(keyspaceName, distance, context); } @@ -380,7 +380,12 @@ private boolean onAllConnected(@SuppressWarnings("unused") Void v) { "[{}] Fatal error while initializing pool, forcing the node down", logPrefix, fatalError); - eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); + // Note: getBroadcastRpcAddress() can only be empty for the control node (and not for modern + // C* versions anyway). If we already have a control connection open to that node, it's + // impossible to get a protocol version or cluster name mismatch error while creating the + // pool, so it's safe to ignore this case. + node.getBroadcastRpcAddress() + .ifPresent(address -> eventBus.fire(TopologyEvent.forceDown(address))); // Don't bother continuing, the pool will get shut down soon anyway return true; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index f45f8a0a25c..1c71b59b385 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; @@ -44,7 +45,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.util.concurrent.EventExecutor; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -80,7 +80,7 @@ public class DefaultSession implements CqlSession { private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class); public static CompletionStage init( - InternalDriverContext context, Set contactPoints, CqlIdentifier keyspace) { + InternalDriverContext context, Set contactPoints, CqlIdentifier keyspace) { return new DefaultSession(context, contactPoints).init(keyspace); } @@ -93,7 +93,7 @@ public static CompletionStage init( private final PoolManager poolManager; private final SessionMetricUpdater metricUpdater; - private DefaultSession(InternalDriverContext context, Set contactPoints) { + private DefaultSession(InternalDriverContext context, Set contactPoints) { LOG.debug("Creating new session {}", context.getSessionName()); this.logPrefix = context.getSessionName(); this.adminExecutor = context.getNettyOptions().adminEventExecutorGroup().next(); @@ -260,7 +260,7 @@ public CompletionStage forceCloseAsync() { private class SingleThreaded { private final InternalDriverContext context; - private final Set initialContactPoints; + private final Set initialContactPoints; private final NodeStateManager nodeStateManager; private final CompletableFuture initFuture = new CompletableFuture<>(); private boolean initWasCalled; @@ -268,7 +268,7 @@ private class SingleThreaded { private boolean closeWasCalled; private boolean forceCloseWasCalled; - private SingleThreaded(InternalDriverContext context, Set contactPoints) { + private SingleThreaded(InternalDriverContext context, Set contactPoints) { this.context = context; this.nodeStateManager = new NodeStateManager(context); this.initialContactPoints = contactPoints; @@ -326,11 +326,10 @@ private void init(CqlIdentifier keyspace) { } MetadataManager metadataManager = context.getMetadataManager(); - metadataManager - // Store contact points in the metadata right away, the control connection will need them - // if it has to initialize (if the set is empty, 127.0.0.1 is used as a default). - .addContactPoints(initialContactPoints) - .thenCompose(v -> context.getTopologyMonitor().init()) + metadataManager.addContactPoints(initialContactPoints); + context + .getTopologyMonitor() + .init() .thenCompose(v -> metadataManager.refreshNodes()) .thenAccept(v -> afterInitialNodeListRefresh(keyspace)) .exceptionally( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java index 2595d3b1c0f..610669e965b 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/PoolManager.java @@ -336,15 +336,23 @@ private void processStateEvent(NodeStateEvent event) { private void onTopologyEvent(TopologyEvent event) { assert adminExecutor.inEventLoop(); if (event.type == TopologyEvent.Type.SUGGEST_UP) { - Node node = context.getMetadataManager().getMetadata().getNodes().get(event.address); - if (node.getDistance() != NodeDistance.IGNORED) { - LOG.debug( - "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", logPrefix, node); - ChannelPool pool = pools.get(node); - if (pool != null) { - pool.reconnectNow(); - } - } + context + .getMetadataManager() + .getMetadata() + .findNode(event.broadcastRpcAddress) + .ifPresent( + node -> { + if (node.getDistance() != NodeDistance.IGNORED) { + LOG.debug( + "[{}] Received a SUGGEST_UP event for {}, reconnecting pool now", + logPrefix, + node); + ChannelPool pool = pools.get(node); + if (pool != null) { + pool.reconnectNow(); + } + } + }); } } @@ -394,7 +402,7 @@ private void reprepareStatements(ChannelPool pool) { assert adminExecutor.inEventLoop(); if (config.getBoolean(DefaultDriverOption.REPREPARE_ENABLED)) { new ReprepareOnUp( - logPrefix + "|" + pool.getNode().getConnectAddress(), + logPrefix + "|" + pool.getNode().getEndPoint(), pool, repreparePayloads, context, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 3c5a7cb408d..e60088b7b25 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.InputStream; @@ -85,11 +86,12 @@ public DefaultSslEngineFactory(DriverContext driverContext) { @NonNull @Override - public SSLEngine newSslEngine(@NonNull SocketAddress remoteEndpoint) { + public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) { SSLEngine engine; - if (remoteEndpoint instanceof InetSocketAddress) { - InetSocketAddress address = (InetSocketAddress) remoteEndpoint; - engine = sslContext.createSSLEngine(address.getHostName(), address.getPort()); + SocketAddress remoteAddress = remoteEndpoint.resolve(); + if (remoteAddress instanceof InetSocketAddress) { + InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; + engine = sslContext.createSSLEngine(socketAddress.getHostName(), socketAddress.getPort()); } else { engine = sslContext.createSSLEngine(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java index 7d6b6af3e5e..73cb73660fb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/JdkSslHandlerFactory.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.internal.core.ssl; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; -import java.net.SocketAddress; import javax.net.ssl.SSLEngine; import net.jcip.annotations.ThreadSafe; @@ -32,7 +32,7 @@ public JdkSslHandlerFactory(SslEngineFactory sslEngineFactory) { } @Override - public SslHandler newSslHandler(Channel channel, SocketAddress remoteEndpoint) { + public SslHandler newSslHandler(Channel channel, EndPoint remoteEndpoint) { SSLEngine engine = sslEngineFactory.newSslEngine(remoteEndpoint); return new SslHandler(engine); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java index d51feaac546..3a96b067ada 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SslHandlerFactory.java @@ -15,10 +15,10 @@ */ package com.datastax.oss.driver.internal.core.ssl; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; -import java.net.SocketAddress; /** * Low-level SSL extension point. @@ -37,5 +37,5 @@ * @see DefaultDriverContext#buildSslHandlerFactory() */ public interface SslHandlerFactory extends AutoCloseable { - SslHandler newSslHandler(Channel channel, SocketAddress remoteEndpoint); + SslHandler newSslHandler(Channel channel, EndPoint remoteEndpoint); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java index 800ff532d4e..afcb507bfad 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.EventBus; @@ -45,10 +46,8 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.DefaultEventLoopGroup; -import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; -import java.net.SocketAddress; import java.time.Duration; import java.util.Collections; import java.util.Optional; @@ -78,8 +77,8 @@ */ @RunWith(DataProviderRunner.class) public abstract class ChannelFactoryTestBase { - static final LocalAddress SERVER_ADDRESS = - new LocalAddress(ChannelFactoryTestBase.class.getSimpleName() + "-server"); + static final EndPoint SERVER_ADDRESS = + new LocalEndPoint(ChannelFactoryTestBase.class.getSimpleName() + "-server"); private static final int TIMEOUT_MILLIS = 500; @@ -141,7 +140,7 @@ public void setup() throws InterruptedException { new ServerBootstrap() .group(serverGroup) .channel(LocalServerChannel.class) - .localAddress(SERVER_ADDRESS) + .localAddress(SERVER_ADDRESS.resolve()) .childHandler(new ServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind().sync(); serverAcceptChannel = (LocalServerChannel) channelFuture.sync().channel(); @@ -222,7 +221,7 @@ private TestChannelFactory(InternalDriverContext internalDriverContext) { @Override ChannelInitializer initializer( - SocketAddress address, + EndPoint endPoint, ProtocolVersion protocolVersion, DriverChannelOptions options, NodeMetricUpdater nodeMetricUpdater, @@ -253,7 +252,7 @@ protected void initChannel(Channel channel) throws Exception { HeartbeatHandler heartbeatHandler = new HeartbeatHandler(defaultProfile); ProtocolInitHandler initHandler = new ProtocolInitHandler( - context, protocolVersion, clusterName, options, heartbeatHandler); + context, protocolVersion, clusterName, endPoint, options, heartbeatHandler); channel.pipeline().addLast("inflight", inFlightHandler).addLast("init", initHandler); } catch (Throwable t) { resultFuture.completeExceptionally(t); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java index 1c4c30a6fe3..75ebcab9efa 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/DriverChannelTest.java @@ -62,7 +62,7 @@ public void setup() { writeCoalescer = new MockWriteCoalescer(); driverChannel = new DriverChannel( - channel.remoteAddress(), channel, writeCoalescer, DefaultProtocolVersion.V3); + new EmbeddedEndPoint(channel), channel, writeCoalescer, DefaultProtocolVersion.V3); } /** diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/EmbeddedEndPoint.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/EmbeddedEndPoint.java new file mode 100644 index 00000000000..53f7c95ad42 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/EmbeddedEndPoint.java @@ -0,0 +1,40 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import io.netty.channel.embedded.EmbeddedChannel; +import java.net.SocketAddress; + +/** Endpoint implementation for unit tests that use an embedded Netty channel. */ +public class EmbeddedEndPoint implements EndPoint { + + private final SocketAddress address; + + public EmbeddedEndPoint(EmbeddedChannel channel) { + this.address = channel.remoteAddress(); + } + + @Override + public SocketAddress resolve() { + throw new UnsupportedOperationException("This should not get called from unit tests"); + } + + @Override + public String asMetricPrefix() { + throw new UnsupportedOperationException("This should not get called from unit tests"); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/LocalEndPoint.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/LocalEndPoint.java new file mode 100644 index 00000000000..c98b5979662 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/LocalEndPoint.java @@ -0,0 +1,40 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.channel; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import io.netty.channel.local.LocalAddress; +import java.net.SocketAddress; + +/** Endpoint implementation for unit tests that use the local Netty transport. */ +public class LocalEndPoint implements EndPoint { + + private final LocalAddress localAddress; + + public LocalEndPoint(String id) { + this.localAddress = new LocalAddress(id); + } + + @Override + public SocketAddress resolve() { + return localAddress; + } + + @Override + public String asMetricPrefix() { + throw new UnsupportedOperationException("This should not get called from unit tests"); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index fc2dfc9f8e8..5b134f9bc26 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -28,10 +28,12 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.CassandraProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.ProtocolVersionRegistry; import com.datastax.oss.driver.internal.core.TestResponses; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.TestNodeFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -60,6 +62,9 @@ public class ProtocolInitHandlerTest extends ChannelHandlerTestBase { private static final long QUERY_TIMEOUT_MILLIS = 100L; + // The handled only uses this to call the auth provider and for exception messages, so the actual + // value doesn't matter: + private static final EndPoint END_POINT = TestNodeFactory.newEndPoint(1); @Mock private InternalDriverContext internalDriverContext; @Mock private DriverConfig driverConfig; @@ -108,6 +113,7 @@ public void should_initialize() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -137,6 +143,7 @@ public void should_add_heartbeat_handler_to_pipeline_on_success() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler); @@ -179,6 +186,7 @@ public void should_fail_to_initialize_if_init_query_times_out() throws Interrupt internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -203,14 +211,14 @@ public void should_initialize_with_authentication() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); - when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) - .thenReturn(authenticator); + when(authProvider.newAuthenticator(END_POINT, serverAuthenticator)).thenReturn(authenticator); when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -223,7 +231,7 @@ public void should_initialize_with_authentication() { writeInboundFrame(requestFrame, new Authenticate(serverAuthenticator)); // The connection should have created an authenticator from the auth provider - verify(authProvider).newAuthenticator(channel.remoteAddress(), serverAuthenticator); + verify(authProvider).newAuthenticator(END_POINT, serverAuthenticator); // And sent an auth response requestFrame = readOutboundFrame(); @@ -267,6 +275,7 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -280,7 +289,7 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { // Simulate a READY response, the provider should be notified writeInboundFrame(buildInboundFrame(requestFrame, new Ready())); - verify(authProvider).onMissingChallenge(channel.remoteAddress()); + verify(authProvider).onMissingChallenge(END_POINT); // Since our mock does nothing, init should proceed normally requestFrame = readOutboundFrame(); @@ -299,14 +308,14 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); String serverAuthenticator = "mockServerAuthenticator"; AuthProvider authProvider = mock(AuthProvider.class); MockAuthenticator authenticator = new MockAuthenticator(); - when(authProvider.newAuthenticator(channel.remoteAddress(), serverAuthenticator)) - .thenReturn(authenticator); + when(authProvider.newAuthenticator(END_POINT, serverAuthenticator)).thenReturn(authenticator); when(internalDriverContext.getAuthProvider()).thenReturn(Optional.of(authProvider)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -330,7 +339,9 @@ public void should_fail_to_initialize_if_server_sends_auth_error() throws Throwa assertThat(e) .isInstanceOf(AuthenticationException.class) .hasMessage( - "Authentication error on host embedded: server replied 'mock error'")); + String.format( + "Authentication error on node %s: server replied 'mock error'", + END_POINT))); } @Test @@ -343,6 +354,7 @@ public void should_check_cluster_name_if_provided() { internalDriverContext, DefaultProtocolVersion.V4, "expectedClusterName", + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -372,6 +384,7 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th internalDriverContext, DefaultProtocolVersion.V4, "expectedClusterName", + END_POINT, DriverChannelOptions.DEFAULT, heartbeatHandler)); @@ -387,7 +400,9 @@ public void should_fail_to_initialize_if_cluster_name_does_not_match() throws Th assertThat(e) .isInstanceOf(ClusterNameMismatchException.class) .hasMessageContaining( - "Node embedded reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.")); + String.format( + "Node %s reports cluster name 'differentClusterName' that doesn't match our cluster name 'expectedClusterName'.", + END_POINT))); } @Test @@ -399,7 +414,12 @@ public void should_initialize_with_keyspace() { .addLast( "init", new ProtocolInitHandler( - internalDriverContext, DefaultProtocolVersion.V4, null, options, heartbeatHandler)); + internalDriverContext, + DefaultProtocolVersion.V4, + null, + END_POINT, + options, + heartbeatHandler)); ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); @@ -428,6 +448,7 @@ public void should_initialize_with_events() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, driverChannelOptions, heartbeatHandler)); @@ -461,6 +482,7 @@ public void should_initialize_with_keyspace_and_events() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, driverChannelOptions, heartbeatHandler)); @@ -494,6 +516,7 @@ public void should_fail_to_initialize_if_keyspace_is_invalid() { internalDriverContext, DefaultProtocolVersion.V4, null, + END_POINT, driverChannelOptions, heartbeatHandler)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java index 16edf993c46..7aaebe73b68 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionEventsTest.java @@ -96,7 +96,6 @@ public void should_process_status_change_events() { callback.onEvent(event); // Then - verify(addressTranslator).translate(ADDRESS1); verify(eventBus).fire(TopologyEvent.suggestUp(ADDRESS1)); } @@ -118,7 +117,6 @@ public void should_process_topology_change_events() { callback.onEvent(event); // Then - verify(addressTranslator).translate(ADDRESS1); verify(eventBus).fire(TopologyEvent.suggestAdded(ADDRESS1)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java index 82c1956d6d1..a25b7c97f52 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/control/ControlConnectionTestBase.java @@ -21,22 +21,22 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.internal.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.internal.core.channel.ChannelFactory; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.DriverChannelOptions; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; +import com.datastax.oss.driver.internal.core.metadata.TestNodeFactory; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; import io.netty.channel.Channel; @@ -75,7 +75,6 @@ abstract class ControlConnectionTestBase { @Mock protected MetadataManager metadataManager; @Mock protected MetricsFactory metricsFactory; - protected AddressTranslator addressTranslator; protected DefaultNode node1; protected DefaultNode node2; @@ -116,15 +115,13 @@ public void setup() { when(context.getLoadBalancingPolicyWrapper()).thenReturn(loadBalancingPolicyWrapper); when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(ADDRESS1, context); - node2 = new DefaultNode(ADDRESS2, context); + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); mockQueryPlan(node1, node2); when(metadataManager.refreshNodes()).thenReturn(CompletableFuture.completedFuture(null)); when(context.getMetadataManager()).thenReturn(metadataManager); - addressTranslator = spy(new PassThroughAddressTranslator(context)); - when(context.getAddressTranslator()).thenReturn(addressTranslator); when(context.getConfig()).thenReturn(config); when(config.getDefaultProfile()).thenReturn(defaultProfile); when(defaultProfile.getBoolean(DefaultDriverOption.CONNECTION_WARN_INIT_ERROR)) @@ -169,7 +166,8 @@ protected DriverChannel newMockDriverChannel(int id) { }); when(driverChannel.closeFuture()).thenReturn(closeFuture); when(driverChannel.toString()).thenReturn("channel" + id); - when(driverChannel.connectAddress()).thenReturn(new InetSocketAddress("127.0.0." + id, 9042)); + when(driverChannel.getEndPoint()) + .thenReturn(new DefaultEndPoint(new InetSocketAddress("127.0.0." + id, 9042))); return driverChannel; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java index 39883e1f4e6..a1bec905103 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyEventsTest.java @@ -28,6 +28,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import java.util.UUID; import java.util.function.Predicate; import org.junit.Before; import org.junit.Test; @@ -50,11 +51,11 @@ public void setup() { when(filter.test(any(Node.class))).thenReturn(true); when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn(filter); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1)); + policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); policy.init( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), - distanceReporter, - ImmutableSet.of(ADDRESS1)); + ImmutableMap.of(UUID.randomUUID(), node1, UUID.randomUUID(), node2), distanceReporter); assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2); reset(distanceReporter); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index 63f69632701..ae51c38cb0d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -27,10 +27,9 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; -import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; -import java.util.Collections; +import java.util.UUID; import org.junit.Test; public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPolicyTestBase { @@ -38,8 +37,7 @@ public class DefaultLoadBalancingPolicyInitTest extends DefaultLoadBalancingPoli @Test public void should_use_local_dc_if_provided_via_config() { // Given - when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) - .thenReturn("dc1"); + // the parent class sets the config option to "dc1" // When DefaultLoadBalancingPolicy policy = @@ -53,6 +51,8 @@ public void should_use_local_dc_if_provided_via_config() { public void should_use_local_dc_if_provided_via_context() { // Given when(context.getLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME)).thenReturn("dc1"); + // note: programmatic takes priority, the config won't even be inspected so no need to stub the + // option to null // When DefaultLoadBalancingPolicy policy = @@ -69,14 +69,13 @@ public void should_infer_local_dc_if_no_explicit_contact_points() { // Given when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn(null); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1)); + when(metadataManager.wasImplicitContactPoint()).thenReturn(true); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When - policy.init( - ImmutableMap.of(MetadataManager.DEFAULT_CONTACT_POINT, node1), - distanceReporter, - Collections.emptySet()); + policy.init(ImmutableMap.of(UUID.randomUUID(), node1), distanceReporter); // Then assertThat(policy.localDc).isEqualTo("dc1"); @@ -87,6 +86,8 @@ public void should_require_local_dc_if_explicit_contact_points() { // Given when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn(null); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node2)); + when(metadataManager.wasImplicitContactPoint()).thenReturn(false); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -94,7 +95,7 @@ public void should_require_local_dc_if_explicit_contact_points() { thrown.expectMessage("You provided explicit contact points, the local DC must be specified"); // When - policy.init(ImmutableMap.of(ADDRESS2, node2), distanceReporter, ImmutableSet.of(ADDRESS2)); + policy.init(ImmutableMap.of(UUID.randomUUID(), node2), distanceReporter); } @Test @@ -102,14 +103,15 @@ public void should_warn_if_contact_points_not_in_local_dc() { // Given when(node2.getDatacenter()).thenReturn("dc2"); when(node3.getDatacenter()).thenReturn("dc3"); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1, node2, node3)); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), - distanceReporter, - ImmutableSet.of(ADDRESS1, ADDRESS2, ADDRESS3)); + ImmutableMap.of( + UUID.randomUUID(), node1, UUID.randomUUID(), node2, UUID.randomUUID(), node3), + distanceReporter); // Then verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); @@ -119,21 +121,22 @@ public void should_warn_if_contact_points_not_in_local_dc() { assertThat(warnLogs.iterator().next().getFormattedMessage()) .contains( "You specified dc1 as the local DC, but some contact points are from a different DC") - .contains("/127.0.0.2:9042=dc2") - .contains("/127.0.0.3:9042=dc3"); + .contains("node2=dc2") + .contains("node3=dc3"); } @Test public void should_include_nodes_from_local_dc() { // Given + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1, node2)); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), - distanceReporter, - ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + ImmutableMap.of( + UUID.randomUUID(), node1, UUID.randomUUID(), node2, UUID.randomUUID(), node3), + distanceReporter); // Then verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); @@ -147,14 +150,15 @@ public void should_ignore_nodes_from_remote_dcs() { // Given when(node2.getDatacenter()).thenReturn("dc2"); when(node3.getDatacenter()).thenReturn("dc3"); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1, node2)); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); // When policy.init( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), - distanceReporter, - ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + ImmutableMap.of( + UUID.randomUUID(), node1, UUID.randomUUID(), node2, UUID.randomUUID(), node3), + distanceReporter); // Then verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); @@ -166,6 +170,7 @@ public void should_ignore_nodes_from_remote_dcs() { @Test public void should_ignore_nodes_excluded_by_filter() { // Given + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1, node2)); when(context.getNodeFilter(DriverExecutionProfile.DEFAULT_NAME)) .thenReturn(node -> node.equals(node1)); @@ -174,9 +179,9 @@ public void should_ignore_nodes_excluded_by_filter() { // When policy.init( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2, ADDRESS3, node3), - distanceReporter, - ImmutableSet.of(ADDRESS1, ADDRESS2)); // make node3 not a contact point to cover all cases + ImmutableMap.of( + UUID.randomUUID(), node1, UUID.randomUUID(), node2, UUID.randomUUID(), node3), + distanceReporter); // Then verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java index 337f8ab4897..60d67923935 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyQueryPlanTest.java @@ -32,7 +32,6 @@ import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.session.Request; -import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; @@ -40,6 +39,7 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.Optional; +import java.util.UUID; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -51,7 +51,6 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin @Mock private Request request; @Mock private DefaultSession session; - @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; @Mock private TokenMap tokenMap; @@ -62,7 +61,8 @@ public class DefaultLoadBalancingPolicyQueryPlanTest extends DefaultLoadBalancin public void setup() { super.setup(); - when(context.getMetadataManager()).thenReturn(metadataManager); + when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1)); + when(metadataManager.getMetadata()).thenReturn(metadata); when(metadata.getTokenMap()).thenAnswer(invocation -> Optional.of(this.tokenMap)); @@ -71,13 +71,12 @@ public void setup() { policy = spy(new NonShufflingPolicy(context, DriverExecutionProfile.DEFAULT_NAME)); policy.init( ImmutableMap.of( - ADDRESS1, node1, - ADDRESS2, node2, - ADDRESS3, node3, - ADDRESS4, node4, - ADDRESS5, node5), - distanceReporter, - ImmutableSet.of(ADDRESS1)); + UUID.randomUUID(), node1, + UUID.randomUUID(), node2, + UUID.randomUUID(), node3, + UUID.randomUUID(), node4, + UUID.randomUUID(), node5), + distanceReporter); // Note: this test relies on the fact that the policy uses a CopyOnWriteArraySet which preserves // insertion order. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java index b5dad76f154..e4f648eb3af 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyTestBase.java @@ -27,8 +27,9 @@ import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import java.net.InetSocketAddress; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -43,24 +44,19 @@ @RunWith(MockitoJUnitRunner.class) public abstract class DefaultLoadBalancingPolicyTestBase { - protected static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - protected static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - protected static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); - protected static final InetSocketAddress ADDRESS4 = new InetSocketAddress("127.0.0.4", 9042); - protected static final InetSocketAddress ADDRESS5 = new InetSocketAddress("127.0.0.5", 9042); - @Rule public ExpectedException thrown = ExpectedException.none(); - @Mock protected Node node1; - @Mock protected Node node2; - @Mock protected Node node3; - @Mock protected Node node4; - @Mock protected Node node5; + @Mock protected DefaultNode node1; + @Mock protected DefaultNode node2; + @Mock protected DefaultNode node3; + @Mock protected DefaultNode node4; + @Mock protected DefaultNode node5; @Mock protected InternalDriverContext context; @Mock protected DriverConfig config; @Mock protected DriverExecutionProfile defaultProfile; @Mock protected LoadBalancingPolicy.DistanceReporter distanceReporter; @Mock protected Appender appender; + @Mock protected MetadataManager metadataManager; @Captor protected ArgumentCaptor loggingEventCaptor; @@ -75,6 +71,8 @@ public void setup() { when(defaultProfile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, null)) .thenReturn("dc1"); + when(context.getMetadataManager()).thenReturn(metadataManager); + logger = (Logger) LoggerFactory.getLogger(DefaultLoadBalancingPolicy.class); logger.addAppender(appender); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java index 9ac8eba62fe..52d509ada88 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/AddNodeRefreshTest.java @@ -46,23 +46,25 @@ public class AddNodeRefreshTest { @Before public void setup() { when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(ADDRESS1, context); + node1 = TestNodeFactory.newNode(1, context); } @Test public void should_add_new_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); - UUID hostId = Uuids.random(); - UUID schemaVersion = Uuids.random(); + new DefaultMetadata( + ImmutableMap.of(node1.getHostId(), node1), Collections.emptyMap(), null); + UUID newHostId = Uuids.random(); + DefaultEndPoint newEndPoint = TestNodeFactory.newEndPoint(2); + UUID newSchemaVersion = Uuids.random(); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() - .withConnectAddress(ADDRESS2) + .withHostId(newHostId) + .withEndPoint(newEndPoint) .withDatacenter("dc1") .withRack("rack2") - .withHostId(hostId) - .withSchemaVersion(schemaVersion) + .withSchemaVersion(newSchemaVersion) .build(); AddNodeRefresh refresh = new AddNodeRefresh(newNodeInfo); @@ -70,13 +72,14 @@ public void should_add_new_node() { MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - Map newNodes = result.newMetadata.getNodes(); - assertThat(newNodes).containsOnlyKeys(ADDRESS1, ADDRESS2); - Node node2 = newNodes.get(ADDRESS2); + Map newNodes = result.newMetadata.getNodes(); + assertThat(newNodes).containsOnlyKeys(node1.getHostId(), newHostId); + Node node2 = newNodes.get(newHostId); + assertThat(node2.getEndPoint()).isEqualTo(newEndPoint); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); - assertThat(node2.getHostId()).isEqualTo(hostId); - assertThat(node2.getSchemaVersion()).isEqualTo(schemaVersion); + assertThat(node2.getHostId()).isEqualTo(newHostId); + assertThat(node2.getSchemaVersion()).isEqualTo(newSchemaVersion); assertThat(result.events).containsExactly(NodeStateEvent.added((DefaultNode) node2)); } @@ -84,10 +87,12 @@ public void should_add_new_node() { public void should_not_add_existing_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(node1.getHostId(), node1), Collections.emptyMap(), null); DefaultNodeInfo newNodeInfo = DefaultNodeInfo.builder() - .withConnectAddress(ADDRESS1) + .withHostId(node1.getHostId()) + .withEndPoint(node1.getEndPoint()) .withDatacenter("dc1") .withRack("rack2") .build(); @@ -97,7 +102,7 @@ public void should_not_add_existing_node() { MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(node1.getHostId()); // Info is not copied over: assertThat(node1.getDatacenter()).isNull(); assertThat(node1.getRack()).isNull(); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java index cbbbde46c29..79e56e1d832 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadataTokenMapTest.java @@ -27,9 +27,9 @@ import com.datastax.oss.driver.internal.core.metadata.token.Murmur3TokenFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; -import java.net.InetSocketAddress; import java.util.Collections; import java.util.Map; +import java.util.UUID; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,8 +41,6 @@ public class DefaultMetadataTokenMapTest { // Simulate the simplest setup possible for a functional token map. We're not testing the token // map itself, only how the metadata interacts with it. - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); private static final String TOKEN1 = "-9000000000000000000"; private static final String TOKEN2 = "9000000000000000000"; private static final Node NODE1 = mockNode(TOKEN1); @@ -66,60 +64,86 @@ public void setup() { @Test public void should_not_build_token_map_when_initializing_with_contact_points() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); assertThat(contactPointsMetadata.getTokenMap()).isNotPresent(); } @Test public void should_build_minimal_token_map_on_first_refresh() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); + ImmutableMap.of(NODE1.getHostId(), NODE1), + true, + true, + new Murmur3TokenFactory(), + context); assertThat(firstRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(1); } @Test public void should_not_build_token_map_when_disabled() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), false, true, new Murmur3TokenFactory(), context); + ImmutableMap.of(NODE1.getHostId(), NODE1), + false, + true, + new Murmur3TokenFactory(), + context); assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); } @Test public void should_stay_empty_on_first_refresh_if_partitioner_missing() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, null, context); + ImmutableMap.of(NODE1.getHostId(), NODE1), true, true, null, context); assertThat(firstRefreshMetadata.getTokenMap()).isNotPresent(); } @Test public void should_update_minimal_token_map_if_new_node_and_still_no_schema() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); + ImmutableMap.of(NODE1.getHostId(), NODE1), + true, + true, + new Murmur3TokenFactory(), + context); DefaultMetadata secondRefreshMetadata = firstRefreshMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1, ADDRESS2, NODE2), true, false, null, context); + ImmutableMap.of(NODE1.getHostId(), NODE1, NODE2.getHostId(), NODE2), + true, + false, + null, + context); assertThat(secondRefreshMetadata.getTokenMap().get().getTokenRanges()).hasSize(2); } @Test public void should_update_token_map_when_schema_changes() { DefaultMetadata contactPointsMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, NODE1), Collections.emptyMap(), null); + new DefaultMetadata( + ImmutableMap.of(NODE1.getHostId(), NODE1), Collections.emptyMap(), null); DefaultMetadata firstRefreshMetadata = contactPointsMetadata.withNodes( - ImmutableMap.of(ADDRESS1, NODE1), true, true, new Murmur3TokenFactory(), context); + ImmutableMap.of(NODE1.getHostId(), NODE1), + true, + true, + new Murmur3TokenFactory(), + context); DefaultMetadata schemaRefreshMetadata = firstRefreshMetadata.withSchema(ImmutableMap.of(KEYSPACE_NAME, KEYSPACE), true, context); assertThat(schemaRefreshMetadata.getTokenMap().get().getTokenRanges(KEYSPACE_NAME, NODE1)) @@ -128,6 +152,7 @@ public void should_update_token_map_when_schema_changes() { private static DefaultNode mockNode(String token) { DefaultNode node = mock(DefaultNode.class); + when(node.getHostId()).thenReturn(UUID.randomUUID()); when(node.getRawTokens()).thenReturn(ImmutableSet.of(token)); return node; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java index 28191e3f241..d2336ab428c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitorTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -50,6 +51,7 @@ import java.util.Map; import java.util.Optional; import java.util.Queue; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.junit.Before; @@ -78,6 +80,10 @@ public class DefaultTopologyMonitorTest { @Before public void setup() { MockitoAnnotations.initMocks(this); + when(context.getMetricsFactory()).thenReturn(metricsFactory); + + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); @@ -87,15 +93,10 @@ public void setup() { addressTranslator = spy(new PassThroughAddressTranslator(context)); when(context.getAddressTranslator()).thenReturn(addressTranslator); - when(channel.connectAddress()).thenReturn(ADDRESS1); + when(channel.getEndPoint()).thenReturn(node1.getEndPoint()); when(controlConnection.channel()).thenReturn(channel); when(context.getControlConnection()).thenReturn(controlConnection); - when(context.getMetricsFactory()).thenReturn(metricsFactory); - - node1 = new DefaultNode(ADDRESS1, context); - node2 = new DefaultNode(ADDRESS2, context); - topologyMonitor = new TestTopologyMonitor(context); } @@ -126,7 +127,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present() { new StubbedQuery( "SELECT * FROM system.peers WHERE peer = :address", ImmutableMap.of("address", ADDRESS2.getAddress()), - mockResult(mockPeersRow(2)))); + mockResult(mockPeersRow(2, node2.getHostId())))); // When CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); @@ -150,7 +151,7 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_present_v2() new StubbedQuery( "SELECT * FROM system.peers_v2 WHERE peer = :address and peer_port = :port", ImmutableMap.of("address", ADDRESS2.getAddress(), "peer", 9042), - mockResult(mockPeersV2Row(2)))); + mockResult(mockPeersV2Row(2, node2.getHostId())))); // When CompletionStage> futureInfo = topologyMonitor.refreshNode(node2); @@ -171,8 +172,8 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() // Given topologyMonitor.isSchemaV2 = false; node2.broadcastAddress = null; - AdminRow peer3 = mockPeersRow(3); - AdminRow peer2 = mockPeersRow(2); + AdminRow peer3 = mockPeersRow(3, UUID.randomUUID()); + AdminRow peer2 = mockPeersRow(2, node2.getHostId()); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2))); @@ -189,12 +190,10 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present() }); // The rpc_address in each row should have been tried, only the last row should have been // converted - verify(peer3).getInetAddress("rpc_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3).getUuid("host_id"); verify(peer3, never()).getString(anyString()); - verify(peer2).getInetAddress("rpc_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2, times(2)).getUuid("host_id"); verify(peer2).getString("data_center"); } @@ -203,8 +202,8 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present_V // Given topologyMonitor.isSchemaV2 = true; node2.broadcastAddress = null; - AdminRow peer3 = mockPeersV2Row(3); - AdminRow peer2 = mockPeersV2Row(2); + AdminRow peer3 = mockPeersV2Row(3, UUID.randomUUID()); + AdminRow peer2 = mockPeersV2Row(2, node2.getHostId()); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers_v2", mockResult(peer3, peer2))); @@ -219,23 +218,21 @@ public void should_refresh_node_from_peers_if_broadcast_address_is_not_present_V NodeInfo info = maybeInfo.get(); assertThat(info.getDatacenter()).isEqualTo("dc2"); }); - // The rpc_address in each row should have been tried, only the last row should have been + // The host_id in each row should have been tried, only the last row should have been // converted - verify(peer3).getInetAddress("native_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); + verify(peer3).getUuid("host_id"); verify(peer3, never()).getString(anyString()); - verify(peer2).getInetAddress("native_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); + verify(peer2, times(2)).getUuid("host_id"); verify(peer2).getString("data_center"); } @Test public void should_get_new_node_from_peers() { // Given - AdminRow peer3 = mockPeersRow(3); - AdminRow peer2 = mockPeersRow(2); - AdminRow peer1 = mockPeersRow(1); + AdminRow peer3 = mockPeersRow(3, UUID.randomUUID()); + AdminRow peer2 = mockPeersRow(2, node2.getHostId()); + AdminRow peer1 = mockPeersRow(1, node1.getHostId()); topologyMonitor.isSchemaV2 = false; topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers", mockResult(peer3, peer2, peer1))); @@ -254,24 +251,21 @@ public void should_get_new_node_from_peers() { // The rpc_address in each row should have been tried, only the last row should have been // converted verify(peer3).getInetAddress("rpc_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); verify(peer3, never()).getString(anyString()); verify(peer2).getInetAddress("rpc_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); verify(peer2, never()).getString(anyString()); verify(peer1).getInetAddress("rpc_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); verify(peer1).getString("data_center"); } @Test public void should_get_new_node_from_peers_v2() { // Given - AdminRow peer3 = mockPeersV2Row(3); - AdminRow peer2 = mockPeersV2Row(2); - AdminRow peer1 = mockPeersV2Row(1); + AdminRow peer3 = mockPeersV2Row(3, UUID.randomUUID()); + AdminRow peer2 = mockPeersV2Row(2, node2.getHostId()); + AdminRow peer1 = mockPeersV2Row(1, node1.getHostId()); topologyMonitor.isSchemaV2 = true; topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.peers_v2", mockResult(peer3, peer2, peer1))); @@ -290,23 +284,20 @@ public void should_get_new_node_from_peers_v2() { // The natove in each row should have been tried, only the last row should have been // converted verify(peer3).getInetAddress("native_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.3", 9042)); verify(peer3, never()).getString(anyString()); verify(peer2).getInetAddress("native_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.2", 9042)); verify(peer2, never()).getString(anyString()); verify(peer1).getInetAddress("native_address"); - verify(addressTranslator).translate(new InetSocketAddress("127.0.0.1", 9042)); verify(peer1).getString("data_center"); } @Test public void should_refresh_node_list_from_local_and_peers() { // Given - AdminRow peer3 = mockPeersRow(3); - AdminRow peer2 = mockPeersRow(2); + AdminRow peer3 = mockPeersRow(3, UUID.randomUUID()); + AdminRow peer2 = mockPeersRow(2, node2.getHostId()); topologyMonitor.stubQueries( new StubbedQuery("SELECT * FROM system.local", mockResult(mockLocalRow(1))), new StubbedQuery( @@ -325,15 +316,14 @@ public void should_refresh_node_list_from_local_and_peers() { infos -> { Iterator iterator = infos.iterator(); NodeInfo info1 = iterator.next(); - assertThat(info1.getConnectAddress()).isEqualTo(ADDRESS1); + assertThat(info1.getEndPoint()).isEqualTo(node1.getEndPoint()); assertThat(info1.getDatacenter()).isEqualTo("dc1"); NodeInfo info3 = iterator.next(); - assertThat(info3.getConnectAddress()) + assertThat(info3.getEndPoint().resolve()) .isEqualTo(new InetSocketAddress("127.0.0.3", 9042)); assertThat(info3.getDatacenter()).isEqualTo("dc3"); NodeInfo info2 = iterator.next(); - assertThat(info2.getConnectAddress()) - .isEqualTo(new InetSocketAddress("127.0.0.2", 9042)); + assertThat(info2.getEndPoint()).isEqualTo(node2.getEndPoint()); assertThat(info2.getDatacenter()).isEqualTo("dc2"); }); } @@ -428,9 +418,10 @@ private AdminRow mockLocalRow(int i) { } } - private AdminRow mockPeersRow(int i) { + private AdminRow mockPeersRow(int i, UUID hostId) { try { AdminRow row = mock(AdminRow.class); + when(row.getUuid("host_id")).thenReturn(hostId); when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); when(row.getString("data_center")).thenReturn("dc" + i); when(row.getString("rack")).thenReturn("rack" + i); @@ -444,9 +435,10 @@ private AdminRow mockPeersRow(int i) { } } - private AdminRow mockPeersV2Row(int i) { + private AdminRow mockPeersV2Row(int i, UUID hostId) { try { AdminRow row = mock(AdminRow.class); + when(row.getUuid("host_id")).thenReturn(hostId); when(row.getInetAddress("peer")).thenReturn(InetAddress.getByName("127.0.0." + i)); when(row.getInteger("peer_port")).thenReturn(7000 + i); when(row.getString("data_center")).thenReturn("dc" + i); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java index 59a7b445b35..1d7b0b0d02f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/FullNodeListRefreshTest.java @@ -23,7 +23,6 @@ import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import java.net.InetSocketAddress; import java.util.Collections; import java.util.UUID; import org.junit.Before; @@ -35,10 +34,6 @@ @RunWith(MockitoJUnitRunner.class) public class FullNodeListRefreshTest { - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - private static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 9042); - @Mock private InternalDriverContext context; @Mock protected MetricsFactory metricsFactory; @@ -50,9 +45,9 @@ public class FullNodeListRefreshTest { public void setup() { when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(ADDRESS1, context); - node2 = new DefaultNode(ADDRESS2, context); - node3 = new DefaultNode(ADDRESS3, context); + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); + node3 = TestNodeFactory.newNode(3, context); } @Test @@ -60,18 +55,27 @@ public void should_add_and_remove_nodes() { // Given DefaultMetadata oldMetadata = new DefaultMetadata( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); + ImmutableMap.of(node1.getHostId(), node1, node2.getHostId(), node2), + Collections.emptyMap(), + null); Iterable newInfos = ImmutableList.of( - DefaultNodeInfo.builder().withConnectAddress(ADDRESS2).build(), - DefaultNodeInfo.builder().withConnectAddress(ADDRESS3).build()); + DefaultNodeInfo.builder() + .withEndPoint(node2.getEndPoint()) + .withHostId(node2.getHostId()) + .build(), + DefaultNodeInfo.builder() + .withEndPoint(node3.getEndPoint()) + .withHostId(node3.getHostId()) + .build()); FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos); // When MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS2, ADDRESS3); + assertThat(result.newMetadata.getNodes()) + .containsOnlyKeys(node2.getHostId(), node3.getHostId()); assertThat(result.events) .containsOnly(NodeStateEvent.removed(node1), NodeStateEvent.added(node3)); } @@ -81,26 +85,26 @@ public void should_update_existing_nodes() { // Given DefaultMetadata oldMetadata = new DefaultMetadata( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); + ImmutableMap.of(node1.getHostId(), node1, node2.getHostId(), node2), + Collections.emptyMap(), + null); - UUID hostId1 = Uuids.random(); - UUID hostId2 = Uuids.random(); UUID schemaVersion1 = Uuids.random(); UUID schemaVersion2 = Uuids.random(); Iterable newInfos = ImmutableList.of( DefaultNodeInfo.builder() - .withConnectAddress(ADDRESS1) + .withEndPoint(node1.getEndPoint()) .withDatacenter("dc1") .withRack("rack1") - .withHostId(hostId1) + .withHostId(node1.getHostId()) .withSchemaVersion(schemaVersion1) .build(), DefaultNodeInfo.builder() - .withConnectAddress(ADDRESS2) + .withEndPoint(node2.getEndPoint()) .withDatacenter("dc1") .withRack("rack2") - .withHostId(hostId2) + .withHostId(node2.getHostId()) .withSchemaVersion(schemaVersion2) .build()); FullNodeListRefresh refresh = new FullNodeListRefresh(newInfos); @@ -109,14 +113,13 @@ public void should_update_existing_nodes() { MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); + assertThat(result.newMetadata.getNodes()) + .containsOnlyKeys(node1.getHostId(), node2.getHostId()); assertThat(node1.getDatacenter()).isEqualTo("dc1"); assertThat(node1.getRack()).isEqualTo("rack1"); - assertThat(node1.getHostId()).isEqualTo(hostId1); assertThat(node1.getSchemaVersion()).isEqualTo(schemaVersion1); assertThat(node2.getDatacenter()).isEqualTo("dc1"); assertThat(node2.getRack()).isEqualTo("rack2"); - assertThat(node2.getHostId()).isEqualTo(hostId2); assertThat(node2.getSchemaVersion()).isEqualTo(schemaVersion2); assertThat(result.events).isEmpty(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java deleted file mode 100644 index ba0485b0047..00000000000 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/InitContactPointsRefreshTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.datastax.oss.driver.internal.core.metadata; - -import static com.datastax.oss.driver.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; -import java.net.InetSocketAddress; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class InitContactPointsRefreshTest { - - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - - @Mock private InternalDriverContext context; - @Mock private MetricsFactory metricsFactory; - - @Before - public void setup() { - when(context.getMetricsFactory()).thenReturn(metricsFactory); - } - - @Test - public void should_create_nodes() { - // Given - InitContactPointsRefresh refresh = - new InitContactPointsRefresh(ImmutableSet.of(ADDRESS1, ADDRESS2)); - - // When - MetadataRefresh.Result result = refresh.compute(DefaultMetadata.EMPTY, false, context); - - // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1, ADDRESS2); - assertThat(result.events).isEmpty(); - } -} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index 1b0b0db3df6..b7ce9dfea77 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -18,7 +18,6 @@ import static com.datastax.oss.driver.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; @@ -39,10 +38,12 @@ import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import com.datastax.oss.driver.shaded.guava.common.collect.Lists; -import java.net.InetSocketAddress; import java.util.Map; import java.util.Queue; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -62,7 +63,8 @@ public class LoadBalancingPolicyWrapperTest { private DefaultNode node2; private DefaultNode node3; - private Map contactPointsMap; + private Map allNodes; + private Set contactPoints; private Queue defaultPolicysQueryPlan; @Mock private InternalDriverContext context; @@ -73,7 +75,7 @@ public class LoadBalancingPolicyWrapperTest { @Mock private MetadataManager metadataManager; @Mock private Metadata metadata; @Mock protected MetricsFactory metricsFactory; - @Captor private ArgumentCaptor> initNodesCaptor; + @Captor private ArgumentCaptor> initNodesCaptor; private LoadBalancingPolicyWrapper wrapper; @@ -81,18 +83,17 @@ public class LoadBalancingPolicyWrapperTest { public void setup() { when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); - node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); - node3 = new DefaultNode(new InetSocketAddress("127.0.0.3", 9042), context); + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); + node3 = TestNodeFactory.newNode(3, context); - contactPointsMap = - ImmutableMap.builder() - .put(node1.getConnectAddress(), node1) - .put(node2.getConnectAddress(), node2) - .build(); - when(metadata.getNodes()).thenReturn(contactPointsMap); + contactPoints = ImmutableSet.of(node1, node2); + allNodes = + ImmutableMap.of( + node1.getHostId(), node1, node2.getHostId(), node2, node3.getHostId(), node3); when(metadataManager.getMetadata()).thenReturn(metadata); - when(metadataManager.getContactPoints()).thenReturn(contactPointsMap.keySet()); + when(metadata.getNodes()).thenReturn(allNodes); + when(metadataManager.getContactPoints()).thenReturn(contactPoints); when(context.getMetadataManager()).thenReturn(metadataManager); defaultPolicysQueryPlan = Lists.newLinkedList(ImmutableList.of(node3, node2, node1)); @@ -124,7 +125,7 @@ public void should_build_query_plan_from_contact_points_before_init() { for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { verify(policy, never()).newQueryPlan(null, null); } - assertThat(queryPlan).containsOnlyElementsOf(contactPointsMap.values()); + assertThat(queryPlan).containsOnlyElementsOf(contactPoints); } @Test @@ -132,7 +133,7 @@ public void should_fetch_query_plan_from_policy_after_init() { // Given wrapper.init(); for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - verify(policy).init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); + verify(policy).init(anyMap(), any(DistanceReporter.class)); } // When @@ -150,25 +151,14 @@ public void should_init_policies_with_up_or_unknown_nodes() { node1.state = NodeState.UP; node2.state = NodeState.UNKNOWN; node3.state = NodeState.DOWN; - Map contactPointsMap2 = - ImmutableMap.builder() - .put(node1.getConnectAddress(), node1) - .put(node2.getConnectAddress(), node2) - .put(node3.getConnectAddress(), node3) - .build(); - when(metadata.getNodes()).thenReturn(contactPointsMap2); // When wrapper.init(); // Then for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - verify(policy) - .init( - initNodesCaptor.capture(), - any(DistanceReporter.class), - eq(contactPointsMap.keySet())); - Map initNodes = initNodesCaptor.getValue(); + verify(policy).init(initNodesCaptor.capture(), any(DistanceReporter.class)); + Map initNodes = initNodesCaptor.getValue(); assertThat(initNodes.values()).containsOnly(node1, node2); } } @@ -178,13 +168,13 @@ public void should_propagate_distances_from_policies() { // Given wrapper.init(); ArgumentCaptor captor1 = ArgumentCaptor.forClass(DistanceReporter.class); - verify(policy1).init(anyMap(), captor1.capture(), eq(contactPointsMap.keySet())); + verify(policy1).init(anyMap(), captor1.capture()); DistanceReporter distanceReporter1 = captor1.getValue(); ArgumentCaptor captor2 = ArgumentCaptor.forClass(DistanceReporter.class); - verify(policy2).init(anyMap(), captor2.capture(), eq(contactPointsMap.keySet())); + verify(policy2).init(anyMap(), captor2.capture()); DistanceReporter distanceReporter2 = captor1.getValue(); ArgumentCaptor captor3 = ArgumentCaptor.forClass(DistanceReporter.class); - verify(policy3).init(anyMap(), captor3.capture(), eq(contactPointsMap.keySet())); + verify(policy3).init(anyMap(), captor3.capture()); DistanceReporter distanceReporter3 = captor3.getValue(); InOrder inOrder = inOrder(eventBus); @@ -257,9 +247,7 @@ public void should_accumulate_events_during_init_and_replay() throws Interrupted return null; }; for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { - doAnswer(mockInit) - .when(policy) - .init(anyMap(), any(DistanceReporter.class), eq(contactPointsMap.keySet())); + doAnswer(mockInit).when(policy).init(anyMap(), any(DistanceReporter.class)); } // When diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 0dcae978494..12dcdb033e2 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; @@ -57,8 +58,9 @@ public class MetadataManagerTest { - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); + // Don't use 1 because that's the default when no contact points are provided + private static final EndPoint END_POINT2 = TestNodeFactory.newEndPoint(2); + private static final EndPoint END_POINT3 = TestNodeFactory.newEndPoint(3); @Mock private InternalDriverContext context; @Mock private NettyOptions nettyOptions; @@ -107,36 +109,62 @@ public void teardown() { @Test public void should_add_contact_points() { // When - CompletionStage addContactPointsFuture = - metadataManager.addContactPoints(ImmutableSet.of(ADDRESS1)); - waitForPendingAdminTasks(); + metadataManager.addContactPoints(ImmutableSet.of(END_POINT2)); // Then - assertThatStage(addContactPointsFuture).isSuccess(); - assertThat(metadataManager.refreshes).hasSize(1); - InitContactPointsRefresh refresh = - ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); - assertThat(refresh.contactPoints).containsExactlyInAnyOrder(ADDRESS1); + assertThat(metadataManager.getContactPoints()) + .extracting(Node::getEndPoint) + .containsOnly(END_POINT2); + assertThat(metadataManager.wasImplicitContactPoint()).isFalse(); } @Test public void should_use_default_if_no_contact_points_provided() { // When - CompletionStage addContactPointsFuture = - metadataManager.addContactPoints(Collections.emptySet()); + metadataManager.addContactPoints(Collections.emptySet()); + + // Then + assertThat(metadataManager.getContactPoints()) + .extracting(Node::getEndPoint) + .containsOnly(MetadataManager.DEFAULT_CONTACT_POINT); + assertThat(metadataManager.wasImplicitContactPoint()).isTrue(); + } + + @Test + public void should_copy_contact_points_on_refresh_of_all_nodes() { + // Given + // Run previous scenario to trigger the addition of the default contact point: + should_use_default_if_no_contact_points_provided(); + + NodeInfo info1 = mock(NodeInfo.class); + NodeInfo info2 = mock(NodeInfo.class); + List infos = ImmutableList.of(info1, info2); + when(topologyMonitor.refreshNodeList()).thenReturn(CompletableFuture.completedFuture(infos)); + + // When + CompletionStage refreshNodesFuture = metadataManager.refreshNodes(); waitForPendingAdminTasks(); // Then - assertThatStage(addContactPointsFuture).isSuccess(); + assertThatStage(refreshNodesFuture).isSuccess(); assertThat(metadataManager.refreshes).hasSize(1); - InitContactPointsRefresh refresh = - ((InitContactPointsRefresh) metadataManager.refreshes.get(0)); - assertThat(refresh.contactPoints).containsExactly(MetadataManager.DEFAULT_CONTACT_POINT); + InitialNodeListRefresh refresh = (InitialNodeListRefresh) metadataManager.refreshes.get(0); + assertThat(refresh.contactPoints) + .extracting(Node::getEndPoint) + .containsOnly(MetadataManager.DEFAULT_CONTACT_POINT); + assertThat(refresh.nodeInfos).containsExactlyInAnyOrder(info1, info2); } @Test public void should_refresh_all_nodes() { // Given + // Run previous scenario to trigger the addition of the default contact point and a first + // refresh: + should_copy_contact_points_on_refresh_of_all_nodes(); + // Discard that first refresh, we don't really care about it in the context of this test, only + // that the next one won't be the first + metadataManager.refreshes.clear(); + NodeInfo info1 = mock(NodeInfo.class); NodeInfo info2 = mock(NodeInfo.class); List infos = ImmutableList.of(info1, info2); @@ -156,7 +184,7 @@ public void should_refresh_all_nodes() { @Test public void should_refresh_single_node() { // Given - Node node = new DefaultNode(ADDRESS1, context); + Node node = TestNodeFactory.newNode(2, context); NodeInfo info = mock(NodeInfo.class); when(info.getDatacenter()).thenReturn("dc1"); when(topologyMonitor.refreshNode(node)) @@ -189,13 +217,14 @@ public void should_ignore_node_refresh_if_topology_monitor_does_not_have_info() @Test public void should_add_node() { // Given + InetSocketAddress broadcastRpcAddress = ((InetSocketAddress) END_POINT2.resolve()); NodeInfo info = mock(NodeInfo.class); - when(info.getConnectAddress()).thenReturn(ADDRESS1); - when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + when(info.getBroadcastRpcAddress()).thenReturn(Optional.of(broadcastRpcAddress)); + when(topologyMonitor.getNewNodeInfo(broadcastRpcAddress)) .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); // When - metadataManager.addNode(ADDRESS1); + metadataManager.addNode(broadcastRpcAddress); waitForPendingAdminTasks(); // Then @@ -205,18 +234,20 @@ public void should_add_node() { } @Test - public void should_not_add_node_if_connect_address_does_not_match() { + public void should_not_add_node_if_broadcast_rpc_address_does_not_match() { // Given + InetSocketAddress broadcastRpcAddress2 = ((InetSocketAddress) END_POINT2.resolve()); + InetSocketAddress broadcastRpcAddress3 = ((InetSocketAddress) END_POINT3.resolve()); NodeInfo info = mock(NodeInfo.class); - when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + when(topologyMonitor.getNewNodeInfo(broadcastRpcAddress2)) .thenReturn(CompletableFuture.completedFuture(Optional.of(info))); - when(info.getConnectAddress()) + when(info.getBroadcastRpcAddress()) .thenReturn( - ADDRESS2 // Does not match the address we got the info with + Optional.of(broadcastRpcAddress3) // Does not match the address we got the info with ); // When - metadataManager.addNode(ADDRESS1); + metadataManager.addNode(broadcastRpcAddress2); waitForPendingAdminTasks(); // Then @@ -226,11 +257,12 @@ public void should_not_add_node_if_connect_address_does_not_match() { @Test public void should_not_add_node_if_topology_monitor_does_not_have_info() { // Given - when(topologyMonitor.getNewNodeInfo(ADDRESS1)) + InetSocketAddress broadcastRpcAddress2 = ((InetSocketAddress) END_POINT2.resolve()); + when(topologyMonitor.getNewNodeInfo(broadcastRpcAddress2)) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); // When - metadataManager.addNode(ADDRESS1); + metadataManager.addNode(broadcastRpcAddress2); waitForPendingAdminTasks(); // Then @@ -239,14 +271,17 @@ public void should_not_add_node_if_topology_monitor_does_not_have_info() { @Test public void should_remove_node() { + // Given + InetSocketAddress broadcastRpcAddress2 = ((InetSocketAddress) END_POINT2.resolve()); + // When - metadataManager.removeNode(ADDRESS1); + metadataManager.removeNode(broadcastRpcAddress2); waitForPendingAdminTasks(); // Then assertThat(metadataManager.refreshes).hasSize(1); RemoveNodeRefresh refresh = (RemoveNodeRefresh) metadataManager.refreshes.get(0); - assertThat(refresh.toRemove).isEqualTo(ADDRESS1); + assertThat(refresh.broadcastRpcAddressToRemove).isEqualTo(broadcastRpcAddress2); } private static class TestMetadataManager extends MetadataManager { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java index 8e52ad9c588..347185bce80 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/NodeStateManagerTest.java @@ -43,6 +43,8 @@ import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import java.time.Duration; +import java.util.Collections; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -61,7 +63,6 @@ public class NodeStateManagerTest { @Mock private DriverExecutionProfile defaultProfile; @Mock private NettyOptions nettyOptions; @Mock private MetadataManager metadataManager; - @Mock private Metadata metadata; @Mock protected MetricsFactory metricsFactory; private DefaultNode node1, node2; private EventBus eventBus; @@ -86,14 +87,14 @@ public void setup() { when(context.getNettyOptions()).thenReturn(nettyOptions); when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(new InetSocketAddress("127.0.0.1", 9042), context); - node2 = new DefaultNode(new InetSocketAddress("127.0.0.2", 9042), context); - ImmutableMap nodes = - ImmutableMap.builder() - .put(node1.getConnectAddress(), node1) - .put(node2.getConnectAddress(), node2) + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); + ImmutableMap nodes = + ImmutableMap.builder() + .put(node1.getHostId(), node1) + .put(node2.getHostId(), node2) .build(); - when(metadata.getNodes()).thenReturn(nodes); + Metadata metadata = new DefaultMetadata(nodes, Collections.emptyMap(), null); when(metadataManager.getMetadata()).thenReturn(metadata); when(metadataManager.refreshNode(any(Node.class))) .thenReturn(CompletableFuture.completedFuture(null)); @@ -114,7 +115,7 @@ public void should_ignore_up_event_if_node_is_already_up_or_forced_down() { node1.state = oldState; // When - eventBus.fire(TopologyEvent.suggestUp(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestUp(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -133,7 +134,7 @@ public void should_apply_up_event_if_node_is_unknown_or_down() { node1.state = oldState; // When - eventBus.fire(TopologyEvent.suggestUp(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestUp(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -168,7 +169,7 @@ public void should_ignore_down_event_if_node_is_down_or_forced_down() { node1.state = oldState; // When - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -186,7 +187,7 @@ public void should_ignore_down_event_if_node_has_active_connections() { assertThat(node1.openConnections).isEqualTo(1); // When - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -204,7 +205,7 @@ public void should_apply_down_event_if_node_has_no_active_connections() { assertThat(node1.openConnections).isEqualTo(0); // When - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -234,7 +235,7 @@ public void should_ignore_force_down_event_if_already_forced_down() { node1.state = NodeState.FORCED_DOWN; // When - eventBus.fire(TopologyEvent.forceDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.forceDown(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -251,7 +252,7 @@ public void should_apply_force_down_event_over_any_other_state() { node1.state = oldState; // When - eventBus.fire(TopologyEvent.forceDown(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.forceDown(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -281,7 +282,7 @@ public void should_ignore_force_up_event_if_node_is_already_up() { node1.state = NodeState.UP; // When - eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.forceUp(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -300,7 +301,7 @@ public void should_apply_force_up_event_if_node_is_not_up() { node1.state = oldState; // When - eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.forceUp(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -346,7 +347,7 @@ public void should_ignore_addition_of_existing_node() { new NodeStateManager(context); // When - eventBus.fire(TopologyEvent.suggestAdded(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestAdded(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then @@ -359,11 +360,11 @@ public void should_notify_metadata_of_node_removal() { new NodeStateManager(context); // When - eventBus.fire(TopologyEvent.suggestRemoved(node1.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestRemoved(node1.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then - verify(metadataManager).removeNode(node1.getConnectAddress()); + verify(metadataManager).removeNode(node1.getBroadcastRpcAddress().get()); } @Test @@ -391,11 +392,11 @@ public void should_coalesce_topology_events() { node2.state = NodeState.DOWN; // When - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); - eventBus.fire(TopologyEvent.forceUp(node1.getConnectAddress())); - eventBus.fire(TopologyEvent.suggestDown(node2.getConnectAddress())); - eventBus.fire(TopologyEvent.suggestDown(node1.getConnectAddress())); - eventBus.fire(TopologyEvent.suggestUp(node2.getConnectAddress())); + eventBus.fire(TopologyEvent.suggestDown(node1.getBroadcastRpcAddress().get())); + eventBus.fire(TopologyEvent.forceUp(node1.getBroadcastRpcAddress().get())); + eventBus.fire(TopologyEvent.suggestDown(node2.getBroadcastRpcAddress().get())); + eventBus.fire(TopologyEvent.suggestDown(node1.getBroadcastRpcAddress().get())); + eventBus.fire(TopologyEvent.suggestUp(node2.getBroadcastRpcAddress().get())); waitForPendingAdminTasks(); // Then diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java index 8d1bec7d6f9..29053f2b08e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/RemoveNodeRefreshTest.java @@ -21,7 +21,6 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; -import java.net.InetSocketAddress; import java.util.Collections; import org.junit.Before; import org.junit.Test; @@ -32,9 +31,6 @@ @RunWith(MockitoJUnitRunner.class) public class RemoveNodeRefreshTest { - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); - @Mock private InternalDriverContext context; @Mock protected MetricsFactory metricsFactory; @@ -44,8 +40,8 @@ public class RemoveNodeRefreshTest { @Before public void setup() { when(context.getMetricsFactory()).thenReturn(metricsFactory); - node1 = new DefaultNode(ADDRESS1, context); - node2 = new DefaultNode(ADDRESS2, context); + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); } @Test @@ -53,14 +49,16 @@ public void should_remove_existing_node() { // Given DefaultMetadata oldMetadata = new DefaultMetadata( - ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2), Collections.emptyMap(), null); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); + ImmutableMap.of(node1.getHostId(), node1, node2.getHostId(), node2), + Collections.emptyMap(), + null); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(node2.getBroadcastRpcAddress().get()); // When MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(node1.getHostId()); assertThat(result.events).containsExactly(NodeStateEvent.removed(node2)); } @@ -68,14 +66,15 @@ public void should_remove_existing_node() { public void should_not_remove_nonexistent_node() { // Given DefaultMetadata oldMetadata = - new DefaultMetadata(ImmutableMap.of(ADDRESS1, node1), Collections.emptyMap(), null); - RemoveNodeRefresh refresh = new RemoveNodeRefresh(ADDRESS2); + new DefaultMetadata( + ImmutableMap.of(node1.getHostId(), node1), Collections.emptyMap(), null); + RemoveNodeRefresh refresh = new RemoveNodeRefresh(node2.getBroadcastRpcAddress().get()); // When MetadataRefresh.Result result = refresh.compute(oldMetadata, false, context); // Then - assertThat(result.newMetadata.getNodes()).containsOnlyKeys(ADDRESS1); + assertThat(result.newMetadata.getNodes()).containsOnlyKeys(node1.getHostId()); assertThat(result.events).isEmpty(); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java index 05bb540d31e..0848ea3dcd9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/SchemaAgreementCheckerTest.java @@ -20,28 +20,22 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; -import com.datastax.oss.driver.internal.core.addresstranslation.PassThroughAddressTranslator; import com.datastax.oss.driver.internal.core.adminrequest.AdminResult; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.Iterators; import io.netty.channel.EventLoop; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.time.Duration; import java.util.ArrayDeque; import java.util.Arrays; @@ -60,8 +54,6 @@ @RunWith(MockitoJUnitRunner.class) public class SchemaAgreementCheckerTest { - private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 9042); - private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 9042); private static final UUID VERSION1 = UUID.randomUUID(); private static final UUID VERSION2 = UUID.randomUUID(); @@ -71,13 +63,18 @@ public class SchemaAgreementCheckerTest { @Mock private DriverChannel channel; @Mock private EventLoop eventLoop; @Mock private MetadataManager metadataManager; + @Mock private MetricsFactory metricsFactory; @Mock private Metadata metadata; @Mock private DefaultNode node1; @Mock private DefaultNode node2; - private AddressTranslator addressTranslator; @Before public void setup() { + when(context.getMetricsFactory()).thenReturn(metricsFactory); + + node1 = TestNodeFactory.newNode(1, context); + node2 = TestNodeFactory.newNode(2, context); + when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT)) .thenReturn(Duration.ofSeconds(1)); when(defaultConfig.getDuration(DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL)) @@ -89,15 +86,12 @@ public void setup() { when(config.getDefaultProfile()).thenReturn(defaultConfig); when(context.getConfig()).thenReturn(config); - addressTranslator = spy(new PassThroughAddressTranslator(context)); - when(context.getAddressTranslator()).thenReturn(addressTranslator); - - Map nodes = ImmutableMap.of(ADDRESS1, node1, ADDRESS2, node2); + Map nodes = ImmutableMap.of(node1.getHostId(), node1, node2.getHostId(), node2); when(metadata.getNodes()).thenReturn(nodes); when(metadataManager.getMetadata()).thenReturn(metadata); when(context.getMetadataManager()).thenReturn(metadataManager); - when(node2.getState()).thenReturn(NodeState.UP); + node2.state = NodeState.UP; when(eventLoop.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer( @@ -130,9 +124,9 @@ public void should_succeed_if_only_one_node() { checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", mockResult(/*empty*/ ))); + "SELECT host_id, schema_version FROM system.peers", mockResult(/*empty*/ ))); // When CompletionStage future = checker.run(); @@ -148,38 +142,36 @@ public void should_succeed_if_versions_match_on_first_try() { checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(node2.getHostId(), VERSION1)))); // When CompletionStage future = checker.run(); // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - verify(addressTranslator).translate(ADDRESS2); } @Test public void should_ignore_down_peers() { // Given TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); - when(node2.getState()).thenReturn(NodeState.DOWN); + node2.state = NodeState.DOWN; checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION2)))); + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(node2.getHostId(), VERSION2)))); // When CompletionStage future = checker.run(); // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - verify(addressTranslator).translate(ADDRESS2); } @Test @@ -189,40 +181,16 @@ public void should_ignore_malformed_rows() { checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), - new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, null, VERSION2)))); - - // When - CompletionStage future = checker.run(); - - // Then - assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - verify(addressTranslator, never()).translate(ADDRESS2); - } - - @Test - public void should_use_peer_if_rpc_address_is_0_0_0_0() { - // Given - TestSchemaAgreementChecker checker = new TestSchemaAgreementChecker(channel, context); - when(node2.getState()).thenReturn(NodeState.DOWN); - checker.stubQueries( - new StubbedQuery( - "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult( - mockRow( - ADDRESS2.getAddress(), SchemaAgreementChecker.BIND_ALL_ADDRESS, VERSION2)))); + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(null, VERSION2)))); // missing host_id // When CompletionStage future = checker.run(); // Then assertThatStage(future).isSuccess(b -> assertThat(b).isTrue()); - verify(addressTranslator).translate(ADDRESS2); } @Test @@ -233,18 +201,18 @@ public void should_reschedule_if_versions_do_not_match_on_first_try() { // First round new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION2))), + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(node2.getHostId(), VERSION2))), // Second round new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(node2.getHostId(), VERSION1)))); // When CompletionStage future = checker.run(); @@ -262,10 +230,10 @@ public void should_fail_if_versions_do_not_match_after_timeout() { checker.stubQueries( new StubbedQuery( "SELECT schema_version FROM system.local WHERE key='local'", - mockResult(mockRow(null, null, VERSION1))), + mockResult(mockRow(null, VERSION1))), new StubbedQuery( - "SELECT peer, rpc_address, schema_version FROM system.peers", - mockResult(mockRow(null, ADDRESS2.getAddress(), VERSION1)))); + "SELECT host_id, schema_version FROM system.peers", + mockResult(mockRow(node2.getHostId(), VERSION1)))); // When CompletionStage future = checker.run(); @@ -291,7 +259,7 @@ private void stubQueries(StubbedQuery... queries) { protected CompletionStage query(String queryString) { StubbedQuery nextQuery = queries.poll(); assertThat(nextQuery).isNotNull(); - assertThat(nextQuery.queryString).isEqualTo(queryString); + assertThat(queryString).isEqualTo(nextQuery.queryString); return CompletableFuture.completedFuture(nextQuery.result); } } @@ -306,11 +274,10 @@ private StubbedQuery(String queryString, AdminResult result) { } } - private AdminRow mockRow(InetAddress peer, InetAddress rpcAddress, UUID uuid) { + private AdminRow mockRow(UUID hostId, UUID schemaVersion) { AdminRow row = mock(AdminRow.class); - when(row.getInetAddress("peer")).thenReturn(peer); - when(row.getInetAddress("rpc_address")).thenReturn(rpcAddress); - when(row.getUuid("schema_version")).thenReturn(uuid); + when(row.getUuid("host_id")).thenReturn(hostId); + when(row.getUuid("schema_version")).thenReturn(schemaVersion); return row; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/TestNodeFactory.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/TestNodeFactory.java new file mode 100644 index 00000000000..3866bbf8ddb --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/TestNodeFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.metadata; + +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import java.net.InetSocketAddress; +import java.util.UUID; + +public class TestNodeFactory { + + public static DefaultNode newNode(int lastIpByte, InternalDriverContext context) { + DefaultEndPoint endPoint = newEndPoint(lastIpByte); + DefaultNode node = new DefaultNode(endPoint, context); + node.hostId = UUID.randomUUID(); + node.broadcastRpcAddress = endPoint.resolve(); + return node; + } + + public static DefaultEndPoint newEndPoint(int lastByteOfIp) { + return new DefaultEndPoint(new InetSocketAddress("127.0.0." + lastByteOfIp, 9042)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java index d59d9660828..3acfeb3b65d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolInitTest.java @@ -122,7 +122,7 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro when(defaultProfile.getInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE)).thenReturn(3); ClusterNameMismatchException error = - new ClusterNameMismatchException(ADDRESS, "actual", "expected"); + new ClusterNameMismatchException(node.getEndPoint(), "actual", "expected"); MockChannelFactoryHelper factoryHelper = MockChannelFactoryHelper.builder(channelFactory) .failure(node, error) @@ -135,7 +135,7 @@ public void should_fire_force_down_event_when_cluster_name_does_not_match() thro factoryHelper.waitForCalls(node, 3); waitForPendingAdminTasks(); - verify(eventBus).fire(TopologyEvent.forceDown(ADDRESS)); + verify(eventBus).fire(TopologyEvent.forceDown(node.getBroadcastRpcAddress().get())); verify(eventBus, never()).fire(ChannelEvent.channelOpened(node)); verify(nodeMetricUpdater, times(3)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java index 0f569d1b00b..16164c950e3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/pool/ChannelPoolTestBase.java @@ -32,6 +32,8 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; +import com.datastax.oss.driver.internal.core.metadata.TestNodeFactory; +import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; import io.netty.channel.Channel; @@ -39,7 +41,6 @@ import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoop; import io.netty.util.concurrent.Future; -import java.net.InetSocketAddress; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -51,8 +52,6 @@ abstract class ChannelPoolTestBase { - static final InetSocketAddress ADDRESS = new InetSocketAddress("localhost", 9042); - @Mock protected InternalDriverContext context; @Mock private DriverConfig config; @Mock protected DriverExecutionProfile defaultProfile; @@ -60,8 +59,9 @@ abstract class ChannelPoolTestBase { @Mock protected ReconnectionPolicy.ReconnectionSchedule reconnectionSchedule; @Mock private NettyOptions nettyOptions; @Mock protected ChannelFactory channelFactory; - @Mock protected DefaultNode node; + @Mock protected MetricsFactory metricsFactory; @Mock protected NodeMetricUpdater nodeMetricUpdater; + protected DefaultNode node; protected EventBus eventBus; private DefaultEventLoopGroup adminEventLoopGroup; @@ -85,8 +85,10 @@ public void setup() { // it. when(reconnectionSchedule.nextDelay()).thenReturn(Duration.ofDays(1)); - when(node.getConnectAddress()).thenReturn(ADDRESS); - when(node.getMetricUpdater()).thenReturn(nodeMetricUpdater); + when(context.getMetricsFactory()).thenReturn(metricsFactory); + when(metricsFactory.newNodeUpdater(any(Node.class))).thenReturn(nodeMetricUpdater); + + node = TestNodeFactory.newNode(1, context); } @After diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java index 6f97c21ec87..d4620c4b522 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/DefaultSessionPoolsTest.java @@ -19,7 +19,6 @@ import static com.datastax.oss.driver.Assertions.assertThatStage; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; @@ -48,11 +47,13 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.context.NettyOptions; import com.datastax.oss.driver.internal.core.control.ControlConnection; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.DistanceEvent; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; +import com.datastax.oss.driver.internal.core.metadata.TestNodeFactory; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.metrics.MetricsFactory; import com.datastax.oss.driver.internal.core.pool.ChannelPool; @@ -64,9 +65,10 @@ import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GlobalEventExecutor; -import java.net.InetSocketAddress; import java.time.Duration; import java.util.Collections; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; @@ -127,8 +129,6 @@ public void setup() { when(context.getConfig()).thenReturn(config); // Init sequence: - when(metadataManager.addContactPoints(anySet())) - .thenReturn(CompletableFuture.completedFuture(null)); when(metadataManager.refreshNodes()).thenReturn(CompletableFuture.completedFuture(null)); when(metadataManager.firstSchemaRefreshFuture()) .thenReturn(CompletableFuture.completedFuture(null)); @@ -154,11 +154,11 @@ public void setup() { node1 = mockLocalNode(1); node2 = mockLocalNode(2); node3 = mockLocalNode(3); - ImmutableMap nodes = + ImmutableMap nodes = ImmutableMap.of( - node1.getConnectAddress(), node1, - node2.getConnectAddress(), node2, - node3.getConnectAddress(), node3); + node1.getHostId(), node1, + node2.getHostId(), node2, + node3.getHostId(), node3); when(metadata.getNodes()).thenReturn(nodes); when(metadataManager.getMetadata()).thenReturn(metadata); @@ -938,7 +938,10 @@ private CompletionStage newSession() { private static DefaultNode mockLocalNode(int i) { DefaultNode node = mock(DefaultNode.class); - when(node.getConnectAddress()).thenReturn(new InetSocketAddress("127.0.0." + i, 9042)); + when(node.getHostId()).thenReturn(UUID.randomUUID()); + DefaultEndPoint endPoint = TestNodeFactory.newEndPoint(i); + when(node.getEndPoint()).thenReturn(endPoint); + when(node.getBroadcastRpcAddress()).thenReturn(Optional.of(endPoint.resolve())); when(node.getDistance()).thenReturn(NodeDistance.LOCAL); when(node.toString()).thenReturn("node" + i); return node; diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 169516500a7..5f5c1a10ad4 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -115,7 +115,7 @@ public void should_cleanup_on_lbp_init_failure() { .without(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER) .build(); CqlSession.builder() - .addContactPoints(simulacronRule.getContactPoints()) + .addContactEndPoints(simulacronRule.getContactPoints()) .withConfigLoader(loader) .build(); fail("Should have thrown a DriverException for no DC with explicit contact point"); @@ -131,7 +131,7 @@ public void should_cleanup_on_lbp_init_failure() { private CompletionStage newSessionAsync( SimulacronRule serverRule, DriverConfigLoader loader) { return SessionUtils.baseBuilder() - .addContactPoints(serverRule.getContactPoints()) + .addContactEndPoints(serverRule.getContactPoints()) .withConfigLoader(loader) .buildAsync(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java index 4a55ac7a82d..adbc4e9338d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ProtocolVersionMixedClusterIT.java @@ -67,7 +67,7 @@ public void should_downgrade_if_peer_does_not_support_negotiated_version() { // Find out which node became the control node after the reconnection (not necessarily node 0) InetSocketAddress controlAddress = - (InetSocketAddress) context.getControlConnection().channel().connectAddress(); + (InetSocketAddress) context.getControlConnection().channel().getEndPoint().resolve(); BoundNode currentControlNode = null; for (BoundNode node : simulacron.getNodes()) { if (node.inetSocketAddress().equals(controlAddress)) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java index 85dec009ec9..8f03e8aad94 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileReloadIT.java @@ -61,7 +61,7 @@ public void should_periodically_reload_configuration() throws Exception { (CqlSession) SessionUtils.baseBuilder() .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) + .addContactEndPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); @@ -99,7 +99,7 @@ public void should_reload_configuration_when_event_fired() throws Exception { (CqlSession) SessionUtils.baseBuilder() .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) + .addContactEndPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); @@ -136,7 +136,7 @@ public void should_not_allow_dynamically_adding_profile() throws Exception { (CqlSession) SessionUtils.baseBuilder() .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) + .addContactEndPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); @@ -178,7 +178,7 @@ public void should_reload_profile_config_when_reloading_config() throws Exceptio (CqlSession) SessionUtils.baseBuilder() .withConfigLoader(loader) - .addContactPoints(simulacron.getContactPoints()) + .addContactEndPoints(simulacron.getContactPoints()) .build()) { simulacron.cluster().prime(when(query).then(noRows()).delay(4, TimeUnit.SECONDS)); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index dafb80fa67b..40f08a728c7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -526,7 +526,7 @@ private static void verifyUnset( private CqlSession sessionWithCustomCodec(CqlIntToStringCodec codec) { return (CqlSession) SessionUtils.baseBuilder() - .addContactPoints(ccm.getContactPoints()) + .addContactEndPoints(ccm.getContactPoints()) .withKeyspace(sessionRule.keyspace()) .addTypeCodecs(codec) .build(); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java index 60ba77c08b8..f01007ce3e1 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/QueryTraceIT.java @@ -19,9 +19,11 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DriverExecutionException; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.categories.ParallelizableTests; +import java.net.InetSocketAddress; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.ClassRule; @@ -91,8 +93,9 @@ public void should_fetch_trace_when_tracing_enabled() { assertThat(queryTrace.getTracingId()).isEqualTo(executionInfo.getTracingId()); assertThat(queryTrace.getRequestType()).isEqualTo("Execute CQL3 query"); assertThat(queryTrace.getDurationMicros()).isPositive(); + EndPoint contactPoint = ccmRule.getContactPoints().iterator().next(); assertThat(queryTrace.getCoordinator()) - .isEqualTo(ccmRule.getContactPoints().iterator().next().getAddress()); + .isEqualTo(((InetSocketAddress) contactPoint.resolve()).getAddress()); assertThat(queryTrace.getParameters()) .containsEntry("consistency_level", "LOCAL_ONE") .containsEntry("page_size", "5000") diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java index 27f0cfb1e1a..41309a36f8a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/DefaultLoadBalancingPolicyIT.java @@ -26,6 +26,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.TokenMap; @@ -183,7 +184,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep for (Node replica : tokenMap.getReplicas(keyspace, routingKey)) { if (replica.getDatacenter().equals(LOCAL_DC)) { localReplicas.add(replica); - context.getEventBus().fire(TopologyEvent.forceDown(replica.getConnectAddress())); + context.getEventBus().fire(TopologyEvent.forceDown(replica.getBroadcastRpcAddress().get())); ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isZero()) .becomesTrue(); } @@ -218,7 +219,7 @@ public void should_hit_non_replicas_when_routing_information_present_but_all_rep } for (Node replica : localReplicas) { - context.getEventBus().fire(TopologyEvent.forceUp(replica.getConnectAddress())); + context.getEventBus().fire(TopologyEvent.forceUp(replica.getBroadcastRpcAddress().get())); ConditionChecker.checkThat(() -> assertThat(replica.getOpenConnections()).isPositive()) .becomesTrue(); } @@ -236,7 +237,7 @@ public void should_apply_node_filter() { // Pick a random node to exclude -- just ensure that it's not the default contact point since // we assert 0 connections at the end of this test (the filter is not applied to contact // points). - InetSocketAddress ignoredAddress = firstNonDefaultContactPoint(localNodes); + EndPoint ignoredEndPoint = firstNonDefaultContactPoint(localNodes); // Open a separate session with a filter try (CqlSession session = @@ -245,26 +246,30 @@ public void should_apply_node_filter() { sessionRule.keyspace(), null, null, - node -> !node.getConnectAddress().equals(ignoredAddress))) { + node -> !node.getEndPoint().equals(ignoredEndPoint))) { // No routing information => should round-robin on white-listed nodes SimpleStatement statement = SimpleStatement.newInstance("SELECT * FROM test.foo WHERE k = 1"); for (int i = 0; i < 12; i++) { ResultSet rs = session.execute(statement); Node coordinator = rs.getExecutionInfo().getCoordinator(); - assertThat(coordinator.getConnectAddress()).isNotEqualTo(ignoredAddress); + assertThat(coordinator.getEndPoint()).isNotEqualTo(ignoredEndPoint); } - Node ignoredNode = session.getMetadata().getNodes().get(ignoredAddress); - assertThat(ignoredNode.getOpenConnections()).isEqualTo(0); + assertThat(session.getMetadata().findNode(ignoredEndPoint)) + .hasValueSatisfying( + ignoredNode -> { + assertThat(ignoredNode.getOpenConnections()).isEqualTo(0); + }); } } - private InetSocketAddress firstNonDefaultContactPoint(Iterable nodes) { + private EndPoint firstNonDefaultContactPoint(Iterable nodes) { for (Node localNode : nodes) { - InetSocketAddress address = localNode.getConnectAddress(); - if (!address.getAddress().getHostAddress().equals("127.0.0.1")) { - return address; + EndPoint endPoint = localNode.getEndPoint(); + InetSocketAddress connectAddress = (InetSocketAddress) endPoint.resolve(); + if (!connectAddress.getAddress().getHostAddress().equals("127.0.0.1")) { + return endPoint; } } fail("should have other nodes than the default contact point"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java index 24e4ac62ce8..0ee418acb1e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/loadbalancing/NodeTargetingIT.java @@ -124,8 +124,11 @@ private Node getNode(int id) { BoundNode boundNode = simulacron.cluster().node(id); assertThat(boundNode).isNotNull(); InetSocketAddress address = (InetSocketAddress) boundNode.getAddress(); - Node node = sessionRule.session().getMetadata().getNodes().get(address); - assertThat(node).isNotNull(); - return node; + return sessionRule + .session() + .getMetadata() + .findNode(address) + .orElseThrow( + () -> new AssertionError(String.format("Expected to find node %d in metadata", id))); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java index fefa4edc8a5..0fc9bf3258a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeMetadataIT.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.internal.core.context.EventBus; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; +import java.net.InetSocketAddress; import java.util.Collection; import org.junit.ClassRule; import org.junit.Test; @@ -42,14 +43,13 @@ public void should_expose_node_metadata() { try (CqlSession session = SessionUtils.newSession(ccmRule)) { Node node = getUniqueNode(session); // Run a few basic checks given what we know about our test environment: - assertThat(node.getConnectAddress()).isNotNull(); + assertThat(node.getEndPoint()).isNotNull(); + InetSocketAddress connectAddress = (InetSocketAddress) node.getEndPoint().resolve(); node.getBroadcastAddress() .ifPresent( broadcastAddress -> - assertThat(broadcastAddress.getAddress()) - .isEqualTo(node.getConnectAddress().getAddress())); - assertThat(node.getListenAddress().get().getAddress()) - .isEqualTo(node.getConnectAddress().getAddress()); + assertThat(broadcastAddress.getAddress()).isEqualTo(connectAddress.getAddress())); + assertThat(node.getListenAddress().get().getAddress()).isEqualTo(connectAddress.getAddress()); assertThat(node.getDatacenter()).isEqualTo("dc1"); assertThat(node.getRack()).isEqualTo("r1"); if (!CcmBridge.DSE_ENABLEMENT) { @@ -67,10 +67,10 @@ public void should_expose_node_metadata() { // Force the node down and back up to check that upSinceMillis gets updated EventBus eventBus = ((InternalDriverContext) session.getContext()).getEventBus(); - eventBus.fire(TopologyEvent.forceDown(node.getConnectAddress())); + eventBus.fire(TopologyEvent.forceDown(node.getBroadcastRpcAddress().get())); ConditionChecker.checkThat(() -> node.getState() == NodeState.FORCED_DOWN).becomesTrue(); assertThat(node.getUpSinceMillis()).isEqualTo(-1); - eventBus.fire(TopologyEvent.forceUp(node.getConnectAddress())); + eventBus.fire(TopologyEvent.forceUp(node.getBroadcastRpcAddress().get())); ConditionChecker.checkThat(() -> node.getState() == NodeState.UP).becomesTrue(); assertThat(node.getUpSinceMillis()).isGreaterThan(upTime1); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java index 77ea21a84d3..7032f392853 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/NodeStateIT.java @@ -17,9 +17,13 @@ import static com.datastax.oss.driver.assertions.Assertions.assertThat; import static com.datastax.oss.driver.assertions.Assertions.fail; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -36,6 +40,7 @@ import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metadata.NodeStateEvent; import com.datastax.oss.driver.internal.core.metadata.TopologyEvent; @@ -54,6 +59,7 @@ import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; @@ -158,11 +164,17 @@ public void setup() { assertThat(simulacronControlNode).isNotNull(); assertThat(simulacronRegularNode).isNotNull(); - Map nodesMetadata = sessionRule.session().getMetadata().getNodes(); + Metadata metadata = sessionRule.session().getMetadata(); metadataControlNode = - (DefaultNode) nodesMetadata.get(simulacronControlNode.inetSocketAddress()); + (DefaultNode) + metadata + .findNode(simulacronControlNode.inetSocketAddress()) + .orElseThrow(AssertionError::new); metadataRegularNode = - (DefaultNode) nodesMetadata.get(simulacronRegularNode.inetSocketAddress()); + (DefaultNode) + metadata + .findNode(simulacronRegularNode.inetSocketAddress()) + .orElseThrow(AssertionError::new); // SessionRule uses all nodes as contact points, so we only get onUp notifications for them (no // onAdd) @@ -287,7 +299,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { driverContext .getEventBus() - .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestDown(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -302,7 +314,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { driverContext .getEventBus() - .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestUp(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -322,7 +334,7 @@ public void should_apply_up_and_down_topology_events_when_ignored() { public void should_ignore_down_topology_event_when_still_connected() throws InterruptedException { driverContext .getEventBus() - .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestDown(metadataRegularNode.getBroadcastRpcAddress().get())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting(); } @@ -345,7 +357,10 @@ public void should_force_immediate_reconnection_when_up_topology_event() DefaultNode localMetadataNode = (DefaultNode) - session.getMetadata().getNodes().get(localSimulacronNode.inetSocketAddress()); + session + .getMetadata() + .findNode(localSimulacronNode.inetSocketAddress()) + .orElseThrow(AssertionError::new); // UP fired a first time as part of the init process verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode); @@ -363,7 +378,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() localSimulacronNode.acceptConnections(); ((InternalDriverContext) session.getContext()) .getEventBus() - .fire(TopologyEvent.suggestUp(localMetadataNode.getConnectAddress())); + .fire(TopologyEvent.suggestUp(localMetadataNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat(() -> assertThat(localMetadataNode).isUp().isNotReconnecting()) .as("Node coming back up") @@ -379,7 +394,7 @@ public void should_force_immediate_reconnection_when_up_topology_event() public void should_force_down_when_not_ignored() throws InterruptedException { driverContext .getEventBus() - .fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.forceDown(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -394,20 +409,20 @@ public void should_force_down_when_not_ignored() throws InterruptedException { // Should ignore up/down topology events while forced down driverContext .getEventBus() - .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestUp(metadataRegularNode.getBroadcastRpcAddress().get())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext .getEventBus() - .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestDown(metadataRegularNode.getBroadcastRpcAddress().get())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); // Should only come back up on a FORCE_UP event driverContext .getEventBus() - .fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.forceUp(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(2).isNotReconnecting()) .as("Node forced back up") @@ -422,7 +437,7 @@ public void should_force_down_when_ignored() throws InterruptedException { driverContext .getEventBus() - .fire(TopologyEvent.forceDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.forceDown(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -437,13 +452,13 @@ public void should_force_down_when_ignored() throws InterruptedException { // Should ignore up/down topology events while forced down driverContext .getEventBus() - .fire(TopologyEvent.suggestUp(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestUp(metadataRegularNode.getBroadcastRpcAddress().get())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); driverContext .getEventBus() - .fire(TopologyEvent.suggestDown(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.suggestDown(metadataRegularNode.getBroadcastRpcAddress().get())); TimeUnit.MILLISECONDS.sleep(500); assertThat(metadataRegularNode).isForcedDown(); @@ -451,7 +466,7 @@ public void should_force_down_when_ignored() throws InterruptedException { // ignored driverContext .getEventBus() - .fire(TopologyEvent.forceUp(metadataRegularNode.getConnectAddress())); + .fire(TopologyEvent.forceUp(metadataRegularNode.getBroadcastRpcAddress().get())); ConditionChecker.checkThat( () -> assertThat(metadataRegularNode) @@ -471,9 +486,9 @@ public void should_force_down_when_ignored() throws InterruptedException { public void should_signal_non_contact_points_as_added() { // Since we need to observe the behavior of non-contact points, build a dedicated session with // just one contact point. - Iterator contactPoints = simulacron.getContactPoints().iterator(); - InetSocketAddress address1 = contactPoints.next(); - InetSocketAddress address2 = contactPoints.next(); + Iterator contactPoints = simulacron.getContactPoints().iterator(); + EndPoint endPoint1 = contactPoints.next(); + EndPoint endPoint2 = contactPoints.next(); NodeStateListener localNodeStateListener = mock(NodeStateListener.class); DriverConfigLoader loader = SessionUtils.configLoaderBuilder() @@ -484,14 +499,14 @@ public void should_signal_non_contact_points_as_added() { try (CqlSession localSession = (CqlSession) SessionUtils.baseBuilder() - .addContactPoint(address1) + .addContactEndPoint(endPoint1) .withNodeStateListener(localNodeStateListener) .withConfigLoader(loader) .build()) { - Map nodes = localSession.getMetadata().getNodes(); - Node localMetadataNode1 = nodes.get(address1); - Node localMetadataNode2 = nodes.get(address2); + Metadata metadata = localSession.getMetadata(); + Node localMetadataNode1 = metadata.findNode(endPoint1).orElseThrow(AssertionError::new); + Node localMetadataNode2 = metadata.findNode(endPoint2).orElseThrow(AssertionError::new); // Successful contact point goes to up directly verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); @@ -503,13 +518,13 @@ public void should_signal_non_contact_points_as_added() { @Test public void should_remove_invalid_contact_point() { - Iterator contactPoints = simulacron.getContactPoints().iterator(); - InetSocketAddress address1 = contactPoints.next(); - InetSocketAddress address2 = contactPoints.next(); + Iterator contactPoints = simulacron.getContactPoints().iterator(); + EndPoint endPoint1 = contactPoints.next(); + EndPoint endPoint2 = contactPoints.next(); NodeStateListener localNodeStateListener = mock(NodeStateListener.class); // Initialize the driver with 1 wrong address and 1 valid address - InetSocketAddress wrongContactPoint = withUnusedPort(address1); + EndPoint wrongContactPoint = withUnusedPort(endPoint1); DriverConfigLoader loader = SessionUtils.configLoaderBuilder() .withDuration(DefaultDriverOption.RECONNECTION_BASE_DELAY, Duration.ofHours(1)) @@ -518,21 +533,21 @@ public void should_remove_invalid_contact_point() { try (CqlSession localSession = (CqlSession) SessionUtils.baseBuilder() - .addContactPoint(address1) - .addContactPoint(wrongContactPoint) + .addContactEndPoint(endPoint1) + .addContactEndPoint(wrongContactPoint) .withNodeStateListener(localNodeStateListener) .withConfigLoader(loader) .build()) { - Map nodes = localSession.getMetadata().getNodes(); - assertThat(nodes).doesNotContainKey(wrongContactPoint); - Node localMetadataNode1 = nodes.get(address1); - Node localMetadataNode2 = nodes.get(address2); + Metadata metadata = localSession.getMetadata(); + assertThat(metadata.findNode(wrongContactPoint)).isEmpty(); + Node localMetadataNode1 = metadata.findNode(endPoint1).orElseThrow(AssertionError::new); + Node localMetadataNode2 = metadata.findNode(endPoint2).orElseThrow(AssertionError::new); // The order of the calls is not deterministic because contact points are shuffled, but it // does not matter here since Mockito.verify does not enforce order. verify(localNodeStateListener, timeout(500)).onRemove(nodeCaptor.capture()); - assertThat(nodeCaptor.getValue().getConnectAddress()).isEqualTo(wrongContactPoint); + assertThat(nodeCaptor.getValue().getEndPoint()).isEqualTo(wrongContactPoint); verify(localNodeStateListener, timeout(500)).onUp(localMetadataNode1); verify(localNodeStateListener, timeout(500)).onAdd(localMetadataNode2); @@ -574,9 +589,9 @@ public void should_mark_unreachable_contact_point_down() { .withConfigLoader(loader) .build()) { - Map nodes = localSession.getMetadata().getNodes(); - Node localMetadataNode1 = nodes.get(address1); - Node localMetadataNode2 = nodes.get(address2); + Metadata metadata = localSession.getMetadata(); + Node localMetadataNode1 = metadata.findNode(address1).orElseThrow(AssertionError::new); + Node localMetadataNode2 = metadata.findNode(address2).orElseThrow(AssertionError::new); if (localMetadataNode2.getState() == NodeState.DOWN) { // Stopped node was tried first and marked down, that's our target scenario verify(localNodeStateListener, timeout(500)).onDown(localMetadataNode2); @@ -609,9 +624,10 @@ private void expect(NodeStateEvent... expectedEvents) { } } - // Generates a socket address that is not the connect address of one of the nodes in the cluster - private InetSocketAddress withUnusedPort(InetSocketAddress address) { - return new InetSocketAddress(address.getAddress(), findAvailablePort()); + // Generates an endpoint that is not the connect address of one of the nodes in the cluster + private EndPoint withUnusedPort(EndPoint endPoint) { + InetSocketAddress address = (InetSocketAddress) endPoint.resolve(); + return new DefaultEndPoint(new InetSocketAddress(address.getAddress(), findAvailablePort())); } /** @@ -647,10 +663,7 @@ public ConfigurableIgnoresPolicy( } @Override - public void init( - @NonNull Map nodes, - @NonNull DistanceReporter distanceReporter, - @NonNull Set contactPoints) { + public void init(@NonNull Map nodes, @NonNull DistanceReporter distanceReporter) { this.distanceReporter = distanceReporter; for (Node node : nodes.values()) { liveNodes.add(node); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java index ecccb72c8b0..e0a26b93a2f 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/ExceptionIT.java @@ -92,7 +92,7 @@ public void should_expose_execution_info_on_exceptions() { exception -> { ExecutionInfo info = ((InvalidQueryException) exception).getExecutionInfo(); assertThat(info).isNotNull(); - assertThat(info.getCoordinator().getConnectAddress()) + assertThat(info.getCoordinator().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); assertThat(((SimpleStatement) info.getStatement()).getQuery()) .isEqualTo(QUERY_STRING); @@ -113,7 +113,7 @@ public void should_expose_execution_info_on_exceptions() { List> errors = info.getErrors(); assertThat(errors).hasSize(1); Map.Entry entry0 = errors.get(0); - assertThat(entry0.getKey().getConnectAddress()) + assertThat(entry0.getKey().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); Throwable node0Exception = entry0.getValue(); assertThat(node0Exception).isInstanceOf(UnavailableException.class); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java index 4a18c6ef23a..e695e5a616a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/session/RequestProcessorIT.java @@ -97,7 +97,7 @@ public static void setupSchema() { private GuavaSession newSession(CqlIdentifier keyspace) { return GuavaSessionUtils.builder() - .addContactPoints(ccm.getContactPoints()) + .addContactEndPoints(ccm.getContactPoints()) .withKeyspace(keyspace) .build(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java index 5d4412bc6b6..5a13c739b40 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistryIT.java @@ -172,7 +172,7 @@ public void should_be_able_to_register_and_use_custom_codec() { (CqlSession) SessionUtils.baseBuilder() .addTypeCodecs(new FloatCIntCodec()) - .addContactPoints(ccm.getContactPoints()) + .addContactEndPoints(ccm.getContactPoints()) .withKeyspace(sessionRule.keyspace()) .build()) { PreparedStatement prepared = session.prepare("INSERT INTO test (k, v) values (?, ?)"); @@ -293,7 +293,7 @@ public void should_be_able_to_register_and_use_custom_codec_with_generic_type() (CqlSession) SessionUtils.baseBuilder() .addTypeCodecs(optionalMapCodec, mapWithOptionalValueCodec) - .addContactPoints(ccm.getContactPoints()) + .addContactEndPoints(ccm.getContactPoints()) .withKeyspace(sessionRule.keyspace()) .build()) { PreparedStatement prepared = diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java index 517b24a8e04..4527de4edf6 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/internal/core/retry/DefaultRetryPolicyIT.java @@ -26,9 +26,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; import static org.mockito.Mockito.after; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -229,11 +230,11 @@ public void should_retry_on_next_host_on_connection_error_if_idempotent() { // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); Map.Entry error = result.getExecutionInfo().getErrors().get(0); - assertThat(error.getKey().getConnectAddress()) + assertThat(error.getKey().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); assertThat(error.getValue()).isInstanceOf(ClosedConnectionException.class); // the host that returned the response should be node 1. - assertThat(result.getExecutionInfo().getCoordinator().getConnectAddress()) + assertThat(result.getExecutionInfo().getCoordinator().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); // should have been retried. @@ -430,11 +431,11 @@ public void should_retry_on_next_host_on_unavailable() { // the host that received the query. assertThat(result.getExecutionInfo().getErrors()).hasSize(1); Map.Entry error = result.getExecutionInfo().getErrors().get(0); - assertThat(error.getKey().getConnectAddress()) + assertThat(error.getKey().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(0).inetSocketAddress()); assertThat(error.getValue()).isInstanceOf(UnavailableException.class); // the host that returned the response should be node 1. - assertThat(result.getExecutionInfo().getCoordinator().getConnectAddress()) + assertThat(result.getExecutionInfo().getCoordinator().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); // should have been retried on another host. @@ -462,7 +463,7 @@ public void should_only_retry_once_on_unavailable() { } catch (UnavailableException ue) { // then we should get an unavailable exception with the host being node 1 (since it was second // tried). - assertThat(ue.getCoordinator().getConnectAddress()) + assertThat(ue.getCoordinator().getEndPoint().resolve()) .isEqualTo(simulacron.cluster().node(1).inetSocketAddress()); assertThat(ue.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_QUORUM); assertThat(ue.getRequired()).isEqualTo(3); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java index e173825bf74..934368d6168 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/osgi/OsgiBaseIT.java @@ -58,7 +58,7 @@ public abstract class OsgiBaseIT { public void should_connect_and_query() { SessionBuilder builder = CqlSession.builder() - .addContactPoints(ccmRule.getContactPoints()) + .addContactEndPoints(ccmRule.getContactPoints()) // use the driver's ClassLoader instead of the OSGI application thread's. .withClassLoader(CqlSession.class.getClassLoader()) .withConfigLoader(configLoader()); diff --git a/manual/core/metrics/README.md b/manual/core/metrics/README.md index 2d37add95c2..468ea436bbe 100644 --- a/manual/core/metrics/README.md +++ b/manual/core/metrics/README.md @@ -20,8 +20,8 @@ followed by a textual representation of the node's address. For example: ``` s0.connected-nodes => 2 -s0.nodes.127_0_0_1_9042.pool.open-connections => 2 -s0.nodes.127_0_0_2_9042.pool.open-connections => 1 +s0.nodes.127_0_0_1:9042.pool.open-connections => 2 +s0.nodes.127_0_0_2:9042.pool.open-connections => 1 ``` ### Configuration diff --git a/pom.xml b/pom.xml index a3b60dedc8a..efd9ffc22a6 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 4.12 1.2.3 4.12.0 - 0.8.4 + 0.8.8 diff --git a/test-infra/revapi.json b/test-infra/revapi.json index 11e4609560d..a560b7bd1b3 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -17,6 +17,29 @@ ] } } - } + }, + "ignore": [ + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Set com.datastax.oss.driver.api.testinfra.CassandraResourceRule::getContactPoints()", + "new": "method java.util.Set com.datastax.oss.driver.api.testinfra.CassandraResourceRule::getContactPoints()", + "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.numberOfParametersChanged", + "old": "method void com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter, java.util.Set)", + "new": "method void com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter)", + "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Set com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule::getContactPoints()", + "new": "method java.util.Set com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule::getContactPoints()", + "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", + "justification": "JAVA-2165: Abstract node connection information" + } + ] } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java index 710622532e2..4e40f2788f7 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/CassandraResourceRule.java @@ -16,7 +16,9 @@ package com.datastax.oss.driver.api.testinfra; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import java.net.InetSocketAddress; import java.util.Collections; import java.util.Set; @@ -48,8 +50,8 @@ public synchronized void setUp() { * @return Default contact points associated with this cassandra resource. By default returns * 127.0.0.1 */ - public Set getContactPoints() { - return Collections.singleton(new InetSocketAddress("127.0.0.1", 9042)); + public Set getContactPoints() { + return Collections.singleton(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))); } /** @return The highest protocol version supported by this resource. */ diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java index 77de5f16968..678b1477dee 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/SortingLoadBalancingPolicy.java @@ -29,6 +29,7 @@ import java.util.Queue; import java.util.Set; import java.util.TreeSet; +import java.util.UUID; public class SortingLoadBalancingPolicy implements LoadBalancingPolicy { @@ -75,10 +76,7 @@ public SortingLoadBalancingPolicy(DriverContext context, String profileName) { public SortingLoadBalancingPolicy() {} @Override - public void init( - @NonNull Map nodes, - @NonNull DistanceReporter distanceReporter, - @NonNull Set contactPoints) { + public void init(@NonNull Map nodes, @NonNull DistanceReporter distanceReporter) { this.nodes.addAll(nodes.values()); this.nodes.forEach(n -> distanceReporter.setDistance(n, NodeDistance.LOCAL)); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java index 73d7d908a64..139631f26c3 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java @@ -140,7 +140,7 @@ private static SessionBuilder builder( Predicate nodeFilter) { SessionBuilder builder = baseBuilder() - .addContactPoints(cassandraResource.getContactPoints()) + .addContactEndPoints(cassandraResource.getContactPoints()) .withKeyspace(keyspace) .withNodeStateListener(nodeStateListener) .withSchemaChangeListener(schemaChangeListener); diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java index c531183c8b0..c15a257a781 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/simulacron/SimulacronRule.java @@ -17,13 +17,13 @@ import com.datastax.oss.driver.api.core.DefaultProtocolVersion; import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; import com.datastax.oss.simulacron.server.BoundCluster; -import com.datastax.oss.simulacron.server.BoundNode; import com.datastax.oss.simulacron.server.Inet4Resolver; import com.datastax.oss.simulacron.server.Server; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -82,12 +82,12 @@ protected void after() { /** @return All nodes in first data center. */ @Override - public Set getContactPoints() { + public Set getContactPoints() { return boundCluster .dc(0) .getNodes() .stream() - .map(BoundNode::inetSocketAddress) + .map(node -> new DefaultEndPoint(node.inetSocketAddress())) .collect(Collectors.toSet()); } From 5fbf2b8144a7759914fb6cb755fe3ec6d01dad88 Mon Sep 17 00:00:00 2001 From: Olivier Michallat Date: Mon, 11 Mar 2019 03:47:43 -0700 Subject: [PATCH 717/742] JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp() (#1210) --- changelog/README.md | 1 + core/revapi.json | 20 +++++++++++++++++++ .../oss/driver/api/core/CqlSession.java | 6 +++--- .../driver/api/core/cql/BatchStatement.java | 2 +- .../driver/api/core/cql/BoundStatement.java | 2 +- .../driver/api/core/cql/SimpleStatement.java | 2 +- .../oss/driver/api/core/cql/Statement.java | 4 ++-- .../driver/api/core/cql/StatementBuilder.java | 6 +++--- .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/DefaultBatchStatement.java | 4 ++-- .../core/cql/DefaultBoundStatement.java | 4 ++-- .../core/cql/DefaultSimpleStatement.java | 4 ++-- faq/README.md | 7 +++++++ .../driver/api/core/cql/BoundStatementIT.java | 4 ++-- .../api/core/cql/SimpleStatementIT.java | 2 +- manual/core/query_timestamps/README.md | 2 +- manual/core/statements/prepared/README.md | 2 +- 17 files changed, 51 insertions(+), 23 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index b19a2678d73..36648c6b719 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp() - [improvement] JAVA-2165: Abstract node connection information - [improvement] JAVA-2090: Add support for additional_write_policy and read_repair table options - [improvement] JAVA-2164: Rename statement builder methods to setXxx diff --git a/core/revapi.json b/core/revapi.json index c8669069303..148d9fc7baa 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -234,6 +234,26 @@ "new": "parameter javax.net.ssl.SSLEngine com.datastax.oss.driver.api.core.ssl.SslEngineFactory::newSslEngine(===com.datastax.oss.driver.api.core.metadata.EndPoint===)", "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" + }, + { + "code": "java.method.addedToInterface", + "new": "method long com.datastax.oss.driver.api.core.cql.Statement>>::getQueryTimestamp()", + "justification": "JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp()" + }, + { + "code": "java.method.removed", + "old": "method long com.datastax.oss.driver.api.core.cql.Statement>>::getTimestamp()", + "justification": "JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp()" + }, + { + "code": "java.method.addedToInterface", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setQueryTimestamp(long)", + "justification": "JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp()" + }, + { + "code": "java.method.removed", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimestamp(long)", + "justification": "JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp()" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index b0a0d01abda..effa3e9079a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -114,9 +114,9 @@ default CompletionStage executeAsync(@NonNull String q * null}), or {@code simpleStatement.getRoutingKeyspace()}; *

      • on the other hand, the following attributes are not propagated: *
          - *
        • {@link Statement#getTimestamp() boundStatement.getTimestamp()} will be set to - * {@link Long#MIN_VALUE}, meaning that the value will be assigned by the session's - * timestamp generator. + *
        • {@link Statement#getQueryTimestamp() boundStatement.getQueryTimestamp()} will be + * set to {@link Long#MIN_VALUE}, meaning that the value will be assigned by the + * session's timestamp generator. *
        • {@link Statement#getNode() boundStatement.getNode()} will always be {@code null}. *
        * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java index d41b8d42b23..95c09653b1a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BatchStatement.java @@ -243,7 +243,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { // timestamp if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) - || getTimestamp() != Long.MIN_VALUE) { + || getQueryTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java index 3c9981af4ff..c8d17189721 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/BoundStatement.java @@ -86,7 +86,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { // timestamp if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) - || getTimestamp() != Long.MIN_VALUE) { + || getQueryTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 3caf71584b0..918451b4537 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -285,7 +285,7 @@ default int computeSizeInBytes(@NonNull DriverContext context) { // timestamp if (!(context.getTimestampGenerator() instanceof ServerSideTimestampGenerator) - || getTimestamp() != Long.MIN_VALUE) { + || getQueryTimestamp() != Long.MIN_VALUE) { size += PrimitiveSizes.LONG; } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index c5abf218957..298813bc5e3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -210,7 +210,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * * @see TimestampGenerator */ - long getTimestamp(); + long getQueryTimestamp(); /** * Sets the query timestamp, in microseconds, to send with the statement. @@ -224,7 +224,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see TimestampGenerator */ @NonNull - SelfT setTimestamp(long newTimestamp); + SelfT setQueryTimestamp(long newTimestamp); /** * Sets how long to wait for this request to complete. This is a global limit on the duration of a diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 030b932139a..209672fa412 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -75,7 +75,7 @@ protected StatementBuilder(StatementT template) { } this.idempotent = template.isIdempotent(); this.tracing = template.isTracing(); - this.timestamp = template.getTimestamp(); + this.timestamp = template.getQueryTimestamp(); this.pagingState = template.getPagingState(); this.pageSize = template.getPageSize(); this.consistencyLevel = template.getConsistencyLevel(); @@ -161,9 +161,9 @@ public SelfT setTracing() { return self; } - /** @see Statement#setTimestamp(long) */ + /** @see Statement#setQueryTimestamp(long) */ @NonNull - public SelfT setTimestamp(long timestamp) { + public SelfT setQueryTimestamp(long timestamp) { this.timestamp = timestamp; return self; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 662b21d27f7..652092e0cff 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -131,7 +131,7 @@ public static Message toMessage( ? consistencyLevelRegistry.nameToCode( config.getString(DefaultDriverOption.REQUEST_SERIAL_CONSISTENCY)) : serialConsistency.getProtocolCode(); - long timestamp = statement.getTimestamp(); + long timestamp = statement.getQueryTimestamp(); if (timestamp == Long.MIN_VALUE) { timestamp = context.getTimestampGenerator().next(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 71c9869fd79..792ad3cc702 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -678,13 +678,13 @@ public BatchStatement setTracing(boolean newTracing) { } @Override - public long getTimestamp() { + public long getQueryTimestamp() { return timestamp; } @NonNull @Override - public BatchStatement setTimestamp(long newTimestamp) { + public BatchStatement setQueryTimestamp(long newTimestamp) { return new DefaultBatchStatement( batchType, statements, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 11d3e049368..7c58b73c176 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -499,13 +499,13 @@ public BoundStatement setTracing(boolean newTracing) { } @Override - public long getTimestamp() { + public long getQueryTimestamp() { return timestamp; } @NonNull @Override - public BoundStatement setTimestamp(long newTimestamp) { + public BoundStatement setQueryTimestamp(long newTimestamp) { return new DefaultBoundStatement( preparedStatement, variableDefinitions, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 2d13a30d9dd..79947a84597 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -504,13 +504,13 @@ public SimpleStatement setTracing(boolean newTracing) { } @Override - public long getTimestamp() { + public long getQueryTimestamp() { return timestamp; } @NonNull @Override - public SimpleStatement setTimestamp(long newTimestamp) { + public SimpleStatement setQueryTimestamp(long newTimestamp) { return new DefaultSimpleStatement( query, positionalValues, diff --git a/faq/README.md b/faq/README.md index 762480d9347..ff36c7eb1e2 100644 --- a/faq/README.md +++ b/faq/README.md @@ -57,3 +57,10 @@ higher latencies. We therefore urge users to carefully choose upfront the consistency level that works best for their use cases. If there is a legitimate reason to downgrade and retry, that should be handled by the application code. + +### I want to set a date on a bound statement, where did `setTimestamp()` go? + +The driver now uses Java 8's improved date and time API. CQL type `timestamp` is mapped to +`java.time.Instant`, and the corresponding getter and setter are `getInstant` and `setInstant`. + +See [Temporal types](../manual/core/temporal_types/) for more details. \ No newline at end of file diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 40f08a728c7..08e7e5d0bd8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -441,7 +441,7 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { .setRoutingKeyspace(mockRoutingKeyspace) .setRoutingKey(mockRoutingKey) .setRoutingToken(mockRoutingToken) - .setTimestamp(42) + .setQueryTimestamp(42) .setIdempotence(true) .setTracing() .setTimeout(mockTimeout) @@ -483,7 +483,7 @@ public void should_propagate_attributes_when_preparing_a_simple_statement() { // Bound statements do not support per-query keyspaces, so this is not set assertThat(boundStatement.getKeyspace()).isNull(); // Should not be propagated - assertThat(boundStatement.getTimestamp()).isEqualTo(Long.MIN_VALUE); + assertThat(boundStatement.getQueryTimestamp()).isEqualTo(Long.MIN_VALUE); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index b9d33f5e2c5..517445d5cfc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -175,7 +175,7 @@ public void should_use_timestamp_when_set() { SimpleStatement insert = SimpleStatement.builder("INSERT INTO test2 (k, v) values (?, ?)") .addPositionalValues(name.getMethodName(), 0) - .setTimestamp(timestamp) + .setQueryTimestamp(timestamp) .build(); sessionRule.session().execute(insert); diff --git a/manual/core/query_timestamps/README.md b/manual/core/query_timestamps/README.md index 0ad0761de28..ff795325f99 100644 --- a/manual/core/query_timestamps/README.md +++ b/manual/core/query_timestamps/README.md @@ -145,7 +145,7 @@ Finally, you can assign a timestamp to a statement directly from application cod ```java Statement statement = SimpleStatement.builder("UPDATE users SET email = 'x@y.com' where id = 1") - .setTimestamp(1432815430948040L) + .setQueryTimestamp(1432815430948040L) .build(); session.execute(statement); ``` diff --git a/manual/core/statements/prepared/README.md b/manual/core/statements/prepared/README.md index e88354456d0..5bb038f2ef0 100644 --- a/manual/core/statements/prepared/README.md +++ b/manual/core/statements/prepared/README.md @@ -161,7 +161,7 @@ BoundStatement bound = .setString(0, "324378") .setString(1, "LCD screen") .setExecutionProfileName("oltp") - .setTimestamp(123456789L) + .setQueryTimestamp(123456789L) .build(); ``` From a6e94eff463b6dea0d31ad933aaec42e2d095acb Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 11 Mar 2019 15:14:21 +0200 Subject: [PATCH 718/742] Fix wrong example in Relation class javadocs --- .../datastax/oss/driver/api/querybuilder/relation/Relation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java index fd6a5721108..48d478e549b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/Relation.java @@ -39,7 +39,7 @@ * example: * *
        {@code
        - * selectFrom("foo").all().whereColumn("k").isEqualTo(literal(1))
        + * selectFrom("foo").all().where(Relation.column("k").isEqualTo(literal(1)))
          * // SELECT * FROM foo WHERE k=1
          * }
        * From 7be8465e5301524ad377d730f88088a006fb068c Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 7 Mar 2019 18:41:55 -0800 Subject: [PATCH 719/742] JAVA-2177: Don't exclude down nodes when initializing LBPs Motivation: In driver 4, the LBP "pushes" the distances to the driver, not the other way around. So if we don't set the distance of a node in init(), it will stay at IGNORED and the driver won't try to connect to it. Therefore all nodes should be passed to init(). Modifications: Don't filter the nodes in LoadBalancingPolicyWrapper. Update the javadocs of LoadBalancingPolicy to explain the contract. Update DefaultLoadBalancingPolicy to only add UP nodes to its live set. Result: Down nodes don't stay ignored anymore. --- changelog/README.md | 1 + .../loadbalancing/LoadBalancingPolicy.java | 42 ++++++++++++------ .../DefaultLoadBalancingPolicy.java | 8 +++- .../metadata/LoadBalancingPolicyWrapper.java | 15 +------ .../DefaultLoadBalancingPolicyInitTest.java | 8 +++- .../LoadBalancingPolicyWrapperTest.java | 4 +- .../oss/driver/api/core/ConnectIT.java | 43 ++++++++++++++++++- 7 files changed, 90 insertions(+), 31 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 36648c6b719..5ca6e2d13f1 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [bug] JAVA-2177: Don't exclude down nodes when initializing LBPs - [improvement] JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp() - [improvement] JAVA-2165: Abstract node connection information - [improvement] JAVA-2090: Add support for additional_write_policy and read_repair table options diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java index c45050aa5b8..425e11c0c5a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/loadbalancing/LoadBalancingPolicy.java @@ -32,13 +32,27 @@ public interface LoadBalancingPolicy extends AutoCloseable { * Initializes this policy with the nodes discovered during driver initialization. * *

        This method is guaranteed to be called exactly once per instance, and before any other - * method in this class. + * method in this class. At this point, the driver has successfully connected to one of the + * contact points, and performed a first refresh of topology information (by default, the contents + * of {@code system.peers}), to discover other nodes in the cluster. * - * @param nodes the nodes discovered by the driver when it connected to the cluster. When this - * method is invoked, their state is guaranteed to be either {@link NodeState#UP} or {@link - * NodeState#UNKNOWN}. Node states may be updated concurrently while this method executes, but - * if so you will receive a notification. + *

        This method must call {@link DistanceReporter#setDistance(Node, NodeDistance) + * distanceReporter.setDistance} for each provided node (otherwise that node will stay at distance + * {@link NodeDistance#IGNORED IGNORED}, and the driver won't open connections to it). Note that + * the node's {@link Node#getState() state} can be either {@link NodeState#UP UP} (for the + * successful contact point), {@link NodeState#DOWN DOWN} (for contact points that were tried + * unsuccessfully), or {@link NodeState#UNKNOWN UNKNOWN} (for contact points that weren't tried, + * or any other node discovered from the topology refresh). Node states may be updated + * concurrently while this method executes, but if so this policy will get notified after this + * method has returned, through other methods such as {@link #onUp(Node)} or {@link + * #onDown(Node)}. + * + * @param nodes all the nodes that are known to exist in the cluster (regardless of their state) + * at the time of invocation. * @param distanceReporter an object that will be used by the policy to signal distance changes. + * Implementations will typically store a this in a field, since new nodes may get {@link + * #onAdd(Node) added} later and will need to have their distance set (or the policy might + * change distances dynamically over time). */ void init(@NonNull Map nodes, @NonNull DistanceReporter distanceReporter); @@ -60,14 +74,18 @@ public interface LoadBalancingPolicy extends AutoCloseable { /** * Called when a node is added to the cluster. * - *

        The new node will have the state {@link NodeState#UNKNOWN}. The actual state will be known - * when: + *

        The new node will be at distance {@link NodeDistance#IGNORED IGNORED}, and have the state + * {@link NodeState#UNKNOWN UNKNOWN}. + * + *

        If this method assigns an active distance to the node, the driver will try to create a + * connection pool to it (resulting in a state change to {@link #onUp(Node) UP} or {@link + * #onDown(Node) DOWN} depending on the outcome). + * + *

        If it leaves it at distance {@link NodeDistance#IGNORED IGNORED}, the driver won't attempt + * any connection. The node state will remain unknown, but might be updated later if a topology + * event is received from the cluster. * - *

          - *
        • the load balancing policy signals an active distance for the node, and the driver tries - * to connect to it. - *
        • or a topology event is received from the cluster. - *
        + * @see #init(Map, DistanceReporter) */ void onAdd(@NonNull Node node); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index f975755fc57..28ef26fdb27 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; @@ -160,7 +161,12 @@ public void init(@NonNull Map nodes, @NonNull DistanceReporter dista for (Node node : nodes.values()) { if (filter.test(node)) { distanceReporter.setDistance(node, NodeDistance.LOCAL); - localDcLiveNodes.add(node); + if (node.getState() != NodeState.DOWN) { + // This includes state == UNKNOWN. If the node turns out to be unreachable, this will be + // detected when we try to open a pool to it, it will get marked down and this will be + // signaled back to this policy + localDcLiveNodes.add(node); + } } else { distanceReporter.setDistance(node, NodeDistance.IGNORED); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java index d1d932eb7c8..bdf0c392e0c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapper.java @@ -36,7 +36,6 @@ import java.util.Map; import java.util.Queue; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; @@ -119,7 +118,7 @@ public void init() { MetadataManager metadataManager = context.getMetadataManager(); Metadata metadata = metadataManager.getMetadata(); for (LoadBalancingPolicy policy : policies) { - policy.init(excludeDownHosts(metadata), reporters.get(policy)); + policy.init(metadata.getNodes(), reporters.get(policy)); } if (stateRef.compareAndSet(State.DURING_INIT, State.RUNNING)) { eventFilter.markReady(); @@ -195,18 +194,6 @@ private void processNodeStateEvent(NodeStateEvent event) { } } - private static Map excludeDownHosts(Metadata metadata) { - ImmutableMap.Builder nodes = ImmutableMap.builder(); - for (Map.Entry entry : metadata.getNodes().entrySet()) { - UUID id = entry.getKey(); - Node node = entry.getValue(); - if (node.getState() == NodeState.UP || node.getState() == NodeState.UNKNOWN) { - nodes.put(id, node); - } - } - return nodes.build(); - } - @Override public void close() { State old; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java index ae51c38cb0d..f1d2c68fa43 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyInitTest.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import java.util.UUID; @@ -129,6 +130,9 @@ public void should_warn_if_contact_points_not_in_local_dc() { public void should_include_nodes_from_local_dc() { // Given when(metadataManager.getContactPoints()).thenReturn(ImmutableSet.of(node1, node2)); + when(node1.getState()).thenReturn(NodeState.UP); + when(node2.getState()).thenReturn(NodeState.DOWN); + when(node3.getState()).thenReturn(NodeState.UNKNOWN); DefaultLoadBalancingPolicy policy = new DefaultLoadBalancingPolicy(context, DriverExecutionProfile.DEFAULT_NAME); @@ -139,10 +143,12 @@ public void should_include_nodes_from_local_dc() { distanceReporter); // Then + // Set distance for all nodes in the local DC verify(distanceReporter).setDistance(node1, NodeDistance.LOCAL); verify(distanceReporter).setDistance(node2, NodeDistance.LOCAL); verify(distanceReporter).setDistance(node3, NodeDistance.LOCAL); - assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node2, node3); + // But only include UP or UNKNOWN nodes in the live set + assertThat(policy.localDcLiveNodes).containsExactlyInAnyOrder(node1, node3); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java index b7ce9dfea77..d7be8e96b0b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/LoadBalancingPolicyWrapperTest.java @@ -146,7 +146,7 @@ public void should_fetch_query_plan_from_policy_after_init() { } @Test - public void should_init_policies_with_up_or_unknown_nodes() { + public void should_init_policies_with_all_nodes() { // Given node1.state = NodeState.UP; node2.state = NodeState.UNKNOWN; @@ -159,7 +159,7 @@ public void should_init_policies_with_up_or_unknown_nodes() { for (LoadBalancingPolicy policy : ImmutableList.of(policy1, policy2, policy3)) { verify(policy).init(initNodesCaptor.capture(), any(DistanceReporter.class)); Map initNodes = initNodesCaptor.getValue(); - assertThat(initNodes.values()).containsOnly(node1, node2); + assertThat(initNodes.values()).containsOnly(node1, node2, node3); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java index 5f5c1a10ad4..6d1b525f23a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/ConnectIT.java @@ -23,15 +23,22 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.NodeState; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule; +import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; import com.datastax.oss.driver.internal.core.connection.ConstantReconnectionPolicy; import com.datastax.oss.simulacron.common.cluster.ClusterSpec; +import com.datastax.oss.simulacron.server.BoundCluster; import com.datastax.oss.simulacron.server.RejectScope; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @@ -47,7 +54,7 @@ public class ConnectIT { @ClassRule public static SimulacronRule simulacronRule = - new SimulacronRule(ClusterSpec.builder().withNodes(1)); + new SimulacronRule(ClusterSpec.builder().withNodes(2)); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -127,6 +134,40 @@ public void should_cleanup_on_lbp_init_failure() { .becomesTrue(); } + /** + * Test for JAVA-2177. This ensures that even if the first attempted contact point is unreachable, + * its distance is set to LOCAL and reconnections are scheduled. + */ + @Test + public void should_mark_unreachable_contact_points_as_local_and_schedule_reconnections() { + // Reject connections only on one node + BoundCluster boundCluster = simulacronRule.cluster(); + boundCluster.node(0).rejectConnections(0, RejectScope.STOP); + + try (CqlSession session = SessionUtils.newSession(simulacronRule)) { + Map nodes = session.getMetadata().getNodes(); + // Node states are updated asynchronously, so guard against race conditions + ConditionChecker.checkThat( + () -> { + // Before JAVA-2177, this would fail every other time because if the node was tried + // first for the initial connection, it was marked down and not passed to + // LBP.init(), and therefore stayed at distance IGNORED. + Node node0 = nodes.get(boundCluster.node(0).getHostId()); + assertThat(node0.getState()).isEqualTo(NodeState.DOWN); + assertThat(node0.getDistance()).isEqualTo(NodeDistance.LOCAL); + assertThat(node0.getOpenConnections()).isEqualTo(0); + assertThat(node0.isReconnecting()).isTrue(); + + Node node1 = nodes.get(boundCluster.node(1).getHostId()); + assertThat(node1.getState()).isEqualTo(NodeState.UP); + assertThat(node1.getDistance()).isEqualTo(NodeDistance.LOCAL); + assertThat(node1.getOpenConnections()).isEqualTo(2); // control + regular + assertThat(node1.isReconnecting()).isFalse(); + }) + .becomesTrue(); + } + } + @SuppressWarnings("unchecked") private CompletionStage newSessionAsync( SimulacronRule serverRule, DriverConfigLoader loader) { From 8dabb699e6afca3e84d7b6626bcec56bd3139a05 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Mar 2019 10:06:53 +0200 Subject: [PATCH 720/742] JAVA-2161: Annotate mutating methods with `@CheckReturnValue` (#1211) --- changelog/README.md | 1 + core/revapi.json | 4179 +++++++++++++++++ .../oss/driver/api/core/cql/Bindable.java | 4 + .../oss/driver/api/core/cql/Statement.java | 19 + .../driver/api/core/data/SettableById.java | 29 + .../driver/api/core/data/SettableByIndex.java | 29 + .../driver/api/core/data/SettableByName.java | 29 + .../config/DriverOptionConfigBuilder.java | 21 + .../internal/core/type/codec/TupleCodec.java | 4 +- .../internal/core/type/codec/UdtCodec.java | 4 +- .../core/data/AccessibleByIdTestBase.java | 63 +- .../core/data/AccessibleByIndexTestBase.java | 30 +- .../core/data/DefaultTupleValueTest.java | 7 +- .../core/data/DefaultUdtValueTest.java | 4 +- .../core/type/codec/TupleCodecTest.java | 12 +- .../core/type/codec/UdtCodecTest.java | 12 +- faq/README.md | 4 + .../driver/api/core/cql/BatchStatementIT.java | 2 +- .../oss/driver/api/core/data/DataTypeIT.java | 6 +- manual/core/statements/README.md | 4 + query-builder/revapi.json | 2730 +++++++++++ .../condition/ConditionalStatement.java | 6 + .../relation/OngoingWhereClause.java | 7 + .../querybuilder/schema/KeyspaceOptions.java | 2 + .../querybuilder/schema/OptionProvider.java | 2 + .../querybuilder/schema/RelationOptions.java | 23 + .../schema/RelationStructure.java | 5 + .../schema/compaction/CompactionStrategy.java | 5 + .../compaction/LeveledCompactionStrategy.java | 2 + .../SizeTieredCompactionStrategy.java | 8 + .../TimeWindowCompactionStrategy.java | 4 + 31 files changed, 7185 insertions(+), 72 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 5ca6e2d13f1..a89d2c070c3 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2161: Annotate mutating methods with `@CheckReturnValue` - [bug] JAVA-2177: Don't exclude down nodes when initializing LBPs - [improvement] JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp() - [improvement] JAVA-2165: Abstract node connection information diff --git a/core/revapi.json b/core/revapi.json index 148d9fc7baa..f933d254c16 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -254,6 +254,4185 @@ "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimestamp(long)", "justification": "JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp()" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.Bindable>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(int) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Bindable>>::unset(java.lang.String) @ com.datastax.oss.driver.api.core.cql.BoundStatementBuilder", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[]) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::copy(java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setCustomPayload(java.util.Map)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setExecutionProfileName(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setIdempotent(java.lang.Boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setNode(com.datastax.oss.driver.api.core.metadata.Node)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPageSize(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setPagingState(java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[])", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKey(java.nio.ByteBuffer[])", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingKeyspace(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTimeout(java.time.Duration)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setTracing(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.SettableById>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.SettableByName>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID)", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.TupleValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.TupleValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::set(com.datastax.oss.driver.api.core.CqlIdentifier, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::set(int, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.codec.TypeCodec) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, com.datastax.oss.driver.api.core.type.reflect.GenericType) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::set(java.lang.String, ValueT, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigDecimal(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigDecimal(int, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigDecimal(java.lang.String, java.math.BigDecimal) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBigInteger(com.datastax.oss.driver.api.core.CqlIdentifier, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBigInteger(int, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBigInteger(java.lang.String, java.math.BigInteger) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBoolean(com.datastax.oss.driver.api.core.CqlIdentifier, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBoolean(int, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBoolean(java.lang.String, boolean) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByte(com.datastax.oss.driver.api.core.CqlIdentifier, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByte(int, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByte(java.lang.String, byte) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setByteBuffer(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setByteBuffer(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setByteBuffer(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setBytesUnsafe(com.datastax.oss.driver.api.core.CqlIdentifier, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setBytesUnsafe(int, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setBytesUnsafe(java.lang.String, java.nio.ByteBuffer) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setCqlDuration(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setCqlDuration(int, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setCqlDuration(java.lang.String, com.datastax.oss.driver.api.core.data.CqlDuration) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setDouble(com.datastax.oss.driver.api.core.CqlIdentifier, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setDouble(int, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setDouble(java.lang.String, double) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setFloat(com.datastax.oss.driver.api.core.CqlIdentifier, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setFloat(int, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setFloat(java.lang.String, float) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInetAddress(com.datastax.oss.driver.api.core.CqlIdentifier, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInetAddress(int, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInetAddress(java.lang.String, java.net.InetAddress) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInstant(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInstant(int, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInstant(java.lang.String, java.time.Instant) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setInt(com.datastax.oss.driver.api.core.CqlIdentifier, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setInt(int, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setInt(java.lang.String, int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setList(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setList(int, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setList(java.lang.String, java.util.List, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalDate(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalDate(int, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalDate(java.lang.String, java.time.LocalDate) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLocalTime(com.datastax.oss.driver.api.core.CqlIdentifier, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLocalTime(int, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLocalTime(java.lang.String, java.time.LocalTime) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setLong(com.datastax.oss.driver.api.core.CqlIdentifier, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setLong(int, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setLong(java.lang.String, long) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setMap(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setMap(int, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setMap(java.lang.String, java.util.Map, java.lang.Class, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setSet(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setSet(int, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setSet(java.lang.String, java.util.Set, java.lang.Class) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setShort(com.datastax.oss.driver.api.core.CqlIdentifier, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setShort(int, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setShort(java.lang.String, short) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setString(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setString(int, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setString(java.lang.String, java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToNull(com.datastax.oss.driver.api.core.CqlIdentifier) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToNull(int) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToNull(java.lang.String) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setToken(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setToken(int, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setToken(java.lang.String, com.datastax.oss.driver.api.core.metadata.token.Token) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setTupleValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setTupleValue(int, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setTupleValue(java.lang.String, com.datastax.oss.driver.api.core.data.TupleValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUdtValue(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUdtValue(int, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUdtValue(java.lang.String, com.datastax.oss.driver.api.core.data.UdtValue) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableById>>::setUuid(com.datastax.oss.driver.api.core.CqlIdentifier, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByIndex>>::setUuid(int, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java index 0542f44b168..dc9577ae23e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Bindable.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.data.SettableById; import com.datastax.oss.driver.api.core.data.SettableByName; import com.datastax.oss.protocol.internal.ProtocolConstants; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; /** A data container with the ability to unset values. */ @@ -70,6 +71,7 @@ default boolean isSet(@NonNull String name) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT unset(int i) { return setBytesUnsafe(i, ProtocolConstants.UNSET_VALUE); } @@ -81,6 +83,7 @@ default SelfT unset(int i) { * @throws IndexOutOfBoundsException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT unset(@NonNull CqlIdentifier id) { return setBytesUnsafe(id, ProtocolConstants.UNSET_VALUE); } @@ -92,6 +95,7 @@ default SelfT unset(@NonNull CqlIdentifier id) { * @throws IndexOutOfBoundsException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT unset(@NonNull String name) { return setBytesUnsafe(name, ProtocolConstants.UNSET_VALUE); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index 298813bc5e3..d45f644c0e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -31,6 +31,7 @@ import com.datastax.oss.driver.api.core.time.TimestampGenerator; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.internal.core.util.RoutingKey; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; @@ -76,6 +77,7 @@ public interface Statement> extends Request { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull + @CheckReturnValue SelfT setExecutionProfileName(@Nullable String newConfigProfileName); /** @@ -85,6 +87,7 @@ public interface Statement> extends Request { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull + @CheckReturnValue SelfT setExecutionProfile(@Nullable DriverExecutionProfile newProfile); /** @@ -95,6 +98,7 @@ public interface Statement> extends Request { * @param newRoutingKeyspace The keyspace to use, or {@code null} to disable token-aware routing. */ @NonNull + @CheckReturnValue SelfT setRoutingKeyspace(@Nullable CqlIdentifier newRoutingKeyspace); /** @@ -119,6 +123,7 @@ public interface Statement> extends Request { * delegate to the configured load balancing policy. */ @NonNull + @CheckReturnValue SelfT setNode(@Nullable Node node); /** @@ -129,6 +134,7 @@ public interface Statement> extends Request { * routing. */ @NonNull + @CheckReturnValue default SelfT setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { return setRoutingKeyspace( newRoutingKeyspaceName == null ? null : CqlIdentifier.fromCql(newRoutingKeyspaceName)); @@ -142,6 +148,7 @@ default SelfT setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { * @param newRoutingKey The routing key to use, or {@code null} to disable token-aware routing. */ @NonNull + @CheckReturnValue SelfT setRoutingKey(@Nullable ByteBuffer newRoutingKey); /** @@ -152,6 +159,7 @@ default SelfT setRoutingKeyspace(@Nullable String newRoutingKeyspaceName) { * can be {@code null}. */ @NonNull + @CheckReturnValue default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { return setRoutingKey(RoutingKey.compose(newRoutingKeyComponents)); } @@ -165,6 +173,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * routing. */ @NonNull + @CheckReturnValue SelfT setRoutingToken(@Nullable Token newRoutingToken); /** @@ -179,6 +188,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * it's never modified after being set on the statement). */ @NonNull + @CheckReturnValue SelfT setCustomPayload(@NonNull Map newCustomPayload); /** @@ -191,6 +201,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * use the default idempotence defined in the configuration. */ @NonNull + @CheckReturnValue SelfT setIdempotent(@Nullable Boolean newIdempotence); /** @@ -200,6 +211,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * method. However custom implementations may choose to be mutable and return the same instance. */ @NonNull + @CheckReturnValue SelfT setTracing(boolean newTracing); /** @@ -224,6 +236,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see TimestampGenerator */ @NonNull + @CheckReturnValue SelfT setQueryTimestamp(long newTimestamp); /** @@ -235,6 +248,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_TIMEOUT */ @NonNull + @CheckReturnValue SelfT setTimeout(@Nullable Duration newTimeout); /** @@ -265,6 +279,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * if you do so, you must override {@link #copy(ByteBuffer)}. */ @NonNull + @CheckReturnValue SelfT setPagingState(@Nullable ByteBuffer newPagingState); /** @@ -285,6 +300,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_PAGE_SIZE */ @NonNull + @CheckReturnValue SelfT setPageSize(int newPageSize); /** @@ -304,6 +320,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * defined in the configuration. * @see DefaultDriverOption#REQUEST_CONSISTENCY */ + @CheckReturnValue SelfT setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel); /** @@ -324,6 +341,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * @see DefaultDriverOption#REQUEST_SERIAL_CONSISTENCY */ @NonNull + @CheckReturnValue SelfT setSerialConsistencyLevel(@Nullable ConsistencyLevel newSerialConsistencyLevel); /** Whether tracing information should be recorded for this statement. */ @@ -351,6 +369,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * your own mutable implementation, make sure it returns a different instance. */ @NonNull + @CheckReturnValue default SelfT copy(@Nullable ByteBuffer newPagingState) { return setPagingState(newPagingState); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java index 943c499e9cf..de9a906ca49 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableById.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigDecimal; @@ -56,6 +57,7 @@ public interface SettableById> * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setBytesUnsafe(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { return setBytesUnsafe(firstIndexOf(id), v); } @@ -75,6 +77,7 @@ default DataType getType(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setToNull(@NonNull CqlIdentifier id) { return setToNull(firstIndexOf(id)); } @@ -96,6 +99,7 @@ default SelfT setToNull(@NonNull CqlIdentifier id) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull TypeCodec codec) { return set(firstIndexOf(id), v, codec); @@ -116,6 +120,7 @@ default SelfT set( * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull GenericType targetType) { return set(firstIndexOf(id), v, targetType); @@ -135,6 +140,7 @@ default SelfT set( * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull CqlIdentifier id, @Nullable ValueT v, @NonNull Class targetClass) { return set(firstIndexOf(id), v, targetClass); @@ -154,6 +160,7 @@ default SelfT set( * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setBoolean(@NonNull CqlIdentifier id, boolean v) { return setBoolean(firstIndexOf(id), v); } @@ -172,6 +179,7 @@ default SelfT setBoolean(@NonNull CqlIdentifier id, boolean v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setByte(@NonNull CqlIdentifier id, byte v) { return setByte(firstIndexOf(id), v); } @@ -190,6 +198,7 @@ default SelfT setByte(@NonNull CqlIdentifier id, byte v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setDouble(@NonNull CqlIdentifier id, double v) { return setDouble(firstIndexOf(id), v); } @@ -208,6 +217,7 @@ default SelfT setDouble(@NonNull CqlIdentifier id, double v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setFloat(@NonNull CqlIdentifier id, float v) { return setFloat(firstIndexOf(id), v); } @@ -226,6 +236,7 @@ default SelfT setFloat(@NonNull CqlIdentifier id, float v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setInt(@NonNull CqlIdentifier id, int v) { return setInt(firstIndexOf(id), v); } @@ -244,6 +255,7 @@ default SelfT setInt(@NonNull CqlIdentifier id, int v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setLong(@NonNull CqlIdentifier id, long v) { return setLong(firstIndexOf(id), v); } @@ -262,6 +274,7 @@ default SelfT setLong(@NonNull CqlIdentifier id, long v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setShort(@NonNull CqlIdentifier id, short v) { return setShort(firstIndexOf(id), v); } @@ -277,6 +290,7 @@ default SelfT setShort(@NonNull CqlIdentifier id, short v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { return setInstant(firstIndexOf(id), v); } @@ -292,6 +306,7 @@ default SelfT setInstant(@NonNull CqlIdentifier id, @Nullable Instant v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { return setLocalDate(firstIndexOf(id), v); } @@ -307,6 +322,7 @@ default SelfT setLocalDate(@NonNull CqlIdentifier id, @Nullable LocalDate v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { return setLocalTime(firstIndexOf(id), v); } @@ -322,6 +338,7 @@ default SelfT setLocalTime(@NonNull CqlIdentifier id, @Nullable LocalTime v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { return setByteBuffer(firstIndexOf(id), v); } @@ -337,6 +354,7 @@ default SelfT setByteBuffer(@NonNull CqlIdentifier id, @Nullable ByteBuffer v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setString(@NonNull CqlIdentifier id, @Nullable String v) { return setString(firstIndexOf(id), v); } @@ -352,6 +370,7 @@ default SelfT setString(@NonNull CqlIdentifier id, @Nullable String v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { return setBigInteger(firstIndexOf(id), v); } @@ -367,6 +386,7 @@ default SelfT setBigInteger(@NonNull CqlIdentifier id, @Nullable BigInteger v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { return setBigDecimal(firstIndexOf(id), v); } @@ -382,6 +402,7 @@ default SelfT setBigDecimal(@NonNull CqlIdentifier id, @Nullable BigDecimal v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { return setUuid(firstIndexOf(id), v); } @@ -397,6 +418,7 @@ default SelfT setUuid(@NonNull CqlIdentifier id, @Nullable UUID v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) { return setInetAddress(firstIndexOf(id), v); } @@ -412,6 +434,7 @@ default SelfT setInetAddress(@NonNull CqlIdentifier id, @Nullable InetAddress v) * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) { return setCqlDuration(firstIndexOf(id), v); } @@ -429,6 +452,7 @@ default SelfT setCqlDuration(@NonNull CqlIdentifier id, @Nullable CqlDuration v) * @throws IllegalArgumentException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setToken(@NonNull CqlIdentifier id, @NonNull Token v) { return setToken(firstIndexOf(id), v); } @@ -447,6 +471,7 @@ default SelfT setToken(@NonNull CqlIdentifier id, @NonNull Token v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setList( @NonNull CqlIdentifier id, @Nullable List v, @@ -468,6 +493,7 @@ default SelfT setList( * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setSet( @NonNull CqlIdentifier id, @Nullable Set v, @@ -489,6 +515,7 @@ default SelfT setSet( * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setMap( @NonNull CqlIdentifier id, @Nullable Map v, @@ -508,6 +535,7 @@ default SelfT setMap( * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { return setUdtValue(firstIndexOf(id), v); } @@ -523,6 +551,7 @@ default SelfT setUdtValue(@NonNull CqlIdentifier id, @Nullable UdtValue v) { * @throws IllegalArgumentException if the id is invalid. */ @NonNull + @CheckReturnValue default SelfT setTupleValue(@NonNull CqlIdentifier id, @Nullable TupleValue v) { return setTupleValue(firstIndexOf(id), v); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java index a3d3d79c084..2ff700cc3fa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByIndex.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.ByteOrderedToken; import com.datastax.oss.driver.internal.core.metadata.token.Murmur3Token; import com.datastax.oss.driver.internal.core.metadata.token.RandomToken; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigDecimal; @@ -61,6 +62,7 @@ public interface SettableByIndex> extends A * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue SelfT setBytesUnsafe(int i, @Nullable ByteBuffer v); /** @@ -69,6 +71,7 @@ public interface SettableByIndex> extends A * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setToNull(int i) { return setBytesUnsafe(i, null); } @@ -86,6 +89,7 @@ default SelfT setToNull(int i) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT set(int i, @Nullable ValueT v, @NonNull TypeCodec codec) { return setBytesUnsafe(i, codec.encode(v, protocolVersion())); } @@ -102,6 +106,7 @@ default SelfT set(int i, @Nullable ValueT v, @NonNull TypeCodec * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull + @CheckReturnValue default SelfT set(int i, @Nullable ValueT v, @NonNull GenericType targetType) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, targetType); @@ -119,6 +124,7 @@ default SelfT set(int i, @Nullable ValueT v, @NonNull GenericType SelfT set(int i, @Nullable ValueT v, @NonNull Class targetClass) { // This is duplicated from the GenericType variant, because we want to give the codec registry // a chance to process the unwrapped class directly, if it can do so in a more efficient way. @@ -138,6 +144,7 @@ default SelfT set(int i, @Nullable ValueT v, @NonNull Class tar * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setBoolean(int i, boolean v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Boolean.class); @@ -157,6 +164,7 @@ default SelfT setBoolean(int i, boolean v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setByte(int i, byte v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Byte.class); @@ -176,6 +184,7 @@ default SelfT setByte(int i, byte v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setDouble(int i, double v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Double.class); @@ -195,6 +204,7 @@ default SelfT setDouble(int i, double v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setFloat(int i, float v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Float.class); @@ -214,6 +224,7 @@ default SelfT setFloat(int i, float v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setInt(int i, int v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Integer.class); @@ -233,6 +244,7 @@ default SelfT setInt(int i, int v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setLong(int i, long v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Long.class); @@ -252,6 +264,7 @@ default SelfT setLong(int i, long v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setShort(int i, short v) { DataType cqlType = getType(i); TypeCodec codec = codecRegistry().codecFor(cqlType, Short.class); @@ -268,6 +281,7 @@ default SelfT setShort(int i, short v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setInstant(int i, @Nullable Instant v) { return set(i, v, Instant.class); } @@ -280,6 +294,7 @@ default SelfT setInstant(int i, @Nullable Instant v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalDate(int i, @Nullable LocalDate v) { return set(i, v, LocalDate.class); } @@ -292,6 +307,7 @@ default SelfT setLocalDate(int i, @Nullable LocalDate v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalTime(int i, @Nullable LocalTime v) { return set(i, v, LocalTime.class); } @@ -304,6 +320,7 @@ default SelfT setLocalTime(int i, @Nullable LocalTime v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setByteBuffer(int i, @Nullable ByteBuffer v) { return set(i, v, ByteBuffer.class); } @@ -316,6 +333,7 @@ default SelfT setByteBuffer(int i, @Nullable ByteBuffer v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setString(int i, @Nullable String v) { return set(i, v, String.class); } @@ -328,6 +346,7 @@ default SelfT setString(int i, @Nullable String v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigInteger(int i, @Nullable BigInteger v) { return set(i, v, BigInteger.class); } @@ -340,6 +359,7 @@ default SelfT setBigInteger(int i, @Nullable BigInteger v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigDecimal(int i, @Nullable BigDecimal v) { return set(i, v, BigDecimal.class); } @@ -352,6 +372,7 @@ default SelfT setBigDecimal(int i, @Nullable BigDecimal v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setUuid(int i, @Nullable UUID v) { return set(i, v, UUID.class); } @@ -364,6 +385,7 @@ default SelfT setUuid(int i, @Nullable UUID v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setInetAddress(int i, @Nullable InetAddress v) { return set(i, v, InetAddress.class); } @@ -376,6 +398,7 @@ default SelfT setInetAddress(int i, @Nullable InetAddress v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setCqlDuration(int i, @Nullable CqlDuration v) { return set(i, v, CqlDuration.class); } @@ -390,6 +413,7 @@ default SelfT setCqlDuration(int i, @Nullable CqlDuration v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setToken(int i, @NonNull Token v) { // Simply enumerate all known implementations. This goes against the concept of TokenFactory, // but injecting the factory here is too much of a hassle. @@ -417,6 +441,7 @@ default SelfT setToken(int i, @NonNull Token v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setList( int i, @Nullable List v, @NonNull Class elementsClass) { return set(i, v, GenericType.listOf(elementsClass)); @@ -433,6 +458,7 @@ default SelfT setList( * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setSet( int i, @Nullable Set v, @NonNull Class elementsClass) { return set(i, v, GenericType.setOf(elementsClass)); @@ -449,6 +475,7 @@ default SelfT setSet( * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setMap( int i, @Nullable Map v, @@ -465,6 +492,7 @@ default SelfT setMap( * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setUdtValue(int i, @Nullable UdtValue v) { return set(i, v, UdtValue.class); } @@ -477,6 +505,7 @@ default SelfT setUdtValue(int i, @Nullable UdtValue v) { * @throws IndexOutOfBoundsException if the index is invalid. */ @NonNull + @CheckReturnValue default SelfT setTupleValue(int i, @Nullable TupleValue v) { return set(i, v, TupleValue.class); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java index bfdb656ef44..0ebd95b22cc 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/SettableByName.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigDecimal; @@ -55,6 +56,7 @@ public interface SettableByName> * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setBytesUnsafe(@NonNull String name, @Nullable ByteBuffer v) { return setBytesUnsafe(firstIndexOf(name), v); } @@ -74,6 +76,7 @@ default DataType getType(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setToNull(@NonNull String name) { return setToNull(firstIndexOf(name)); } @@ -95,6 +98,7 @@ default SelfT setToNull(@NonNull String name) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull String name, @Nullable ValueT v, @NonNull TypeCodec codec) { return set(firstIndexOf(name), v, codec); @@ -115,6 +119,7 @@ default SelfT set( * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull String name, @Nullable ValueT v, @NonNull GenericType targetType) { return set(firstIndexOf(name), v, targetType); @@ -135,6 +140,7 @@ default SelfT set( * @throws CodecNotFoundException if no codec can perform the conversion. */ @NonNull + @CheckReturnValue default SelfT set( @NonNull String name, @Nullable ValueT v, @NonNull Class targetClass) { return set(firstIndexOf(name), v, targetClass); @@ -154,6 +160,7 @@ default SelfT set( * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setBoolean(@NonNull String name, boolean v) { return setBoolean(firstIndexOf(name), v); } @@ -172,6 +179,7 @@ default SelfT setBoolean(@NonNull String name, boolean v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setByte(@NonNull String name, byte v) { return setByte(firstIndexOf(name), v); } @@ -190,6 +198,7 @@ default SelfT setByte(@NonNull String name, byte v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setDouble(@NonNull String name, double v) { return setDouble(firstIndexOf(name), v); } @@ -208,6 +217,7 @@ default SelfT setDouble(@NonNull String name, double v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setFloat(@NonNull String name, float v) { return setFloat(firstIndexOf(name), v); } @@ -226,6 +236,7 @@ default SelfT setFloat(@NonNull String name, float v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setInt(@NonNull String name, int v) { return setInt(firstIndexOf(name), v); } @@ -244,6 +255,7 @@ default SelfT setInt(@NonNull String name, int v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setLong(@NonNull String name, long v) { return setLong(firstIndexOf(name), v); } @@ -262,6 +274,7 @@ default SelfT setLong(@NonNull String name, long v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setShort(@NonNull String name, short v) { return setShort(firstIndexOf(name), v); } @@ -277,6 +290,7 @@ default SelfT setShort(@NonNull String name, short v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setInstant(@NonNull String name, @Nullable Instant v) { return setInstant(firstIndexOf(name), v); } @@ -292,6 +306,7 @@ default SelfT setInstant(@NonNull String name, @Nullable Instant v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalDate(@NonNull String name, @Nullable LocalDate v) { return setLocalDate(firstIndexOf(name), v); } @@ -307,6 +322,7 @@ default SelfT setLocalDate(@NonNull String name, @Nullable LocalDate v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setLocalTime(@NonNull String name, @Nullable LocalTime v) { return setLocalTime(firstIndexOf(name), v); } @@ -322,6 +338,7 @@ default SelfT setLocalTime(@NonNull String name, @Nullable LocalTime v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { return setByteBuffer(firstIndexOf(name), v); } @@ -337,6 +354,7 @@ default SelfT setByteBuffer(@NonNull String name, @Nullable ByteBuffer v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setString(@NonNull String name, @Nullable String v) { return setString(firstIndexOf(name), v); } @@ -352,6 +370,7 @@ default SelfT setString(@NonNull String name, @Nullable String v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigInteger(@NonNull String name, @Nullable BigInteger v) { return setBigInteger(firstIndexOf(name), v); } @@ -367,6 +386,7 @@ default SelfT setBigInteger(@NonNull String name, @Nullable BigInteger v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { return setBigDecimal(firstIndexOf(name), v); } @@ -382,6 +402,7 @@ default SelfT setBigDecimal(@NonNull String name, @Nullable BigDecimal v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setUuid(@NonNull String name, @Nullable UUID v) { return setUuid(firstIndexOf(name), v); } @@ -397,6 +418,7 @@ default SelfT setUuid(@NonNull String name, @Nullable UUID v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setInetAddress(@NonNull String name, @Nullable InetAddress v) { return setInetAddress(firstIndexOf(name), v); } @@ -412,6 +434,7 @@ default SelfT setInetAddress(@NonNull String name, @Nullable InetAddress v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { return setCqlDuration(firstIndexOf(name), v); } @@ -429,6 +452,7 @@ default SelfT setCqlDuration(@NonNull String name, @Nullable CqlDuration v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setToken(@NonNull String name, @NonNull Token v) { return setToken(firstIndexOf(name), v); } @@ -447,6 +471,7 @@ default SelfT setToken(@NonNull String name, @NonNull Token v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setList( @NonNull String name, @Nullable List v, @NonNull Class elementsClass) { return setList(firstIndexOf(name), v, elementsClass); @@ -466,6 +491,7 @@ default SelfT setList( * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setSet( @NonNull String name, @Nullable Set v, @NonNull Class elementsClass) { return setSet(firstIndexOf(name), v, elementsClass); @@ -485,6 +511,7 @@ default SelfT setSet( * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setMap( @NonNull String name, @Nullable Map v, @@ -505,6 +532,7 @@ default SelfT setMap( * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setUdtValue(@NonNull String name, @Nullable UdtValue v) { return setUdtValue(firstIndexOf(name), v); } @@ -520,6 +548,7 @@ default SelfT setUdtValue(@NonNull String name, @Nullable UdtValue v) { * @throws IllegalArgumentException if the name is invalid. */ @NonNull + @CheckReturnValue default SelfT setTupleValue(@NonNull String name, @Nullable TupleValue v) { return setTupleValue(firstIndexOf(name), v); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java index cc6c8cbe901..5c871d5a16d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/DriverOptionConfigBuilder.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.config.DriverOption; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; @@ -38,57 +39,68 @@ public interface DriverOptionConfigBuilder { @NonNull + @CheckReturnValue default SelfT withBoolean(@NonNull DriverOption option, boolean value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withBooleanList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withInt(@NonNull DriverOption option, int value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withIntList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withLong(@NonNull DriverOption option, long value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withLongList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withDouble(@NonNull DriverOption option, double value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withDoubleList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withString(@NonNull DriverOption option, @NonNull String value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withStringList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @SuppressWarnings("unchecked") @NonNull + @CheckReturnValue default SelfT withStringMap(@NonNull DriverOption option, @NonNull Map value) { SelfT v = (SelfT) this; for (String key : value.keySet()) { @@ -103,42 +115,50 @@ default SelfT withStringMap(@NonNull DriverOption option, @NonNull Map value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withDuration(@NonNull DriverOption option, @NonNull Duration value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withDurationList(@NonNull DriverOption option, @NonNull List value) { return with(option, value); } @NonNull + @CheckReturnValue default SelfT withClass(@NonNull DriverOption option, @NonNull Class value) { return with(option, value.getName()); } /** Unsets an option. */ @NonNull + @CheckReturnValue default SelfT without(@NonNull DriverOption option) { return with(option, null); } @NonNull + @CheckReturnValue default SelfT with(@NonNull DriverOption option, @Nullable Object value) { return with(option.getPath(), value); } @@ -148,5 +168,6 @@ default SelfT with(@NonNull DriverOption option, @Nullable Object value) { * not recommended that it is used directly other than by these defaults. */ @NonNull + @CheckReturnValue SelfT with(@NonNull String path, @Nullable Object value); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java index 2b5e3e4a1a4..2a900ce7a10 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodec.java @@ -116,7 +116,7 @@ public TupleValue decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion pr element.limit(elementSize); input.position(input.position() + elementSize); } - value.setBytesUnsafe(i, element); + value = value.setBytesUnsafe(i, element); i += 1; } return value; @@ -194,7 +194,7 @@ public TupleValue parse(@Nullable String value) { String fieldValue = value.substring(position, n); DataType elementType = cqlType.getComponentTypes().get(i); TypeCodec codec = registry.codecFor(elementType); - tuple.set(i, codec.parse(fieldValue), codec); + tuple = tuple.set(i, codec.parse(fieldValue), codec); position = n; i += 1; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java index 04b08330e9e..2e2df95ad33 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java @@ -119,7 +119,7 @@ public UdtValue decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion prot element.limit(elementSize); input.position(input.position() + elementSize); } - value.setBytesUnsafe(i, element); + value = value.setBytesUnsafe(i, element); i += 1; } return value; @@ -224,7 +224,7 @@ public UdtValue parse(@Nullable String value) { // This works because ids occur at most once in UDTs DataType fieldType = cqlType.getFieldTypes().get(cqlType.firstIndexOf(id)); TypeCodec codec = registry.codecFor(fieldType); - udt.set(id, codec.parse(fieldValue), codec); + udt = udt.set(id, codec.parse(fieldValue), codec); position = n; position = ParseUtils.skipSpaces(value, position); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index a8eb7c7c72f..ad3ee2f199e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -50,7 +50,7 @@ public void should_set_primitive_value_by_id() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setInt(FIELD0_ID, 1); + t = t.setInt(FIELD0_ID, 1); // Then verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); @@ -64,7 +64,7 @@ public void should_set_object_value_by_id() { T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); // When - t.setString(FIELD0_ID, "a"); + t = t.setString(FIELD0_ID, "a"); // Then verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); @@ -78,7 +78,7 @@ public void should_set_bytes_by_id() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // Then verifyZeroInteractions(codecRegistry); @@ -89,10 +89,10 @@ public void should_set_bytes_by_id() { public void should_set_to_null_by_id() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When - t.setToNull(FIELD0_ID); + t = t.setToNull(FIELD0_ID); // Then verifyZeroInteractions(codecRegistry); @@ -107,7 +107,7 @@ public void should_set_with_explicit_class_by_id() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_ID, "1", String.class); + t = t.set(FIELD0_ID, "1", String.class); // Then verify(codecRegistry).codecFor(DataTypes.INT, String.class); @@ -124,7 +124,7 @@ public void should_set_with_explicit_type_by_id() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_ID, "1", GenericType.STRING); + t = t.set(FIELD0_ID, "1", GenericType.STRING); // Then verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); @@ -139,7 +139,7 @@ public void should_set_with_explicit_codec_by_id() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_ID, "1", intToStringCodec); + t = t.set(FIELD0_ID, "1", intToStringCodec); // Then verifyZeroInteractions(codecRegistry); @@ -151,7 +151,7 @@ public void should_set_with_explicit_codec_by_id() { public void should_get_primitive_value_by_id() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When int i = t.getInt(FIELD0_ID); @@ -166,7 +166,7 @@ public void should_get_primitive_value_by_id() { public void should_get_object_value_by_id() { // Given T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x61")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x61")); // When String s = t.getString(FIELD0_ID); @@ -181,7 +181,7 @@ public void should_get_object_value_by_id() { public void should_get_bytes_by_id() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When ByteBuffer bytes = t.getBytesUnsafe(FIELD0_ID); @@ -195,7 +195,7 @@ public void should_get_bytes_by_id() { public void should_test_if_null_by_id() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, null); + t = t.setBytesUnsafe(FIELD0_ID, null); // When boolean isNull = t.isNull(FIELD0_ID); @@ -211,7 +211,7 @@ public void should_get_with_explicit_class_by_id() { CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_ID, String.class); @@ -229,7 +229,7 @@ public void should_get_with_explicit_type_by_id() { when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_ID, GenericType.STRING); @@ -245,7 +245,7 @@ public void should_get_with_explicit_codec_by_id() { // Given CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_ID, intToStringCodec); @@ -262,7 +262,7 @@ public void should_set_primitive_value_by_name() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setInt(FIELD0_NAME, 1); + t = t.setInt(FIELD0_NAME, 1); // Then verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); @@ -276,7 +276,7 @@ public void should_set_object_value_by_name() { T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); // When - t.setString(FIELD0_NAME, "a"); + t = t.setString(FIELD0_NAME, "a"); // Then verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); @@ -290,7 +290,7 @@ public void should_set_bytes_by_name() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // Then verifyZeroInteractions(codecRegistry); @@ -301,10 +301,10 @@ public void should_set_bytes_by_name() { public void should_set_to_null_by_name() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When - t.setToNull(FIELD0_NAME); + t = t.setToNull(FIELD0_NAME); // Then verifyZeroInteractions(codecRegistry); @@ -319,7 +319,7 @@ public void should_set_with_explicit_class_by_name() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_NAME, "1", String.class); + t = t.set(FIELD0_NAME, "1", String.class); // Then verify(codecRegistry).codecFor(DataTypes.INT, String.class); @@ -336,7 +336,7 @@ public void should_set_with_explicit_type_by_name() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_NAME, "1", GenericType.STRING); + t = t.set(FIELD0_NAME, "1", GenericType.STRING); // Then verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); @@ -351,7 +351,7 @@ public void should_set_with_explicit_codec_by_name() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(FIELD0_NAME, "1", intToStringCodec); + t = t.set(FIELD0_NAME, "1", intToStringCodec); // Then verifyZeroInteractions(codecRegistry); @@ -363,7 +363,7 @@ public void should_set_with_explicit_codec_by_name() { public void should_get_primitive_value_by_name() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When int i = t.getInt(FIELD0_NAME); @@ -378,7 +378,7 @@ public void should_get_primitive_value_by_name() { public void should_get_object_value_by_name() { // Given T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x61")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x61")); // When String s = t.getString(FIELD0_NAME); @@ -393,7 +393,7 @@ public void should_get_object_value_by_name() { public void should_get_bytes_by_name() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When ByteBuffer bytes = t.getBytesUnsafe(FIELD0_NAME); @@ -407,7 +407,7 @@ public void should_get_bytes_by_name() { public void should_test_if_null_by_name() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, null); + t = t.setBytesUnsafe(FIELD0_NAME, null); // When boolean isNull = t.isNull(FIELD0_NAME); @@ -423,7 +423,7 @@ public void should_get_with_explicit_class_by_name() { CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_NAME, String.class); @@ -441,7 +441,7 @@ public void should_get_with_explicit_type_by_name() { when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_NAME, GenericType.STRING); @@ -457,7 +457,7 @@ public void should_get_with_explicit_codec_by_name() { // Given CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // When String s = t.get(FIELD0_NAME, intToStringCodec); @@ -468,6 +468,7 @@ public void should_get_with_explicit_codec_by_name() { assertThat(s).isEqualTo("1"); } + @SuppressWarnings("UnusedAssignment") @Test(expected = IllegalArgumentException.class) public void should_fail_when_id_does_not_exists() { final CqlIdentifier invalidField = CqlIdentifier.fromInternal("invalidField"); @@ -475,7 +476,7 @@ public void should_fail_when_id_does_not_exists() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setInt(invalidField, 1); + t = t.setInt(invalidField, 1); // Then the method will throw IllegalArgumentException up to the client. } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index e574360ef45..3239a655ece 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -88,7 +88,7 @@ public void should_set_primitive_value_by_index() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setInt(0, 1); + t = t.setInt(0, 1); // Then verify(codecRegistry).codecFor(DataTypes.INT, Integer.class); @@ -102,7 +102,7 @@ public void should_set_object_value_by_index() { T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); // When - t.setString(0, "a"); + t = t.setString(0, "a"); // Then verify(codecRegistry).codecFor(DataTypes.TEXT, String.class); @@ -116,7 +116,7 @@ public void should_set_bytes_by_index() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // Then verifyZeroInteractions(codecRegistry); @@ -127,10 +127,10 @@ public void should_set_bytes_by_index() { public void should_set_to_null_by_index() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When - t.setToNull(0); + t = t.setToNull(0); // Then verifyZeroInteractions(codecRegistry); @@ -145,7 +145,7 @@ public void should_set_with_explicit_class_by_index() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(0, "1", String.class); + t = t.set(0, "1", String.class); // Then verify(codecRegistry).codecFor(DataTypes.INT, String.class); @@ -162,7 +162,7 @@ public void should_set_with_explicit_type_by_index() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(0, "1", GenericType.STRING); + t = t.set(0, "1", GenericType.STRING); // Then verify(codecRegistry).codecFor(DataTypes.INT, GenericType.STRING); @@ -177,7 +177,7 @@ public void should_set_with_explicit_codec_by_index() { T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); // When - t.set(0, "1", intToStringCodec); + t = t.set(0, "1", intToStringCodec); // Then verifyZeroInteractions(codecRegistry); @@ -235,7 +235,7 @@ public void should_fail_to_set_values_in_bulk_when_too_many_values() { public void should_get_primitive_value_by_index() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When int i = t.getInt(0); @@ -250,7 +250,7 @@ public void should_get_primitive_value_by_index() { public void should_get_object_value_by_index() { // Given T t = newInstance(ImmutableList.of(DataTypes.TEXT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x61")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x61")); // When String s = t.getString(0); @@ -265,7 +265,7 @@ public void should_get_object_value_by_index() { public void should_get_bytes_by_index() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When ByteBuffer bytes = t.getBytesUnsafe(0); @@ -279,7 +279,7 @@ public void should_get_bytes_by_index() { public void should_test_if_null_by_index() { // Given T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, null); + t = t.setBytesUnsafe(0, null); // When boolean isNull = t.isNull(0); @@ -295,7 +295,7 @@ public void should_get_with_explicit_class_by_index() { CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); when(codecRegistry.codecFor(DataTypes.INT, String.class)).thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When String s = t.get(0, String.class); @@ -313,7 +313,7 @@ public void should_get_with_explicit_type_by_index() { when(codecRegistry.codecFor(DataTypes.INT, GenericType.STRING)) .thenAnswer(i -> intToStringCodec); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When String s = t.get(0, GenericType.STRING); @@ -329,7 +329,7 @@ public void should_get_with_explicit_codec_by_index() { // Given CqlIntToStringCodec intToStringCodec = spy(new CqlIntToStringCodec()); T t = newInstance(ImmutableList.of(DataTypes.INT), attachmentPoint); - t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // When String s = t.get(0, intToStringCodec); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java index 6c2b86dd1ef..07c1dc42a89 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultTupleValueTest.java @@ -28,7 +28,6 @@ import com.datastax.oss.driver.internal.core.type.DefaultTupleType; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import com.datastax.oss.protocol.internal.util.Bytes; -import java.io.UnsupportedEncodingException; import java.util.List; import org.junit.Test; @@ -52,8 +51,8 @@ public void should_serialize_and_deserialize() { DefaultTupleType type = new DefaultTupleType(ImmutableList.of(DataTypes.INT, DataTypes.TEXT), attachmentPoint); TupleValue in = type.newValue(); - in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); - in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); + in = in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + in = in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); TupleValue out = SerializationHelper.serializeAndDeserialize(in); @@ -64,7 +63,7 @@ public void should_serialize_and_deserialize() { } @Test - public void should_support_null_items_when_setting_in_bulk() throws UnsupportedEncodingException { + public void should_support_null_items_when_setting_in_bulk() { DefaultTupleType type = new DefaultTupleType(ImmutableList.of(DataTypes.INT, DataTypes.TEXT), attachmentPoint); when(codecRegistry.codecFor(DataTypes.INT)).thenReturn(TypeCodecs.INT); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java index 4a2752dc80a..c097528e46d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/DefaultUdtValueTest.java @@ -70,8 +70,8 @@ public void should_serialize_and_deserialize() { .withField(CqlIdentifier.fromInternal("field2"), DataTypes.TEXT) .build(); UdtValue in = type.newValue(); - in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); - in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); + in = in.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); + in = in.setBytesUnsafe(1, Bytes.fromHexString("0x61")); UdtValue out = SerializationHelper.serializeAndDeserialize(in); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index 95d5def804c..f7d609ea967 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -86,9 +86,9 @@ public void should_encode_null_tuple() { @Test public void should_encode_tuple() { TupleValue tuple = tupleType.newValue(); - tuple.setInt(0, 1); - tuple.setToNull(1); - tuple.setString(2, "a"); + tuple = tuple.setInt(0, 1); + tuple = tuple.setToNull(1); + tuple = tuple.setString(2, "a"); assertThat(encode(tuple)) .isEqualTo( @@ -130,9 +130,9 @@ public void should_format_null_tuple() { @Test public void should_format_tuple() { TupleValue tuple = tupleType.newValue(); - tuple.setInt(0, 1); - tuple.setToNull(1); - tuple.setString(2, "a"); + tuple = tuple.setInt(0, 1); + tuple = tuple.setToNull(1); + tuple = tuple.setString(2, "a"); assertThat(format(tuple)).isEqualTo("(1,NULL,'a')"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index bca5f73e569..5947cfffef3 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -95,9 +95,9 @@ public void should_encode_null_udt() { @Test public void should_encode_udt() { UdtValue udt = userType.newValue(); - udt.setInt("field1", 1); - udt.setToNull("field2"); - udt.setString("field3", "a"); + udt = udt.setInt("field1", 1); + udt = udt.setToNull("field2"); + udt = udt.setString("field3", "a"); assertThat(encode(udt)) .isEqualTo( @@ -139,9 +139,9 @@ public void should_format_null_udt() { @Test public void should_format_udt() { UdtValue udt = userType.newValue(); - udt.setInt(0, 1); - udt.setToNull(1); - udt.setString(2, "a"); + udt = udt.setInt(0, 1); + udt = udt.setToNull(1); + udt = udt.setString(2, "a"); assertThat(format(udt)).isEqualTo("{field1:1,field2:NULL,field3:'a'}"); diff --git a/faq/README.md b/faq/README.md index ff36c7eb1e2..1b5bf5e9e1a 100644 --- a/faq/README.md +++ b/faq/README.md @@ -17,6 +17,10 @@ session.execute(boundSelect); boundSelect = boundSelect.setInt("k", key).setPageSize(1000); ``` +All of these mutating methods are annotated with `@CheckReturnValue`. Some code analysis tools -- +such as [ErrorProne](https://errorprone.info/) -- can check correct usage at build time, and report +mistakes as compiler errors. + The driver also provides builders: ```java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java index 1b932d72550..85f21a7a79c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BatchStatementIT.java @@ -137,7 +137,7 @@ public void should_execute_batch_of_bound_statements_with_unset_values() { BoundStatement boundStatement = preparedStatement.bind(i, i + 2); // unset v every 20 statements. if (i % 20 == 0) { - boundStatement.unset(1); + boundStatement = boundStatement.unset(1); } builder.addStatement(boundStatement); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java index 580aed22f25..f9d81be5608 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/data/DataTypeIT.java @@ -222,7 +222,7 @@ public static Object[][] typeSamples() { types.add(dataType); TupleType tupleType = new DefaultTupleType(types); TupleValue tupleValue = tupleType.newValue(); - tupleValue.setInt(0, 0); + tupleValue = tupleValue.setInt(0, 0); setValue(1, tupleValue, dataType, o[1]); samples.add(new Object[] {tupleType, tupleValue}); @@ -248,7 +248,7 @@ public static Object[][] typeSamples() { types); UdtValue udtValue = udt.newValue(); - udtValue.setInt(0, 0); + udtValue = udtValue.setInt(0, 0); setValue(1, udtValue, dataType, o[1]); samples.add(new Object[] {udt, udtValue}); @@ -430,7 +430,7 @@ public void should_insert_non_primary_key_column_bound_statement_named_value PreparedStatement preparedSelect = sessionRule.session().prepare(select); BoundStatement boundSelect = setValue("k", preparedSelect.bind(), DataTypes.INT, key); - boundSelect.setInt("k", key); + boundSelect = boundSelect.setInt("k", key); readValue(boundSelect, dataType, value, expectedPrimitiveValue); } diff --git a/manual/core/statements/README.md b/manual/core/statements/README.md index 21d7b54de25..505e6569e88 100644 --- a/manual/core/statements/README.md +++ b/manual/core/statements/README.md @@ -40,6 +40,10 @@ statement.setIdempotent(true); statement = statement.setConfigProfileName("oltp").setIdempotent(true); ``` +All of these mutating methods are annotated with `@CheckReturnValue`. Some code analysis tools -- +such as [ErrorProne](https://errorprone.info/) -- can check correct usage at build time, and report +mistakes as compiler errors. + Note that some attributes can either be set programmatically, or inherit a default value defined in the [configuration](../configuration/). Namely, these are: idempotent flag, query timeout, consistency levels and page size. We recommended the configuration approach whenever possible (you diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 709f06a1130..01529dd2545 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -18,6 +18,2736 @@ } }, "ignore": [ + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists()", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists()", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[])", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[])", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists() @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists() @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[]) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[]) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.Delete", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[])", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[])", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspace", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspace", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspace", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspace", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterKeyspaceStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterMaterializedViewStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.AlterTableWithOptionsEnd", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateIndex", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateIndex", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspace", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspace", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspace", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateKeyspace", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedView", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewPrimaryKey", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewSelectionWithColumns", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhere", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateMaterializedViewWhereStart", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTable", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.CreateTableWithOptions", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>::withDurableWrites(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.KeyspaceOptions>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression()", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression()", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression()", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression()", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression()", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression()", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression()", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression()", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withBloomFilterFpChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCDC(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCaching(boolean, com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.lang.String, com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrder(java.util.Map)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>::withClusteringOrderByIds(java.util.Map)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withComment(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompaction(com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withCompression(java.lang.String, int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDcLocalReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDefaultTimeToLiveSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withDeflateCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withGcGraceSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withLZ4Compression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMaxIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMemtableFlushPeriodInMs(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withMinIndexInterval(int) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withNoCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withReadRepairChance(double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression() @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSnappyCompression(int, double) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.RelationOptions>>::withSpeculativeRetry(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.schema.RelationStructure>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>::withSSTableSizeInMB(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>::withSSTableSizeInMB(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.LeveledCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketHigh(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketHigh(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketLow(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketLow(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withColdReadsToOmit(double)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withColdReadsToOmit(double)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMaxThreshold(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMaxThreshold(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinSSTableSizeInBytes(long)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinSSTableSizeInBytes(long)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinThreshold(int)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinThreshold(int)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withOnlyPurgeRepairedTombstones(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withOnlyPurgeRepairedTombstones(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketHigh(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketHigh(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketLow(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withBucketLow(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withColdReadsToOmit(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withColdReadsToOmit(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withCompactionWindow(long, com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.CompactionWindowUnit)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withCompactionWindow(long, com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.CompactionWindowUnit)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withEnabled(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMaxThreshold(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMaxThreshold(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinSSTableSizeInBytes(long) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinSSTableSizeInBytes(long) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinThreshold(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withMinThreshold(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withOnlyPurgeRepairedTombstones(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.SizeTieredCompactionStrategy>>::withOnlyPurgeRepairedTombstones(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.OptionProvider>>::withOption(java.lang.String, java.lang.Object) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withTimestampResolution(com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.TimestampResolution)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withTimestampResolution(com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy.TimestampResolution)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneCompactionIntervalInSeconds(int) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withTombstoneThreshold(double) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy>>::withUncheckedTombstoneCompaction(boolean) @ com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withUnsafeAggressiveSSTableExpiration(boolean)", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.schema.compaction.TimeWindowCompactionStrategy>>::withUnsafeAggressiveSSTableExpiration(boolean)", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.select.Select", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists() @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists() @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[]) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(com.datastax.oss.driver.api.querybuilder.condition.Condition[]) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::if_(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.Update", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(com.datastax.oss.driver.api.querybuilder.relation.Relation[]) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::where(java.lang.Iterable) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereCustomIndex(java.lang.String, com.datastax.oss.driver.api.querybuilder.term.Term) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", + "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", + "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + } ] } } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java index e193e221d01..db9e7f27cb2 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/condition/ConditionalStatement.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnComponentLeftOperand; import com.datastax.oss.driver.internal.querybuilder.lhs.ColumnLeftOperand; import com.datastax.oss.driver.internal.querybuilder.lhs.FieldLeftOperand; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; @@ -38,6 +39,7 @@ public interface ConditionalStatement> *

        If any column conditions were added before, they will be cleared. */ @NonNull + @CheckReturnValue SelfT ifExists(); /** @@ -51,6 +53,7 @@ public interface ConditionalStatement> * alternative. */ @NonNull + @CheckReturnValue SelfT if_(@NonNull Condition condition); /** @@ -64,10 +67,12 @@ public interface ConditionalStatement> * {@link Condition#column(CqlIdentifier) column}. */ @NonNull + @CheckReturnValue SelfT if_(@NonNull Iterable conditions); /** Var-arg equivalent of {@link #if_(Iterable)}. */ @NonNull + @CheckReturnValue default SelfT if_(@NonNull Condition... conditions) { return if_(Arrays.asList(conditions)); } @@ -158,6 +163,7 @@ default ConditionBuilder ifElement(@NonNull String columnName, @NonNull T * @see QueryBuilder#raw(String) */ @NonNull + @CheckReturnValue default SelfT ifRaw(@NonNull String raw) { return if_(QueryBuilder.raw(raw)); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java index 917ef937138..6f34fa0d7ec 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/OngoingWhereClause.java @@ -25,6 +25,7 @@ import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnRelationBuilder; import com.datastax.oss.driver.internal.querybuilder.relation.DefaultMultiColumnRelationBuilder; import com.datastax.oss.driver.internal.querybuilder.relation.DefaultTokenRelationBuilder; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; @@ -41,6 +42,7 @@ public interface OngoingWhereClause> { * alternative. */ @NonNull + @CheckReturnValue SelfT where(@NonNull Relation relation); /** @@ -55,10 +57,12 @@ public interface OngoingWhereClause> { * @see #where(Relation) */ @NonNull + @CheckReturnValue SelfT where(@NonNull Iterable additionalRelations); /** Var-arg equivalent of {@link #where(Iterable)}. */ @NonNull + @CheckReturnValue default SelfT where(@NonNull Relation... additionalRelations) { return where(Arrays.asList(additionalRelations)); } @@ -215,6 +219,7 @@ default MultiColumnRelationBuilder whereColumns(@NonNull String... names) * Relation#customIndex(CqlIdentifier, Term)} and passing it to {@link #where(Relation)}. */ @NonNull + @CheckReturnValue default SelfT whereCustomIndex(@NonNull CqlIdentifier indexId, @NonNull Term expression) { return where(new CustomIndexRelation(indexId, expression)); } @@ -227,6 +232,7 @@ default SelfT whereCustomIndex(@NonNull CqlIdentifier indexId, @NonNull Term exp * Term)} and passing it to {@link #where(Relation)}. */ @NonNull + @CheckReturnValue default SelfT whereCustomIndex(@NonNull String indexName, @NonNull Term expression) { return whereCustomIndex(CqlIdentifier.fromCql(indexName), expression); } @@ -243,6 +249,7 @@ default SelfT whereCustomIndex(@NonNull String indexName, @NonNull Term expressi * features that are not yet covered by the query builder. */ @NonNull + @CheckReturnValue default SelfT whereRaw(@NonNull String raw) { return where(new DefaultRaw(raw)); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java index dac516bbefb..d1c4468e05d 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/KeyspaceOptions.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.querybuilder.schema; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface KeyspaceOptions> @@ -25,6 +26,7 @@ public interface KeyspaceOptions> * keyspace will bypass the commit log. */ @NonNull + @CheckReturnValue default SelfT withDurableWrites(boolean durableWrites) { return withOption("durable_writes", durableWrites); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java index 3c9c6aac295..1f6437fea9c 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/OptionProvider.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.querybuilder.schema; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; @@ -24,6 +25,7 @@ public interface OptionProvider> { * been added to this API. */ @NonNull + @CheckReturnValue SelfT withOption(@NonNull String name, @NonNull Object value); @NonNull diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java index 271fa9951f5..0c7d2b6a8c7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; import com.datastax.oss.driver.api.querybuilder.schema.compaction.CompactionStrategy; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface RelationOptions> @@ -36,6 +37,7 @@ public interface RelationOptions> * */ @NonNull + @CheckReturnValue default SelfT withBloomFilterFpChance(double bloomFilterFpChance) { return withOption("bloom_filter_fp_chance", bloomFilterFpChance); } @@ -49,6 +51,7 @@ default SelfT withBloomFilterFpChance(double bloomFilterFpChance) { *

        If no call is made to this method, the default value set is {@code false}. */ @NonNull + @CheckReturnValue default SelfT withCDC(boolean enabled) { return withOption("cdc", enabled); } @@ -63,6 +66,7 @@ default SelfT withCDC(boolean enabled) { * @param rowsPerPartition Whether to cache ALL, NONE or the first N rows per partition. */ @NonNull + @CheckReturnValue default SelfT withCaching(boolean keys, @NonNull RowsPerPartition rowsPerPartition) { return withOption( "caching", @@ -72,6 +76,7 @@ default SelfT withCaching(boolean keys, @NonNull RowsPerPartition rowsPerPartiti /** Defines documentation for this relation. */ @NonNull + @CheckReturnValue default SelfT withComment(@NonNull String comment) { return withOption("comment", comment); } @@ -84,6 +89,7 @@ default SelfT withComment(@NonNull String comment) { * @see SchemaBuilder#timeWindowCompactionStrategy() */ @NonNull + @CheckReturnValue default SelfT withCompaction(@NonNull CompactionStrategy compactionStrategy) { return withOption("compaction", compactionStrategy.getOptions()); } @@ -95,6 +101,7 @@ default SelfT withCompaction(@NonNull CompactionStrategy compactionStrategy) * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withLZ4Compression(int chunkLengthKB, double crcCheckChance) { return withCompression("LZ4Compressor", chunkLengthKB, crcCheckChance); } @@ -106,6 +113,7 @@ default SelfT withLZ4Compression(int chunkLengthKB, double crcCheckChance) { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withLZ4Compression() { return withCompression("LZ4Compressor"); } @@ -117,6 +125,7 @@ default SelfT withLZ4Compression() { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withSnappyCompression(int chunkLengthKB, double crcCheckChance) { return withCompression("SnappyCompressor", chunkLengthKB, crcCheckChance); } @@ -128,6 +137,7 @@ default SelfT withSnappyCompression(int chunkLengthKB, double crcCheckChance) { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withSnappyCompression() { return withCompression("SnappyCompressor"); } @@ -139,6 +149,7 @@ default SelfT withSnappyCompression() { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withDeflateCompression(int chunkLengthKB, double crcCheckChance) { return withCompression("DeflateCompressor", chunkLengthKB, crcCheckChance); } @@ -150,6 +161,7 @@ default SelfT withDeflateCompression(int chunkLengthKB, double crcCheckChance) { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withDeflateCompression() { return withCompression("DeflateCompressor"); } @@ -165,6 +177,7 @@ default SelfT withDeflateCompression() { * @see #withCompression(String, int, double) */ @NonNull + @CheckReturnValue default SelfT withCompression(@NonNull String compressionAlgorithmName) { return withOption("compression", ImmutableMap.of("class", compressionAlgorithmName)); } @@ -182,6 +195,7 @@ default SelfT withCompression(@NonNull String compressionAlgorithmName) { * Defaults to 1.0. */ @NonNull + @CheckReturnValue default SelfT withCompression( @NonNull String compressionAlgorithmName, int chunkLengthKB, double crcCheckChance) { return withOption( @@ -197,6 +211,7 @@ default SelfT withCompression( /** Defines that compression should be disabled. */ @NonNull + @CheckReturnValue default SelfT withNoCompression() { return withOption("compression", ImmutableMap.of("sstable_compression", "")); } @@ -211,6 +226,7 @@ default SelfT withNoCompression() { * @return this {@code TableOptions} object. */ @NonNull + @CheckReturnValue default SelfT withDcLocalReadRepairChance(double dcLocalReadRepairChance) { return withOption("dclocal_read_repair_chance", dcLocalReadRepairChance); } @@ -221,6 +237,7 @@ default SelfT withDcLocalReadRepairChance(double dcLocalReadRepairChance) { *

        If no call is made to this method, the default value is 0 (no TTL). */ @NonNull + @CheckReturnValue default SelfT withDefaultTimeToLiveSeconds(int ttl) { return withOption("default_time_to_live", ttl); } @@ -235,6 +252,7 @@ default SelfT withDefaultTimeToLiveSeconds(int ttl) { *

        If no call is made to this method, the default value set is 864000 secs (10 days). */ @NonNull + @CheckReturnValue default SelfT withGcGraceSeconds(int gcGraceSeconds) { return withOption("gc_grace_seconds", gcGraceSeconds); } @@ -247,6 +265,7 @@ default SelfT withGcGraceSeconds(int gcGraceSeconds) { *

        If no call is made to this method, the default value is 0 (unset). */ @NonNull + @CheckReturnValue default SelfT withMemtableFlushPeriodInMs(int memtableFlushPeriodInMs) { return withOption("memtable_flush_period_in_ms", memtableFlushPeriodInMs); } @@ -259,6 +278,7 @@ default SelfT withMemtableFlushPeriodInMs(int memtableFlushPeriodInMs) { *

        If no call is made to this method, the default value set is 128. */ @NonNull + @CheckReturnValue default SelfT withMinIndexInterval(int min) { return withOption("min_index_interval", min); } @@ -271,6 +291,7 @@ default SelfT withMinIndexInterval(int min) { * @see #withMinIndexInterval(int) */ @NonNull + @CheckReturnValue default SelfT withMaxIndexInterval(int max) { return withOption("max_index_interval", max); } @@ -282,6 +303,7 @@ default SelfT withMaxIndexInterval(int max) { *

        If no call is made to this method, the default value set is 0.1. */ @NonNull + @CheckReturnValue default SelfT withReadRepairChance(double readRepairChance) { return withOption("read_repair_chance", readRepairChance); } @@ -308,6 +330,7 @@ default SelfT withReadRepairChance(double readRepairChance) { *

        If no call is made to this method, the default value set is {@code 99percentile}. */ @NonNull + @CheckReturnValue default SelfT withSpeculativeRetry(@NonNull String speculativeRetry) { return withOption("speculative_retry", speculativeRetry); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java index 695048243e6..d6da402bfe7 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationStructure.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.internal.core.CqlIdentifiers; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; @@ -32,6 +33,7 @@ public interface RelationStructure> * position in the provided map. */ @NonNull + @CheckReturnValue SelfT withClusteringOrderByIds(@NonNull Map orderings); /** @@ -42,6 +44,7 @@ public interface RelationStructure> * identifier, for example "foo" and "Foo"; if this happens, a runtime exception will be thrown. */ @NonNull + @CheckReturnValue default SelfT withClusteringOrder(@NonNull Map orderings) { return withClusteringOrderByIds(CqlIdentifiers.wrapKeys(orderings)); } @@ -53,6 +56,7 @@ default SelfT withClusteringOrder(@NonNull Map ordering * clause will be appended at the end of the current clustering order. */ @NonNull + @CheckReturnValue SelfT withClusteringOrder(@NonNull CqlIdentifier columnName, @NonNull ClusteringOrder order); /** @@ -60,6 +64,7 @@ default SelfT withClusteringOrder(@NonNull Map ordering * withClusteringOrder(CqlIdentifier.fromCql(columnName), order)}. */ @NonNull + @CheckReturnValue default SelfT withClusteringOrder(@NonNull String columnName, @NonNull ClusteringOrder order) { return withClusteringOrder(CqlIdentifier.fromCql(columnName), order); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java index a8021ea9c67..a7ff86b01a4 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/CompactionStrategy.java @@ -16,27 +16,32 @@ package com.datastax.oss.driver.api.querybuilder.schema.compaction; import com.datastax.oss.driver.api.querybuilder.schema.OptionProvider; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface CompactionStrategy> extends OptionProvider { @NonNull + @CheckReturnValue default SelfT withEnabled(boolean enabled) { return withOption("enabled", enabled); } @NonNull + @CheckReturnValue default SelfT withTombstoneCompactionIntervalInSeconds(int seconds) { return withOption("tombstone_compaction_interval", seconds); } @NonNull + @CheckReturnValue default SelfT withTombstoneThreshold(double threshold) { return withOption("tombstone_threshold", threshold); } @NonNull + @CheckReturnValue default SelfT withUncheckedTombstoneCompaction(boolean enabled) { return withOption("unchecked_tombstone_compaction", enabled); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java index 785ddd1a2cb..c65db949b67 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/LeveledCompactionStrategy.java @@ -15,12 +15,14 @@ */ package com.datastax.oss.driver.api.querybuilder.schema.compaction; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface LeveledCompactionStrategy> extends CompactionStrategy { @NonNull + @CheckReturnValue default SelfT withSSTableSizeInMB(int ssTableSizeInMB) { return withOption("sstable_size_in_mb", ssTableSizeInMB); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java index 968916d074e..28566555458 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/SizeTieredCompactionStrategy.java @@ -15,43 +15,51 @@ */ package com.datastax.oss.driver.api.querybuilder.schema.compaction; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface SizeTieredCompactionStrategy> extends CompactionStrategy { @NonNull + @CheckReturnValue default SelfT withMaxThreshold(int maxThreshold) { return withOption("max_threshold", maxThreshold); } @NonNull + @CheckReturnValue default SelfT withMinThreshold(int minThreshold) { return withOption("min_threshold", minThreshold); } @NonNull + @CheckReturnValue default SelfT withMinSSTableSizeInBytes(long bytes) { return withOption("min_sstable_size", bytes); } @NonNull + @CheckReturnValue default SelfT withOnlyPurgeRepairedTombstones(boolean enabled) { return withOption("only_purge_repaired_tombstones", enabled); } @NonNull + @CheckReturnValue default SelfT withBucketHigh(double bucketHigh) { return withOption("bucket_high", bucketHigh); } @NonNull + @CheckReturnValue default SelfT withBucketLow(double bucketHigh) { return withOption("bucket_low", bucketHigh); } // 2.1 only @NonNull + @CheckReturnValue default SelfT withColdReadsToOmit(double ratio) { return withOption("cold_reads_to_omit", ratio); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java index 22e5c77dbbd..0166d924792 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/compaction/TimeWindowCompactionStrategy.java @@ -15,6 +15,7 @@ */ package com.datastax.oss.driver.api.querybuilder.schema.compaction; +import edu.umd.cs.findbugs.annotations.CheckReturnValue; import edu.umd.cs.findbugs.annotations.NonNull; public interface TimeWindowCompactionStrategy> @@ -32,17 +33,20 @@ enum TimestampResolution { } @NonNull + @CheckReturnValue default SelfT withCompactionWindow(long size, @NonNull CompactionWindowUnit unit) { return withOption("compaction_window_size", size) .withOption("compaction_window_unit", unit.toString()); } @NonNull + @CheckReturnValue default SelfT withUnsafeAggressiveSSTableExpiration(boolean enabled) { return withOption("unsafe_aggressive_sstable_expiration", enabled); } @NonNull + @CheckReturnValue default SelfT withTimestampResolution(@NonNull TimestampResolution timestampResolution) { return withOption("timestamp_resolution", timestampResolution.toString()); } From e9166b35d2be585ab60fa38e1d83d4f8a2544751 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Mar 2019 13:28:49 +0200 Subject: [PATCH 721/742] Add missing `@NonNull` annotation to Statement.setConsistencyLevel --- .../java/com/datastax/oss/driver/api/core/cql/Statement.java | 1 + .../oss/driver/internal/core/cql/DefaultBatchStatement.java | 1 + .../oss/driver/internal/core/cql/DefaultBoundStatement.java | 1 + .../oss/driver/internal/core/cql/DefaultSimpleStatement.java | 1 + 4 files changed, 4 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java index d45f644c0e7..c06b24e1982 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/Statement.java @@ -320,6 +320,7 @@ default SelfT setRoutingKey(@NonNull ByteBuffer... newRoutingKeyComponents) { * defined in the configuration. * @see DefaultDriverOption#REQUEST_CONSISTENCY */ + @NonNull @CheckReturnValue SelfT setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index 792ad3cc702..ad9fdbc0913 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -310,6 +310,7 @@ public ConsistencyLevel getConsistencyLevel() { return consistencyLevel; } + @NonNull @Override public BatchStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { return new DefaultBatchStatement( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 7c58b73c176..b0842670f06 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -629,6 +629,7 @@ public ConsistencyLevel getConsistencyLevel() { return consistencyLevel; } + @NonNull @Override public BoundStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { return new DefaultBoundStatement( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 79947a84597..acad2e11051 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -631,6 +631,7 @@ public ConsistencyLevel getConsistencyLevel() { return consistencyLevel; } + @NonNull @Override public SimpleStatement setConsistencyLevel(@Nullable ConsistencyLevel newConsistencyLevel) { return new DefaultSimpleStatement( From 0ab2b90e1befe0921715980001058783855d190e Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Mar 2019 15:10:48 +0200 Subject: [PATCH 722/742] Add missing revapi exceptions --- core/revapi.json | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/core/revapi.json b/core/revapi.json index f933d254c16..43a90fc02f5 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -4433,6 +4433,41 @@ "new": "method SelfT com.datastax.oss.driver.api.core.data.SettableByName>>::setUuid(java.lang.String, java.util.UUID) @ com.datastax.oss.driver.api.core.data.UdtValue", "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BatchableStatement>>", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.BoundStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel) @ com.datastax.oss.driver.api.core.cql.SimpleStatement", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" + }, + { + "code": "java.annotation.added", + "old": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", + "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", + "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" } ] } From a124dba1dcca2088979064fcecd85b7d22716c18 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Tue, 12 Mar 2019 14:19:07 +0100 Subject: [PATCH 723/742] JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder (#1207) --- changelog/README.md | 1 + .../api/querybuilder/JacksonJsonCodec.java | 122 ++++++++ .../driver/api/querybuilder/JsonInsertIT.java | 260 ++++++++++++++++++ query-builder/revapi.json | 5 + .../api/querybuilder/insert/InsertInto.java | 44 +++ .../querybuilder/insert/DefaultInsert.java | 14 + 6 files changed, 446 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JacksonJsonCodec.java create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JsonInsertIT.java diff --git a/changelog/README.md b/changelog/README.md index a89d2c070c3..7b8e5a1fcfb 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder - [improvement] JAVA-2161: Annotate mutating methods with `@CheckReturnValue` - [bug] JAVA-2177: Don't exclude down nodes when initializing LBPs - [improvement] JAVA-2143: Rename Statement.setTimestamp() to setQueryTimestamp() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JacksonJsonCodec.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JacksonJsonCodec.java new file mode 100644 index 00000000000..37fb471774b --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JacksonJsonCodec.java @@ -0,0 +1,122 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.util.Strings; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class JacksonJsonCodec implements TypeCodec { + + private final ObjectMapper objectMapper; + private final GenericType javaType; + + JacksonJsonCodec(Class javaClass) { + this(javaClass, new ObjectMapper()); + } + + private JacksonJsonCodec(Class javaClass, ObjectMapper objectMapper) { + this.javaType = GenericType.of(javaClass); + this.objectMapper = objectMapper; + } + + @NonNull + @Override + public GenericType getJavaType() { + return javaType; + } + + @NonNull + @Override + public DataType getCqlType() { + return DataTypes.TEXT; + } + + @Nullable + @Override + public ByteBuffer encode(@Nullable T value, @NonNull ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } + try { + return ByteBuffer.wrap(objectMapper.writeValueAsBytes(value)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @Nullable + @Override + public T decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + if (bytes == null) { + return null; + } + try { + return objectMapper.readValue(Bytes.getArray(bytes), toJacksonJavaType()); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @NonNull + @Override + public String format(T value) { + if (value == null) { + return "NULL"; + } + String json; + try { + json = objectMapper.writeValueAsString(value); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + return Strings.quote(json); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public T parse(String value) { + if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) { + return null; + } + if (!Strings.isQuoted(value)) { + throw new IllegalArgumentException("JSON strings must be enclosed by single quotes"); + } + String json = Strings.unquote(value); + try { + return (T) objectMapper.readValue(json, toJacksonJavaType()); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + private JavaType toJacksonJavaType() { + return TypeFactory.defaultInstance().constructType(getJavaType().getType()); + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JsonInsertIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JsonInsertIT.java new file mode 100644 index 00000000000..c9621405968 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/querybuilder/JsonInsertIT.java @@ -0,0 +1,260 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.querybuilder; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; +import static com.datastax.oss.driver.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +@Category(ParallelizableTests.class) +public class JsonInsertIT { + private static final CcmRule ccmRule = CcmRule.getInstance(); + private static final JacksonJsonCodec JACKSON_JSON_CODEC = + new JacksonJsonCodec<>(User.class); + + private static SessionRule sessionRule = + SessionRule.builder(ccmRule) + .withConfigLoader( + SessionUtils.configLoaderBuilder() + .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .build()) + .build(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + + @BeforeClass + public static void setup() { + sessionRule + .session() + .execute("CREATE TABLE json_jackson_row(id int PRIMARY KEY, name text, age int)"); + } + + @After + public void clearTable() { + sessionRule.session().execute("TRUNCATE TABLE json_jackson_row"); + } + + @Test + public void should_insert_string_as_json_using_simple_statement() { + // given a simple statement + try (CqlSession session = sessionWithCustomCodec()) { + String jsonUser = "{ \"id\": 2, \"name\": \"Alice\", \"age\": 3 }"; + Statement stmt = insertInto("json_jackson_row").json(jsonUser).build(); + + // when + session.execute(stmt); + + // then + String jsonUserResult = + session + .execute(selectFrom("json_jackson_row").json().all().build()) + .all() + .get(0) + .getString(0); + + assertThat(jsonUserResult).contains("\"id\": 2"); + assertThat(jsonUserResult).contains(" \"name\": \"Alice\""); + assertThat(jsonUserResult).contains("\"age\": 3"); + } + } + + @Test + public void should_insert_json_using_prepare_statement() { + // given prepare statement + try (CqlSession session = sessionWithCustomCodec()) { + User user = new User(2, "bob", 35); + PreparedStatement pst = + session.prepare(insertInto("json_jackson_row").json(bindMarker("user")).build()); + + // when + session.execute(pst.bind().set("user", user, User.class)); + + // then + List rows = session.execute(selectFrom("json_jackson_row").json().all().build()).all(); + assertThat(rows.get(0).get(0, User.class)).isEqualTo(user); + } + } + + @Test + public void should_insert_json_using_simple_statement_with_custom_codec() { + // given a simple statement + try (CqlSession session = sessionWithCustomCodec()) { + User user = new User(1, "alice", 30); + Statement stmt = insertInto("json_jackson_row").json(user, JACKSON_JSON_CODEC).build(); + + // when + session.execute(stmt); + + // then + List rows = session.execute(selectFrom("json_jackson_row").json().all().build()).all(); + + assertThat(rows.get(0).get(0, User.class)).isEqualTo(user); + } + } + + @Test + public void should_insert_json_using_simple_statement_with_custom_codec_without_codec_registry() { + try (CqlSession session = sessionWithoutCustomCodec()) { + // given + User user = new User(1, "alice", 30); + SimpleStatement stmt = insertInto("json_jackson_row").json(user, JACKSON_JSON_CODEC).build(); + + // when + session.execute(stmt); + + // then + List rows = session.execute(selectFrom("json_jackson_row").json().all().build()).all(); + assertThat(rows.get(0).get(0, JACKSON_JSON_CODEC)).isEqualTo(user); + } + } + + @Test + public void should_insert_json_using_simple_statement_with_codec_registry() { + // given a simple statement + try (CqlSession session = sessionWithCustomCodec()) { + User user = new User(1, "alice", 30); + Statement stmt = + insertInto("json_jackson_row") + .json(user, session.getContext().getCodecRegistry()) + .build(); + + // when + session.execute(stmt); + + // then + List rows = session.execute(selectFrom("json_jackson_row").json().all().build()).all(); + + assertThat(rows.get(0).get(0, User.class)).isEqualTo(user); + } + } + + @Test + public void + should_throw_when_insert_json_using_simple_statement_with_codec_registry_without_custom_codec() { + assertThatThrownBy( + () -> { + try (CqlSession session = sessionWithoutCustomCodec()) { + insertInto("json_jackson_row") + .json(new User(1, "alice", 30), session.getContext().getCodecRegistry()) + .build(); + } + }) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage( + String.format( + "Could not inline JSON literal of type %s. " + + "This happens because the provided CodecRegistry does not contain " + + "a codec for this type. Try registering your TypeCodec in the registry " + + "first, or use json(Object, TypeCodec).", + User.class.getName())) + .hasCauseInstanceOf(CodecNotFoundException.class); + } + + @SuppressWarnings("unchecked") + private CqlSession sessionWithCustomCodec() { + return (CqlSession) + SessionUtils.baseBuilder() + .addContactEndPoints(ccmRule.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) + .addTypeCodecs(JACKSON_JSON_CODEC) + .build(); + } + + @SuppressWarnings("unchecked") + private CqlSession sessionWithoutCustomCodec() { + return (CqlSession) + SessionUtils.baseBuilder() + .addContactEndPoints(ccmRule.getContactPoints()) + .withKeyspace(sessionRule.keyspace()) + .build(); + } + + @SuppressWarnings("unused") + public static class User { + + private final int id; + + private final String name; + + private final int age; + + @JsonCreator + public User( + @JsonProperty("id") int id, + @JsonProperty("name") String name, + @JsonProperty("age") int age) { + this.id = id; + this.name = name; + this.age = age; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + @Override + public String toString() { + return String.format("%s (id %d, age %d)", name, id, age); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return id == user.id && age == user.age && Objects.equals(name, user.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, age); + } + } +} diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 01529dd2545..818c85d0d1b 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -18,6 +18,11 @@ } }, "ignore": [ + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.querybuilder.insert.JsonInsert com.datastax.oss.driver.api.querybuilder.insert.InsertInto::json(T, com.datastax.oss.driver.api.core.type.codec.TypeCodec)", + "justification": "JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder" + }, { "code": "java.annotation.added", "old": "method SelfT com.datastax.oss.driver.api.querybuilder.condition.ConditionalStatement>>::ifExists()", diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java index 0a4316d1e07..5b78286ac58 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java @@ -15,7 +15,12 @@ */ package com.datastax.oss.driver.api.querybuilder.insert; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.api.querybuilder.BindMarker; +import com.datastax.oss.driver.internal.querybuilder.insert.DefaultInsert; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -31,4 +36,43 @@ public interface InsertInto extends OngoingValues { /** Makes this statement an INSERT JSON with a bind marker, as in {@code INSERT JSON ?}. */ @NonNull JsonInsert json(@NonNull BindMarker bindMarker); + + /** + * Makes this statement an INSERT JSON with a custom type mapping. The provided {@code Object + * value} will be mapped to a JSON string. + * + *

        This is an alternative to {@link #json(String)} for custom type mappings. The provided + * registry should contain a codec that can format the value. Typically, this will be your + * session's registry, which is accessible via {@code session.getContext().getCodecRegistry()}. + * + * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can + * handle {@code value}. + * @see DriverContext#getCodecRegistry() + * @see DefaultInsert#json(Object, CodecRegistry) + */ + @NonNull + default JsonInsert json(@NonNull Object value, @NonNull CodecRegistry codecRegistry) { + try { + return json(value, codecRegistry.codecFor(value)); + } catch (CodecNotFoundException e) { + throw new IllegalArgumentException( + String.format( + "Could not inline JSON literal of type %s. " + + "This happens because the provided CodecRegistry does not contain " + + "a codec for this type. Try registering your TypeCodec in the registry first, " + + "or use json(Object, TypeCodec).", + value.getClass().getName()), + e); + } + } + + /** + * Makes this statement an INSERT JSON with a custom type mapping. The provided {@code Object + * value} will be mapped to a JSON string. The value will be turned into a string with {@link + * TypeCodec#format(Object)}, and inlined in the query. + * + * @see DefaultInsert#json(T, TypeCodec) + */ + @NonNull + JsonInsert json(@NonNull T value, @NonNull TypeCodec codec); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index c1996dac624..0e51c316dc5 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.querybuilder.BindMarker; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.insert.Insert; @@ -95,6 +96,19 @@ public JsonInsert json(@NonNull BindMarker json) { keyspace, table, json, missingJsonBehavior, ImmutableMap.of(), timestamp, ifNotExists); } + @NonNull + @Override + public JsonInsert json(@NonNull T value, @NonNull TypeCodec codec) { + return new DefaultInsert( + keyspace, + table, + QueryBuilder.literal(value, codec), + missingJsonBehavior, + ImmutableMap.of(), + timestamp, + ifNotExists); + } + @NonNull @Override public JsonInsert defaultNull() { From 801102cb1f063a8961975f866c237d8c2da4b1b3 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Mar 2019 15:21:57 +0200 Subject: [PATCH 724/742] Perform a full mvn install before running tests with JDK 11 --- .travis.yml | 6 ++++-- pom.xml | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f720550319..f3745f6a60f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,14 +8,16 @@ matrix: jdk: openjdk8 # 11 - env: JDK='OpenJDK 11' - install: . $TRAVIS_BUILD_DIR/ci/install-jdk.sh -F 11 -L GPL + before_script: . $TRAVIS_BUILD_DIR/ci/install-jdk.sh -F 11 -L GPL before_install: + - jdk_switcher use openjdk8 - git clone --depth 1 https://github.com/datastax/native-protocol.git - pushd native-protocol - mvn install -Dmaven.javadoc.skip=true -B -V - popd - rm -rf native-protocol -script: mvn test -B -Dmaven.main.skip=true -Dmaven.test.skip=true +install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V +script: mvn test -Djacoco.skip=true -B -V cache: directories: - $HOME/.m2 diff --git a/pom.xml b/pom.xml index efd9ffc22a6..3edb78b3048 100644 --- a/pom.xml +++ b/pom.xml @@ -323,6 +323,7 @@ true true + false From 66834b278774e9bb4b30b8f8ffb6e96c6973e9dc Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Mar 2019 18:40:54 +0200 Subject: [PATCH 725/742] Remove native-protocol build step from Travis configuration file Installing the native-protocol project does not guarantee that the driver build will succeed, since the exact version of native-protocol being built does not necessarily correspond to the one used by the driver. --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3745f6a60f..a7f970a8c20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,16 +8,13 @@ matrix: jdk: openjdk8 # 11 - env: JDK='OpenJDK 11' + # switch to JDK 11 before running tests before_script: . $TRAVIS_BUILD_DIR/ci/install-jdk.sh -F 11 -L GPL before_install: + # Require JDK8 for compiling - jdk_switcher use openjdk8 - - git clone --depth 1 https://github.com/datastax/native-protocol.git - - pushd native-protocol - - mvn install -Dmaven.javadoc.skip=true -B -V - - popd - - rm -rf native-protocol install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V script: mvn test -Djacoco.skip=true -B -V cache: directories: - - $HOME/.m2 + - $HOME/.m2 From e44a3c93998cc4fd7f38193f39f6f61baa74f87e Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 11 Mar 2019 18:48:03 -0700 Subject: [PATCH 726/742] Remove unnecessary check in default LBP --- .../core/loadbalancing/DefaultLoadBalancingPolicy.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 28ef26fdb27..978d16965f6 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -141,11 +141,9 @@ public void init(@NonNull Map nodes, @NonNull DistanceReporter dista } else { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Node node : contactPoints) { - if (node != null) { - String datacenter = node.getDatacenter(); - if (!Objects.equals(localDc, datacenter)) { - builder.put(node, (datacenter == null) ? "" : datacenter); - } + String datacenter = node.getDatacenter(); + if (!Objects.equals(localDc, datacenter)) { + builder.put(node, (datacenter == null) ? "" : datacenter); } } ImmutableMap badContactPoints = builder.build(); From 6214b81de2ad6b969a55c65579ffd0be26c66733 Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 11 Mar 2019 10:25:11 -0700 Subject: [PATCH 727/742] JAVA-2183: Enable materialized views when testing against Cassandra 4 --- changelog/README.md | 1 + .../api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 7b8e5a1fcfb..1674ed743ab 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -18,6 +18,7 @@ ### 4.0.0-rc1 +- [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 - [improvement] JAVA-2106: Log server side warnings returned from a query - [improvement] JAVA-2151: Drop "Dsl" suffix from query builder main classes - [new feature] JAVA-2144: Expose internal API to hook into the session lifecycle diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java index c9ed2faae67..01cf3888aa2 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DefaultCcmBridgeBuilderCustomizer.java @@ -15,9 +15,14 @@ */ package com.datastax.oss.driver.api.testinfra.ccm; +import com.datastax.oss.driver.api.core.Version; + public class DefaultCcmBridgeBuilderCustomizer { public static CcmBridge.Builder configureBuilder(CcmBridge.Builder builder) { + if (!CcmBridge.DSE_ENABLEMENT && CcmBridge.VERSION.compareTo(Version.V4_0_0) >= 0) { + builder.withCassandraConfiguration("enable_materialized_views", true); + } return builder; } } From 1d9bcf4eb2e2f00614b7373985b85e7c0d7213fd Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 12 Mar 2019 09:56:36 -0700 Subject: [PATCH 728/742] Fix changelog entry --- changelog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/README.md b/changelog/README.md index 1674ed743ab..8103568ac71 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 - [improvement] JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder - [improvement] JAVA-2161: Annotate mutating methods with `@CheckReturnValue` - [bug] JAVA-2177: Don't exclude down nodes when initializing LBPs @@ -18,7 +19,6 @@ ### 4.0.0-rc1 -- [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 - [improvement] JAVA-2106: Log server side warnings returned from a query - [improvement] JAVA-2151: Drop "Dsl" suffix from query builder main classes - [new feature] JAVA-2144: Expose internal API to hook into the session lifecycle From bebff8b6d69a15a646e6801bc58457e72a794a5c Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 11 Mar 2019 10:26:50 -0700 Subject: [PATCH 729/742] Re-enable WARN logs by default in integration tests Disabling warnings can hide non-fatal bugs, such as token map refresh issues. Since JAVA-2053, re-preparing the same query doesn't warn anymore, so that should not pollute the logs anymore. --- integration-tests/src/test/resources/logback-test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/resources/logback-test.xml b/integration-tests/src/test/resources/logback-test.xml index 63a78b7735d..7a4c0da88a1 100644 --- a/integration-tests/src/test/resources/logback-test.xml +++ b/integration-tests/src/test/resources/logback-test.xml @@ -24,7 +24,7 @@ - + \ No newline at end of file From d451ef9f91cb07aa819084ea445a3405f5999a4e Mon Sep 17 00:00:00 2001 From: olim7t Date: Mon, 11 Mar 2019 10:33:56 -0700 Subject: [PATCH 730/742] JAVA-2189: Exclude virtual keyspaces from token map computation --- changelog/README.md | 1 + .../core/metadata/token/DefaultTokenMap.java | 4 +- .../driver/api/core/metadata/SchemaIT.java | 42 +++++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index 8103568ac71..bca3a151d28 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [bug] JAVA-2189: Exclude virtual keyspaces from token map computation - [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 - [improvement] JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder - [improvement] JAVA-2161: Annotate mutating methods with `@CheckReturnValue` diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java index 6b90c6f0c0a..f9c7dd47855 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -275,7 +275,9 @@ private static Map> buildReplicationConfigs( Collection keyspaces, String logPrefix) { ImmutableMap.Builder> builder = ImmutableMap.builder(); for (KeyspaceMetadata keyspace : keyspaces) { - builder.put(keyspace.getName(), keyspace.getReplication()); + if (!keyspace.isVirtual()) { + builder.put(keyspace.getName(), keyspace.getReplication()); + } } ImmutableMap> result = builder.build(); LOG.debug("[{}] Computing keyspace-level data for {}", logPrefix, result); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 874dbb37580..9ab40d4c4de 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -34,6 +34,8 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.api.testinfra.utils.ConditionChecker; import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; import org.junit.AssumptionViolatedException; @@ -46,6 +48,8 @@ @Category(ParallelizableTests.class) public class SchemaIT { + private static final Version DSE_MIN_VIRTUAL_TABLES = Version.parse("6.7.0"); + private CcmRule ccmRule = CcmRule.getInstance(); private SessionRule sessionRule = SessionRule.builder(ccmRule).build(); @@ -180,14 +184,9 @@ public void should_refresh_schema_manually() { @CassandraRequirement(min = "4.0", description = "virtual tables introduced in 4.0") @Test public void should_get_virtual_metadata() { + skipIfDse60(); + Metadata md = sessionRule.session().getMetadata(); - // Special case: DSE 6.0 reports C* 4.0 but does not support virtual tables - if (ccmRule.getDseVersion().isPresent()) { - Version dseVersion = ccmRule.getDseVersion().get(); - if (dseVersion.compareTo(Version.parse("6.7.0")) < 0) { - throw new AssumptionViolatedException("DSE 6.0 does not support virtual tables"); - } - } KeyspaceMetadata kmd = md.getKeyspace("system_views").get(); // Keyspace name should be set, marked as virtual, and have at least sstable_tasks table. @@ -241,4 +240,33 @@ public void should_get_virtual_metadata() { assertThat(cm.getType()).isEqualTo(DataTypes.BIGINT); assertThat(cm.getName().toString()).isEqualTo("progress"); } + + @CassandraRequirement(min = "4.0", description = "virtual tables introduced in 4.0") + @Test + public void should_exclude_virtual_keyspaces_from_token_map() { + skipIfDse60(); + + Metadata metadata = sessionRule.session().getMetadata(); + Map keyspaces = metadata.getKeyspaces(); + assertThat(keyspaces) + .containsKey(CqlIdentifier.fromCql("system_views")) + .containsKey(CqlIdentifier.fromCql("system_virtual_schema")); + + TokenMap tokenMap = metadata.getTokenMap().orElseThrow(AssertionError::new); + ByteBuffer partitionKey = Bytes.fromHexString("0x00"); // value does not matter + assertThat(tokenMap.getReplicas("system_views", partitionKey)).isEmpty(); + assertThat(tokenMap.getReplicas("system_virtual_schema", partitionKey)).isEmpty(); + // Check that a non-virtual keyspace is present + assertThat(tokenMap.getReplicas(sessionRule.keyspace(), partitionKey)).isNotEmpty(); + } + + private void skipIfDse60() { + // Special case: DSE 6.0 reports C* 4.0 but does not support virtual tables + if (ccmRule.getDseVersion().isPresent()) { + Version dseVersion = ccmRule.getDseVersion().get(); + if (dseVersion.compareTo(DSE_MIN_VIRTUAL_TABLES) < 0) { + throw new AssumptionViolatedException("DSE 6.0 does not support virtual tables"); + } + } + } } From 054e4975c801abb4aa13347d52952e2803aa7298 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Wed, 13 Mar 2019 10:56:25 +0100 Subject: [PATCH 731/742] JAVA-2182: Don't reference prod code from documentation in internal.* package (#1215) --- .../oss/driver/api/querybuilder/insert/InsertInto.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java index 5b78286ac58..0fbc0a595b8 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/InsertInto.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.api.querybuilder.BindMarker; -import com.datastax.oss.driver.internal.querybuilder.insert.DefaultInsert; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -48,7 +47,6 @@ public interface InsertInto extends OngoingValues { * @throws CodecNotFoundException if {@code codecRegistry} does not contain any codec that can * handle {@code value}. * @see DriverContext#getCodecRegistry() - * @see DefaultInsert#json(Object, CodecRegistry) */ @NonNull default JsonInsert json(@NonNull Object value, @NonNull CodecRegistry codecRegistry) { @@ -70,8 +68,6 @@ default JsonInsert json(@NonNull Object value, @NonNull CodecRegistry codecRegis * Makes this statement an INSERT JSON with a custom type mapping. The provided {@code Object * value} will be mapped to a JSON string. The value will be turned into a string with {@link * TypeCodec#format(Object)}, and inlined in the query. - * - * @see DefaultInsert#json(T, TypeCodec) */ @NonNull JsonInsert json(@NonNull T value, @NonNull TypeCodec codec); From 643c374b77a10203d1e8c33fbb271e52877b937c Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 13 Mar 2019 09:40:23 -0700 Subject: [PATCH 732/742] Remove oldArchive field in revapi.json It is not mandatory, and can make the check fail if a custom build has a version that resolves as "more recent" than the declared version (e.g. 4.0.0-rc1-internal > 4.0.0-rc1). --- core/revapi.json | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/core/revapi.json b/core/revapi.json index 43a90fc02f5..11f8ccfb8ff 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -19,196 +19,165 @@ { "code": "java.method.removed", "old": "method com.datastax.oss.driver.api.core.cql.BatchStatementBuilder com.datastax.oss.driver.api.core.cql.BatchStatementBuilder::withKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method com.datastax.oss.driver.api.core.cql.BatchStatementBuilder com.datastax.oss.driver.api.core.cql.BatchStatementBuilder::withKeyspace(java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withKeyspace(java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder::withQuery(java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withExecutionProfile(com.datastax.oss.driver.api.core.config.DriverExecutionProfile)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withExecutionProfileName(java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withIdempotence(java.lang.Boolean)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withNode(com.datastax.oss.driver.api.core.metadata.Node)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withPageSize(int)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withPagingState(java.nio.ByteBuffer)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKey(java.nio.ByteBuffer)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingKeyspace(java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withRoutingToken(com.datastax.oss.driver.api.core.metadata.token.Token)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withSerialConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTimeout(java.time.Duration)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTimestamp(long)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.removed", "old": "method SelfT com.datastax.oss.driver.api.core.cql.StatementBuilder>, StatementT>, StatementT extends com.datastax.oss.driver.api.core.cql.Statement>>::withTracing()", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2164: Rename statement builder methods to setXxx" }, { "code": "java.method.parameterTypeChanged", "old": "parameter void com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::(===java.net.SocketAddress===, java.lang.String, java.util.List)", "new": "parameter void com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String, java.util.List)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forNegotiation(===java.net.SocketAddress===, java.util.List)", "new": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forNegotiation(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.util.List)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forSingleAttempt(===java.net.SocketAddress===, com.datastax.oss.driver.api.core.ProtocolVersion)", "new": "parameter com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::forSingleAttempt(===com.datastax.oss.driver.api.core.metadata.EndPoint===, com.datastax.oss.driver.api.core.ProtocolVersion)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.removed", "old": "method java.net.SocketAddress com.datastax.oss.driver.api.core.UnsupportedProtocolVersionException::getAddress()", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter com.datastax.oss.driver.api.core.auth.Authenticator com.datastax.oss.driver.api.core.auth.AuthProvider::newAuthenticator(===java.net.SocketAddress===, java.lang.String) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", "new": "parameter com.datastax.oss.driver.api.core.auth.Authenticator com.datastax.oss.driver.api.core.auth.AuthProvider::newAuthenticator(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthProvider::onMissingChallenge(===java.net.SocketAddress===) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthProvider::onMissingChallenge(===com.datastax.oss.driver.api.core.metadata.EndPoint===) throws com.datastax.oss.driver.api.core.auth.AuthenticationException", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===java.net.SocketAddress===, java.lang.String)", "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.parameterTypeChanged", "old": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===java.net.SocketAddress===, java.lang.String, java.lang.Throwable)", "new": "parameter void com.datastax.oss.driver.api.core.auth.AuthenticationException::(===com.datastax.oss.driver.api.core.metadata.EndPoint===, java.lang.String, java.lang.Throwable)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.removed", "old": "method java.net.SocketAddress com.datastax.oss.driver.api.core.auth.AuthenticationException::getAddress()", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.numberOfParametersChanged", "old": "method void com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter, java.util.Set)", "new": "method void com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getNodes()", "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getNodes()", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { @@ -219,7 +188,6 @@ { "code": "java.method.removed", "old": "method java.net.InetSocketAddress com.datastax.oss.driver.api.core.metadata.Node::getConnectAddress()", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { @@ -232,7 +200,6 @@ "code": "java.method.parameterTypeChanged", "old": "parameter javax.net.ssl.SSLEngine com.datastax.oss.driver.api.core.ssl.SslEngineFactory::newSslEngine(===java.net.SocketAddress===)", "new": "parameter javax.net.ssl.SSLEngine com.datastax.oss.driver.api.core.ssl.SslEngineFactory::newSslEngine(===com.datastax.oss.driver.api.core.metadata.EndPoint===)", - "oldArchive": "com.datastax.oss:java-driver-core:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { From 43ccaed6b178430f1283db54f2484a4df8f8249a Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 14 Mar 2019 09:56:53 -0700 Subject: [PATCH 733/742] Also remove oldArchive in test-infra's revapi.json --- test-infra/revapi.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/test-infra/revapi.json b/test-infra/revapi.json index a560b7bd1b3..179c6f1df52 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -23,21 +23,18 @@ "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.util.Set com.datastax.oss.driver.api.testinfra.CassandraResourceRule::getContactPoints()", "new": "method java.util.Set com.datastax.oss.driver.api.testinfra.CassandraResourceRule::getContactPoints()", - "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.numberOfParametersChanged", "old": "method void com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter, java.util.Set)", "new": "method void com.datastax.oss.driver.api.testinfra.loadbalancing.SortingLoadBalancingPolicy::init(java.util.Map, com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy.DistanceReporter)", - "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" }, { "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.util.Set com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule::getContactPoints()", "new": "method java.util.Set com.datastax.oss.driver.api.testinfra.simulacron.SimulacronRule::getContactPoints()", - "oldArchive": "com.datastax.oss:java-driver-test-infra:jar:4.0.0-rc1", "justification": "JAVA-2165: Abstract node connection information" } ] From 83babd331322132229149ce5fa55bc573be2e637 Mon Sep 17 00:00:00 2001 From: tomekl007 Date: Mon, 4 Mar 2019 13:15:02 +0100 Subject: [PATCH 734/742] JAVA-2148: Add examples --- changelog/README.md | 1 + examples/README.md | 9 + examples/pom.xml | 181 +++++++ .../basic/CreateAndPopulateKeyspace.java | 152 ++++++ .../examples/basic/ReadCassandraVersion.java | 60 +++ .../basic/ReadTopologyAndSchemaMetadata.java | 62 +++ .../LimitConcurrencyRequestThrottler.java | 117 +++++ .../oss/driver/examples/datatypes/Blobs.java | 255 ++++++++++ .../driver/examples/json/PlainTextJson.java | 195 ++++++++ .../json/codecs/JacksonJsonCodec.java | 161 +++++++ .../examples/json/codecs/Jsr353JsonCodec.java | 201 ++++++++ .../json/jackson/JacksonJsonColumn.java | 170 +++++++ .../json/jackson/JacksonJsonFunction.java | 229 +++++++++ .../examples/json/jackson/JacksonJsonRow.java | 170 +++++++ .../examples/json/jsr/Jsr353JsonColumn.java | 160 +++++++ .../examples/json/jsr/Jsr353JsonFunction.java | 202 ++++++++ .../examples/json/jsr/Jsr353JsonRow.java | 156 ++++++ .../examples/paging/ForwardPagingRestUi.java | 301 ++++++++++++ .../examples/paging/RandomPagingRestUi.java | 373 +++++++++++++++ .../examples/retry/DowngradingRetry.java | 447 ++++++++++++++++++ examples/src/main/resources/application.conf | 14 + .../src/main/resources/cassandra_logo.png | Bin 0 -> 12623 bytes examples/src/main/resources/logback.xml | 37 ++ pom.xml | 58 ++- 24 files changed, 3710 insertions(+), 1 deletion(-) create mode 100644 examples/README.md create mode 100644 examples/pom.xml create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/basic/CreateAndPopulateKeyspace.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadCassandraVersion.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadTopologyAndSchemaMetadata.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/concurrent/LimitConcurrencyRequestThrottler.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/datatypes/Blobs.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/PlainTextJson.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/JacksonJsonCodec.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/Jsr353JsonCodec.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonColumn.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonFunction.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonRow.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonColumn.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonFunction.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonRow.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/retry/DowngradingRetry.java create mode 100644 examples/src/main/resources/application.conf create mode 100644 examples/src/main/resources/cassandra_logo.png create mode 100644 examples/src/main/resources/logback.xml diff --git a/changelog/README.md b/changelog/README.md index bca3a151d28..223af4aba9e 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2148: Add examples - [bug] JAVA-2189: Exclude virtual keyspaces from token map computation - [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 - [improvement] JAVA-2182: Add insertInto().json() variant that takes an object in QueryBuilder diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..83c61f27971 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# DataStax Java Driver for Apache Cassandra(R) - Examples + +This module contains examples of how to use the DataStax Java driver for +Apache Cassandra(R). + +## Usage + +Unless otherwise stated, all examples assume that you have a single-node Cassandra 3.0 cluster +listening on localhost:9042. diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 00000000000..08bf52db2f5 --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + + + java-driver-parent + com.datastax.oss + 4.0.0-rc2-SNAPSHOT + + + java-driver-examples + DataStax Java driver for Apache Cassandra(R) - examples. + A collection of examples to demonstrate DataStax Java Driver for Apache + Cassandra(R). + + + + + + + + + ${project.groupId} + java-driver-core + ${project.version} + + + ${project.groupId} + java-driver-query-builder + ${project.version} + + + + + com.fasterxml.jackson.core + jackson-databind + + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-base + ${jackson.version} + true + + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson.version} + true + + + + + + javax.json + javax.json-api + true + + + + org.glassfish + javax.json + true + runtime + + + + + + javax.ws.rs + javax.ws.rs-api + true + + + + + + org.glassfish.jersey.core + jersey-server + true + + + + org.glassfish.jersey.media + jersey-media-json-jackson + true + + + + org.glassfish.jersey.containers + jersey-container-jdk-http + true + + + + + + org.glassfish.hk2 + hk2-api + true + + + + + + javax.inject + javax.inject + true + + + + javax.annotation + javax.annotation-api + true + + + + + ch.qos.logback + logback-classic + runtime + + + + + + + + org.revapi + revapi-maven-plugin + + true + + + + maven-javadoc-plugin + + true + + + + maven-gpg-plugin + + true + + + + maven-install-plugin + + true + + + + maven-deploy-plugin + + true + + + + + + \ No newline at end of file diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/basic/CreateAndPopulateKeyspace.java b/examples/src/main/java/com/datastax/oss/driver/examples/basic/CreateAndPopulateKeyspace.java new file mode 100644 index 00000000000..7b6d75c53dd --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/basic/CreateAndPopulateKeyspace.java @@ -0,0 +1,152 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.basic; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; + +/** + * Creates a keyspace and tables, and loads some data into them. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "simplex" in the session. If a keyspace with this name already + * exists, it will be reused; + *
        • creates two tables "simplex.songs" and "simplex.playlists". If they exist already, they + * will be reused; + *
        • inserts a row in each table. + *
        + * + * @see Java driver online + * manual + */ +public class CreateAndPopulateKeyspace { + + public static void main(String[] args) { + + CreateAndPopulateKeyspace client = new CreateAndPopulateKeyspace(); + + try { + client.connect(); + client.createSchema(); + client.loadData(); + client.querySchema(); + + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + client.close(); + } + } + + private CqlSession session; + + /** Initiates a connection to the session specified by the application.conf. */ + public void connect() { + + session = CqlSession.builder().build(); + + System.out.printf("Connected session: %s%n", session.getName()); + } + + /** Creates the schema (keyspace) and tables for this example. */ + public void createSchema() { + + session.execute( + "CREATE KEYSPACE IF NOT EXISTS simplex WITH replication " + + "= {'class':'SimpleStrategy', 'replication_factor':1};"); + + session.execute( + "CREATE TABLE IF NOT EXISTS simplex.songs (" + + "id uuid PRIMARY KEY," + + "title text," + + "album text," + + "artist text," + + "tags set," + + "data blob" + + ");"); + + session.execute( + "CREATE TABLE IF NOT EXISTS simplex.playlists (" + + "id uuid," + + "title text," + + "album text, " + + "artist text," + + "song_id uuid," + + "PRIMARY KEY (id, title, album, artist)" + + ");"); + } + + /** Inserts data into the tables. */ + public void loadData() { + + session.execute( + "INSERT INTO simplex.songs (id, title, album, artist, tags) " + + "VALUES (" + + "756716f7-2e54-4715-9f00-91dcbea6cf50," + + "'La Petite Tonkinoise'," + + "'Bye Bye Blackbird'," + + "'Joséphine Baker'," + + "{'jazz', '2013'})" + + ";"); + + session.execute( + "INSERT INTO simplex.playlists (id, song_id, title, album, artist) " + + "VALUES (" + + "2cc9ccb7-6221-4ccb-8387-f22b6a1b354d," + + "756716f7-2e54-4715-9f00-91dcbea6cf50," + + "'La Petite Tonkinoise'," + + "'Bye Bye Blackbird'," + + "'Joséphine Baker'" + + ");"); + } + + /** Queries and displays data. */ + public void querySchema() { + + ResultSet results = + session.execute( + "SELECT * FROM simplex.playlists " + + "WHERE id = 2cc9ccb7-6221-4ccb-8387-f22b6a1b354d;"); + + System.out.printf("%-30s\t%-20s\t%-20s%n", "title", "album", "artist"); + System.out.println( + "-------------------------------+-----------------------+--------------------"); + + for (Row row : results) { + + System.out.printf( + "%-30s\t%-20s\t%-20s%n", + row.getString("title"), row.getString("album"), row.getString("artist")); + } + } + + /** Closes the session. */ + public void close() { + if (session != null) { + session.close(); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadCassandraVersion.java b/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadCassandraVersion.java new file mode 100644 index 00000000000..58e58fac497 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadCassandraVersion.java @@ -0,0 +1,60 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.basic; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; + +/** + * Connects to a Cassandra cluster and extracts basic information from it. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: none. + * + * @see Java driver online + * manual + */ +public class ReadCassandraVersion { + + public static void main(String[] args) { + + // The Session is what you use to execute queries. It is thread-safe and should be + // reused. + try (CqlSession session = CqlSession.builder().build()) { + // We use execute to send a query to Cassandra. This returns a ResultSet, which + // is essentially a collection of Row objects. + ResultSet rs = session.execute("select release_version from system.local"); + // Extract the first row (which is the only one in this case). + Row row = rs.one(); + + // Extract the value of the first (and only) column from the row. + assert row != null; + String releaseVersion = row.getString("release_version"); + System.out.printf("Cassandra version is: %s%n", releaseVersion); + } + // The try-with-resources block automatically close the session after we’re done with it. + // This step is important because it frees underlying resources (TCP connections, thread + // pools...). In a real application, you would typically do this at shutdown + // (for example, when undeploying your webapp). + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadTopologyAndSchemaMetadata.java b/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadTopologyAndSchemaMetadata.java new file mode 100644 index 00000000000..012c92702d9 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/basic/ReadTopologyAndSchemaMetadata.java @@ -0,0 +1,62 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.basic; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; + +/** + * Gathers information about a Cassandra cluster's topology (which nodes belong to the cluster) and + * schema (what keyspaces, tables, etc. exist in this cluster). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: none. + * + * @see Java driver online + * manual + */ +public class ReadTopologyAndSchemaMetadata { + + public static void main(String[] args) { + + try (CqlSession session = CqlSession.builder().build()) { + + Metadata metadata = session.getMetadata(); + System.out.printf("Connected session: %s%n", session.getName()); + + for (Node node : metadata.getNodes().values()) { + System.out.printf( + "Datatacenter: %s; Host: %s; Rack: %s%n", + node.getDatacenter(), node.getEndPoint(), node.getRack()); + } + + for (KeyspaceMetadata keyspace : metadata.getKeyspaces().values()) { + for (TableMetadata table : keyspace.getTables().values()) { + System.out.printf("Keyspace: %s; Table: %s%n", keyspace.getName(), table.getName()); + } + } + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/concurrent/LimitConcurrencyRequestThrottler.java b/examples/src/main/java/com/datastax/oss/driver/examples/concurrent/LimitConcurrencyRequestThrottler.java new file mode 100644 index 00000000000..995054f2c52 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/concurrent/LimitConcurrencyRequestThrottler.java @@ -0,0 +1,117 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.concurrent; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.internal.core.session.throttling.ConcurrencyLimitingRequestThrottler; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * Creates a keyspace and tables, and loads data using Async API into them. + * + *

        This example makes usage of a {@link CqlSession#executeAsync(String)} method, which is + * responsible for executing requests in a non-blocking way. It uses {@link + * ConcurrencyLimitingRequestThrottler} to limit number of concurrent requests to 32. It uses + * advanced.throttler configuration to limit async concurrency (max-concurrent-requests = 32) The + * max-queue-size is set to 10000 to buffer {@code TOTAL_NUMBER_OF_INSERTS} in a queue in a case of + * initial delay. (see application.conf) + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the session. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.tbl_sample_kv". If it exists already, it will be reused; + *
        • inserts {@code TOTAL_NUMBER_OF_INSERTS} rows into the table. + *
        + * + * @see Java + * driver online manual: Request throttling + */ +public class LimitConcurrencyRequestThrottler { + private static final int TOTAL_NUMBER_OF_INSERTS = 10_000; + + public static void main(String[] args) throws InterruptedException, ExecutionException { + + try (CqlSession session = CqlSession.builder().build()) { + createSchema(session); + insertConcurrent(session); + } + } + + private static void insertConcurrent(CqlSession session) + throws InterruptedException, ExecutionException { + PreparedStatement pst = + session.prepare( + insertInto("examples", "tbl_sample_kv") + .value("id", bindMarker("id")) + .value("value", bindMarker("value")) + .build()); + // Create list of pending CompletableFutures. + // We will add every operation returned from executeAsync. + // Next, we will wait for completion of all TOTAL_NUMBER_OF_INSERTS + List> pending = new ArrayList<>(); + + // For every i we will insert a record to db + for (int i = 0; i < TOTAL_NUMBER_OF_INSERTS; i++) { + pending.add( + session + .executeAsync(pst.bind().setUuid("id", UUID.randomUUID()).setInt("value", i)) + // Transform CompletionState toCompletableFuture to be able to wait for execution of + // all using CompletableFuture.allOf + .toCompletableFuture()); + } + + // Wait for completion of all TOTAL_NUMBER_OF_INSERTS pending requests + CompletableFuture.allOf(pending.toArray(new CompletableFuture[0])).get(); + + System.out.println( + String.format( + "Finished executing %s queries with a concurrency level of %s.", + pending.size(), + session + .getContext() + .getConfig() + .getDefaultProfile() + .getInt(DefaultDriverOption.REQUEST_THROTTLER_MAX_CONCURRENT_REQUESTS))); + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + + session.execute( + "CREATE TABLE IF NOT EXISTS examples.tbl_sample_kv (id uuid, value int, PRIMARY KEY (id))"); + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/datatypes/Blobs.java b/examples/src/main/java/com/datastax/oss/driver/examples/datatypes/Blobs.java new file mode 100644 index 00000000000..343a03306e9 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/datatypes/Blobs.java @@ -0,0 +1,255 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.datatypes; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Inserts and retrieves values in BLOB columns. + * + *

        By default, the Java driver maps this type to {@link java.nio.ByteBuffer}. The ByteBuffer API + * is a bit tricky to use at times, so we will show common pitfalls as well. We strongly recommend + * that you read the {@link java.nio.Buffer} and {@link ByteBuffer} API docs and become familiar + * with the capacity, limit and position properties. This tutorial might also help. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        • FILE references an existing file. + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.blobs". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + */ +public class Blobs { + + private static File FILE = new File(Blobs.class.getResource("/cassandra_logo.png").getFile()); + + public static void main(String[] args) throws IOException { + + try (CqlSession session = CqlSession.builder().build()) { + + createSchema(session); + allocateAndInsert(session); + retrieveSimpleColumn(session); + retrieveMapColumn(session); + insertConcurrent(session); + retrieveFromFileAndInsertInto(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.blobs(k int PRIMARY KEY, b blob, m map)"); + } + + private static void allocateAndInsert(CqlSession session) { + // One way to get a byte buffer is to allocate it and fill it yourself: + ByteBuffer buffer = ByteBuffer.allocate(16); + while (buffer.hasRemaining()) { + buffer.put((byte) 0xFF); + } + + // Don't forget to flip! The driver expects a buffer that is ready for reading. That is, it will + // consider all the data between buffer.position() and buffer.limit(). + // Right now we are positioned at the end because we just finished writing, so if we passed the + // buffer as-is it would appear to be empty: + assert buffer.limit() - buffer.position() == 0; + + buffer.flip(); + // Now position is back to the beginning, so the driver will see all 16 bytes. + assert buffer.limit() - buffer.position() == 16; + + Map map = new HashMap(); + map.put("test", buffer); + + PreparedStatement prepared = + session.prepare("INSERT INTO examples.blobs (k, b, m) VALUES (1, ?, ?)"); + + session.execute(prepared.bind(buffer, map)); + } + + private static void retrieveSimpleColumn(CqlSession session) { + Row row = session.execute("SELECT b, m FROM examples.blobs WHERE k = 1").one(); + + assert row != null; + ByteBuffer buffer = row.getByteBuffer("b"); + + // The driver always returns buffers that are ready for reading. + assert buffer != null; + assert buffer.limit() - buffer.position() == 16; + + // One way to read from the buffer is to use absolute getters. Do NOT start reading at index 0, + // as the buffer might start at a different position (we'll see an example of that later). + for (int i = buffer.position(); i < buffer.limit(); i++) { + byte b = buffer.get(i); + assert b == (byte) 0xFF; + } + + // Another way is to use relative getters. + while (buffer.hasRemaining()) { + byte b = buffer.get(); + assert b == (byte) 0xFF; + } + // Note that relative getters change the position, + // so when we're done reading we're at the end again. + assert buffer.position() == buffer.limit(); + + // Reset the position for the next operation. + buffer.flip(); + + // Yet another way is to convert the buffer to a byte array. + // Do NOT use buffer.array(), because it returns the + // buffer's *backing array*, which is not the same thing as its contents: + // - not all byte buffers have backing arrays + // - even then, the backing array might be larger than the buffer's contents + // + // The driver provides a utility method that handles those details for you: + byte[] array = Bytes.getArray(buffer); + assert array.length == 16; + for (byte b : array) { + assert b == (byte) 0xFF; + } + } + + private static void retrieveMapColumn(CqlSession session) { + Row row = session.execute("SELECT b, m FROM examples.blobs WHERE k = 1").one(); + + // The map columns illustrates the pitfalls with position() and array(). + assert row != null; + Map m = row.getMap("m", String.class, ByteBuffer.class); + assert m != null; + ByteBuffer buffer = m.get("test"); + + // We did get back a buffer that contains 16 bytes as expected. + assert buffer.limit() - buffer.position() == 16; + // However, it is not positioned at 0. And you can also see that its backing array contains more + // than 16 bytes. + // What happens is that the buffer is a "view" of the last 16 of a 32-byte array. + // This is an implementation detail and you shouldn't have to worry about it if you process the + // buffer correctly (don't iterate from 0, use Bytes.getArray()). + assert buffer.position() == 16; + assert buffer.array().length == 32; + } + + private static void insertConcurrent(CqlSession session) { + PreparedStatement preparedStatement = + session.prepare("INSERT INTO examples.blobs (k, b) VALUES (1, :b)"); + + // This is another convenient utility provided by the driver. It's useful for tests. + ByteBuffer buffer = Bytes.fromHexString("0xffffff"); + + // When you pass a byte buffer to a bound statement, it creates a shallow copy internally with + // the buffer.duplicate() method. + BoundStatement boundStatement = preparedStatement.bind().setByteBuffer("b", buffer); + + // This means you can now move in the original buffer, without affecting the insertion if it + // happens later. + buffer.position(buffer.limit()); + + session.execute(boundStatement); + Row row = session.execute("SELECT b FROM examples.blobs WHERE k = 1").one(); + assert row != null; + assert Objects.equals(Bytes.toHexString(row.getByteBuffer("b")), "0xffffff"); + + buffer.flip(); + + // HOWEVER duplicate() only performs a shallow copy. The two buffers still share the same + // contents. So if you modify the contents of the original buffer, + // this will affect another execution of the bound statement. + buffer.put(0, (byte) 0xaa); + session.execute(boundStatement); + row = session.execute("SELECT b FROM examples.blobs WHERE k = 1").one(); + assert row != null; + assert Objects.equals(Bytes.toHexString(row.getByteBuffer("b")), "0xaaffff"); + + // This will also happen if you use the async API, e.g. create the bound statement, call + // executeAsync() on it and reuse the buffer immediately. + + // If you reuse buffers concurrently and want to avoid those issues, perform a deep copy of the + // buffer before passing it to the bound statement. + int startPosition = buffer.position(); + ByteBuffer buffer2 = ByteBuffer.allocate(buffer.limit() - startPosition); + buffer2.put(buffer); + buffer.position(startPosition); + buffer2.flip(); + boundStatement = boundStatement.setByteBuffer("b", buffer2); + session.execute(boundStatement); + + // Note: unlike BoundStatement, SimpleStatement does not duplicate its arguments, so even the + // position will be affected if you change it before executing the statement. + // Again, resort to deep copies if required. + } + + private static void retrieveFromFileAndInsertInto(CqlSession session) throws IOException { + ByteBuffer buffer = readAll(FILE); + PreparedStatement prepared = session.prepare("INSERT INTO examples.blobs (k, b) VALUES (1, ?)"); + session.execute(prepared.bind(buffer)); + + File tmpFile = File.createTempFile("blob", ".png"); + System.out.printf("Writing retrieved buffer to %s%n", tmpFile.getAbsoluteFile()); + + Row row = session.execute("SELECT b FROM examples.blobs WHERE k = 1").one(); + assert row != null; + writeAll(row.getByteBuffer("b"), tmpFile); + } + + // Note: + // - This can be improved by using new-io + // - this reads the whole file in memory in one go. If your file does not fit in memory you should + // probably not insert it into Cassandra either ;) + private static ByteBuffer readAll(File file) throws IOException { + try (FileInputStream inputStream = new FileInputStream(file)) { + FileChannel channel = inputStream.getChannel(); + ByteBuffer buffer = ByteBuffer.allocate((int) channel.size()); + channel.read(buffer); + buffer.flip(); + return buffer; + } + } + + private static void writeAll(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + FileChannel channel = outputStream.getChannel(); + channel.write(buffer); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/PlainTextJson.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/PlainTextJson.java new file mode 100644 index 00000000000..3d2982f0429 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/PlainTextJson.java @@ -0,0 +1,195 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.querybuilder.select.Selector; + +/** + * Illustrates basic JSON support with plain JSON strings. For more advanced examples using complex + * objects and custom codecs, refer to the other examples in this package. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.querybuilder_json". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + * + * @see What’s + * New in Cassandra 2.2: JSON Support + */ +public class PlainTextJson { + + public static void main(String[] args) { + + try (CqlSession session = CqlSession.builder().build()) { + createSchema(session); + + insertWithCoreApi(session); + selectWithCoreApi(session); + + insertWithQueryBuilder(session); + selectWithQueryBuilder(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.querybuilder_json(" + + "id int PRIMARY KEY, name text, specs map)"); + } + + /** Demonstrates data insertion with the "core" API, i.e. providing the full query strings. */ + private static void insertWithCoreApi(CqlSession session) { + // Bind in a simple statement: + session.execute( + SimpleStatement.newInstance( + "INSERT INTO examples.querybuilder_json JSON ?", + "{ \"id\": 1, \"name\": \"Mouse\", \"specs\": { \"color\": \"silver\" } }")); + + // Bind in a prepared statement: + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = session.prepare("INSERT INTO examples.querybuilder_json JSON :payload"); + session.execute( + pst.bind() + .setString( + "payload", + "{ \"id\": 2, \"name\": \"Keyboard\", \"specs\": { \"layout\": \"qwerty\" } }")); + + // fromJson lets you provide individual columns as JSON: + session.execute( + SimpleStatement.newInstance( + "INSERT INTO examples.querybuilder_json " + + "(id, name, specs) VALUES (?, ?, fromJson(?))", + 3, + "Screen", + "{ \"size\": \"24-inch\" }")); + } + + /** Demonstrates data retrieval with the "core" API, i.e. providing the full query strings. */ + private static void selectWithCoreApi(CqlSession session) { + // Reading the whole row as a JSON object: + Row row = + session + .execute( + SimpleStatement.newInstance( + "SELECT JSON * FROM examples.querybuilder_json WHERE id = ?", 1)) + .one(); + assert row != null; + System.out.printf("Entry #1 as JSON: %s%n", row.getString("[json]")); + + // Extracting a particular column as JSON: + row = + session + .execute( + SimpleStatement.newInstance( + "SELECT id, toJson(specs) AS json_specs FROM examples.querybuilder_json WHERE id = ?", + 2)) + .one(); + assert row != null; + System.out.printf( + "Entry #%d's specs as JSON: %s%n", row.getInt("id"), row.getString("json_specs")); + } + + /** + * Same as {@link #insertWithCoreApi(CqlSession)}, but using {@link + * com.datastax.oss.driver.api.querybuilder.QueryBuilder} to construct the queries. + */ + private static void insertWithQueryBuilder(CqlSession session) { + // Simple statement: + Statement stmt = + insertInto("examples", "querybuilder_json") + .json("{ \"id\": 1, \"name\": \"Mouse\", \"specs\": { \"color\": \"silver\" } }") + .build(); + session.execute(stmt); + + // Prepare and bind: + PreparedStatement pst = + session.prepare( + insertInto("examples", "querybuilder_json").json(bindMarker("payload")).build()); + session.execute( + pst.bind() + .setString( + "payload", + "{ \"id\": 2, \"name\": \"Keyboard\", \"specs\": { \"layout\": \"qwerty\" } }")); + + // fromJson on a single column: + stmt = + insertInto("examples", "querybuilder_json") + .value("id", literal(3)) + .value("name", literal("Screen")) + .value("specs", function("fromJson", literal("{ \"size\": \"24-inch\" }"))) + .build(); + session.execute(stmt); + } + + /** + * Same as {@link #selectWithCoreApi(CqlSession)}, but using {@link + * com.datastax.oss.driver.api.querybuilder.QueryBuilder} to construct the queries. + */ + private static void selectWithQueryBuilder(CqlSession session) { + // Reading the whole row as a JSON object: + Statement stmt = + selectFrom("examples", "querybuilder_json") + .json() + .all() + .whereColumn("id") + .isEqualTo(literal(1)) + .build(); + Row row = session.execute(stmt).one(); + assert row != null; + System.out.printf("Entry #1 as JSON: %s%n", row.getString("[json]")); + + // Extracting a particular column as JSON: + stmt = + selectFrom("examples", "querybuilder_json") + .column("id") + .function("toJson", Selector.column("specs")) + .as("json_specs") + .whereColumn("id") + .isEqualTo(literal(2)) + .build(); + + row = session.execute(stmt).one(); + assert row != null; + + System.out.printf( + "Entry #%d's specs as JSON: %s%n", row.getInt("id"), row.getString("json_specs")); + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/JacksonJsonCodec.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/JacksonJsonCodec.java new file mode 100644 index 00000000000..763d325f7c9 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/JacksonJsonCodec.java @@ -0,0 +1,161 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.codecs; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.util.Strings; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A JSON codec that uses the Jackson library to + * perform serialization and deserialization of JSON objects. + * + *

        This codec maps a single Java object to a single JSON structure at a time; mapping of arrays + * or collections to root-level JSON arrays is not supported, but such a codec can be easily crafted + * after this one. + * + *

        Note that this codec requires the presence of Jackson library at runtime. If you use Maven, + * this can be done by declaring the following dependency in your project: + * + *

        {@code
        + * 
        + *   com.fasterxml.jackson.core
        + *   jackson-databind
        + *   2.9.8
        + * 
        + * }
        + */ +public class JacksonJsonCodec implements TypeCodec { + + private final ObjectMapper objectMapper; + private final GenericType javaType; + + /** + * Creates a new instance for the provided {@code javaClass}, using a default, newly-allocated + * {@link ObjectMapper}. + * + * @param javaClass the Java class this codec maps to. + */ + public JacksonJsonCodec(Class javaClass) { + this(javaClass, new ObjectMapper()); + } + + /** + * Creates a new instance for the provided {@code javaClass}, and using the provided {@link + * ObjectMapper}. + * + * @param javaClass the Java class this codec maps to. + */ + public JacksonJsonCodec(Class javaClass, ObjectMapper objectMapper) { + this.javaType = GenericType.of(javaClass); + this.objectMapper = objectMapper; + } + + @NonNull + @Override + public GenericType getJavaType() { + return javaType; + } + + @NonNull + @Override + public DataType getCqlType() { + return DataTypes.TEXT; + } + + @Nullable + @Override + public ByteBuffer encode(@Nullable T value, @NonNull ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } + try { + return ByteBuffer.wrap(objectMapper.writeValueAsBytes(value)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @Nullable + @Override + public T decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + if (bytes == null) { + return null; + } + try { + return objectMapper.readValue(Bytes.getArray(bytes), toJacksonJavaType()); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @NonNull + @Override + public String format(T value) { + if (value == null) { + return "NULL"; + } + String json; + try { + json = objectMapper.writeValueAsString(value); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + return Strings.quote(json); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public T parse(String value) { + if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) { + return null; + } + if (!Strings.isQuoted(value)) { + throw new IllegalArgumentException("JSON strings must be enclosed by single quotes"); + } + String json = Strings.unquote(value); + try { + return (T) objectMapper.readValue(json, toJacksonJavaType()); + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + /** + * This method acts as a bridge between the driver's {@link + * com.datastax.oss.driver.api.core.type.reflect.GenericType GenericType} API and Jackson's {@link + * JavaType} API. + * + * @return A {@link JavaType} instance corresponding to the codec's {@link #getJavaType() Java + * type}. + */ + private JavaType toJacksonJavaType() { + return TypeFactory.defaultInstance().constructType(getJavaType().getType()); + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/Jsr353JsonCodec.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/Jsr353JsonCodec.java new file mode 100644 index 00000000000..1b7eeae4a08 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/codecs/Jsr353JsonCodec.java @@ -0,0 +1,201 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.codecs; + +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.core.type.reflect.GenericType; +import com.datastax.oss.driver.internal.core.util.Strings; +import com.datastax.oss.protocol.internal.util.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.Map; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import javax.json.JsonStructure; +import javax.json.JsonWriter; +import javax.json.JsonWriterFactory; + +/** + * A JSON codec that uses the Java API for JSON + * processing to perform serialization and deserialization of JSON structures. + * + *

        More specifically, this codec maps an arbitrary {@link JsonStructure} to a CQL {@code varchar} + * column. + * + *

        This codec handles the Java type {@link JsonStructure}. It is therefore required that values + * are set and retrieved using that exact Java type; users should manually downcast to either {@link + * JsonObject} or {@link JsonArray}, as in the example below: + * + *

        {@code
        + * // setting values
        + * JsonObject myObject = ...
        + * PreparedStatement ps = ...
        + * // set values using JsonStructure as target Java type
        + * BoundStatement bs = ps.bind().set(1, myObject, JsonStructure.class);
        + *
        + * // retrieving values
        + * Row row = session.execute(bs).one();
        + * // use JsonStructure as target Java type to retrieve values
        + * JsonStructure json = row.get(0, JsonStructure.class);
        + * if (json instanceof JsonObject) {
        + *     myObject = (JsonObject) json;
        + *     ...
        + * }
        + * }
        + * + *

        Note that at runtime, this codec requires the presence of both JSR-353 API and a + * JSR-353-compatible runtime library, such as JSR-353's reference implementation. If you use + * Maven, this can be done by declaring the following dependencies in your project: + * + *

        {@code
        + * 
        + *   javax.json
        + *   javax.json-api
        + *   1.0
        + * 
        + *
        + * 
        + *   org.glassfish
        + *   javax.json
        + *   1.1.4
        + * 
        + * }
        + */ +public class Jsr353JsonCodec implements TypeCodec { + + private final JsonReaderFactory readerFactory; + + private final JsonWriterFactory writerFactory; + + /** Creates a new instance using a default configuration. */ + public Jsr353JsonCodec() { + this(null); + } + + /** + * Creates a new instance using the provided configuration. + * + * @param config A map of provider-specific configuration properties. May be empty or {@code + * null}. + */ + public Jsr353JsonCodec(Map config) { + readerFactory = Json.createReaderFactory(config); + writerFactory = Json.createWriterFactory(config); + } + + @NonNull + @Override + public GenericType getJavaType() { + return GenericType.of(JsonStructure.class); + } + + @NonNull + @Override + public DataType getCqlType() { + return DataTypes.TEXT; + } + + @Nullable + @Override + public ByteBuffer encode( + @Nullable JsonStructure value, @NonNull ProtocolVersion protocolVersion) { + if (value == null) { + return null; + } + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try { + JsonWriter writer = writerFactory.createWriter(baos); + writer.write(value); + return ByteBuffer.wrap(baos.toByteArray()); + } catch (JsonException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @Nullable + @Override + public JsonStructure decode( + @Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion) { + if (bytes == null) { + return null; + } + try (ByteArrayInputStream bais = new ByteArrayInputStream(Bytes.getArray(bytes))) { + try { + JsonReader reader = readerFactory.createReader(bais); + return reader.read(); + } catch (JsonException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @NonNull + @Override + public String format(JsonStructure value) throws IllegalArgumentException { + if (value == null) { + return "NULL"; + } + String json; + try (StringWriter sw = new StringWriter()) { + try { + JsonWriter writer = writerFactory.createWriter(sw); + writer.write(value); + json = sw.toString(); + } catch (JsonException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + return Strings.quote(json); + } + + @Override + public JsonStructure parse(String value) throws IllegalArgumentException { + if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL")) { + return null; + } + if (!Strings.isQuoted(value)) { + throw new IllegalArgumentException("JSON strings must be enclosed by single quotes"); + } + String json = Strings.unquote(value); + try (StringReader sr = new StringReader(json)) { + JsonReader reader = readerFactory.createReader(sr); + return reader.read(); + } catch (JsonException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonColumn.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonColumn.java new file mode 100644 index 00000000000..1873ecbfc3e --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonColumn.java @@ -0,0 +1,170 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jackson; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.JacksonJsonCodec; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Illustrates how to map a single table column of type {@code VARCHAR}, containing JSON payloads, + * into a Java object using the Jackson library. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link JacksonJsonCodec}, which + * is implemented in the java-driver-examples module. If you plan to follow this example, make sure + * to include the following Maven dependencies in your project: + * + *

        {@code
        + * 
        + *   com.fasterxml.jackson.core
        + *   jackson-databind
        + *   2.9.8
        + * 
        + * }
        + * + *

        This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.json_jackson_column". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + */ +public class JacksonJsonColumn { + + // A codec to convert JSON payloads into User instances; + private static final TypeCodec USER_CODEC = new JacksonJsonCodec<>(User.class); + + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().addTypeCodecs(USER_CODEC).build()) { + createSchema(session); + insertJsonColumn(session); + selectJsonColumn(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jackson_column(" + + "id int PRIMARY KEY, json text)"); + } + + // Mapping a User instance to a table column + private static void insertJsonColumn(CqlSession session) { + + User alice = new User("alice", 30); + User bob = new User("bob", 35); + + // Build and execute a simple statement + + Statement stmt = + insertInto("examples", "json_jackson_column") + .value("id", literal(1)) + // the User object will be converted into a String and persisted into the VARCHAR column + // "json" + .value("json", literal(alice, session.getContext().getCodecRegistry())) + .build(); + session.execute(stmt); + + // The JSON object can be a bound value if the statement is prepared + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = + session.prepare( + insertInto("examples", "json_jackson_column") + .value("id", bindMarker("id")) + .value("json", bindMarker("json")) + .build()); + session.execute(pst.bind().setInt("id", 2).set("json", bob, User.class)); + } + + // Retrieving User instances from a table column + private static void selectJsonColumn(CqlSession session) { + + Statement stmt = + selectFrom("examples", "json_jackson_column") + .all() + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + int id = row.getInt("id"); + // retrieve the JSON payload and convert it to a User instance + User user = row.get("json", User.class); + // it is also possible to retrieve the raw JSON payload + String json = row.getString("json"); + System.out.printf( + "Retrieved row:%n id %d%n user %s%n user (raw) %s%n%n", + id, user, json); + } + } + + @SuppressWarnings("unused") + public static class User { + + private final String name; + + private final int age; + + @JsonCreator + public User(@JsonProperty("name") String name, @JsonProperty("age") int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + @Override + public String toString() { + return String.format("%s (%s)", name, age); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonFunction.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonFunction.java new file mode 100644 index 00000000000..9e214572067 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonFunction.java @@ -0,0 +1,229 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jackson; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.JacksonJsonCodec; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Illustrates how to map a single table column of an arbitrary type to a Java object using the Jackson library, and leveraging the {@code + * toJson()} and {@code fromJson()} functions introduced in Cassandra 2.2. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link JacksonJsonCodec}. If you + * plan to follow this example, make sure to include the following Maven dependencies in your + * project: + * + *

        {@code
        + * 
        + *   com.fasterxml.jackson.core
        + *   jackson-databind
        + *   2.9.8
        + * 
        + * }
        + * + * This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a user-defined type (UDT) "examples.json_jackson_function_user". If it already + * exists, it will be reused; + *
        • creates a table "examples.json_jackson_function". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + * + * @see What’s + * New in Cassandra 2.2: JSON Support + */ +public class JacksonJsonFunction { + + // A codec to convert JSON payloads into User instances; + private static final TypeCodec USER_CODEC = new JacksonJsonCodec<>(User.class); + + // A codec to convert generic JSON payloads into JsonNode instances + private static final TypeCodec JSON_NODE_CODEC = new JacksonJsonCodec<>(JsonNode.class); + + public static void main(String[] args) { + try (CqlSession session = + CqlSession.builder().addTypeCodecs(USER_CODEC, JSON_NODE_CODEC).build()) { + createSchema(session); + insertFromJson(session); + selectToJson(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TYPE IF NOT EXISTS examples.json_jackson_function_user(" + "name text, age int)"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jackson_function(" + + "id int PRIMARY KEY, user frozen, scores map)"); + } + + // Mapping JSON payloads to table columns of arbitrary types, + // using fromJson() function + private static void insertFromJson(CqlSession session) { + + User alice = new User("alice", 30); + User bob = new User("bob", 35); + + ObjectNode aliceScores = + JsonNodeFactory.instance.objectNode().put("call_of_duty", 4.8).put("pokemon_go", 9.7); + ObjectNode bobScores = + JsonNodeFactory.instance.objectNode().put("zelda", 8.3).put("pokemon_go", 12.4); + + // Build and execute a simple statement + Statement stmt = + insertInto("examples", "json_jackson_function") + .value("id", literal(1)) + // client-side, the User object will be converted into a JSON String; + // then, server-side, the fromJson() function will convert that JSON string + // into an instance of the json_jackson_function_user user-defined type (UDT), + // which will be persisted into the column "user" + .value( + "user", + function("fromJson", literal(alice, session.getContext().getCodecRegistry()))) + // same thing, but this time converting from + // a generic JsonNode to a JSON string, then from this string to a map + .value( + "scores", + function("fromJson", literal(aliceScores, session.getContext().getCodecRegistry()))) + .build(); + System.out.println(((SimpleStatement) stmt).getQuery()); + session.execute(stmt); + System.out.println("after"); + + // The JSON object can be a bound value if the statement is prepared + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = + session.prepare( + insertInto("examples", "json_jackson_function") + .value("id", bindMarker("id")) + .value("user", function("fromJson", bindMarker("user"))) + .value("scores", function("fromJson", bindMarker("scores"))) + .build()); + System.out.println(pst.getQuery()); + session.execute( + pst.bind() + .setInt("id", 2) + .set("user", bob, User.class) + // note that the codec requires that the type passed to the set() method + // be always JsonNode, and not a subclass of it, such as ObjectNode + .set("scores", bobScores, JsonNode.class)); + } + + // Retrieving JSON payloads from table columns of arbitrary types, + // using toJson() function + private static void selectToJson(CqlSession session) { + + Statement stmt = + selectFrom("examples", "json_jackson_function") + .column("id") + .function("toJson", Selector.column("user")) + .as("user") + .function("toJson", Selector.column("scores")) + .as("scores") + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + System.out.println(((SimpleStatement) stmt).getQuery()); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + int id = row.getInt("id"); + // retrieve the JSON payload and convert it to a User instance + User user = row.get("user", User.class); + // it is also possible to retrieve the raw JSON payload + String userJson = row.getString("user"); + // retrieve the JSON payload and convert it to a JsonNode instance + // note that the codec requires that the type passed to the get() method + // be always JsonNode, and not a subclass of it, such as ObjectNode + JsonNode scores = row.get("scores", JsonNode.class); + // it is also possible to retrieve the raw JSON payload + String scoresJson = row.getString("scores"); + System.out.printf( + "Retrieved row:%n" + + "id %d%n" + + "user %s%n" + + "user (raw) %s%n" + + "scores %s%n" + + "scores (raw) %s%n%n", + id, user, userJson, scores, scoresJson); + } + } + + @SuppressWarnings("unused") + public static class User { + + private final String name; + + private final int age; + + @JsonCreator + public User(@JsonProperty("name") String name, @JsonProperty("age") int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + @Override + public String toString() { + return String.format("%s (%s)", name, age); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonRow.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonRow.java new file mode 100644 index 00000000000..2ce4ef4abc8 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jackson/JacksonJsonRow.java @@ -0,0 +1,170 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jackson; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.JacksonJsonCodec; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Illustrates how to map an entire table row to a Java object using the Jackson library, and leveraging the {@code + * SELECT JSON} and {@code INSERT JSON} syntaxes introduced in Cassandra 2.2. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link JacksonJsonCodec}. If you + * plan to follow this example, make sure to include the following Maven dependencies in your + * project: + * + *

        {@code
        + * 
        + *   com.fasterxml.jackson.core
        + *   jackson-databind
        + *   2.9.8
        + * 
        + * }
        + * + * This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.json_jackson_row". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + * + * @see What’s + * New in Cassandra 2.2: JSON Support + */ +public class JacksonJsonRow { + // A codec to convert JSON payloads into User instances; + private static final TypeCodec USER_CODEC = new JacksonJsonCodec<>(User.class); + + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().addTypeCodecs(USER_CODEC).build()) { + createSchema(session); + insertJsonRow(session); + selectJsonRow(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jackson_row(" + + "id int PRIMARY KEY, name text, age int)"); + } + + // Mapping a User instance to a table row using INSERT JSON + private static void insertJsonRow(CqlSession session) { + // Build and execute a simple statement + Statement stmt = + insertInto("examples", "json_jackson_row") + .json(new User(1, "alice", 30), session.getContext().getCodecRegistry()) + .build(); + session.execute(stmt); + + // The JSON object can be a bound value if the statement is prepared + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = + session.prepare( + insertInto("examples", "json_jackson_row").json(bindMarker("user")).build()); + session.execute(pst.bind().set("user", new User(2, "bob", 35), User.class)); + } + + // Retrieving User instances from table rows using SELECT JSON + private static void selectJsonRow(CqlSession session) { + + // Reading the whole row as a JSON object + Statement stmt = + selectFrom("examples", "json_jackson_row") + .json() + .all() + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + // SELECT JSON returns only one column for each row, of type VARCHAR, + // containing the row as a JSON payload + User user = row.get(0, User.class); + System.out.printf("Retrieved user: %s%n", user); + } + } + + @SuppressWarnings("unused") + public static class User { + + private final int id; + + private final String name; + + private final int age; + + @JsonCreator + public User( + @JsonProperty("id") int id, + @JsonProperty("name") String name, + @JsonProperty("age") int age) { + this.id = id; + this.name = name; + this.age = age; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + @Override + public String toString() { + return String.format("%s (id %d, age %d)", name, id, age); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonColumn.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonColumn.java new file mode 100644 index 00000000000..6776399699b --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonColumn.java @@ -0,0 +1,160 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jsr; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.Jsr353JsonCodec; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonStructure; + +/** + * Illustrates how to map a single table column of type {@code VARCHAR}, containing JSON payloads, + * into a Java object using the Java API for JSON + * processing. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link Jsr353JsonCodec}, which + * is declared in the java-driver-examples module. If you plan to follow this example, make sure to + * include the following Maven dependencies in your project: + * + *

        {@code
        + * 
        + *     javax.json
        + *     javax.json-api
        + *     1.0
        + * 
        + *
        + * 
        + *     org.glassfish
        + *     javax.json
        + *     1.1.4
        + *     runtime
        + * 
        + * }
        + * + * This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.json_jsr353_column". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + */ +public class Jsr353JsonColumn { + + // A codec to convert JSON payloads into JsonObject instances; + private static final Jsr353JsonCodec USER_CODEC = new Jsr353JsonCodec(); + + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().addTypeCodecs(USER_CODEC).build()) { + createSchema(session); + insertJsonColumn(session); + selectJsonColumn(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jsr353_column(" + + "id int PRIMARY KEY, json text)"); + } + + // Mapping a JSON object to a table column + private static void insertJsonColumn(CqlSession session) { + + JsonObject alice = Json.createObjectBuilder().add("name", "alice").add("age", 30).build(); + + JsonObject bob = Json.createObjectBuilder().add("name", "bob").add("age", 35).build(); + + // Build and execute a simple statement + Statement stmt = + insertInto("examples", "json_jsr353_column") + .value("id", literal(1)) + // the JSON object will be converted into a String and persisted into the VARCHAR column + // "json" + .value("json", literal(alice, session.getContext().getCodecRegistry())) + .build(); + session.execute(stmt); + + // The JSON object can be a bound value if the statement is prepared + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = + session.prepare( + insertInto("examples", "json_jsr353_column") + .value("id", bindMarker("id")) + .value("json", bindMarker("json")) + .build()); + session.execute( + pst.bind() + .setInt("id", 2) + // note that the codec requires that the type passed to the set() method + // be always JsonStructure, and not a subclass of it, such as JsonObject + .set("json", bob, JsonStructure.class)); + } + + // Retrieving JSON objects from a table column + private static void selectJsonColumn(CqlSession session) { + + Statement stmt = + selectFrom("examples", "json_jsr353_column") + .all() + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + int id = row.getInt("id"); + // retrieve the JSON payload and convert it to a JsonObject instance + // note that the codec requires that the type passed to the get() method + // be always JsonStructure, and not a subclass of it, such as JsonObject, + // hence the need to downcast to JsonObject manually + JsonObject user = (JsonObject) row.get("json", JsonStructure.class); + // it is also possible to retrieve the raw JSON payload + String json = row.getString("json"); + System.out.printf( + "Retrieved row:%n id %d%n user %s%n user (raw) %s%n%n", + id, user, json); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonFunction.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonFunction.java new file mode 100644 index 00000000000..60d58275955 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonFunction.java @@ -0,0 +1,202 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jsr; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.api.querybuilder.select.Selector; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.Jsr353JsonCodec; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonStructure; + +/** + * Illustrates how to map a single table column of an arbitrary type to a Java object using the Java API for JSON processing, and leveraging the + * {@code toJson()} and {@code fromJson()} functions introduced in Cassandra 2.2. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link Jsr353JsonCodec}, which + * is declared in the java-driver-examples module. If you plan to follow this example, make sure to + * include the following Maven dependencies in your project: + * + *

        {
        + * 
        + *     javax.json
        + *     javax.json-api
        + *     1.0
        + * 
        + *
        + * 
        + *     org.glassfish
        + *     javax.json
        + *     1.1.4
        + *     runtime
        + * 
        + * }
        + * + * This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a user-defined type (UDT) "examples.json_jsr353_function_user". If it already + * exists, it will be reused; + *
        • creates a table "examples.json_jsr353_function". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + * + * @see What’s + * New in Cassandra 2.2: JSON Support + */ +public class Jsr353JsonFunction { + + // A codec to convert JSON payloads into JsonObject instances; + private static final Jsr353JsonCodec USER_CODEC = new Jsr353JsonCodec(); + + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().addTypeCodecs(USER_CODEC).build()) { + + createSchema(session); + insertFromJson(session); + selectToJson(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TYPE IF NOT EXISTS examples.json_jsr353_function_user(name text, age int)"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jsr353_function(" + + "id int PRIMARY KEY, user frozen, scores map)"); + } + + // Mapping JSON payloads to table columns of arbitrary types, + // using fromJson() function + private static void insertFromJson(CqlSession session) { + + JsonObject alice = Json.createObjectBuilder().add("name", "alice").add("age", 30).build(); + + JsonObject bob = Json.createObjectBuilder().add("name", "bob").add("age", 35).build(); + + JsonObject aliceScores = + Json.createObjectBuilder().add("call_of_duty", 4.8).add("pokemon_go", 9.7).build(); + + JsonObject bobScores = + Json.createObjectBuilder().add("zelda", 8.3).add("pokemon_go", 12.4).build(); + + // Build and execute a simple statement + Statement stmt = + insertInto("examples", "json_jsr353_function") + .value("id", literal(1)) + // client-side, the JsonObject will be converted into a JSON String; + // then, server-side, the fromJson() function will convert that JSON string + // into an instance of the json_jsr353_function_user user-defined type (UDT), + // which will be persisted into the column "user" + .value( + "user", + function("fromJson", literal(alice, session.getContext().getCodecRegistry()))) + // same thing, but this time converting from + // a JsonObject to a JSON string, then from this string to a map + .value( + "scores", + function("fromJson", literal(aliceScores, session.getContext().getCodecRegistry()))) + .build(); + session.execute(stmt); + + // The JSON object can be a bound value if the statement is prepared + // (subsequent calls to the prepare() method will return cached statement) + PreparedStatement pst = + session.prepare( + insertInto("examples", "json_jsr353_function") + .value("id", bindMarker("id")) + .value("user", function("fromJson", bindMarker("user"))) + .value("scores", function("fromJson", bindMarker("scores"))) + .build()); + session.execute( + pst.bind() + .setInt("id", 2) + // note that the codec requires that the type passed to the set() method + // be always JsonStructure, and not a subclass of it, such as JsonObject + .set("user", bob, JsonStructure.class) + .set("scores", bobScores, JsonStructure.class)); + } + + // Retrieving JSON payloads from table columns of arbitrary types, + // using toJson() function + private static void selectToJson(CqlSession session) { + + Statement stmt = + selectFrom("examples", "json_jsr353_function") + .column("id") + .function("toJson", Selector.column("user")) + .as("user") + .function("toJson", Selector.column("scores")) + .as("scores") + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + int id = row.getInt("id"); + // retrieve the JSON payload and convert it to a JsonObject instance + // note that the codec requires that the type passed to the get() method + // be always JsonStructure, and not a subclass of it, such as JsonObject, + // hence the need to downcast to JsonObject manually + JsonObject user = (JsonObject) row.get("user", JsonStructure.class); + // it is also possible to retrieve the raw JSON payload + String userJson = row.getString("user"); + // retrieve the JSON payload and convert it to a JsonObject instance + JsonObject scores = (JsonObject) row.get("scores", JsonStructure.class); + // it is also possible to retrieve the raw JSON payload + String scoresJson = row.getString("scores"); + System.out.printf( + "Retrieved row:%n" + + "id %d%n" + + "user %s%n" + + "user (raw) %s%n" + + "scores %s%n" + + "scores (raw) %s%n%n", + id, user, userJson, scores, scoresJson); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonRow.java b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonRow.java new file mode 100644 index 00000000000..c7f389f6017 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/json/jsr/Jsr353JsonRow.java @@ -0,0 +1,156 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.json.jsr; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.type.codec.TypeCodec; +import com.datastax.oss.driver.examples.json.PlainTextJson; +import com.datastax.oss.driver.examples.json.codecs.Jsr353JsonCodec; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonStructure; + +/** + * Illustrates how to map an entire table row to a Java object using the Java API for JSON processing, and leveraging the + * {@code SELECT JSON} and {@code INSERT JSON} syntaxes introduced in Cassandra 2.2. + * + *

        This example makes usage of a custom {@link TypeCodec codec}, {@link Jsr353JsonCodec}, which + * is declared in the java-driver-examples module. If you plan to follow this example, make sure to + * include the following Maven dependencies in your project: + * + *

        {@code
        + * 
        + *     javax.json
        + *     javax.json-api
        + *     1.0
        + * 
        + *
        + * 
        + *     org.glassfish
        + *     javax.json
        + *     1.1.4
        + *     runtime
        + * 
        + * }
        + * + * This example also uses the {@link com.datastax.oss.driver.api.querybuilder.QueryBuilder + * QueryBuilder}; for examples using the "core" API, see {@link PlainTextJson} (they are easily + * translatable to the queries in this class). + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.json_jsr353_row". If it already exists, it will be reused; + *
        • inserts data in the table. + *
        + * + * @see What’s + * New in Cassandra 2.2: JSON Support + */ +public class Jsr353JsonRow { + + // A codec to convert JSON payloads into JsonObject instances; + private static final Jsr353JsonCodec USER_CODEC = new Jsr353JsonCodec(); + + public static void main(String[] args) { + try (CqlSession session = CqlSession.builder().addTypeCodecs(USER_CODEC).build()) { + createSchema(session); + insertJsonRow(session); + selectJsonRow(session); + } + } + + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.json_jsr353_row(" + + "id int PRIMARY KEY, name text, age int)"); + } + + // Mapping a User instance to a table row using INSERT JSON + private static void insertJsonRow(CqlSession session) { + + JsonObject alice = + Json.createObjectBuilder().add("id", 1).add("name", "alice").add("age", 30).build(); + + JsonObject bob = + Json.createObjectBuilder().add("id", 2).add("name", "bob").add("age", 35).build(); + + // Build and execute a simple statement + Statement stmt = + insertInto("examples", "json_jsr353_row") + .json(alice, session.getContext().getCodecRegistry()) + .build(); + session.execute(stmt); + + // The JSON object can be a bound value if the statement is prepared + // (we use a local variable here for the sake of example, but in a real application you would + // cache and reuse the prepared statement) + PreparedStatement pst = + session.prepare(insertInto("examples", "json_jsr353_row").json(bindMarker("user")).build()); + session.execute( + pst.bind() + // note that the codec requires that the type passed to the set() method + // be always JsonStructure, and not a subclass of it, such as JsonObject + .set("user", bob, JsonStructure.class)); + } + + // Retrieving User instances from table rows using SELECT JSON + private static void selectJsonRow(CqlSession session) { + + // Reading the whole row as a JSON object + Statement stmt = + selectFrom("examples", "json_jsr353_row") + .json() + .all() + .whereColumn("id") + .in(literal(1), literal(2)) + .build(); + + ResultSet rows = session.execute(stmt); + + for (Row row : rows) { + // SELECT JSON returns only one column for each row, of type VARCHAR, + // containing the row as a JSON payload. + // Note that the codec requires that the type passed to the get() method + // be always JsonStructure, and not a subclass of it, such as JsonObject, + // hence the need to downcast to JsonObject manually + JsonObject user = (JsonObject) row.get(0, JsonStructure.class); + System.out.printf("Retrieved user: %s%n", user); + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java b/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java new file mode 100644 index 00000000000..c59d22815c1 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java @@ -0,0 +1,301 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.paging; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.internal.core.type.codec.DateCodec; +import com.datastax.oss.protocol.internal.util.Bytes; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * A stateless REST service (backed by Jersey, HK2 and the JDK HttpServer) that displays paginated results for + * a CQL query. + * + *

        Conversion to and from JSON is made through Jersey Jackson + * providers. + * + *

        Navigation is forward-only. The implementation relies on the paging state returned by + * Cassandra, and encodes it in HTTP URLs. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.forward_paging_rest_ui". If it already exists, it will be reused; + *
        • inserts data in the table; + *
        • launches a REST server listening on HTTP_PORT. + *
        + */ +public class ForwardPagingRestUi { + + private static final int HTTP_PORT = 8080; + + private static final int ITEMS_PER_PAGE = 10; + + private static final URI BASE_URI = + UriBuilder.fromUri("http://localhost/").path("").port(HTTP_PORT).build(); + + public static void main(String[] args) throws Exception { + + try (CqlSession session = CqlSession.builder().addTypeCodecs(new DateCodec()).build()) { + createSchema(session); + populateSchema(session); + startRestService(session); + } + } + + // Creates a table storing videos by users, in a typically denormalized way + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.forward_paging_rest_ui(" + + "userid int, username text, " + + "added timestamp, " + + "videoid int, title text, " + + "PRIMARY KEY (userid, added, videoid)" + + ") WITH CLUSTERING ORDER BY (added DESC, videoid ASC)"); + } + + private static void populateSchema(CqlSession session) { + PreparedStatement prepare = + session.prepare( + "INSERT INTO examples.forward_paging_rest_ui (userid, username, added, videoid, title) VALUES (?, ?, ?, ?, ?)"); + // 3 users + for (int i = 0; i < 3; i++) { + // 49 videos each + for (int j = 0; j < 49; j++) { + int videoid = i * 100 + j; + session.execute( + prepare.bind( + i, "user " + i, Instant.ofEpochMilli(j * 100000), videoid, "video " + videoid)); + } + } + } + + // starts the REST server using JDK HttpServer (com.sun.net.httpserver.HttpServer) + private static void startRestService(CqlSession session) + throws IOException, InterruptedException { + + final HttpServer server = + JdkHttpServerFactory.createHttpServer(BASE_URI, new VideoApplication(session), false); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + server.setExecutor(executor); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + System.out.println(); + System.out.println("Stopping REST Service"); + server.stop(0); + executor.shutdownNow(); + System.out.println("REST Service stopped"); + })); + server.start(); + + System.out.println(); + System.out.printf( + "REST Service started on http://localhost:%d/users, press CTRL+C to stop%n", HTTP_PORT); + System.out.println( + "To explore this example, start with the following request and walk from there:"); + System.out.printf("curl -i http://localhost:%d/users/1/videos%n", HTTP_PORT); + System.out.println(); + + Thread.currentThread().join(); + } + + /** + * Configures the REST application and handles injection of custom objects, such as the driver + * session. + * + *

        This is also the place where you would normally configure JSON serialization, etc. + * + *

        Note that in this example, we rely on the automatic discovery and configuration of Jackson + * through {@code org.glassfish.jersey.jackson.JacksonFeature}. + */ + public static class VideoApplication extends ResourceConfig { + + public VideoApplication(final CqlSession session) { + super(UserService.class); + // AbstractBinder is provided by HK2 + register( + new AbstractBinder() { + + @Override + protected void configure() { + bind(session).to(CqlSession.class); + } + }); + } + } + + /** + * A typical REST service, handling requests involving users. + * + *

        Typically, this service would contain methods for listing and searching for users, and + * methods to retrieve user details. Here, for brevity, only one method, listing videos by user, + * is implemented. + */ + @Singleton + @Path("/users") + @Produces("application/json") + public static class UserService { + + @Inject private CqlSession session; + + @Context private UriInfo uri; + + private PreparedStatement videosByUser; + + @PostConstruct + @SuppressWarnings("unused") + public void init() { + this.videosByUser = + session.prepare( + "SELECT videoid, title, added FROM examples.forward_paging_rest_ui WHERE userid = ?"); + } + + /** + * Returns a paginated list of all the videos created by the given user. + * + * @param userid the user ID. + * @param page the page to request, or {@code null} to get the first page. + */ + @GET + @Path("/{userid}/videos") + public UserVideosResponse getUserVideos( + @PathParam("userid") int userid, @QueryParam("page") String page) { + + BoundStatementBuilder statementBuilder = + videosByUser.boundStatementBuilder(userid).setPageSize(ITEMS_PER_PAGE); + if (page != null) { + statementBuilder.setPagingState(Bytes.fromHexString(page)); + } + + ResultSet rs = session.execute(statementBuilder.build()); + String nextPage = Bytes.toHexString(rs.getExecutionInfo().getPagingState()); + + int remaining = rs.getAvailableWithoutFetching(); + List videos = new ArrayList<>(remaining); + + if (remaining > 0) { + for (Row row : rs) { + + UserVideo video = + new UserVideo(row.getInt("videoid"), row.getString("title"), row.getInstant("added")); + videos.add(video); + + // Make sure we don't go past the current page (we don't want the driver to fetch the next + // one) + if (--remaining == 0) { + break; + } + } + } + + URI next = null; + if (nextPage != null) + next = uri.getAbsolutePathBuilder().queryParam("page", nextPage).build(); + + return new UserVideosResponse(videos, next); + } + } + + @SuppressWarnings("unused") + public static class UserVideosResponse { + + private final List videos; + + private final URI nextPage; + + public UserVideosResponse(List videos, URI nextPage) { + this.videos = videos; + this.nextPage = nextPage; + } + + public List getVideos() { + return videos; + } + + public URI getNextPage() { + return nextPage; + } + } + + @SuppressWarnings("unused") + public static class UserVideo { + + private final int videoid; + + private final String title; + + private final Instant added; + + public UserVideo(int videoid, String title, Instant added) { + this.videoid = videoid; + this.title = title; + this.added = added; + } + + public int getVideoid() { + return videoid; + } + + public String getTitle() { + return title; + } + + public Instant getAdded() { + return added; + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java b/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java new file mode 100644 index 00000000000..7e0ed814ec4 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java @@ -0,0 +1,373 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.paging; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.internal.core.type.codec.DateCodec; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * A stateless REST service (backed by Jersey, HK2 and the JDK HttpServer) that displays paginated results for + * a CQL query. + * + *

        Conversion to and from JSON is made through Jersey Jackson + * providers. + * + *

        Navigation is bidirectional, and you can jump to a random page (by modifying the URL). + * Cassandra does not support offset queries (see + * https://issues.apache.org/jira/browse/CASSANDRA-6511), so we emulate it by restarting from the + * beginning each time, and iterating through the results until we reach the requested page. This is + * fundamentally inefficient (O(n) in the number of rows skipped), but the tradeoff might be + * acceptable for some use cases; for example, if you show 10 results per page and you think users + * are unlikely to browse past page 10, you only need to retrieve at most 100 rows. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        • creates a new keyspace "examples" in the cluster. If a keyspace with this name already + * exists, it will be reused; + *
        • creates a table "examples.random_paging_rest_ui". If it already exists, it will be reused; + *
        • inserts data in the table; + *
        • launches a REST server listening on HTTP_PORT. + *
        + */ +public class RandomPagingRestUi { + private static final int HTTP_PORT = 8080; + + private static final int ITEMS_PER_PAGE = 10; + // How many rows the driver will retrieve at a time. + // This is set artificially low for the sake of this example. + // Unless your rows are very large, you can probably use a much higher value (the driver's default + // is 5000). + private static final int FETCH_SIZE = 60; + + private static final URI BASE_URI = + UriBuilder.fromUri("http://localhost/").path("").port(HTTP_PORT).build(); + + public static void main(String[] args) throws Exception { + + try (CqlSession session = CqlSession.builder().addTypeCodecs(new DateCodec()).build()) { + createSchema(session); + populateSchema(session); + startRestService(session); + } + } + + // Creates a table storing videos by users, in a typically denormalized way + private static void createSchema(CqlSession session) { + session.execute( + "CREATE KEYSPACE IF NOT EXISTS examples " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + session.execute( + "CREATE TABLE IF NOT EXISTS examples.random_paging_rest_ui(" + + "userid int, username text, " + + "added timestamp, " + + "videoid int, title text, " + + "PRIMARY KEY (userid, added, videoid)" + + ") WITH CLUSTERING ORDER BY (added DESC, videoid ASC)"); + } + + private static void populateSchema(CqlSession session) { + PreparedStatement prepare = + session.prepare( + "INSERT INTO examples.random_paging_rest_ui (userid, username, added, videoid, title) VALUES (?, ?, ?, ?, ?)"); + + // 3 users + for (int i = 0; i < 3; i++) { + // 49 videos each + for (int j = 0; j < 49; j++) { + int videoid = i * 100 + j; + session.execute( + prepare.bind( + i, "user " + i, Instant.ofEpochMilli(j * 100000), videoid, "video " + videoid)); + } + } + } + + // starts the REST server using JDK HttpServer (com.sun.net.httpserver.HttpServer) + private static void startRestService(CqlSession session) + throws IOException, InterruptedException { + + final HttpServer server = + JdkHttpServerFactory.createHttpServer(BASE_URI, new VideoApplication(session), false); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + server.setExecutor(executor); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + System.out.println(); + System.out.println("Stopping REST Service"); + server.stop(0); + executor.shutdownNow(); + System.out.println("REST Service stopped"); + })); + server.start(); + + System.out.println(); + System.out.printf( + "REST Service started on http://localhost:%d/users, press CTRL+C to stop%n", HTTP_PORT); + System.out.println( + "To explore this example, start with the following request and walk from there:"); + System.out.printf("curl -i http://localhost:%d/users/1/videos%n", HTTP_PORT); + System.out.println(); + + Thread.currentThread().join(); + } + + /** + * Configures the REST application and handles injection of custom objects, such as the driver + * session. + * + *

        This is also the place where you would normally configure JSON serialization, etc. + * + *

        Note that in this example, we rely on the automatic discovery and configuration of Jackson + * through {@code org.glassfish.jersey.jackson.JacksonFeature}. + */ + public static class VideoApplication extends ResourceConfig { + + public VideoApplication(final CqlSession session) { + super(UserService.class); + // AbstractBinder is provided by HK2 + register( + new AbstractBinder() { + + @Override + protected void configure() { + bind(session).to(CqlSession.class); + } + }); + } + } + + /** + * A typical REST service, handling requests involving users. + * + *

        Typically, this service would contain methods for listing and searching for users, and + * methods to retrieve user details. Here, for brevity, only one method, listing videos by user, + * is implemented. + */ + @Singleton + @Path("/users") + @Produces("application/json") + public static class UserService { + + @Inject private CqlSession session; + + @Context private UriInfo uri; + + private PreparedStatement videosByUser; + private Pager pager; + + @PostConstruct + @SuppressWarnings("unused") + public void init() { + this.pager = new Pager(session, ITEMS_PER_PAGE); + this.videosByUser = + session.prepare( + "SELECT videoid, title, added FROM examples.random_paging_rest_ui WHERE userid = ?"); + } + + /** + * Returns a paginated list of all the videos created by the given user. + * + * @param userid the user ID. + * @param page the page to request, or {@code null} to get the first page. + */ + @GET + @Path("/{userid}/videos") + public UserVideosResponse getUserVideos( + @PathParam("userid") int userid, @QueryParam("page") Integer page) { + + Statement statement = videosByUser.bind(userid).setPageSize(FETCH_SIZE); + + if (page == null) { + page = 1; + } + ResultSet rs = pager.skipTo(statement, page); + + List videos; + boolean empty = !rs.iterator().hasNext(); + if (empty) { + videos = Collections.emptyList(); + } else { + int remaining = ITEMS_PER_PAGE; + videos = new ArrayList<>(remaining); + for (Row row : rs) { + UserVideo video = + new UserVideo(row.getInt("videoid"), row.getString("title"), row.getInstant("added")); + videos.add(video); + + if (--remaining == 0) { + break; + } + } + } + + URI previous = + (page == 1) ? null : uri.getAbsolutePathBuilder().queryParam("page", page - 1).build(); + URI next = (empty) ? null : uri.getAbsolutePathBuilder().queryParam("page", page + 1).build(); + return new UserVideosResponse(videos, previous, next); + } + } + + @SuppressWarnings("unused") + public static class UserVideosResponse { + + private final List videos; + + private final URI previousPage; + + private final URI nextPage; + + public UserVideosResponse(List videos, URI previousPage, URI nextPage) { + this.videos = videos; + this.previousPage = previousPage; + this.nextPage = nextPage; + } + + public List getVideos() { + return videos; + } + + public URI getPreviousPage() { + return previousPage; + } + + public URI getNextPage() { + return nextPage; + } + } + + @SuppressWarnings("unused") + public static class UserVideo { + + private final int videoid; + + private final String title; + + private final Instant added; + + public UserVideo(int videoid, String title, Instant added) { + this.videoid = videoid; + this.title = title; + this.added = added; + } + + public int getVideoid() { + return videoid; + } + + public String getTitle() { + return title; + } + + public Instant getAdded() { + return added; + } + } + + /** + * Helper class to emulate random paging. + * + *

        Note that it MUST be stateless, because it is cached as a field in our HTTP handler. + */ + static class Pager { + private final CqlSession session; + private final int pageSize; + + Pager(CqlSession session, int pageSize) { + this.session = session; + this.pageSize = pageSize; + } + + ResultSet skipTo(Statement statement, int displayPage) { + // Absolute index of the first row we want to display on the web page. Our goal is that + // rs.next() returns that row. + int targetRow = (displayPage - 1) * pageSize; + + ResultSet rs = session.execute(statement); + // Absolute index of the next row returned by rs (if it is not exhausted) + int currentRow = 0; + int fetchedSize = rs.getAvailableWithoutFetching(); + ByteBuffer nextState = rs.getExecutionInfo().getPagingState(); + + // Skip protocol pages until we reach the one that contains our target row. + // For example, if the first query returned 60 rows and our target is row number 90, we know + // we can skip those 60 rows directly without even iterating through them. + // This part is optional, we could simply iterate through the rows with the for loop below, + // but that's slightly less efficient because iterating each row involves a bit of internal + // decoding. + while (fetchedSize > 0 && nextState != null && currentRow + fetchedSize < targetRow) { + statement = statement.setPagingState(nextState); + rs = session.execute(statement); + currentRow += fetchedSize; + fetchedSize = rs.getAvailableWithoutFetching(); + nextState = rs.getExecutionInfo().getPagingState(); + } + + if (currentRow < targetRow) { + for (@SuppressWarnings("unused") Row row : rs) { + if (++currentRow == targetRow) { + break; + } + } + } + // If targetRow is past the end, rs will be exhausted. + // This means you can request a page past the end in the web UI (e.g. request page 12 while + // there are only 10 pages), and it will show up as empty. + // One improvement would be to detect that and take a different action, for example redirect + // to page 10 or show an error message, this is left as an exercise for the reader. + return rs; + } + } +} diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/retry/DowngradingRetry.java b/examples/src/main/java/com/datastax/oss/driver/examples/retry/DowngradingRetry.java new file mode 100644 index 00000000000..c2cd119a1c8 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/retry/DowngradingRetry.java @@ -0,0 +1,447 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.examples.retry; + +import static com.datastax.oss.driver.api.core.DefaultConsistencyLevel.QUORUM; +import static com.datastax.oss.driver.api.core.cql.DefaultBatchType.UNLOGGED; + +import com.datastax.oss.driver.api.core.AllNodesFailedException; +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.DriverException; +import com.datastax.oss.driver.api.core.NoNodeAvailableException; +import com.datastax.oss.driver.api.core.cql.BatchStatement; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.servererrors.DefaultWriteType; +import com.datastax.oss.driver.api.core.servererrors.QueryConsistencyException; +import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException; +import com.datastax.oss.driver.api.core.servererrors.UnavailableException; +import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; + +/** + * This example illustrates how to implement a downgrading retry strategy from application code. + * + *

        This was provided as a built-in policy in driver 3 ({@code + * DowngradingConsistencyRetryPolicy}), but has been removed from driver 4. See the FAQ. + * + *

        Preconditions: + * + *

          + *
        • An Apache Cassandra(R) cluster is running and accessible through the contacts points + * identified by basic.contact-points (see application.conf). + *
        + * + *

        Side effects: + * + *

          + *
        1. Creates a new keyspace {@code downgrading} in the cluster, with replication factor 3. If a + * keyspace with this name already exists, it will be reused; + *
        2. Creates a new table {@code downgrading.sensor_data}. If a table with that name exists + * already, it will be reused; + *
        3. Inserts a few rows, downgrading the consistency level if the operation fails; + *
        4. Queries the table, downgrading the consistency level if the operation fails; + *
        5. Displays the results on the console. + *
        + * + * Notes: + * + *
          + *
        • The downgrading logic here is similar to what {@code DowngradingConsistencyRetryPolicy} in + * 3.x driver does; feel free to adapt it to your application needs; + *
        • You should never attempt to retry a non-idempotent write. See the driver's manual page on + * idempotence for more information. + *
        + * + * @see Java driver online + * manual + */ +public class DowngradingRetry { + /** The maximum number of retries to attempt. */ + private static final int MAX_RETRIES = 1; + + /** The initial consistency level to use. */ + private static final ConsistencyLevel INITIAL_CL = QUORUM; + + public static void main(String[] args) { + + DowngradingRetry client = new DowngradingRetry(MAX_RETRIES); + + try { + + client.connect(); + client.createSchema(); + client.write(INITIAL_CL, 0); + ResultSet rows = client.read(INITIAL_CL, 0); + client.display(rows); + + } finally { + client.close(); + } + } + + private final int maxRetries; + + private CqlSession session; + + private DowngradingRetry(int maxRetries) { + this.maxRetries = maxRetries; + } + + /** Initiates a connection to the session specified by the application.conf. */ + private void connect() { + session = CqlSession.builder().build(); + + System.out.printf("Connected to session: %s%n", session.getName()); + } + + /** Creates the schema (keyspace) and table for this example. */ + private void createSchema() { + + session.execute( + "CREATE KEYSPACE IF NOT EXISTS downgrading WITH replication " + + "= {'class':'SimpleStrategy', 'replication_factor':3}"); + + session.execute( + "CREATE TABLE IF NOT EXISTS downgrading.sensor_data (" + + "sensor_id uuid," + + "date date," + + // emulates bucketing by day + "timestamp timestamp," + + "value double," + + "PRIMARY KEY ((sensor_id,date),timestamp)" + + ")"); + } + + /** + * Inserts data, retrying if necessary with a downgraded CL. + * + * @param cl the consistency level to apply. + * @param retryCount the current retry count. + * @throws DriverException if the current consistency level cannot be downgraded. + */ + private void write(ConsistencyLevel cl, int retryCount) { + + System.out.printf("Writing at %s (retry count: %d)%n", cl, retryCount); + + BatchStatement batch = + BatchStatement.newInstance(UNLOGGED) + .add( + SimpleStatement.newInstance( + "INSERT INTO downgrading.sensor_data " + + "(sensor_id, date, timestamp, value) " + + "VALUES (" + + "756716f7-2e54-4715-9f00-91dcbea6cf50," + + "'2018-02-26'," + + "'2018-02-26T13:53:46.345+01:00'," + + "2.34)")) + .add( + SimpleStatement.newInstance( + "INSERT INTO downgrading.sensor_data " + + "(sensor_id, date, timestamp, value) " + + "VALUES (" + + "756716f7-2e54-4715-9f00-91dcbea6cf50," + + "'2018-02-26'," + + "'2018-02-26T13:54:27.488+01:00'," + + "2.47)")) + .add( + SimpleStatement.newInstance( + "INSERT INTO downgrading.sensor_data " + + "(sensor_id, date, timestamp, value) " + + "VALUES (" + + "756716f7-2e54-4715-9f00-91dcbea6cf50," + + "'2018-02-26'," + + "'2018-02-26T13:56:33.739+01:00'," + + "2.52)")) + .setConsistencyLevel(cl); + + try { + + session.execute(batch); + System.out.println("Write succeeded at " + cl); + + } catch (DriverException e) { + + if (retryCount == maxRetries) { + throw e; + } + + e = unwrapAllNodesFailedException(e); + + System.out.println("Write failed: " + e); + + // General intent: + // 1) If we know the write has been fully persisted on at least one replica, + // ignore the exception since the write will be eventually propagated to other replicas. + // 2) If the write couldn't be persisted at all, abort as it is unlikely that a retry would + // succeed. + // 3) If the write was only partially persisted, retry at the highest consistency + // level that is likely to succeed. + + if (e instanceof UnavailableException) { + + // With an UnavailableException, we know that the write wasn't even attempted. + // Downgrade to the number of replicas reported alive and retry. + int aliveReplicas = ((UnavailableException) e).getAlive(); + + ConsistencyLevel downgraded = downgrade(cl, aliveReplicas, e); + write(downgraded, retryCount + 1); + + } else if (e instanceof WriteTimeoutException) { + + DefaultWriteType writeType = (DefaultWriteType) ((WriteTimeoutException) e).getWriteType(); + int acknowledgements = ((WriteTimeoutException) e).getReceived(); + + switch (writeType) { + case SIMPLE: + case BATCH: + // For simple and batch writes, as long as one replica acknowledged the write, + // ignore the exception; if none responded however, abort as it is unlikely that + // a retry would ever succeed. + if (acknowledgements == 0) { + throw e; + } + break; + + case UNLOGGED_BATCH: + // For unlogged batches, the write might have been persisted only partially, + // so we can't simply ignore the exception: instead, we need to retry with + // consistency level equal to the number of acknowledged writes. + ConsistencyLevel downgraded = downgrade(cl, acknowledgements, e); + write(downgraded, retryCount + 1); + break; + + case BATCH_LOG: + // Rare edge case: the peers that were chosen by the coordinator + // to receive the distributed batch log failed to respond. + // Simply retry with same consistency level. + write(cl, retryCount + 1); + break; + + default: + // Other write types are uncommon and should not be retried. + throw e; + } + + } else { + + // Unexpected error: just retry with same consistency level + // and hope to talk to a healthier coordinator. + write(cl, retryCount + 1); + } + } + } + + /** + * Queries data, retrying if necessary with a downgraded CL. + * + * @param cl the consistency level to apply. + * @param retryCount the current retry count. + * @throws DriverException if the current consistency level cannot be downgraded. + */ + private ResultSet read(ConsistencyLevel cl, int retryCount) { + + System.out.printf("Reading at %s (retry count: %d)%n", cl, retryCount); + + Statement stmt = + SimpleStatement.newInstance( + "SELECT sensor_id, date, timestamp, value " + + "FROM downgrading.sensor_data " + + "WHERE " + + "sensor_id = 756716f7-2e54-4715-9f00-91dcbea6cf50 AND " + + "date = '2018-02-26' AND " + + "timestamp > '2018-02-26+01:00'") + .setConsistencyLevel(cl); + + try { + + ResultSet rows = session.execute(stmt); + System.out.println("Read succeeded at " + cl); + return rows; + + } catch (DriverException e) { + + if (retryCount == maxRetries) { + throw e; + } + + e = unwrapAllNodesFailedException(e); + + System.out.println("Read failed: " + e); + + // General intent: downgrade and retry at the highest consistency level + // that is likely to succeed. + + if (e instanceof UnavailableException) { + + // Downgrade to the number of replicas reported alive and retry. + int aliveReplicas = ((UnavailableException) e).getAlive(); + + ConsistencyLevel downgraded = downgrade(cl, aliveReplicas, e); + return read(downgraded, retryCount + 1); + + } else if (e instanceof ReadTimeoutException) { + + ReadTimeoutException readTimeout = (ReadTimeoutException) e; + int received = readTimeout.getReceived(); + int required = readTimeout.getBlockFor(); + + // If fewer replicas responded than required by the consistency level + // (but at least one replica did respond), retry with a consistency level + // equal to the number of received acknowledgements. + if (received < required) { + + ConsistencyLevel downgraded = downgrade(cl, received, e); + return read(downgraded, retryCount + 1); + } + + // If we received enough replies to meet the consistency level, + // but the actual data was not present among the received responses, + // then retry with the initial consistency level, we might be luckier next time + // and get the data back. + if (!readTimeout.wasDataPresent()) { + + return read(cl, retryCount + 1); + } + + // Otherwise, abort since the read timeout is unlikely to be solved by a retry. + throw e; + + } else { + + // Unexpected error: just retry with same consistency level + // and hope to talk to a healthier coordinator. + return read(cl, retryCount + 1); + } + } + } + + /** + * Displays the results on the console. + * + * @param rows the results to display. + */ + private void display(ResultSet rows) { + + final int width1 = 38; + final int width2 = 12; + final int width3 = 30; + final int width4 = 21; + + String format = "%-" + width1 + "s%-" + width2 + "s%-" + width3 + "s%-" + width4 + "s%n"; + + // headings + System.out.printf(format, "sensor_id", "date", "timestamp", "value"); + + // separators + drawLine(width1, width2, width3, width4); + + // data + for (Row row : rows) { + + System.out.printf( + format, + row.getUuid("sensor_id"), + row.getLocalDate("date"), + row.getInstant("timestamp"), + row.getDouble("value")); + } + } + + /** Closes the session and the cluster. */ + private void close() { + if (session != null) { + session.close(); + } + } + + /** + * Downgrades the current consistency level to the highest level that is likely to succeed, given + * the number of acknowledgements received. Rethrows the original exception if the current + * consistency level cannot be downgraded any further. + * + * @param current the current CL. + * @param acknowledgements the acknowledgements received. + * @param original the original exception. + * @return the downgraded CL. + * @throws DriverException if the current consistency level cannot be downgraded. + */ + private static ConsistencyLevel downgrade( + ConsistencyLevel current, int acknowledgements, DriverException original) { + if (acknowledgements >= 3) { + return DefaultConsistencyLevel.THREE; + } + if (acknowledgements == 2) { + return DefaultConsistencyLevel.TWO; + } + if (acknowledgements == 1) { + return DefaultConsistencyLevel.ONE; + } + // Edge case: EACH_QUORUM does not report a global number of alive replicas + // so even if we get 0 alive replicas, there might be + // a node up in some other datacenter, so retry at ONE. + if (current == DefaultConsistencyLevel.EACH_QUORUM) { + return DefaultConsistencyLevel.ONE; + } + throw original; + } + + /** + * If the driver was unable to contact any node, it throws an umbrella {@link + * NoNodeAvailableException} containing a map of the actual errors, keyed by host. + * + *

        This method unwraps this exception, inspects the map of errors, and returns the first + * exploitable {@link DriverException}. + * + * @param e the exception to unwrap. + * @return the unwrapped exception, or the original exception, if it is not an instance of {@link + * NoNodeAvailableException}. + * @throws NoNodeAvailableException the original exception, if it cannot be unwrapped. + */ + private static DriverException unwrapAllNodesFailedException(DriverException e) { + if (e instanceof AllNodesFailedException) { + AllNodesFailedException noHostAvailable = (AllNodesFailedException) e; + for (Throwable error : noHostAvailable.getErrors().values()) { + if (error instanceof QueryConsistencyException || error instanceof UnavailableException) { + return (DriverException) error; + } + } + // Couldn't find an exploitable error to unwrap: abort. + throw e; + } + // the original exceptional wasn't a NoHostAvailableException: proceed. + return e; + } + + /** + * Draws a line to isolate headings from rows. + * + * @param widths the column widths. + */ + private static void drawLine(int... widths) { + for (int width : widths) { + for (int i = 1; i < width; i++) { + System.out.print('-'); + } + System.out.print('+'); + } + System.out.println(); + } +} diff --git a/examples/src/main/resources/application.conf b/examples/src/main/resources/application.conf new file mode 100644 index 00000000000..002018efc91 --- /dev/null +++ b/examples/src/main/resources/application.conf @@ -0,0 +1,14 @@ +datastax-java-driver { + basic.contact-points = ["127.0.0.1:9042"] + basic { + load-balancing-policy { + local-datacenter = datacenter1 + } + } + # need in LimitConcurrencyRequestThrottler example + advanced.throttler { + class = ConcurrencyLimitingRequestThrottler + max-concurrent-requests = 32 + max-queue-size = 10000 + } +} \ No newline at end of file diff --git a/examples/src/main/resources/cassandra_logo.png b/examples/src/main/resources/cassandra_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1637e61c14e5b7b9d0b76fde6c825365596de96d GIT binary patch literal 12623 zcmY+r19WAZ?s;B(5j}2IfHy2Id#|-*~^HFEj%f*oi(E7$6l449hOFRe}2p3uh;;;Rpuy-QYjv z+dsZy3ox*68WzgxPUi=QW6BGRx#L0?>SY1Y*NXXW~gou@ng^q!k7oLcSh}*%~6rd<9`aj*jT0F$& zPEK|JdU{t^S2|Z_I$H-bdPYu8PI?9=dL|~?F9fZlyN#298?B8a$$ty^|KtdpI2t)v z*g09)+7SImu7RPgvl9<7@qZNk@AKc|bh0r0|25e-{*PH-2GalM3q2zp1O5NX{_4v8 zA1*-7!NTNA^MB;?GIIYH^8exej~;IN|0w^z2J_#R{ulSlR9<*)`v09aUU=zV3JowY zdVL9D0cE#u=iV@0Dk3W%=ra<2qy<9af*~N!1mf^ed}T34d!pixViq1=;cL0a(N#Mb7#bvY= zNpTkrEB;u1H{bB@->_F;+s*O1MGOItC8&)1r6R`LXRG_$Pfmw^?!}Iq{Y3iJ_qAjL zCeKc8Yz_!UJ5h8GcktSNDnVLGN(RDhr=e(klfV))cX#&#U)PWQv6TGkY7nA7>0@Vy zCsLjK%Xk1xjrB6qT%}H%b*K{`A0J@|GCuWKzNaBI_`52eHGW9iBokUf_w9a9H%_2G zb25nbo5W1`7NpIeY}Q+`r{`xJ@by{~1TSxIne-$YHN&ZF9uwj6j7^_cS_cOQ6ciNw znb3a`loM8S|NP7QLar~`F7uR%We?I){X*66BRDBzd}z)OCNgqcAy=Dh#VjpLB{CXq z2^AF;x6aRjGwG0TioD<580^UPw<3w1rh`I-`VSIU#vBjE8HF1?A8pRoTF`lUc~g14 zaTQ%I^6GgaS8Y3>91kZ4-g10;`Sjer$?<-05R;I|aK1iX4vo=w1Y&f({n734!14Oz z_T^Qr(v5;f!X=`iL2zBOhneJj)D8>|PL}W@%N+AKohyOx_4O4RFa4bT{J@$>XJw}T zkSnL><=sf+^nP(7B_~hce2{ua>AUiSfiMGyZs+**DPNO+LaUHS5&4b-1L|_6U*mir zAQ6BF_%mJVY^BX{Fp_<&N9M~TsOtL%0MaM!$_^;SR(QyKU=*r zn8vV`&Gz;g=Fb-hqP35}UX$l{_E_9_7hiA0ITpLQ9>CHwocN=m9@h?iUBfs$Jlr4m zrhNw}V}Gz-1%DYqo%f5Mqarte$@^Cg*3#0#k?0O95{*UXKr1XPjE&*D{gxBwa|K4= zwWn}*x{e1i#RD@HH!M<}<8_BBy6#4DGBjYY-s&t$<93?mit_WPwem-XXv|=@KZN~6 z!{QE1WOqspewu5;VD(^N{koFJ^>Qf}%?QNqXR(fU&S)Zj zl_y6&TSp?SGCTp-9j4<5A5R1}U+Pum%lZ+D>blbUJlViQXYvhmd|Y(i%CcClVY9#Q z)2DK~GhKAP+K?2U&X<{4zfdU^VK?YyyFDXA#^2xroVpzVZ!5Rd&_r-uF=)J`r2R{8 z zusP9kZUJsrTTGh)nZ8UAd)0Lxp|z3!TFRw+!F!_dm%#nY0-=^dQdC+nZ zouaUU1D;(jq=vTdI+nUTu^!Hsf;%!6yZL^@hx}Wif7!-!I$neF_y3kE7t^UanNx7G zku1y`j@;@==!If**)nt|BEW`#Mc0p4)fGL}^#oqk`9f{evY*U^WD1}1P|)dmIo&6; z*VPyJpsc_MEqON)o}=#5YzDAK)iV)A`Ud?dfhB)T+Hj zo10gwHOl6CEF&25L8G5uo58{bVsI?WAjwvkuY3vNu-g=Np#mlDu4(&sMn8_yS zDs^3<)wGizhECPh_XJ zdd53^qgV&3QTgF=KsW$;vP0az#JNquDPHe=LpfWm!>hRt8`RR9or2LdK>de~86Daz z{BvZSzhjVgy53vuY@-!UOO<{oS|wr&9Xs-`=C7rZqqgkzw*1&>J2hPoDrARaX;7S$ zHBhUex58*}tJI*c+G3Xcjm0)w!?Ekq9|ja6<(B--19tuI`0YK6{@<0-w$+2V+JxkO z(I{MtR)=#L(yNWbJRgLWhMH+uB*iPZSbIu5b{{4@EqHj;QymB@sDWm_Cos!FRsnqI z%E&X!7+pD;{?Vuw{$f#edmz16gE2E&Z@@Gq^^fcJm#3H9#;)(=Rcp}*@jECtJIay8 z)Ovi>kSSiBuoY;=5gu&Yg^o=btz!WOc*1l;D+J)wUh#IUrS(8=`A zl_?s^X&eGA{zXDoxWr(Xu+FH!`R|o|gtXLf-W3>ve=5<#uTnVz7p3?Ef}^JEq$qxC zGY~3uX8j>2-AES@FzwJC7mxzt93LI!7=LZC=EFJ(x{4ruFAFNmU)9>pKzG5ZqQ3MZ&H z#V?u2!ORLpcgvpvlmh`ix=)XT0qV`nFm(Z5c8h+E!$kqVQ-n8Kb=R5TO4!`$d;~IH zw%^W!iaRy_gk*^Av|C|cc<8gA#CXG)gCOIk6=3t&P%&_F=bPZI_v0H&%Jc7*5dNG5 z{xv-Zz=l)VvO;7xL9Ff7CIu5m-eCCTSOefrNy{1y7;YCT1mLgIf6w{`5S@ z&%lRhyo-;<8gbJ~f)E@C4xg%g_ zvXsmpINPqUi$k4AaWY7s@;zf=E~R(}@wCpwyI;zSB5UyEBSx7BB3yY^uloCR zE6Q*2m-6y{`>(cB zP;T4~e;xH05M-^F5~XvHjlz{C`7jqTRw4!R%ncg_Tlz zB&qrLD3=N$eJr0I~{JAwqGPFzh&jXtRB3O$78Mct{!sklHZrF z;G8e?Fvks6CY=Ijupo-{zD2s2tnnUkQw1Qk$7ZWi)rk1mP-@~f^XY;OdZYh*KDXq0 zC(BXJv)X|(jYmtrCMMtwtMei}@jcx9jL31xUAHC-A7EY=+cqExby{_$AgHZNNg4!Y$5ZB~`zXq2 zfSrxvALDu^#!Xf+EIS-&9Ej(dr+-&(>;MDP)C4;;!GlZOJCErKWto3dnl+|0Q{!-J z!&0UbS$A?^bg@O+UKjYf(6UX&*jibl#(4d_M`y?|WK}ha_4rWiUc>6nxJNJ#`<` zfoPHh(j+=~_R#n(?mF2%6gw zId*4~5B~|MhX}>mN4UE0892w=-US{j*CAZ3gJjJ!QEbCCcuUzU-z_;WP7?hBTN|n= zrVwaFl87yJrRYV&BT(wclf_s&toqEybyC7RcUuZn+fW|a*ZfWaO@Y2%2u7P$eu=y( zcfy#1UEu;Dx=#*7--`;j`F06}-P+j7Bgn&G(=-kkhz_KJ+gvWza59>#89n#!r89J7 zp~)gkgoPA6*{qlRj0bzIzQ5@b*cLi_$PnNM=DuGuq3@xCK5Yg)YdyH9%3{7n*iQF$ z>H6TUPq96m^%JDns}u%iOr&1AXJ(IP-m zwKNwRVZXzOkVETgiqH+T06jGd3;BfiHnd$+?mo`u?NcAto6I+Bm>Ur2P*L*S{01m( zwA~-l%&y4TTPSa2Ag%Z)cCIeqa---7+rPF=-G^Wne?c5{dgy$z5$i!vjPE(Cl3AT- z6hoM1BHLW^&>;7n*ZToB2nPnBR*jXSC^T%>wO=_PMc+1Vu6@DyK7pqg1@9wr+RTBo85^4Jiy98F{KfZXg741ee&RMNNHL{hzYgz&JO3C+M>*Zq`7{Ut z>AoiqM=U*#*tUn>E0J$Ct`2T7G&P;HH@_!dMNqk8?36-K+O|ELzGcBE9L=Snqc)$^ zpbI{|r?x%T8={KzUWkcq4^g)9aQoWeWO-tqFl`-_XY-({HqmvyyjbmaRZlsoxV!vb zdtC2g6?Mt{50z7np0KLA@Otqsb(c$=TClHeOu1}pi`pOK)6k$eiMYAgc?Q3231IZ^ zQ3QC(qi6}V_g}9lck(Q3(ED!w(7q^R0@9}x_WH*Ba#AT5J=&Wc!YS5`IPGUP<M) zV)f5%L$$O?Mw7Od39fZ`O#A7G0g9EN-WknjNrERF->(;h9uJ(m8>%;WXV|)zv%h`L zzq;q?Q)m;AS~(U;Wgvyy{ul`L1yaWo5(IreC>MQ?C9;;}3XFz}TN<%q8_TG+=4!lt zl!92@TCi2yI?ib#A}7YC*IPh$4uB9pT0@`wHnwfUR&w4&?odI^LCV4R^Ou6}`+}6) z>t)RCT-tQ=ZDm(nsJC5gh%$BXXNGNOPgje^{_GefNn?>EoGgqAEOl>OM{yZT zLDvmKgpc4c9m8^&#dpUv28MIP_Rk%p|=xF$w?QU+RAdyy4IrYp-#BOhjr6RD3y zMMa=Wg@`Za&J4@P3-bRYI&qg&zERVUcL}ejQ0pWxo^s7FF&f^0pJ{f&ujb$ciyanh zRdu8rB4iQS+R}74Bdp}_K50B{zg)Q4TzEvDZ=`N`Y!wz#Vb2g_l6=%5rZiQB;A>}4 zJY3%etsUSJ2(D&PbFC_MdobNBQS&+m6f~b%@iiJuJ^YY<8Om2@Jr*Lp6EyzaTi*QGqbO~-hF)ty49v^0;NRmZ$NY)}$51UiX63w)4@`4?SRfSHI*LY-N%8VXocvw}ur|645Q|nSf|GxxCH2fbV=P z9JMCqHn1tlcFvp8W}>3O%V!0(R!phojwy7a5Zw4~)f^nhNk{<2HmWoVO#*59W%yjoSC1$@Bn`$>LYOtDVQGp|nb{AcgJes^So z*R`I2PkVyY_BoBrKnLLB)J&%GHtK#_(ex8}hH<@kcYIs6q1KbzaOKx_(oXJKV;`LU$F_!3 zQTbPLnTw~@*%46p_5fT3WNMp)1ql_>$2c-!gb1rAM+ctUrOoE*%_f$xC4W+u`Yx%c zao!$I(T}Np48@a4Wop^B3`msgeX2dH=oTa6XKwcebIuu#nmL!IFFuq2fK4U^g@tJ@ zNI2X}Ef48dnNJ?~KeOSsVi^H)+#k(Ke1MnK52y3F5sh}Yn&SZ4K3!+Wm3oWGAS`V= zX@dLDb-U{P8iXpHRrjadkkP^Usg3#S0 z*by{|<>tnwxT6u}j(wfS{DjG;CY^X?DiuS~&H(wzVg5=Ls}b7NwTo;iPlN4~sI_EW z@%ikM@b>GQQGC%&*}Mec^d#bJA`Z8;As@+RY*Gvyx9&+}at-!U>#xOW5}8$%Y|<9# zpaA0!+2flW(q`Q9S&jJuZ!SfK+q&=9$8|Bp5uBehUZDZH3LjimO@B3?toW%2DHJ|%K<(us2#xi@$uE|-oHh{fX z;4Qc0sL5Sc`^zZJ!5uM^Vtv6}QlN^btD;d$*fk;ieDWx8HScB*1>e38$$SbU`WyGd zD_G&BV)h}u+tGH2y&vuxTe|iA(Qg%v*OsANh)0&Arh$-3Ko$QpOH;}f;Gl8!3VMp7 zIRqxLey4O~dF?7unsA(KZRfXdbn*$NJ;_o@WbR2NHMU?A{vrYzq$YWMy1ItEm`X73 zPq==lMSizf!bZCYHvp@}IbuFd%Nb`vB5GHt{E26EPVz{7^jQ!AFx}uggt;K`?$v#$ z&y&x&Y)%1GS1Lh72yg z_Ht@8KQJx{?w4FgbVoXe0eCt&*{?t6RZ|sxOgK)_Sy@DygLs~cI!B@yiZG@&MxMbs zjz}K0-X|HzO>?}#?!To#$-+ePA4(He_6k{zWr+S8;UuuR%(t*IDVWxq33pWIWO2H^ zsnDopgSBa)SqGtiT8loPNZ4ZI!axnBxm%@|?)ssj+96?->nu#*s7L7B+iHinon!m; z-;Y8hh@yk^w)M7L`j34Xe{7dysCfIrmtFjT1z5hRycpe$#tJj-@uv$~{6xrbeQgAa zAB6Q41Abx@6k} z+-T_NW7ts>rMqQhWIrxJDFoiA0pz(Wz_cb+lzvL+YuM9E?7MvS`N+NaTs-(@>c3vf zJQKA!-Y#EZp4itBs3k!Pj#_+yzr$@UJYh}@r);~XptreizaHb*ry^!)kt|?73D1Je z(qKneSP`n|$!t1%T)x{as(7QN)rFB|t9Et9_pH$W)1i5MA$nYBC8N7h40n^9DK6bO z-7?{(iZuVnENE@ojqjYaf*cMk|gkI>#GLXrN$x5f$pf6ZmqQMpr zBajRsprs%J(v(^gYoN*z*6F`fNPI(0bOYy1aq?MFEpil^O1A%pzX%ST?D!FPErXJ= z6zkb>nsDV6+Kv13#|#B$1MCx+T=2EYRg|*32#C>P9>om_t*ncFHUU-qA`OYjxRNCi zdvFpQ7Pt40;e|7Ok#Yz-8G8!It*-hX%Bzx6Y^@Jcyl-~Ya}{??$LJQ8UodbrAM-mf zlhcY^*B8hQJ$p=k@PHxb)e%(#p}6Rbmce>0sOl&J#%-{c=cOt8`wwIJ}XE<3%k>fF%`DQiSKsO($+*!|npn@ZmQCzyn6h=pi z8#Y{qn>*EVR-jCBPLyD#7B8JCqiZQc5Wp`>hxAw3v|~s*^G2*x0vC~oFIJ+cnh(M@ zx?}!l?NX_d=bY6v0aE-5Nv;S@_I9%0RxE>~VI(1^8B(Ko+wz{$z6M63U!*~CKlyrw z2_q^1Pe0mR0X|d4PXrk&JmZq!d(H=W|G1sl zEvDlcRt2glq>qC$F1kr3v*RF=&F*kVH-&1wCyUPhd19FM!?h~H$duIGSMuqjEM0cF zmpkc_B}QW;M~C^YfS{BCl`PJx2i02@{W%kQ+zj+rrS3Gf7Sxh7Yp}vEyynAsCMUPJ zrOCPi4i!V6u1IL!a`?R+`S|JSw`s5GgK0tOH~c8 zXA)?0gF*$)DvqCsTPauQ|IFgv~0M&&2Lo3L;G%I zrQN^h(#Dwkhx&)3154GGP!2IadoKvhh@y~U3LGctN#ifoxW04!yE$J@mmZR`vf;!J zk1(HdF5W$G=}g>~5zWBC)Y?u%5i2V@bDp0 zRvCAdV1HRW6V6F)qM&bB~5=c+j;e zfJu`8^`Eiv$U$Y=M8zH#LXnjRn3CogtSMNY%-f zwjvnf!8p7}wCON5VY=DOodrmLcJ>ewOE7PX8?GW|o+)84oBfrKMI-60rf1w4+8qSJ zS#qaEuUe1HqN?U@hg4q>tjs0x?VXoEea-6k8RRr6?#TU`1+(dvwyaIZgG6T{!th^X z_)vFoPd9(tug*d22X$jn*MTckD%V|OqdDzOlD_qj6r{5o#s3S(@>+U4# z#VfzszgC9K#Yv-%bJ@@3KE)Oc1xn(Ikw&rUCS!s0qM2}hA&YQHSjvZ7_CrGyI87qC z7t|sJ;k=)&_#EHwPU!pxnks8^4`)jjW852Z1oefwC-~lG+U4z(z|%sVvMZdbk#;j{ zjmjJ#i>(l838ZsXo=RguK%Z6d*U#(9lfY1IMLRSVOei;TG|fE;CZEsuM^mG25iA{J z@tpca)~OBm(Gr1K&rr}fQ+#TK|ygAz58G0Dusk1 z@e>eE+0#vso#Zg7>`mnRF)V3u*X>4Esy~5PSbu}uZKE=0H4Ek8ZBV##swmf0>kMlC zNd9B~$Xau_6rnfAKgF^j`FjcPSuw81y?==TlBQ7?D$snAB%%~QVOf?<6LQVbM)IrJ zN(c}dwY3_v%c8)u#3+dQa%R+BoCzg&&4!f=)3wy4C{`8I@g4dlfIldMH~x&OZ1xWe zpEYfGlqr0QV4vbJFlFsKzOR`(oc{|ONyeWHqj7E+bw57B8wC4h5wtloSs}Yh%PX7; zzCDh@V2`TRnl@a7T8Z(7CbWZop*Z|@xA4=-1-2NR5sEXV5MO+(R(~vw#|l$s2+c5A zbs-6S)MEmunI<-aVtG5!W{HmFH6coxl9fW;-5908i0$jka}bz zHbo&C1+|4dkw2Q}mj}nWo;#k%9_Dhq^(`e8Oco!?cfLEYU|q&QmutB-4yNAz#i}P0 zsFOLQArd+6pkh5YxG`0--T1NKRM%Xdo)l{C*SmyNK3il>iU$5Ef^`Yux+vp>klVE4 z9JUQ*q+tGGwKjqs=H(h^ixj!UyR}XH~**Jpr9IWyQO>;Yrg~o=(7x!>R9brY68@q>vZ&3VNgKEu021&dSj0 zC*(t<`v$|Lf<)&FouIrP?QSeID9(Fk)#R>dNsjORhtfzaS%7kOW=NxU(zz)hX~arZ ze2K83sTJ4+To~iG*4jfygILtEgKVs5v)Cv<&{qEGXGalx+4HA^BC>?gkbsR?)_cye zWEU``X9g}w6Dzus*@;mh-6Ej51J5MEh@jOaV`LU`Q6?Rw)P6`PQfKk*`0NyU6hAw9^#q*hNyy;G{l*hhR1G9bbAng| zG~f~e`A?^inzK|~x=71X;YpIJ1L7ZKE0I3;i!!c$;P}lkb;#_KGZCOhKp_>74{oNa z`jgsVd(5j0Y7~LNHE5jw_!slgv@u{xu|5)Ob9GY1<*@a#QjCqFho(a6~t2*V{4@c z{e`O+{mO}T<^}!8B$oGjG)F5VKcNwR$RZ8C6(3(XKsB)96#nb4m{psRlXsWAeD&je zGWh4;G-fkgn8*tnRSlC+B5S-omFyuM^Y}^XRi>g-i#wTPff~PF@sECGM~L3nX{4~O;_QMhpi&bsBMToiPj=LhXBZe}N=@(umaj)CN#CDK;yvHc?a^3?XVzsBYjdV)Q=r zxKboyHVYQQc>Y(V)#;9f#Lg=D{bXU($lYiFkV)+{zJilnEFatp5L+X00>6eEf-eMQ zLZh6ZpA&qUNihK0C6R^XVJBy%t{7F$dW8{1Zl6TxE>Ojq{u|~`D!E2c&q?snBBw)= z{_9@Wx|dNaIKsV^GSG3L?K~(CRO=`PFAf69Dce5>m&^uB#WURJLHsI=OyzLje@}LL zj%^Fj-CMTysLB0yfG*AqV&nx+B9s5f9ydcRPjy-rtUWxVqIH;5N8&$*x=|l1AroBW z;c{!Gch);EFTZ1hQoS;P(J-MoksLojmvn_PurBDQ{T@CSzt@~5Tl;aA(7hw3U|aQe zs@I&ATV(@kTPMtQ!b*6Fo25lis4$rNFG&U^TphQY|v71Pxf#AOTW6(G^+D0oZAj=led; zkU;XfCQ!f=;SdH%pY_NOR!o2=KY0d{a;*tv)a2t~Fg8iKG2Lj45mb(h+1Nq@z|Ag2 zX@1jWRkZ2B8M6I2%MJX{D2sE|5lwKy}ban3oJPcD1246!yY35P#GS)JYk zVTwvT-`=?&)f4}! z8xZH*a9k{7DUM&%5p`bCRrn+&TYsN_DvQEYZklPf-?E8WsNYXOMGmB)6_G?x#wnfB zX=F86Ga2iz|LdR=S*%bLxz)EjfM&>7fN2 zXHU3hMm0phTt>sPMkfE8TaL?ubG>7d?Bl(?qYLT{m-4lklF2O5N_(TM3@%c=XZ3lV zQirCfQ|+5&M7NIX^FzznKgpI0rgf8MU$ushlf4S-)pcj(M^};eI+JYJDYl6cT{-0k z+pv&LZD&4I!LN%LV*Zn~=JHTE{mauP8G5`indP*FA z^lb1Sqt@ekw~PhtyVf$yVC*CsgoX&7>&I_%#o|87mXxu|zgKIXCg zlaNFvs?T@9#NEY%{nX{>&n)VU#bm-frpk+>(2831b(}}dZ8SZrBpb%kAdH%YttTri zw`V&MAZCtWKM8eQ6hkmBlqkea + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3edb78b3048..fc1ec771eeb 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ test-infra integration-tests distribution + examples @@ -57,11 +58,16 @@ 3.12.1 1.3 - 2.9.5 4.12 1.2.3 4.12.0 0.8.8 + 1.0 + 2.23.1 + 2.4.0-b34 + 2.0.1 + 1.1.4 + 2.9.8 @@ -192,6 +198,56 @@ org.apache.felix.framework 6.0.0 + + org.glassfish + javax.json + ${jsr353-ri.version} + + + javax.json + javax.json-api + ${jsr353-api.version} + + + javax.ws.rs + javax.ws.rs-api + ${jax-rs.version} + + + org.glassfish.jersey.core + jersey-server + ${jersey.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + org.glassfish.jersey.containers + jersey-container-jdk-http + ${jersey.version} + + + org.glassfish.hk2 + hk2-api + ${hk2.version} + + + javax.inject + javax.inject + 1 + + + javax.annotation + javax.annotation-api + 1.2 + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + From a0d71897b1d85cb6b4afbf891b93b9e5660d4152 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Wed, 27 Feb 2019 14:51:13 -0600 Subject: [PATCH 735/742] Fix flaky test for JAVA-2106 --- .../api/core/cql/ExecutionInfoWarningsIT.java | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java index cf9a278f8e9..b9c5aae9e04 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/ExecutionInfoWarningsIT.java @@ -17,7 +17,7 @@ package com.datastax.oss.driver.api.core.cql; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.after; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import ch.qos.logback.classic.Level; @@ -26,6 +26,7 @@ import ch.qos.logback.core.Appender; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.testinfra.CassandraRequirement; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -34,10 +35,10 @@ import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.google.common.base.Strings; import java.util.List; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; @@ -51,9 +52,11 @@ @RunWith(MockitoJUnitRunner.class) public class ExecutionInfoWarningsIT { - private static final CustomCcmRule CCM = new CustomCcmRule.Builder().build(); - private static final SessionRule SESSION_RULE = - SessionRule.builder(CCM) + private static final String KEY = "test"; + + private final CustomCcmRule ccm = new CustomCcmRule.Builder().build(); + private final SessionRule sessionRule = + SessionRule.builder(ccm) .withConfigLoader( SessionUtils.configLoaderBuilder() .withInt(DefaultDriverOption.REQUEST_PAGE_SIZE, 20) @@ -67,36 +70,31 @@ public class ExecutionInfoWarningsIT { .build()) .build()) .build(); - private static final String KEY = "test"; - @ClassRule public static final TestRule CCM_RULE = RuleChain.outerRule(CCM).around(SESSION_RULE); + @Rule public final TestRule ccmRule = RuleChain.outerRule(ccm).around(sessionRule); @Mock private Appender appender; @Captor private ArgumentCaptor loggingEventCaptor; private Logger logger; private Level originalLoggerLevel; - @BeforeClass - public static void setupSchema() { + @Before + public void setupLogger() { // table with simple primary key, single cell. - SESSION_RULE + sessionRule .session() .execute( SimpleStatement.builder("CREATE TABLE IF NOT EXISTS test (k int primary key, v text)") - .setExecutionProfile(SESSION_RULE.slowProfile()) + .setExecutionProfile(sessionRule.slowProfile()) .build()); for (int i = 0; i < 100; i++) { - SESSION_RULE + sessionRule .session() .execute( SimpleStatement.builder("INSERT INTO test (k, v) VALUES (?, ?)") .addPositionalValues(KEY, i) .build()); } - } - - @Before - public void setupLogger() { // setup the log appender logger = (Logger) LoggerFactory.getLogger(CqlRequestHandler.class); originalLoggerLevel = logger.getLevel(); @@ -111,21 +109,20 @@ public void cleanupLogger() { } @Test + @CassandraRequirement(min = "3.0") public void should_execute_query_and_log_server_side_warnings() { final String query = "SELECT count(*) FROM test;"; - Statement st = SimpleStatement.builder(String.format(query)).build(); - ResultSet result = SESSION_RULE.session().execute(st); + Statement st = SimpleStatement.builder(query).build(); + ResultSet result = sessionRule.session().execute(st); ExecutionInfo executionInfo = result.getExecutionInfo(); assertThat(executionInfo).isNotNull(); List warnings = executionInfo.getWarnings(); - assertThat(warnings).isNotNull(); assertThat(warnings).isNotEmpty(); String warning = warnings.get(0); - assertThat(warning).isNotNull(); assertThat(warning).isEqualTo("Aggregation query used without partition key"); // verify the log was generated - verify(appender, after(500).times(1)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500).times(1)).doAppend(loggingEventCaptor.capture()); assertThat(loggingEventCaptor.getValue().getMessage()).isNotNull(); String logMessage = loggingEventCaptor.getValue().getFormattedMessage(); assertThat(logMessage) @@ -136,27 +133,25 @@ public void should_execute_query_and_log_server_side_warnings() { } @Test + @CassandraRequirement(min = "3.0") public void should_execute_query_and_not_log_server_side_warnings() { final String query = "SELECT count(*) FROM test;"; Statement st = - SimpleStatement.builder(String.format(query)) - .setExecutionProfileName("log-disabled") - .build(); - ResultSet result = SESSION_RULE.session().execute(st); + SimpleStatement.builder(query).setExecutionProfileName("log-disabled").build(); + ResultSet result = sessionRule.session().execute(st); ExecutionInfo executionInfo = result.getExecutionInfo(); assertThat(executionInfo).isNotNull(); List warnings = executionInfo.getWarnings(); - assertThat(warnings).isNotNull(); assertThat(warnings).isNotEmpty(); String warning = warnings.get(0); - assertThat(warning).isNotNull(); assertThat(warning).isEqualTo("Aggregation query used without partition key"); // verify the log was NOT generated - verify(appender, after(500).times(0)).doAppend(loggingEventCaptor.capture()); + verify(appender, timeout(500).times(0)).doAppend(loggingEventCaptor.capture()); } @Test + @CassandraRequirement(min = "2.2") public void should_expose_warnings_on_execution_info() { // the default batch size warn threshold is 5 * 1024 bytes, but after CASSANDRA-10876 there must // be multiple mutations in a batch to trigger this warning so the batch includes 2 different @@ -168,25 +163,33 @@ public void should_expose_warnings_on_execution_info() { + "INSERT INTO test (k, v) VALUES (2, '%s')\n" + "APPLY BATCH", Strings.repeat("1", 2 * 1024), Strings.repeat("1", 3 * 1024)); - Statement st = SimpleStatement.builder(String.format(query)).build(); - ResultSet result = SESSION_RULE.session().execute(st); + Statement st = SimpleStatement.builder(query).build(); + ResultSet result = sessionRule.session().execute(st); ExecutionInfo executionInfo = result.getExecutionInfo(); assertThat(executionInfo).isNotNull(); List warnings = executionInfo.getWarnings(); assertThat(warnings).isNotEmpty(); // verify the log was generated - verify(appender, after(500).times(1)).doAppend(loggingEventCaptor.capture()); - assertThat(loggingEventCaptor.getValue().getMessage()).isNotNull(); - String logMessage = loggingEventCaptor.getValue().getFormattedMessage(); - assertThat(logMessage) - .startsWith("Query '") - // query will only be logged up to MAX_QUERY_LENGTH - // characters - .contains(query.substring(0, RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH)) - .contains("' generated server side warning(s): ") - .contains( - String.format( - "Batch for [%s.test] is of size 5152, exceeding specified threshold of 5120 by 32.", - SESSION_RULE.keyspace().asCql(true))); + verify(appender, timeout(500).atLeast(1)).doAppend(loggingEventCaptor.capture()); + List logMessages = + loggingEventCaptor + .getAllValues() + .stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + assertThat(logMessages) + .anySatisfy( + logMessage -> + assertThat(logMessage) + .startsWith("Query '") + // query will only be logged up to MAX_QUERY_LENGTH characters + .contains( + query.substring(0, RequestLogger.DEFAULT_REQUEST_LOGGER_MAX_QUERY_LENGTH)) + .contains("' generated server side warning(s): ") + .contains("Batch") + .contains( + String.format( + "for [%s.test] is of size", sessionRule.keyspace().asCql(true))) + .contains("exceeding specified threshold")); } } From 4c3aa932fc76566e998e0bddb17a3589977d33b3 Mon Sep 17 00:00:00 2001 From: olim7t Date: Tue, 19 Mar 2019 11:44:41 -0700 Subject: [PATCH 736/742] JAVA-2081: Fix reference configuration page Abandon the idea of inlining the file's contents, this complicates our documentation pipeline too much (it requires reStructuredText and additional Python libraries). Instead, link to the file in our GitHub repo. The link will have to be updated before every release, but this already happens in other places so we do a global search and replace. --- manual/core/configuration/reference/README.md | 8 ++++++++ manual/core/configuration/reference/README.rst | 16 ---------------- 2 files changed, 8 insertions(+), 16 deletions(-) create mode 100644 manual/core/configuration/reference/README.md delete mode 100644 manual/core/configuration/reference/README.rst diff --git a/manual/core/configuration/reference/README.md b/manual/core/configuration/reference/README.md new file mode 100644 index 00000000000..fbef6eec9d7 --- /dev/null +++ b/manual/core/configuration/reference/README.md @@ -0,0 +1,8 @@ +## Reference configuration + +The `reference.conf` file is packaged in the `java-driver-core` JAR artifact. It is used at runtime +to provide default values for all configuration options (anything that is not overridden in a custom +`application.conf` or with system properties). + +Here is a [link to the file in our GitHub +repository](https://github.com/datastax/java-driver/blob/4.0.0-rc1/core/src/main/resources/reference.conf). diff --git a/manual/core/configuration/reference/README.rst b/manual/core/configuration/reference/README.rst deleted file mode 100644 index e6da9306a75..00000000000 --- a/manual/core/configuration/reference/README.rst +++ /dev/null @@ -1,16 +0,0 @@ -Reference configuration ------------------------ - -The following is a copy of the ``reference.conf`` file matching the version of this documentation. -It is packaged in the ``java-driver-core`` JAR artifact, and used at runtime to provide the default -values for all configuration options (in the sources, it can be found under -``core/src/main/resources``). - -See the `configuration page <../>`_ for more explanations. - -.. raw:: html - - - -.. include:: core/src/main/resources/reference.conf - :code: properties From 4bad94de6a15eb5e3396c032b803bef6ace1a5e5 Mon Sep 17 00:00:00 2001 From: olim7t Date: Fri, 15 Mar 2019 16:24:40 -0700 Subject: [PATCH 737/742] JAVA-2192: Don't return generic types with wildcards --- changelog/README.md | 1 + core/revapi.json | 286 ++++++++++++++++++ .../driver/api/core/AsyncPagingIterable.java | 7 +- .../oss/driver/api/core/CqlSession.java | 11 +- .../api/core/MappedAsyncPagingIterable.java | 22 ++ .../driver/api/core/cql/AsyncResultSet.java | 9 +- .../driver/api/core/metadata/Metadata.java | 8 +- .../metadata/schema/KeyspaceMetadata.java | 47 ++- .../metadata/schema/RelationMetadata.java | 12 +- .../core/metadata/schema/TableMetadata.java | 5 +- .../oss/driver/api/core/session/Session.java | 6 +- .../core/AsyncPagingIterableWrapper.java | 10 +- .../core/cql/DefaultAsyncResultSet.java | 2 +- .../DefaultLoadBalancingPolicy.java | 2 +- .../core/metadata/DefaultMetadata.java | 10 +- .../schema/refresh/SchemaRefresh.java | 8 +- .../core/metadata/token/DefaultTokenMap.java | 6 +- .../internal/core/metrics/MetricsFactory.java | 2 +- .../internal/core/session/DefaultSession.java | 6 +- .../internal/core/session/SessionWrapper.java | 6 +- .../core/AsyncPagingIterableWrapperTest.java | 9 +- .../core/cql/DefaultAsyncResultSetTest.java | 2 +- .../internal/core/cql/ResultSetTestBase.java | 7 +- .../schema/parsing/TableParserTest.java | 2 +- .../schema/parsing/ViewParserTest.java | 2 +- .../core/config/DriverExecutionProfileIT.java | 2 +- .../api/core/connection/FrameLengthIT.java | 2 +- .../driver/api/core/cql/AsyncResultSetIT.java | 2 +- .../driver/api/core/cql/BoundStatementIT.java | 4 +- .../api/core/cql/PreparedStatementIT.java | 4 +- .../api/core/cql/SimpleStatementIT.java | 2 +- .../driver/api/core/metadata/DescribeIT.java | 6 +- .../api/core/metadata/SchemaChangesIT.java | 11 +- .../driver/api/core/metadata/SchemaIT.java | 4 +- manual/core/async/README.md | 10 +- manual/core/paging/README.md | 2 +- 36 files changed, 415 insertions(+), 122 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/api/core/MappedAsyncPagingIterable.java diff --git a/changelog/README.md b/changelog/README.md index 223af4aba9e..9bae5a20cad 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.0 (in progress) +- [improvement] JAVA-2192: Don't return generic types with wildcards - [improvement] JAVA-2148: Add examples - [bug] JAVA-2189: Exclude virtual keyspaces from token map computation - [improvement] JAVA-2183: Enable materialized views when testing against Cassandra 4 diff --git a/core/revapi.json b/core/revapi.json index 11f8ccfb8ff..b4b80558561 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -4435,6 +4435,292 @@ "new": "method SelfT com.datastax.oss.driver.api.core.cql.Statement>>::setConsistencyLevel(com.datastax.oss.driver.api.core.ConsistencyLevel)", "annotation": "@edu.umd.cs.findbugs.annotations.NonNull", "justification": "Add missing `@NonNull` annotation to Statement.setConsistencyLevel" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage> com.datastax.oss.driver.api.core.AsyncPagingIterable::fetchNextPage() throws java.lang.IllegalStateException", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.AsyncPagingIterable>>::fetchNextPage() throws java.lang.IllegalStateException", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeChangedCovariantly", + "old": "method com.datastax.oss.driver.api.core.AsyncPagingIterable com.datastax.oss.driver.api.core.AsyncPagingIterable::map(java.util.function.Function)", + "new": "method com.datastax.oss.driver.api.core.MappedAsyncPagingIterable com.datastax.oss.driver.api.core.AsyncPagingIterable>>::map(java.util.function.Function)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.generics.formalTypeParameterAdded", + "old": "interface com.datastax.oss.driver.api.core.AsyncPagingIterable", + "new": "interface com.datastax.oss.driver.api.core.AsyncPagingIterable>", + "typeParameter": "SelfT extends com.datastax.oss.driver.api.core.AsyncPagingIterable>", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(com.datastax.oss.driver.api.core.cql.Statement)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::executeAsync(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.PrepareRequest)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(com.datastax.oss.driver.api.core.cql.SimpleStatement)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.CqlSession::prepareAsync(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.cql.AsyncResultSet::fetchNextPage() throws java.lang.IllegalStateException", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.AsyncPagingIterable>>::fetchNextPage() throws java.lang.IllegalStateException @ com.datastax.oss.driver.api.core.cql.AsyncResultSet", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.class.noLongerImplementsInterface", + "old": "interface com.datastax.oss.driver.api.core.cql.AsyncResultSet", + "new": "interface com.datastax.oss.driver.api.core.cql.AsyncResultSet", + "interface": "com.datastax.oss.driver.api.core.AsyncPagingIterable", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.class.superTypeTypeParametersChanged", + "old": "interface com.datastax.oss.driver.api.core.cql.AsyncResultSet", + "new": "interface com.datastax.oss.driver.api.core.cql.AsyncResultSet", + "oldSuperType": "com.datastax.oss.driver.api.core.AsyncPagingIterable", + "newSuperType": "com.datastax.oss.driver.api.core.AsyncPagingIterable", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.session.Session::refreshSchemaAsync()", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.session.Session::refreshSchemaAsync()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.session.Session::setSchemaMetadataEnabled(java.lang.Boolean)", + "new": "method java.util.concurrent.CompletionStage com.datastax.oss.driver.api.core.session.Session::setSchemaMetadataEnabled(java.lang.Boolean)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.session.Session::getMetrics()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.session.Session::getMetrics()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspace(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspace(java.lang.String)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspace(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspaces()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.Metadata::getKeyspaces()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getTokenMap()", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.Metadata::getTokenMap()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.type.DataType[])", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.type.DataType[])", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.Iterable)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.Iterable)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(java.lang.String, com.datastax.oss.driver.api.core.type.DataType[])", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(java.lang.String, com.datastax.oss.driver.api.core.type.DataType[])", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(java.lang.String, java.lang.Iterable)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregate(java.lang.String, java.lang.Iterable)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregates()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getAggregates()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.type.DataType[])", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.type.DataType[])", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.Iterable)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.CqlIdentifier, java.lang.Iterable)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(java.lang.String, com.datastax.oss.driver.api.core.type.DataType[])", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(java.lang.String, com.datastax.oss.driver.api.core.type.DataType[])", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(java.lang.String, java.lang.Iterable)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunction(java.lang.String, java.lang.Iterable)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunctions()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getFunctions()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTable(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTable(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTable(java.lang.String)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTable(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTables()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getTables()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedType(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedType(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedType(java.lang.String)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedType(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedTypes()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getUserDefinedTypes()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getView(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getView(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getView(java.lang.String)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getView(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getViews()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getViews()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getViewsOnTable(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata::getViewsOnTable(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getClusteringColumns()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getClusteringColumns()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumn(com.datastax.oss.driver.api.core.CqlIdentifier)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumn(com.datastax.oss.driver.api.core.CqlIdentifier)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumn(java.lang.String)", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumn(java.lang.String)", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumns()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getColumns()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.List com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getPartitionKey()", + "new": "method java.util.List com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getPartitionKey()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.List com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getPrimaryKey()", + "new": "method java.util.List com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata::getPrimaryKey()", + "justification": "JAVA-2192: Don't return generic types with wildcards" + }, + { + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::getIndexes()", + "new": "method java.util.Map com.datastax.oss.driver.api.core.metadata.schema.TableMetadata::getIndexes()", + "justification": "JAVA-2192: Don't return generic types with wildcards" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java index fcd03e15f77..9abe4136f66 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/AsyncPagingIterable.java @@ -28,7 +28,7 @@ * An iterable of elements which are fetched asynchronously by the driver, possibly in multiple * requests. */ -public interface AsyncPagingIterable { +public interface AsyncPagingIterable> { /** Metadata about the columns returned by the CQL request that was used to build this result. */ @NonNull @@ -76,8 +76,7 @@ default ElementT one() { * if you can call this method. */ @NonNull - CompletionStage> fetchNextPage() - throws IllegalStateException; + CompletionStage fetchNextPage() throws IllegalStateException; /** * If the query that produced this result was a CQL conditional update, indicate whether it was @@ -101,7 +100,7 @@ CompletionStage> fetchNextPage() *

        Note that both instances share the same underlying data: consuming elements from the * transformed iterable will also consume them from this object, and vice-versa. */ - default AsyncPagingIterable map( + default MappedAsyncPagingIterable map( Function elementMapper) { return new AsyncPagingIterableWrapper<>(this, elementMapper); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java index effa3e9079a..04a98054dc0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlSession.java @@ -61,7 +61,7 @@ default ResultSet execute(@NonNull String query) { * generally before the result is available). */ @NonNull - default CompletionStage executeAsync(@NonNull Statement statement) { + default CompletionStage executeAsync(@NonNull Statement statement) { return Objects.requireNonNull( execute(statement, Statement.ASYNC), "The CQL processor should never return a null result"); } @@ -71,7 +71,7 @@ default CompletionStage executeAsync(@NonNull Statemen * generally before the result is available). */ @NonNull - default CompletionStage executeAsync(@NonNull String query) { + default CompletionStage executeAsync(@NonNull String query) { return executeAsync(SimpleStatement.newInstance(query)); } @@ -191,8 +191,7 @@ default PreparedStatement prepare(@NonNull PrepareRequest request) { * explanations). */ @NonNull - default CompletionStage prepareAsync( - @NonNull SimpleStatement statement) { + default CompletionStage prepareAsync(@NonNull SimpleStatement statement) { return Objects.requireNonNull( execute(new DefaultPrepareRequest(statement), PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); @@ -206,7 +205,7 @@ default CompletionStage prepareAsync( * explanations). */ @NonNull - default CompletionStage prepareAsync(@NonNull String query) { + default CompletionStage prepareAsync(@NonNull String query) { return Objects.requireNonNull( execute(new DefaultPrepareRequest(query), PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); @@ -225,7 +224,7 @@ default CompletionStage prepareAsync(@NonNull Strin * explanations). */ @NonNull - default CompletionStage prepareAsync(PrepareRequest request) { + default CompletionStage prepareAsync(PrepareRequest request) { return Objects.requireNonNull( execute(request, PrepareRequest.ASYNC), "The CQL prepare processor should never return a null result"); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/MappedAsyncPagingIterable.java b/core/src/main/java/com/datastax/oss/driver/api/core/MappedAsyncPagingIterable.java new file mode 100644 index 00000000000..c6cb7ae5831 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/MappedAsyncPagingIterable.java @@ -0,0 +1,22 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core; + +import java.util.function.Function; + +/** The result of calling {@link #map(Function)} on another async iterable. */ +public interface MappedAsyncPagingIterable + extends AsyncPagingIterable> {} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java index 87673208d99..a21c0ee8cd6 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/AsyncResultSet.java @@ -17,8 +17,6 @@ import com.datastax.oss.driver.api.core.AsyncPagingIterable; import com.datastax.oss.driver.api.core.CqlSession; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.CompletionStage; /** * The result of an asynchronous CQL query. @@ -26,12 +24,7 @@ * @see CqlSession#executeAsync(Statement) * @see CqlSession#executeAsync(String) */ -public interface AsyncResultSet extends AsyncPagingIterable { - - // overridden to use a covariant return type: - @NonNull - @Override - CompletionStage fetchNextPage() throws IllegalStateException; +public interface AsyncResultSet extends AsyncPagingIterable { // overridden to amend the javadocs: /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java index af841f60633..a996d8a1eaf 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/Metadata.java @@ -88,10 +88,10 @@ default Optional findNode(@NonNull InetSocketAddress broadcastRpcAddress) * @see DefaultDriverOption#METADATA_SCHEMA_REFRESHED_KEYSPACES */ @NonNull - Map getKeyspaces(); + Map getKeyspaces(); @NonNull - default Optional getKeyspace(@NonNull CqlIdentifier keyspaceId) { + default Optional getKeyspace(@NonNull CqlIdentifier keyspaceId) { return Optional.ofNullable(getKeyspaces().get(keyspaceId)); } @@ -100,7 +100,7 @@ default Optional getKeyspace(@NonNull CqlIdentifier * getKeyspace(CqlIdentifier.fromCql(keyspaceName))}. */ @NonNull - default Optional getKeyspace(@NonNull String keyspaceName) { + default Optional getKeyspace(@NonNull String keyspaceName) { return getKeyspace(CqlIdentifier.fromCql(keyspaceName)); } @@ -113,5 +113,5 @@ default Optional getKeyspace(@NonNull String keyspac * @see DefaultDriverOption#METADATA_TOKEN_MAP_ENABLED */ @NonNull - Optional getTokenMap(); + Optional getTokenMap(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java index db4a592aceb..abda435ba02 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/KeyspaceMetadata.java @@ -42,26 +42,25 @@ public interface KeyspaceMetadata extends Describable { Map getReplication(); @NonNull - Map getTables(); + Map getTables(); @NonNull - default Optional getTable(@NonNull CqlIdentifier tableId) { + default Optional getTable(@NonNull CqlIdentifier tableId) { return Optional.ofNullable(getTables().get(tableId)); } /** Shortcut for {@link #getTable(CqlIdentifier) getTable(CqlIdentifier.fromCql(tableName))}. */ @NonNull - default Optional getTable(@NonNull String tableName) { + default Optional getTable(@NonNull String tableName) { return getTable(CqlIdentifier.fromCql(tableName)); } @NonNull - Map getViews(); + Map getViews(); /** Gets the views based on a given table. */ @NonNull - default Map getViewsOnTable( - @NonNull CqlIdentifier tableId) { + default Map getViewsOnTable(@NonNull CqlIdentifier tableId) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (ViewMetadata view : getViews().values()) { if (view.getBaseTable().equals(tableId)) { @@ -72,21 +71,21 @@ default Optional getTable(@NonNull String tableName) { } @NonNull - default Optional getView(@NonNull CqlIdentifier viewId) { + default Optional getView(@NonNull CqlIdentifier viewId) { return Optional.ofNullable(getViews().get(viewId)); } /** Shortcut for {@link #getView(CqlIdentifier) getView(CqlIdentifier.fromCql(viewName))}. */ @NonNull - default Optional getView(@NonNull String viewName) { + default Optional getView(@NonNull String viewName) { return getView(CqlIdentifier.fromCql(viewName)); } @NonNull - Map getUserDefinedTypes(); + Map getUserDefinedTypes(); @NonNull - default Optional getUserDefinedType(@NonNull CqlIdentifier typeId) { + default Optional getUserDefinedType(@NonNull CqlIdentifier typeId) { return Optional.ofNullable(getUserDefinedTypes().get(typeId)); } @@ -95,21 +94,20 @@ default Optional getUserDefinedType(@NonNull CqlIdent * getUserDefinedType(CqlIdentifier.fromCql(typeName))}. */ @NonNull - default Optional getUserDefinedType(@NonNull String typeName) { + default Optional getUserDefinedType(@NonNull String typeName) { return getUserDefinedType(CqlIdentifier.fromCql(typeName)); } @NonNull - Map getFunctions(); + Map getFunctions(); @NonNull - default Optional getFunction( - @NonNull FunctionSignature functionSignature) { + default Optional getFunction(@NonNull FunctionSignature functionSignature) { return Optional.ofNullable(getFunctions().get(functionSignature)); } @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull CqlIdentifier functionId, @NonNull Iterable parameterTypes) { return Optional.ofNullable( getFunctions().get(new FunctionSignature(functionId, parameterTypes))); @@ -120,7 +118,7 @@ default Optional getFunction( * getFunction(CqlIdentifier.fromCql(functionName), parameterTypes)}. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull String functionName, @NonNull Iterable parameterTypes) { return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); } @@ -129,7 +127,7 @@ default Optional getFunction( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull CqlIdentifier functionId, @NonNull DataType... parameterTypes) { return Optional.ofNullable( getFunctions().get(new FunctionSignature(functionId, parameterTypes))); @@ -142,22 +140,21 @@ default Optional getFunction( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getFunction( + default Optional getFunction( @NonNull String functionName, @NonNull DataType... parameterTypes) { return getFunction(CqlIdentifier.fromCql(functionName), parameterTypes); } @NonNull - Map getAggregates(); + Map getAggregates(); @NonNull - default Optional getAggregate( - @NonNull FunctionSignature aggregateSignature) { + default Optional getAggregate(@NonNull FunctionSignature aggregateSignature) { return Optional.ofNullable(getAggregates().get(aggregateSignature)); } @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull CqlIdentifier aggregateId, @NonNull Iterable parameterTypes) { return Optional.ofNullable( getAggregates().get(new FunctionSignature(aggregateId, parameterTypes))); @@ -168,7 +165,7 @@ default Optional getAggregate( * getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes)}. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull String aggregateName, @NonNull Iterable parameterTypes) { return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); } @@ -177,7 +174,7 @@ default Optional getAggregate( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull CqlIdentifier aggregateId, @NonNull DataType... parameterTypes) { return Optional.ofNullable( getAggregates().get(new FunctionSignature(aggregateId, parameterTypes))); @@ -190,7 +187,7 @@ default Optional getAggregate( * @param parameterTypes neither the individual types, nor the vararg array itself, can be null. */ @NonNull - default Optional getAggregate( + default Optional getAggregate( @NonNull String aggregateName, @NonNull DataType... parameterTypes) { return getAggregate(CqlIdentifier.fromCql(aggregateName), parameterTypes); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java index 5c8f01ca498..2cf5a803aa0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/RelationMetadata.java @@ -45,7 +45,7 @@ public interface RelationMetadata extends Describable { * @see #getClusteringColumns() */ @NonNull - default List getPrimaryKey() { + default List getPrimaryKey() { return ImmutableList.builder() .addAll(getPartitionKey()) .addAll(getClusteringColumns().keySet()) @@ -53,16 +53,16 @@ default List getPrimaryKey() { } @NonNull - List getPartitionKey(); + List getPartitionKey(); @NonNull - Map getClusteringColumns(); + Map getClusteringColumns(); @NonNull - Map getColumns(); + Map getColumns(); @NonNull - default Optional getColumn(@NonNull CqlIdentifier columnId) { + default Optional getColumn(@NonNull CqlIdentifier columnId) { return Optional.ofNullable(getColumns().get(columnId)); } @@ -70,7 +70,7 @@ default Optional getColumn(@NonNull CqlIdentifier colu * Shortcut for {@link #getColumn(CqlIdentifier) getColumn(CqlIdentifier.fromCql(columnName))}. */ @NonNull - default Optional getColumn(@NonNull String columnName) { + default Optional getColumn(@NonNull String columnName) { return getColumn(CqlIdentifier.fromCql(columnName)); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java index 8db9961f7a6..425d08945c0 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/schema/TableMetadata.java @@ -30,7 +30,7 @@ public interface TableMetadata extends RelationMetadata { boolean isVirtual(); @NonNull - Map getIndexes(); + Map getIndexes(); @NonNull @Override @@ -91,8 +91,7 @@ default String describe(boolean pretty) { if (getClusteringColumns().containsValue(ClusteringOrder.DESC)) { builder.andWith().append("CLUSTERING ORDER BY ("); boolean first = true; - for (Map.Entry entry : - getClusteringColumns().entrySet()) { + for (Map.Entry entry : getClusteringColumns().entrySet()) { if (first) { first = false; } else { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java index 482a14778c8..65c49988f0c 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/Session.java @@ -113,7 +113,7 @@ public interface Session extends AsyncAutoCloseable { * complete. Otherwise, a completed future with the current metadata. */ @NonNull - CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue); + CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue); /** * Force an immediate refresh of the schema metadata, even if it is currently disabled (either in @@ -123,7 +123,7 @@ public interface Session extends AsyncAutoCloseable { * #getMetadata()} when that future completes). */ @NonNull - CompletionStage refreshSchemaAsync(); + CompletionStage refreshSchemaAsync(); /** * Convenience method to call {@link #refreshSchemaAsync()} and block for the result. @@ -197,7 +197,7 @@ default boolean checkSchemaAgreement() { * disabled. */ @NonNull - Optional getMetrics(); + Optional getMetrics(); /** * Executes an arbitrary request. diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java index 63589c25eeb..77490e57416 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapper.java @@ -16,6 +16,7 @@ package com.datastax.oss.driver.internal.core; import com.datastax.oss.driver.api.core.AsyncPagingIterable; +import com.datastax.oss.driver.api.core.MappedAsyncPagingIterable; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.shaded.guava.common.collect.AbstractIterator; @@ -24,15 +25,16 @@ import java.util.concurrent.CompletionStage; import java.util.function.Function; -public class AsyncPagingIterableWrapper implements AsyncPagingIterable { +public class AsyncPagingIterableWrapper + implements MappedAsyncPagingIterable { - private final AsyncPagingIterable source; + private final AsyncPagingIterable source; private final Function elementMapper; private final Iterable currentPage; public AsyncPagingIterableWrapper( - AsyncPagingIterable source, + AsyncPagingIterable source, Function elementMapper) { this.source = source; this.elementMapper = elementMapper; @@ -80,7 +82,7 @@ public boolean hasMorePages() { @NonNull @Override - public CompletionStage> fetchNextPage() + public CompletionStage> fetchNextPage() throws IllegalStateException { return source .fetchNextPage() diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java index 64c2a42061e..b2630006b9a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSet.java @@ -95,7 +95,7 @@ public boolean hasMorePages() { @NonNull @Override - public CompletionStage fetchNextPage() throws IllegalStateException { + public CompletionStage fetchNextPage() throws IllegalStateException { ByteBuffer nextState = executionInfo.getPagingState(); if (nextState == null) { throw new IllegalStateException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 978d16965f6..31fafe8e228 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -232,7 +232,7 @@ private Set getReplicas(Request request, Session session) { return Collections.emptySet(); } - Optional maybeTokenMap = metadataManager.getMetadata().getTokenMap(); + Optional maybeTokenMap = metadataManager.getMetadata().getTokenMap(); if (maybeTokenMap.isPresent()) { TokenMap tokenMap = maybeTokenMap.get(); return (token != null) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java index f4677cebac7..b8c4008775a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultMetadata.java @@ -49,13 +49,11 @@ public class DefaultMetadata implements Metadata { new DefaultMetadata(Collections.emptyMap(), Collections.emptyMap(), null); protected final Map nodes; - protected final Map keyspaces; + protected final Map keyspaces; protected final TokenMap tokenMap; protected DefaultMetadata( - Map nodes, - Map keyspaces, - TokenMap tokenMap) { + Map nodes, Map keyspaces, TokenMap tokenMap) { this.nodes = nodes; this.keyspaces = keyspaces; this.tokenMap = tokenMap; @@ -69,7 +67,7 @@ public Map getNodes() { @NonNull @Override - public Map getKeyspaces() { + public Map getKeyspaces() { return keyspaces; } @@ -120,7 +118,7 @@ public DefaultMetadata withSchema( @Nullable protected TokenMap rebuildTokenMap( Map newNodes, - Map newKeyspaces, + Map newKeyspaces, boolean tokenMapEnabled, boolean forceFullRebuild, TokenFactory tokenFactory, diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java index afb9e56e7c4..0838b26e728 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/refresh/SchemaRefresh.java @@ -49,7 +49,7 @@ public Result compute( DefaultMetadata oldMetadata, boolean tokenMapEnabled, InternalDriverContext context) { ImmutableList.Builder events = ImmutableList.builder(); - Map oldKeyspaces = oldMetadata.getKeyspaces(); + Map oldKeyspaces = oldMetadata.getKeyspaces(); for (CqlIdentifier removedKey : Sets.difference(oldKeyspaces.keySet(), newKeyspaces.keySet())) { events.add(KeyspaceChangeEvent.dropped(oldKeyspaces.get(removedKey))); } @@ -132,8 +132,8 @@ private void computeChildEvents( } private void computeChildEvents( - Map oldChildren, - Map newChildren, + Map oldChildren, + Map newChildren, Function newDroppedEvent, Function newCreatedEvent, BiFunction newUpdatedEvent, @@ -141,7 +141,7 @@ private void computeChildEvents( for (K removedKey : Sets.difference(oldChildren.keySet(), newChildren.keySet())) { events.add(newDroppedEvent.apply(oldChildren.get(removedKey))); } - for (Map.Entry entry : newChildren.entrySet()) { + for (Map.Entry entry : newChildren.entrySet()) { K key = entry.getKey(); V newChild = entry.getValue(); V oldChild = oldChildren.get(key); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java index f9c7dd47855..0eeb399d672 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/DefaultTokenMap.java @@ -49,7 +49,7 @@ public class DefaultTokenMap implements TokenMap { public static DefaultTokenMap build( @NonNull Collection nodes, - @NonNull Collection keyspaces, + @NonNull Collection keyspaces, @NonNull TokenFactory tokenFactory, @NonNull ReplicationStrategyFactory replicationStrategyFactory, @NonNull String logPrefix) { @@ -197,7 +197,7 @@ private KeyspaceTokenMap getKeyspaceMap(CqlIdentifier keyspace) { /** Called when only the schema has changed. */ public DefaultTokenMap refresh( @NonNull Collection nodes, - @NonNull Collection keyspaces, + @NonNull Collection keyspaces, @NonNull ReplicationStrategyFactory replicationStrategyFactory) { Map> newReplicationConfigs = @@ -272,7 +272,7 @@ private TokenToPrimaryAndRing(Map tokenToPrimary, List ring) } private static Map> buildReplicationConfigs( - Collection keyspaces, String logPrefix) { + Collection keyspaces, String logPrefix) { ImmutableMap.Builder> builder = ImmutableMap.builder(); for (KeyspaceMetadata keyspace : keyspaces) { if (!keyspace.isVirtual()) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricsFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricsFactory.java index c26bf0a3430..26440c42b6c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricsFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metrics/MetricsFactory.java @@ -21,7 +21,7 @@ public interface MetricsFactory { - Optional getMetrics(); + Optional getMetrics(); /** @return the unique instance for this session (this must return the same object every time). */ SessionMetricUpdater getSessionUpdater(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java index 1c71b59b385..d29823f3d03 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/DefaultSession.java @@ -145,13 +145,13 @@ public boolean isSchemaMetadataEnabled() { @NonNull @Override - public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { + public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { return metadataManager.setSchemaEnabled(newValue); } @NonNull @Override - public CompletionStage refreshSchemaAsync() { + public CompletionStage refreshSchemaAsync() { return metadataManager.refreshSchema(null, true, true); } @@ -175,7 +175,7 @@ public Optional getKeyspace() { @NonNull @Override - public Optional getMetrics() { + public Optional getMetrics() { return context.getMetricsFactory().getMetrics(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java index 9b29f50ef83..c697718a2d0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/SessionWrapper.java @@ -74,13 +74,13 @@ public boolean isSchemaMetadataEnabled() { @NonNull @Override - public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { + public CompletionStage setSchemaMetadataEnabled(@Nullable Boolean newValue) { return delegate.setSchemaMetadataEnabled(newValue); } @NonNull @Override - public CompletionStage refreshSchemaAsync() { + public CompletionStage refreshSchemaAsync() { return delegate.refreshSchemaAsync(); } @@ -104,7 +104,7 @@ public Optional getKeyspace() { @NonNull @Override - public Optional getMetrics() { + public Optional getMetrics() { return delegate.getMetrics(); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java index 8741d1a231a..554e23c7720 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/AsyncPagingIterableWrapperTest.java @@ -19,9 +19,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.datastax.oss.driver.api.core.AsyncPagingIterable; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +import com.datastax.oss.driver.api.core.MappedAsyncPagingIterable; import com.datastax.oss.driver.api.core.cql.ColumnDefinition; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; @@ -85,7 +85,7 @@ public void should_wrap_result_set() throws Exception { .thenAnswer(invocation -> CompletableFuture.completedFuture(resultSet2)); // When - AsyncPagingIterable iterable1 = resultSet1.map(row -> row.getInt("i")); + MappedAsyncPagingIterable iterable1 = resultSet1.map(row -> row.getInt("i")); // Then for (int i = 0; i < 5; i++) { @@ -94,7 +94,8 @@ public void should_wrap_result_set() throws Exception { } assertThat(iterable1.hasMorePages()).isTrue(); - AsyncPagingIterable iterable2 = iterable1.fetchNextPage().toCompletableFuture().get(); + MappedAsyncPagingIterable iterable2 = + iterable1.fetchNextPage().toCompletableFuture().get(); for (int i = 5; i < 10; i++) { assertThat(iterable2.one()).isEqualTo(i); assertThat(iterable2.remaining()).isEqualTo(resultSet2.remaining()).isEqualTo(9 - i); @@ -111,7 +112,7 @@ public void should_share_iteration_progress_with_wrapped_result_set() { columnDefinitions, mockExecutionInfo(), mockData(0, 10), session, context); // When - AsyncPagingIterable iterable = resultSet.map(row -> row.getInt("i")); + MappedAsyncPagingIterable iterable = resultSet.map(row -> row.getInt("i")); // Then // Consume alternatively from the source and mapped iterable, and check that they stay in sync diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java index 4d6f86080ea..ebd9a6d0f0d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultAsyncResultSetTest.java @@ -95,7 +95,7 @@ public void should_invoke_session_to_fetch_next_page() { new DefaultAsyncResultSet( columnDefinitions, executionInfo, new ArrayDeque<>(), session, context); assertThat(resultSet.hasMorePages()).isTrue(); - CompletionStage nextPageFuture = resultSet.fetchNextPage(); + CompletionStage nextPageFuture = resultSet.fetchNextPage(); // Then verify(statement).copy(mockPagingState); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java index ded7dffb6a6..5ac3c8531d5 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/ResultSetTestBase.java @@ -75,11 +75,8 @@ private Row mockRow(int index) { return row; } - protected static void complete( - CompletionStage stage, AsyncResultSet result) { - @SuppressWarnings("unchecked") - CompletableFuture future = (CompletableFuture) stage; - future.complete(result); + protected static void complete(CompletionStage stage, AsyncResultSet result) { + stage.toCompletableFuture().complete(result); } protected void assertNextRow(Iterator iterator, int expectedValue) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java index 0732e1b2ad0..e361fb8a39d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/TableParserTest.java @@ -128,7 +128,7 @@ private void checkTable(TableMetadata table) { assertThat(pk1.getType()).isEqualTo(DataTypes.TEXT); assertThat(table.getClusteringColumns().entrySet()).hasSize(2); - Iterator clusteringColumnsIterator = + Iterator clusteringColumnsIterator = table.getClusteringColumns().keySet().iterator(); ColumnMetadata clusteringColumn1 = clusteringColumnsIterator.next(); assertThat(clusteringColumn1.getName().asInternal()).isEqualTo("cc1"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java index 805834ad40a..6ba458bebfb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/ViewParserTest.java @@ -67,7 +67,7 @@ public void should_parse_view() { assertThat(pk0.getType()).isEqualTo(DataTypes.TEXT); assertThat(view.getClusteringColumns().entrySet()).hasSize(5); - Iterator clusteringColumnsIterator = + Iterator clusteringColumnsIterator = view.getClusteringColumns().keySet().iterator(); assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("score"); assertThat(clusteringColumnsIterator.next().getName().asInternal()).isEqualTo("user"); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java index bcc1c58e014..fcc50f4ca35 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/config/DriverExecutionProfileIT.java @@ -225,7 +225,7 @@ public void should_use_profile_page_size() { String query = "SELECT * FROM test where k=0"; // Execute query without profile, should use global page size (100) - CompletionStage future = session.executeAsync(query); + CompletionStage future = session.executeAsync(query); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); assertThat(result.remaining()).isEqualTo(100); result = CompletableFutures.getUninterruptibly(result.fetchNextPage()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java index 5e599af39ba..507bd4cfaee 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/connection/FrameLengthIT.java @@ -94,7 +94,7 @@ public void should_fail_if_request_exceeds_max_frame_length() { @Test public void should_fail_if_response_exceeds_max_frame_length() { - CompletionStage slowResultFuture = + CompletionStage slowResultFuture = sessionRule.session().executeAsync(SLOW_QUERY); try { sessionRule.session().execute(LARGE_QUERY); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java index 7c890639696..83749d5259e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/AsyncResultSetIT.java @@ -88,7 +88,7 @@ public static void setupSchema() { public void should_only_iterate_over_rows_in_current_page() throws Exception { // very basic test that just ensures that iterating over an AsyncResultSet only visits the first // page. - CompletionStage result = + CompletionStage result = sessionRule .session() .executeAsync( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java index 08e7e5d0bd8..7ea0b4c154a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/BoundStatementIT.java @@ -257,7 +257,7 @@ public void should_use_page_size_from_simple_statement() { try (CqlSession session = SessionUtils.newSession(ccm, sessionRule.keyspace())) { SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); PreparedStatement prepared = session.prepare(st); - CompletionStage future = session.executeAsync(prepared.bind()); + CompletionStage future = session.executeAsync(prepared.bind()); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); // Should have only fetched 10 (page size) rows. @@ -272,7 +272,7 @@ public void should_use_page_size() { // overridden by bound statement. SimpleStatement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); PreparedStatement prepared = session.prepare(st); - CompletionStage future = + CompletionStage future = session.executeAsync(prepared.bind().setPageSize(12)); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java index 97966f8ac6c..994890eeb81 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/PreparedStatementIT.java @@ -186,7 +186,7 @@ public void should_update_metadata_when_schema_changed_across_pages() { ByteBuffer idBefore = ps.getResultMetadataId(); assertThat(ps.getResultSetDefinitions()).hasSize(3); - CompletionStage future = session.executeAsync(ps.bind()); + CompletionStage future = session.executeAsync(ps.bind()); AsyncResultSet rows = CompletableFutures.getUninterruptibly(future); assertThat(rows.getColumnDefinitions()).hasSize(3); assertThat(rows.getColumnDefinitions().contains("d")).isFalse(); @@ -417,7 +417,7 @@ public void should_create_separate_instances_for_different_statement_parameters( assertThat(firstPageOf(session.executeAsync(preparedStatement2.bind()))).hasSize(4); } - private static Iterable firstPageOf(CompletionStage stage) { + private static Iterable firstPageOf(CompletionStage stage) { return CompletableFutures.getUninterruptibly(stage).currentPage(); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java index 517445d5cfc..2cc6d520da5 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cql/SimpleStatementIT.java @@ -385,7 +385,7 @@ public void should_use_positional_value_with_case_sensitive_id() { @Test public void should_use_page_size() { Statement st = SimpleStatement.builder("SELECT v FROM test").setPageSize(10).build(); - CompletionStage future = sessionRule.session().executeAsync(st); + CompletionStage future = sessionRule.session().executeAsync(st); AsyncResultSet result = CompletableFutures.getUninterruptibly(future); // Should have only fetched 10 (page size) rows. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java index cf36e343035..94fb74ae1a8 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/DescribeIT.java @@ -94,8 +94,7 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { // connect session to this keyspace. session.execute(String.format("USE %s", keyspace.asCql(false))); - Optional originalKsMeta = - session.getMetadata().getKeyspace(keyspace); + Optional originalKsMeta = session.getMetadata().getKeyspace(keyspace); // Usertype 'ztype' with two columns. Given name to ensure that even though it has an // alphabetically later name, it shows up before other user types ('ctype') that depend on it. @@ -206,8 +205,7 @@ public void create_schema_and_ensure_exported_cql_is_as_expected() { assertThat(originalKsMeta.get().getUserDefinedTypes()).isEmpty(); // validate that the exported schema matches what was expected exactly. - Optional ks = - sessionRule.session().getMetadata().getKeyspace(keyspace); + Optional ks = sessionRule.session().getMetadata().getKeyspace(keyspace); assertThat(ks.get().describeWithChildren(true).trim()).isEqualTo(expectedCql); // Also validate that when you create a Session with schema already created that the exported diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java index cdd90eae022..be1d7ebf16b 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaChangesIT.java @@ -16,7 +16,8 @@ package com.datastax.oss.driver.api.core.metadata; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -415,7 +416,7 @@ public void should_handle_aggregate_update() { private void should_handle_creation( String beforeStatement, String createStatement, - Function> extract, + Function> extract, Consumer verifyMetadata, BiConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -469,7 +470,7 @@ private void should_handle_creation( private void should_handle_drop( Iterable beforeStatements, String dropStatement, - Function> extract, + Function> extract, BiConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -516,7 +517,7 @@ private void should_handle_drop( private void should_handle_update( Iterable beforeStatements, String updateStatement, - Function> extract, + Function> extract, Consumer verifyNewMetadata, TriConsumer verifyListener, CqlIdentifier... keyspaces) { @@ -568,7 +569,7 @@ private void should_handle_update_via_drop_and_recreate( Iterable beforeStatements, String dropStatement, String recreateStatement, - Function> extract, + Function> extract, Consumer verifyNewMetadata, TriConsumer verifyListener, CqlIdentifier... keyspaces) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java index 9ab40d4c4de..96f13aa8141 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/metadata/SchemaIT.java @@ -58,7 +58,7 @@ public class SchemaIT { @Test public void should_expose_system_and_test_keyspace() { - Map keyspaces = + Map keyspaces = sessionRule.session().getMetadata().getKeyspaces(); assertThat(keyspaces) .containsKeys( @@ -247,7 +247,7 @@ public void should_exclude_virtual_keyspaces_from_token_map() { skipIfDse60(); Metadata metadata = sessionRule.session().getMetadata(); - Map keyspaces = metadata.getKeyspaces(); + Map keyspaces = metadata.getKeyspaces(); assertThat(keyspaces) .containsKey(CqlIdentifier.fromCql("system_views")) .containsKey(CqlIdentifier.fromCql("system_virtual_schema")); diff --git a/manual/core/async/README.md b/manual/core/async/README.md index b345e02f7d3..8b63e30243f 100644 --- a/manual/core/async/README.md +++ b/manual/core/async/README.md @@ -10,7 +10,7 @@ Here is a short example that opens a session and runs a query asynchronously: CompletionStage sessionStage = CqlSession.builder().buildAsync(); // Chain one async operation after another: -CompletionStage responseStage = +CompletionStage responseStage = sessionStage.thenCompose( session -> session.executeAsync("SELECT release_version FROM system.local")); @@ -45,7 +45,7 @@ CompletionStage sessionStage = CqlSession.builder().buildAsync(); sessionStage.thenAccept(session -> System.out.println(Thread.currentThread().getName())); // prints s0-admin-n (admin pool thread) -CompletionStage resultStage = +CompletionStage resultStage = session.executeAsync("SELECT release_version FROM system.local"); resultStage.thenAccept(resultSet -> System.out.println(Thread.currentThread().getName())); // prints s0-io-n (I/O pool thread) @@ -56,11 +56,11 @@ method from inside a callback: ```java // Get the department id for a given user: -CompletionStage idStage = +CompletionStage idStage = session.executeAsync("SELECT department_id FROM user WHERE id = 1"); // Once we have the id, query the details of that department: -CompletionStage dataStage = +CompletionStage dataStage = idStage.thenCompose( resultSet -> { UUID departmentId = resultSet.one().getUuid(0); @@ -122,7 +122,7 @@ specific to the driver). When all your callback does is a side effect, it's easy swallow an exception: ```java -CompletionStage responseStage = +CompletionStage responseStage = sessionStage.thenCompose( session -> session.executeAsync("SELECT release_version FROM system.local")); responseStage.thenAccept( diff --git a/manual/core/paging/README.md b/manual/core/paging/README.md index ac11c5c804e..6e7193b34cc 100644 --- a/manual/core/paging/README.md +++ b/manual/core/paging/README.md @@ -97,7 +97,7 @@ iteration only yields the current page, and the next page must be explicitly fet that translates to our example: ```java -CompletionStage futureRs = +CompletionStage futureRs = session.executeAsync("SELECT * FROM myTable WHERE id = 1"); futureRs.whenComplete(this::processRows); From f342ae94974b7e9ac1c95210c5a8eecbdfa60160 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 14 Mar 2019 16:23:01 -0700 Subject: [PATCH 738/742] Revisit upgrade guide Expand contents, include more code examples, add more links to relevant sections in the manual. --- upgrade_guide/README.md | 435 +++++++++++++++++++++++++++++++--------- 1 file changed, 336 insertions(+), 99 deletions(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index e339107db45..69d39e617a2 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -2,77 +2,207 @@ ### 4.0.0 -Java driver 4 is **not binary compatible** with previous versions. However, most of the concepts -remain unchanged, and the new API will look very familiar to 2.x and 3.x users. +Version 4 is major redesign of the internal architecture. As such, it is **not binary compatible** +with previous versions. However, most of the concepts remain unchanged, and the new API will look +very familiar to 2.x and 3.x users. + +#### New Maven coordinates + +The core driver is available from: + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-rc1 + +``` #### Runtime requirements -The driver now requires Java 8. It does not depend on Guava anymore (we still use it internally but -it's shaded). +The driver now requires **Java 8 or above**. It does not depend on Guava anymore (we still use it +internally but it's shaded). + +We have dropped support for legacy protocol versions v1 and v2. As a result, the driver is +compatible with: + +* **Apache Cassandra®: 2.1 and above**; +* **Datastax Enterprise: 4.7 and above**. #### Packages -The root package names have changed. There are also more sub-packages than previously. See [API -conventions] for important information about the naming conventions. - -Generally, the public types have kept the same name, so you can use the "find class" feature in your -IDE to find out the new locations. +We've adopted new [API conventions] to better organize the driver code and make it more modular. As +a result, package names have changed. However most public API types have the same names; you can use +the auto-import or "find class" features of your IDE to discover the new locations. + +Here's a side-by-side comparison with the legacy driver for a basic example: + +```java +// Driver 3: +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.SimpleStatement; + +SimpleStatement statement = + new SimpleStatement("SELECT release_version FROM system.local"); +ResultSet resultSet = session.execute(statement); +Row row = resultSet.one(); +System.out.println(row.getString("release_version")); + + +// Driver 4: +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; + +SimpleStatement statement = + SimpleStatement.newInstance("SELECT release_version FROM system.local"); +ResultSet resultSet = session.execute(statement); +Row row = resultSet.one(); +System.out.println(row.getString("release_version")); +``` + +Notable changes: + +* the imports; +* simple statement instances are now created with the `newInstance` static factory method. This is + because `SimpleStatement` is now an interface (as most public API types). [API conventions]: ../manual/api_conventions -#### New configuration API +#### Configuration The configuration has been completely revamped. Instead of ad-hoc configuration classes, the default -configuration mechanism is now file-based, using the [Typesafe Config] library. This is a better -choice for most deployments, since it allows configuration changes without recompiling the client -application. This is fully customizable, including loading from different sources, or completely -overriding the default implementation. +mechanism is now file-based, using the [Typesafe Config] library. This is a better choice for most +deployments, since it allows configuration changes without recompiling the client application (note +that there are still programmatic setters for things that are likely to be injected dynamically, +such as contact points). + +The driver JAR contains a `reference.conf` file that defines the options with their defaults: + +``` +datastax-java-driver { + basic.request { + timeout = 2 seconds + consistency = LOCAL_ONE + page-size = 5000 + } + // ... and many more (~10 basic options, 70 advanced ones) +} +``` + +You can place an `application.conf` in your application's classpath to override options selectively: + +``` +datastax-java-driver { + basic.request.consistency = ONE +} +``` + +Options can also be overridden with system properties when launching your application: + +``` +java -Ddatastax-java-driver.basic.request.consistency=ONE MyApp +``` + +The configuration also supports *execution profiles*, that allow you to capture and reuse common +sets of options: + +```java +// application.conf: +datastax-java-driver { + profiles { + profile1 { basic.request.consistency = QUORUM } + profile2 { basic.request.consistency = ONE } + } +} + +// Application code: +SimpleStatement statement1 = + SimpleStatement.newInstance("...").setExecutionProfileName("profile1"); +SimpleStatement statement2 = + SimpleStatement.newInstance("...").setExecutionProfileName("profile2"); +``` + +The configuration can be reloaded periodically at runtime: + +``` +datastax-java-driver { + basic.config-reload-interval = 5 minutes +} +``` + +This is fully customizable: the configuration is exposed to the rest of the driver as an abstract +`DriverConfig` interface; if the default implementation doesn't work for you, you can write your +own. For more details, refer to the [manual](../manual/core/configuration). [Typesafe Config]: https://github.com/typesafehub/config -#### Expose interfaces, not classes +#### Session -Most types in the public API are now interfaces (as opposed to 3.x: `Session`, statement classes, -etc). The actual implementations are part of the internal API. This provides more flexibility in -client code (e.g. to wrap them and write delegates). +`Cluster` does not exist anymore; the session is now the main component, initialized in a single +step: -Thanks to Java 8, factory methods can now be part of these interfaces directly, e.g. -`CqlSession.builder()`, `SimpleStatement.newInstance`. +```java +CqlSession session = CqlSession.builder().build(); +session.execute("..."); +``` -#### No more `Cluster` +Protocol negotiation in mixed clusters has been improved: you no longer need to force the protocol +version during a rolling upgrade. The driver will detect that there are older nodes, and downgrade +to the best common denominator (see +[JAVA-1295](https://datastax-oss.atlassian.net/browse/JAVA-1295)). -In previous driver versions, initialization was done in two steps: create a `Cluster`, and then call -its `connect` method to create a `Session`. +Reconnection is now possible at startup: if no contact point is reachable, the driver will retry at +periodic intervals (controlled by the [reconnection policy](../manual/core/reconnection/)) instead +of throwing an error. To turn this on, set the following configuration option: -Those two types have now been merged: there is only one `Session` object, that you initialize -directly. +``` +datastax-java-driver { + advanced.reconnect-on-init = true +} +``` -#### Generic session API +The session now has a built-in [throttler](../manual/core/throttling/) to limit how many requests +can execute concurrently. Here's an example based on the number of requests (a rate-based variant is +also available): -`Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, -the driver exposes a more familiar subtype `CqlSession`, that provides familiar signatures for CQL -queries (`execute(Statement)`, `prepare(String)`, etc). +``` +datastax-java-driver { + advanced.throttler { + class = ConcurrencyLimitingRequestThrottler + max-concurrent-requests = 10000 + max-queue-size = 100000 + } +} +``` -However, the request execution logic is completely pluggable, and supports arbitrary request types -(as long as you write the boilerplate to convert them to protocol messages). In the future, we will -take advantage of that to provide: +#### Load balancing policy + +Previous driver versions came with multiple load balancing policies that could be nested into each +other. In our experience, this was one of the most complicated aspects of the configuration. + +In driver 4, we are taking a more opinionated approach: we provide a single [default +policy](../manual/core/load_balancing/#default-policy), with what we consider as the best practices: + +* local only: we believe that failover should be handled at infrastructure level, not by application + code. +* token-aware. +* optionally filtering nodes with a custom predicate. -* a reactive API; -* a high-performance implementation that exposes bare Netty buffers; -* specialized requests in our DataStax Enterprise driver. +You can still provide your own policy by implementing the `LoadBalancingPolicy` interface. -If you're interested, take a look at `RequestProcessor`. - -#### Immutable statement types +#### Statements -Simple, bound and batch statements implementations are now all immutable. This makes them -automatically thread-safe: you don't need to worry anymore about sharing them or reusing them -between asynchronous executions. +Simple, bound and batch [statements](../manual/core/statements/) are now exposed in the public API +as interfaces. The internal implementations are immutable. This makes them automatically +thread-safe: you don't need to worry anymore about sharing them or reusing them between asynchronous +executions. -One word of warning -- all mutating methods return a new instance, so make sure you don't -accidentally ignore their result: +Note that all mutating methods return a new instance, so make sure you don't accidentally ignore +their result: ```java BoundStatement boundSelect = preparedSelect.bind(); @@ -85,19 +215,47 @@ session.execute(boundSelect); boundSelect = boundSelect.setInt("k", key); ``` -Note that, as indicated in the previous section, the public API exposes these types as interfaces: -if for some reason you prefer a mutable implementation, it's possible to write your own. +These methods are annotated with `@CheckReturnValue`. Some code analysis tools -- such as +[ErrorProne](https://errorprone.info/) -- can check correct usage at build time, and report mistakes +as compiler errors. -#### Prepared statement cache +Unlike 3.x, the request timeout now spans the entire request. In other words, it's the +maximum amount of time that `session.execute` will take, including any retry, speculative execution, +etc. You can set it with `Statement.setTimeout`, or globally in the configuration with the +`basic.request.timeout` option. -In 3.x, calling `session.prepare()` multiple times with the same query was considered an -anti-pattern, and the driver would log a warning. Client applications were encouraged to cache -`PreparedStatement` instances on their own. +[Prepared statements](../manual/core/statements/prepared/) are now cached client-side: if you call +`session.prepare()` twice with the same query string, it will no longer log a warning. The second +call will return the same statement instance, without sending anything to the server: -This cache is now built in: the driver will cache the first `prepare()` call and return the same -instance on subsequent invocations. Calling the method multiple times is no longer an anti-pattern. -See [prepared statements](../manual/core/statements/prepared/) for more details. +```java +PreparedStatement ps1 = session.prepare("SELECT * FROM product WHERE sku = ?"); +PreparedStatement ps2 = session.prepare("SELECT * FROM product WHERE sku = ?"); +assert ps1 == ps2; +``` + +This cache takes into account all execution parameters. For example, if you prepare the same query +string with different consistency levels, you will get two distinct prepared statements, each +propagating its own consistency level to its bound statements: +```java +PreparedStatement ps1 = + session.prepare( + SimpleStatement.newInstance("SELECT * FROM product WHERE sku = ?") + .setConsistencyLevel(DefaultConsistencyLevel.ONE)); +PreparedStatement ps2 = + session.prepare( + SimpleStatement.newInstance("SELECT * FROM product WHERE sku = ?") + .setConsistencyLevel(DefaultConsistencyLevel.TWO)); + +assert ps1 != ps2; + +BoundStatement bs1 = ps1.bind(); +assert bs1.getConsistencyLevel() == DefaultConsistencyLevel.ONE; + +BoundStatement bs2 = ps2.bind(); +assert bs2.getConsistencyLevel() == DefaultConsistencyLevel.TWO; +``` #### Dual result set APIs @@ -108,15 +266,14 @@ risk of accidentally triggering background synchronous fetches. There are now two separate APIs: synchronous queries return a `ResultSet`; asynchronous queries return a future of `AsyncResultSet`. -`ResultSet` behaves much like its 3.x counterpart, except that background pre-fetching was -deliberately removed, in order to keep this interface simple and intuitive. This is why methods such -as `fetchMoreResults`, `getAvailableWithoutFetching` and `isFullyFetched` have disappeared. If you -were using synchronous iterations with background pre-fetching, you should now switch to fully -asynchronous iterations (see below). +`ResultSet` behaves much like its 3.x counterpart, except that background pre-fetching +(`fetchMoreResults`) was deliberately removed, in order to keep this interface simple and intuitive. +If you were using synchronous iterations with background pre-fetching, you should now switch to +fully asynchronous iterations (see below). `AsyncResultSet` is a simplified type that only contains the rows of the current page. When iterating asynchronously, you no longer need to stop the iteration manually: just consume all the -rows in the iterator, and then call `fetchNextPage` to retrieve the next page asynchronously. You +rows in `currentPage()`, and then call `fetchNextPage` to retrieve the next page asynchronously. You will find more information about asynchronous iterations in the manual pages about [asynchronous programming][4.x async programming] and [paging][4.x paging]. @@ -126,20 +283,16 @@ programming][4.x async programming] and [paging][4.x paging]. #### CQL to Java type mappings -Since the driver now has access to Java 8 types, some of the [CQL to Java type -mappings] have changed when it comes to [temporal types] such as `date` and -`timestamp`. These changes are: +Since the driver now has access to Java 8 types, some of the [CQL to Java type mappings] have +changed when it comes to [temporal types] such as `date` and `timestamp`: -* `getDate` has been replaced by `getLocalDate` and returns - [java.time.LocalDate]; -* `getTime` has been replaced by `getLocalTime` and returns - [java.time.LocalTime] instead of a `long` representing nanoseconds since - midnight; -* `getTimestamp` has been replaced by `getInstant` and returns - [java.time.Instant] instead of [java.util.Date]. +* `getDate` has been replaced by `getLocalDate` and returns [java.time.LocalDate]; +* `getTime` has been replaced by `getLocalTime` and returns [java.time.LocalTime] instead of a + `long` representing nanoseconds since midnight; +* `getTimestamp` has been replaced by `getInstant` and returns [java.time.Instant] instead of + [java.util.Date]. -The corresponding setter methods were also changed to expect these new types -as inputs. +The corresponding setter methods were also changed to expect these new types as inputs. [CQL to Java type mappings]: ../manual/core#cql-to-java-type-mapping [temporal types]: ../manual/core/temporal_types @@ -148,47 +301,131 @@ as inputs. [java.time.Instant]: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html [java.util.Date]: https://docs.oracle.com/javase/8/docs/api/java/util/Date.html -#### Simplified request timeout +#### Metrics -The driver-side request timeout -- defined by the `request.timeout` configuration option -- now -spans the entire request, including all retries, speculative executions, etc. In other -words, it's the maximum amount of time that the driver will spend processing the request. If it -fires, all pending tasks are cancelled, and a `DriverTimeoutException` is returned to the client. -(Note that the "cancellation" is only driver-side, currently the protocol does not provide a way to -tell the server to stop processing a request; if a message was "on the wire" when the timeout fired, -then the driver will simply ignore the response when it eventually comes back.) - -This is in contrast to 3.x, where the timeout defined in the configuration was per retry, and a -global timeout required specific user code. +[Metrics](../manual/core/metrics/) are now divided into two categories: session-wide and per-node. +Each metric can be enabled or disabled individually in the configuration: -#### Dedicated type for CQL identifiers +``` +datastax-java-driver { + advanced.metrics { + // more are available, see reference.conf for the full list + session.enabled = [ bytes-sent, bytes-received, cql-requests ] + node.enabled = [ bytes-sent, bytes-received, pool.in-flight ] + } +} +``` -Instead of raw strings, the names of schema objects (keyspaces, tables, columns, etc.) are now -wrapped in a dedicated `CqlIdentifier` type. This avoids ambiguities with regard to case -sensitivity. +Note that unlike 3.x, JMX is not supported out of the box. You'll need to add the dependency +explicitly: -For example, this type is used in schema metadata or when creating a session connected to a specific -keyspace. When manipulating "data containers" such as rows, UDTs and tuples, columns can also be -referenced by a `CqlIdentifier`; however, we've also kept a raw string variant for convenience, with -the same rules as in 3.x (see `GettableById` and `GettableByName` for details). +```xml + + io.dropwizard.metrics + metrics-jmx + 4.0.2 + +``` -#### Atomic metadata updates +#### Metadata `Session.getMetadata()` is now immutable and updated atomically. The node list, schema metadata and -token map exposed by a given `Metadata` instance are guaranteed to be in sync. +token map exposed by a given `Metadata` instance are guaranteed to be in sync. This is convenient +for analytics clients that need a consistent view of the cluster at a given point in time; for +example, a keyspace in `metadata.getKeyspaces()` will always have a corresponding entry in +`metadata.getTokenMap()`. On the other hand, this means you have to call `getMetadata()` again each time you need a fresh -copy; do not cache the result. +copy; do not cache the result: + +```java +Metadata metadata = session.getMetadata(); +Optional ks = metadata.getKeyspace("test"); +assert !ks.isPresent(); + +session.execute( + "CREATE KEYSPACE IF NOT EXISTS test " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + +// This is still the same metadata from before the CREATE +ks = metadata.getKeyspace("test"); +assert !ks.isPresent(); + +// You need to fetch the whole metadata again +metadata = session.getMetadata(); +ks = metadata.getKeyspace("test"); +assert ks.isPresent(); +``` + +Refreshing the metadata can be CPU-intensive, in particular the token map. To help alleviate that, +it can now be filtered to a subset of keyspaces. This is useful if your application connects to a +shared cluster, but does not use the whole schema: + +``` +datastax-java-driver { + // defaults to empty (= all keyspaces) + advanced.metadata.schema.refreshed-keyspaces = [ "users", "products" ] +} +``` See the [manual](../manual/core/metadata/) for all the details. -#### Improved protocol version negotiation +#### Query builder + +The query builder is now distributed as a separate artifact: + +```xml + + com.datastax.oss + java-driver-query-builder + 4.0.0-rc1 + +``` + +It is more cleanly separated from the core driver, and only focuses on query string generation. +Built queries are no longer directly executable, you need to convert them into a string or a +statement: + +```java +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; + +BuildableQuery query = + insertInto("user") + .value("id", bindMarker()) + .value("first_name", bindMarker()) + .value("last_name", bindMarker()); + +String cql = query.asCql(); +// INSERT INTO user (id,first_name,last_name) VALUES (?,?,?) + +SimpleStatement statement = query + .builder() + .addNamedValue("id", 0) + .addNamedValue("first_name", "Jane") + .addNamedValue("last_name", "Doe") + .build(); +``` + +All query builder types are immutable, making them inherently thread-safe and share-safe. -You no longer need to force the protocol version in a mixed cluster: upon connecting to the first -node, the driver will read the release version of all the nodes in the cluster and infer the best -protocol version that works with all of them. +The query builder has its own [manual chapter](../manual/query_builder/), where the syntax is +covered in detail. -#### Improved metrics +#### Dedicated type for CQL identifiers + +Instead of raw strings, the names of schema objects (keyspaces, tables, columns, etc.) are now +wrapped in a dedicated `CqlIdentifier` type. This avoids ambiguities with regard to [case +sensitivity](../manual/case_sensitivity). + +#### Pluggable request execution logic + +`Session` is now a high-level abstraction capable of executing arbitrary requests. Out of the box, +the driver exposes a more familiar subtype `CqlSession`, that provides familiar signatures for CQL +queries (`execute(Statement)`, `prepare(String)`, etc). + +However, the request execution logic is completely pluggable, and supports arbitrary request types +(as long as you write the boilerplate to convert them to protocol messages). -[Metrics](../manual/core/metrics/) can now be enabled selectively. In addition, they are exposed per -node when that is relevant. \ No newline at end of file +We use that in our DSE driver to implement a reactive API and support for DSE graph. You can also +take advantage of it to plug your own request types (if you're interested, take a look at +`RequestProcessor` in the internal API). From 183cae4ff7f4858110b39d7cdfe9b947ad8ce28a Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 20 Mar 2019 15:58:42 -0700 Subject: [PATCH 739/742] Mention Maven coordinates in root readme --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31e9bdcc065..091c587c1c0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,23 @@ Language v3. ## Getting the driver The driver artifacts are published in Maven central, under the group id [com.datastax.oss]; there -are multiple modules, all prefixed with `java-driver-`. Refer to the [manual] for more details. +are multiple modules, all prefixed with `java-driver-`. + +```xml + + com.datastax.oss + java-driver-core + 4.0.0-rc1 + + + + com.datastax.oss + java-driver-query-builder + 4.0.0-rc1 + +``` + +Refer to the [manual] for more details. [com.datastax.oss]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.datastax.oss%22 [manual]: manual/ From 3f5ce4528ffc2e1f19c80dad26e5766cd082bd45 Mon Sep 17 00:00:00 2001 From: olim7t Date: Wed, 20 Mar 2019 16:50:15 -0700 Subject: [PATCH 740/742] Clarify explanations on local datacenter --- manual/core/load_balancing/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/manual/core/load_balancing/README.md b/manual/core/load_balancing/README.md index 2e8c9aef10a..25d6c42d6ce 100644 --- a/manual/core/load_balancing/README.md +++ b/manual/core/load_balancing/README.md @@ -93,19 +93,15 @@ means a catastrophic failure happened in Region1, and the application node is do Failover should be cross-region instead (handled by the load balancer in this example). Therefore the default policy does not allow remote nodes; it only ever assigns the `LOCAL` or -`IGNORED` distance, based on the local datacenter name specified in the configuration: +`IGNORED` distance. You **must** provide a local datacenter name, either in the configuration: ``` datastax-java-driver.basic.load-balancing-policy { - class = DefaultLoadBalancingPolicy local-datacenter = datacenter1 } ``` -This option is required, except when you didn't specify any contact points and let the driver -default to 127.0.0.1:9042 (this is mostly for convenience during the development phase). - -Note that the local datacenter can also be provided programmatically when building the session: +Or programmatically when building the session: ```java CqlSession session = CqlSession.builder() @@ -115,6 +111,11 @@ CqlSession session = CqlSession.builder() If both are provided, the programmatic value takes precedence. +For convenience, the local datacenter name may be omitted if no contact points were provided: in +that case, the driver will connect to 127.0.0.1:9042, and use that node's datacenter. This is just +for a better out-of-the-box experience for users who have just downloaded the driver; beyond that +initial development phase, you should provide explicit contact points and a local datacenter. + #### Token-aware The default policy is **token-aware** by default: requests will be routed in priority to the From 8402f53eb4dc96413137ddfe741193ce380f2dc4 Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 21 Mar 2019 07:51:59 -0700 Subject: [PATCH 741/742] Update version in docs --- README.md | 6 +++--- changelog/README.md | 2 +- manual/core/README.md | 2 +- manual/core/compression/README.md | 2 +- manual/core/configuration/reference/README.md | 2 +- manual/core/integration/README.md | 16 ++++++++-------- manual/core/shaded_jar/README.md | 6 +++--- manual/query_builder/README.md | 2 +- upgrade_guide/README.md | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 091c587c1c0..06baa564e02 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ *If you're reading this on github.com, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the documentation for latest version through [DataStax Docs] or via the release tags, e.g. -[4.0.0-rc1](https://github.com/datastax/java-driver/tree/4.0.0-rc1).* +[4.0.0](https://github.com/datastax/java-driver/tree/4.0.0).* A modern, feature-rich and highly tunable Java client library for [Apache Cassandra®] \(2.1+) and [DataStax Enterprise] \(4.7+), using exclusively Cassandra's binary protocol and Cassandra Query @@ -22,13 +22,13 @@ are multiple modules, all prefixed with `java-driver-`. com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 com.datastax.oss java-driver-query-builder - 4.0.0-rc1 + 4.0.0 ``` diff --git a/changelog/README.md b/changelog/README.md index 9bae5a20cad..abf3e12fe7a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,7 +2,7 @@ -### 4.0.0 (in progress) +### 4.0.0 - [improvement] JAVA-2192: Don't return generic types with wildcards - [improvement] JAVA-2148: Add examples diff --git a/manual/core/README.md b/manual/core/README.md index 0417714d24f..799c6f3644b 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -7,7 +7,7 @@ following coordinates: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 ``` diff --git a/manual/core/compression/README.md b/manual/core/compression/README.md index b5c06e7d54c..e6593e01e30 100644 --- a/manual/core/compression/README.md +++ b/manual/core/compression/README.md @@ -70,4 +70,4 @@ Dependency: Always double-check the exact Snappy version needed; you can find it in the driver's [parent POM]. -[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0-rc1%7Cpom \ No newline at end of file +[parent POM]: https://search.maven.org/#artifactdetails%7Ccom.datastax.oss%7Cjava-driver-parent%7C4.0.0%7Cpom \ No newline at end of file diff --git a/manual/core/configuration/reference/README.md b/manual/core/configuration/reference/README.md index fbef6eec9d7..28da3a2c01a 100644 --- a/manual/core/configuration/reference/README.md +++ b/manual/core/configuration/reference/README.md @@ -5,4 +5,4 @@ to provide default values for all configuration options (anything that is not ov `application.conf` or with system properties). Here is a [link to the file in our GitHub -repository](https://github.com/datastax/java-driver/blob/4.0.0-rc1/core/src/main/resources/reference.conf). +repository](https://github.com/datastax/java-driver/blob/4.0.0/core/src/main/resources/reference.conf). diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index 5c28d5e83a4..0fdcda8ab3b 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -39,7 +39,7 @@ dependencies, and tell Maven that we're going to use Java 8: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 ch.qos.logback @@ -144,7 +144,7 @@ You should see output similar to: [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3.1:java (default-cli) @ yourapp --- -11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0-rc1 +11:39:45.355 [Main.main()] INFO c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.0.0 11:39:45.648 [poc-admin-0] INFO c.d.o.d.internal.core.time.Clock - Using native clock for microsecond precision 11:39:45.649 [poc-admin-0] INFO c.d.o.d.i.c.metadata.MetadataManager - [poc] No contact points provided, defaulting to /127.0.0.1:9042 3.11.2 @@ -176,7 +176,7 @@ repositories { } dependencies { - compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0-rc1' + compile group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } ``` @@ -260,7 +260,7 @@ In that case, you can exclude the dependency: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 com.typesafe @@ -288,7 +288,7 @@ are not available on your platform, you can exclude the following dependencies: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 com.github.jnr @@ -322,7 +322,7 @@ and never call [Session.getMetrics] anywhere in your application, you can remove com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 io.dropwizard.metrics @@ -343,7 +343,7 @@ If all of these metrics are disabled, you can remove the dependency: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 org.hdrhistogram @@ -369,7 +369,7 @@ exclude them: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 com.github.stephenc.jcip diff --git a/manual/core/shaded_jar/README.md b/manual/core/shaded_jar/README.md index c7007a3b3b4..cacdcf2970d 100644 --- a/manual/core/shaded_jar/README.md +++ b/manual/core/shaded_jar/README.md @@ -12,7 +12,7 @@ package name: com.datastax.oss java-driver-core-shaded - 4.0.0-rc1 + 4.0.0 ``` @@ -23,12 +23,12 @@ dependency to the non-shaded JAR: com.datastax.oss java-driver-core-shaded - 4.0.0-rc1 + 4.0.0 com.datastax.oss java-driver-query-builder - 4.0.0-rc1 + 4.0.0 com.datastax.oss diff --git a/manual/query_builder/README.md b/manual/query_builder/README.md index 09b0f8c369c..e74247a0274 100644 --- a/manual/query_builder/README.md +++ b/manual/query_builder/README.md @@ -14,7 +14,7 @@ To use it in your application, add the following dependency: com.datastax.oss java-driver-query-builder - 4.0.0-rc1 + 4.0.0 ``` diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 69d39e617a2..ab684b23541 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -14,7 +14,7 @@ The core driver is available from: com.datastax.oss java-driver-core - 4.0.0-rc1 + 4.0.0 ``` @@ -378,7 +378,7 @@ The query builder is now distributed as a separate artifact: com.datastax.oss java-driver-query-builder - 4.0.0-rc1 + 4.0.0 ``` From 9bdb5255af36de197b3f526340aa723bddffd55c Mon Sep 17 00:00:00 2001 From: olim7t Date: Thu, 21 Mar 2019 07:56:45 -0700 Subject: [PATCH 742/742] [maven-release-plugin] prepare release 4.0.0 --- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- examples/pom.xml | 6 ++---- integration-tests/pom.xml | 2 +- pom.xml | 4 ++-- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 8 files changed, 10 insertions(+), 12 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 91f8a68377b..041da3a3b5f 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -22,7 +22,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-core-shaded diff --git a/core/pom.xml b/core/pom.xml index 0052882d1f1..e46538ebb6d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-core diff --git a/distribution/pom.xml b/distribution/pom.xml index c95458f001d..45f39aad8aa 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-distribution diff --git a/examples/pom.xml b/examples/pom.xml index 08bf52db2f5..29b7eeb9dc3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -15,15 +15,13 @@ limitations under the License. --> - + 4.0.0 java-driver-parent com.datastax.oss - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-examples diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 3a64ad2567f..845a098395d 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-integration-tests diff --git a/pom.xml b/pom.xml index fc1ec771eeb..4953cb2dec5 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 pom DataStax Java driver for Apache Cassandra(R) @@ -674,7 +674,7 @@ limitations under the License.]]> scm:git:git@github.com:datastax/java-driver.git scm:git:git@github.com:datastax/java-driver.git https://github.com/datastax/java-driver - HEAD + 4.0.0 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index ff9f4390c0b..452add4c7a3 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-query-builder diff --git a/test-infra/pom.xml b/test-infra/pom.xml index d89c09d45d8..9d3998c5294 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -21,7 +21,7 @@ com.datastax.oss java-driver-parent - 4.0.0-rc2-SNAPSHOT + 4.0.0 java-driver-test-infra